diff --git a/translate/public/locale/en-US/translate.ftl b/translate/public/locale/en-US/translate.ftl index b1c22b84ef..8097c2dedd 100644 --- a/translate/public/locale/en-US/translate.ftl +++ b/translate/public/locale/en-US/translate.ftl @@ -300,6 +300,9 @@ entitydetails-ContextIssueButton--context-issue-button = REQUEST CONTEXT or REPO entitieslist-Entity--sibling-strings-title = .title = Click to reveal sibling strings +entitieslist-Entity--listitem-label = + .aria-label = Select "{ $original }" for translation. + entitieslist-EntitiesList--clear-selected = CLEAR .title = Uncheck selected strings diff --git a/translate/src/modules/entitieslist/components/EntitiesList.test.js b/translate/src/modules/entitieslist/components/EntitiesList.test.js index 80d1795ad2..af3dc8aecb 100644 --- a/translate/src/modules/entitieslist/components/EntitiesList.test.js +++ b/translate/src/modules/entitieslist/components/EntitiesList.test.js @@ -88,6 +88,9 @@ describe('', () => { const history = createMemoryHistory({ initialEntries: ['/kg/firefox/all-resources/?string=1'], }); + const testLabel = 'test-listitem'; + const resource = `entitieslist-Entity--listitem-label = + .aria-label = ${testLabel}`; const store = createReduxStore(); store.dispatch({ @@ -100,9 +103,10 @@ describe('', () => { store, {}, history, + resource, ); - expect(getAllByRole('listitem')).toHaveLength(2); + expect(getAllByRole('button', { name: testLabel })).toHaveLength(2); }); // FIXME: https://github.com/mozilla/pontoon/issues/3883 diff --git a/translate/src/modules/entitieslist/components/Entity.test.jsx b/translate/src/modules/entitieslist/components/Entity.test.jsx index f0367a5165..00c11f5304 100644 --- a/translate/src/modules/entitieslist/components/Entity.test.jsx +++ b/translate/src/modules/entitieslist/components/Entity.test.jsx @@ -1,9 +1,10 @@ -import { mount, shallow } from 'enzyme'; import React from 'react'; import * as hookModule from '~/hooks/useTranslator'; import { Entity } from './Entity'; -import { vitest } from 'vitest'; +import { it, vitest } from 'vitest'; +import { fireEvent, render } from '@testing-library/react'; +import { MockLocalizationProvider } from '~/test/utils'; beforeAll(() => { vitest.mock('~/hooks/useTranslator', () => ({ @@ -61,67 +62,70 @@ describe('', () => { warnings: ['warning'], }, }; + const WrapEntity = (props) => { + return ( + + + + ); + }; it('renders the source string and the first translation', () => { - const wrapper = shallow(); - - const contents = wrapper.find('Translation'); - expect(contents.first().props().content).toContain(ENTITY_A.original); - expect(contents.last().props().content).toContain( - ENTITY_A.translation.string, + const { getByText } = render( + , ); - }); - it('shows the correct status class', () => { - let wrapper = shallow(); - expect(wrapper.find('.approved')).toHaveLength(1); - - wrapper = shallow(); - expect(wrapper.find('.pretranslated')).toHaveLength(1); - - wrapper = shallow(); - expect(wrapper.find('.missing')).toHaveLength(1); + getByText(ENTITY_A.original); + getByText(ENTITY_A.translation.string); + }); - wrapper = shallow(); - expect(wrapper.find('.errors')).toHaveLength(1); + it.each([ + { entity: ENTITY_A, classname: 'approved' }, + { entity: ENTITY_B, classname: 'pretranslated' }, + { entity: ENTITY_C, classname: 'missing' }, + { entity: ENTITY_D, classname: 'errors' }, + { entity: ENTITY_E, classname: 'warnings' }, + ])('renders $classname state correctly', ({ entity, classname }) => { + const { container } = render( + , + ); - wrapper = shallow(); - expect(wrapper.find('.warnings')).toHaveLength(1); + expect(container.querySelector(`.${classname}`)).toBeInTheDocument(); }); it('calls the selectEntity function on click on li', () => { const selectEntityFn = vi.fn(); - const wrapper = mount( - , ); - wrapper.find('li').simulate('click'); + fireEvent.click(getByRole('button')); expect(selectEntityFn).toHaveBeenCalledOnce(); }); it('calls the toggleForBatchEditing function on click on .status', () => { hookModule.useTranslator.mockReturnValue(true); const toggleForBatchEditingFn = vi.fn(); - const wrapper = mount( - , ); - wrapper.find('.status').simulate('click'); + fireEvent.click(getByRole('checkbox')); expect(toggleForBatchEditingFn).toHaveBeenCalledOnce(); }); it('does not call the toggleForBatchEditing function if user not translator', () => { const toggleForBatchEditingFn = vi.fn(); const selectEntityFn = vi.fn(); - const wrapper = mount( - ', () => { parameters={{}} />, ); - wrapper.find('.status').simulate('click'); + fireEvent.click(getByRole('checkbox')); expect(toggleForBatchEditingFn).not.toHaveBeenCalled(); }); it('does not call the toggleForBatchEditing function if read-only editor', () => { const toggleForBatchEditingFn = vi.fn(); const selectEntityFn = vi.fn(); - const wrapper = mount( - ', () => { parameters={{}} />, ); - wrapper.find('.status').simulate('click'); + fireEvent.click(getByRole('checkbox')); expect(toggleForBatchEditingFn).not.toHaveBeenCalled(); }); }); diff --git a/translate/src/modules/entitieslist/components/Entity.tsx b/translate/src/modules/entitieslist/components/Entity.tsx index 788634779c..334f046e31 100644 --- a/translate/src/modules/entitieslist/components/Entity.tsx +++ b/translate/src/modules/entitieslist/components/Entity.tsx @@ -113,47 +113,58 @@ export function Entity({ ); return ( - - - {selected && !entity.isSibling ? ( + + + + {selected && !entity.isSibling ? ( + + {!areSiblingsActive && showSiblingEntitiesButton() && ( + + + + )} + + ) : null} - {!areSiblingsActive && showSiblingEntitiesButton() && ( - - - - )} + + + + + + + - ) : null} - - - - - - - - - - + + ); } diff --git a/translate/src/modules/entitydetails/components/FluentAttribute.test.jsx b/translate/src/modules/entitydetails/components/FluentAttribute.test.jsx index af9d827ede..51ddb2883e 100644 --- a/translate/src/modules/entitydetails/components/FluentAttribute.test.jsx +++ b/translate/src/modules/entitydetails/components/FluentAttribute.test.jsx @@ -1,8 +1,9 @@ import ftl from '@fluent/dedent'; -import { shallow } from 'enzyme'; import React from 'react'; import { parseEntry } from '~/utils/message'; import { FluentAttribute } from './FluentAttribute'; +import { render } from '@testing-library/react'; +import { MockLocalizationProvider } from '~/test/utils'; describe('isSimpleSingleAttributeMessage', () => { it('renders nonempty for a string with a single attribute', () => { @@ -11,8 +12,12 @@ describe('isSimpleSingleAttributeMessage', () => { .an-atribute = Hello! `; const entry = parseEntry('fluent', original); - const wrapper = shallow(); - expect(wrapper.isEmptyRender()).toEqual(false); + const { container } = render( + + + , + ); + expect(container).not.toBeEmptyDOMElement(); }); it('renders null for string with value', () => { @@ -21,8 +26,8 @@ describe('isSimpleSingleAttributeMessage', () => { .an-atribute = Hello! `; const entry = parseEntry('fluent', original); - const wrapper = shallow(); - expect(wrapper.isEmptyRender()).toEqual(true); + const { container } = render(); + expect(container).toBeEmptyDOMElement(); }); it('renders null for string with several attributes', () => { @@ -32,7 +37,7 @@ describe('isSimpleSingleAttributeMessage', () => { .two-attrites = World! `; const entry = parseEntry('fluent', original); - const wrapper = shallow(); - expect(wrapper.isEmptyRender()).toEqual(true); + const { container } = render(); + expect(container).toBeEmptyDOMElement(); }); }); diff --git a/translate/src/modules/entitydetails/components/Metadata.test.jsx b/translate/src/modules/entitydetails/components/Metadata.test.jsx index b848025ea6..fbd5e81c9b 100644 --- a/translate/src/modules/entitydetails/components/Metadata.test.jsx +++ b/translate/src/modules/entitydetails/components/Metadata.test.jsx @@ -1,4 +1,3 @@ -import { mount } from 'enzyme'; import React from 'react'; import { Provider } from 'react-redux'; @@ -7,6 +6,7 @@ import { createReduxStore } from '~/test/store'; import { MockLocalizationProvider } from '~/test/utils'; import { Metadata } from './Metadata'; +import { render } from '@testing-library/react'; const SRC_LOC = 'file_source.rs:31'; @@ -39,10 +39,11 @@ const TERMS = { const USER = { user: 'A_Ludgate', }; +const entityCommentTitle = 'COMMENT'; function createMetadata(entity = ENTITY) { const store = createReduxStore({ user: USER }); - return mount( + return render( @@ -55,34 +56,32 @@ function createMetadata(entity = ENTITY) { describe('', () => { it('renders correctly', () => { - const wrapper = createMetadata(); + const { getByText } = createMetadata(); - expect(wrapper.text()).toContain(SRC_LOC); - expect(wrapper.find('#entitydetails-Metadata--comment').text()).toContain( - ENTITY.comment, - ); - - expect( - wrapper.find('#entitydetails-Metadata--context a.resource-path').text(), - ).toContain(ENTITY.path); + getByText(SRC_LOC); + getByText(entityCommentTitle); + getByText(ENTITY.comment); + getByText(ENTITY.path); }); it('does not require a comment', () => { - const wrapper = createMetadata({ ...ENTITY, comment: '' }); - expect(wrapper.text()).toContain(SRC_LOC); - expect(wrapper.find('#entitydetails-Metadata--comment')).toHaveLength(0); + const { getByText, queryByText } = createMetadata({ + ...ENTITY, + comment: '', + }); + getByText(SRC_LOC); + expect(queryByText(entityCommentTitle)).toBeNull(); }); it('does not require a source', () => { - const wrapper = createMetadata({ ...ENTITY, meta: [] }); - expect(wrapper.text()).not.toContain(SRC_LOC); - expect(wrapper.find('#entitydetails-Metadata--comment').text()).toContain( - ENTITY.comment, - ); + const { queryByText, getByText } = createMetadata({ ...ENTITY, meta: [] }); + expect(queryByText(SRC_LOC)).toBeNull(); + getByText(entityCommentTitle); + getByText(ENTITY.comment); }); it('finds examples for placeholders with source', () => { - const wrapper = createMetadata({ + const { container } = createMetadata({ ...ENTITY, format: 'webext', original: ` @@ -91,13 +90,13 @@ describe('', () => { {{{$REMAINING @source=|$REMAINING$|}/{$MAXIMUM @source=|$MAXIMUM$|} masks available.}}`, }); - expect(wrapper.find('div.placeholder .content').text()).toBe( - '$MAXIMUM$: 5, $REMAINING$: 1', - ); + expect( + container.querySelector('div.placeholder .content').textContent, + ).toBe('$MAXIMUM$: 5, $REMAINING$: 1'); }); it('only shows examples for placeholders with source and example', () => { - const wrapper = createMetadata({ + const { container } = createMetadata({ ...ENTITY, format: 'webext', original: ` @@ -106,6 +105,6 @@ describe('', () => { {{{$REMAINING}/{$MAXIMUM @source=|$MAXIMUM$|} masks available.}}`, }); - expect(wrapper.find('div.placeholder')).toHaveLength(0); + expect(container.querySelector('div.placeholder')).toBeNull(); }); }); diff --git a/translate/src/modules/entitydetails/components/Screenshots.test.jsx b/translate/src/modules/entitydetails/components/Screenshots.test.jsx index 3313f04965..0004555df8 100644 --- a/translate/src/modules/entitydetails/components/Screenshots.test.jsx +++ b/translate/src/modules/entitydetails/components/Screenshots.test.jsx @@ -1,64 +1,68 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; import { Screenshots } from './Screenshots'; -import { mount } from 'enzyme'; +import { fireEvent, render } from '@testing-library/react'; +import { expect } from 'vitest'; +// TODO: Replace the querySelector with testing-library-ish approaches describe('', () => { it('finds several images', () => { const source = ` Here we have 2 images: http://link.to/image.png and https://example.org/en-US/test.jpg `; - const wrapper = mount(); - - expect(wrapper.find('img')).toHaveLength(2); - expect(wrapper.find('img').at(0).prop('src')).toBe( - 'http://link.to/image.png', - ); - expect(wrapper.find('img').at(1).prop('src')).toBe( - 'https://example.org/kg/test.jpg', + const { getAllByRole } = render( + , ); + const images = getAllByRole('img'); + expect(images).toHaveLength(2); + expect(images[0]).toHaveAttribute('src', 'http://link.to/image.png'); + expect(images[1]).toHaveAttribute('src', 'https://example.org/kg/test.jpg'); }); it('does not find non PNG or JPG images', () => { const source = 'That is a non-supported image URL: http://link.to/image.bmp'; - const wrapper = mount(); + const { queryByRole } = render(); - expect(wrapper.find('img')).toHaveLength(0); + expect(queryByRole('img')).toBeNull(); }); it('shows a Lightbox on image click', () => { const source = 'That is an image URL: http://link.to/image.png'; - const wrapper = mount(); - - expect(wrapper.find('.lightbox')).toHaveLength(0); + const { container, getByRole } = render( + , + ); - wrapper.find('img').simulate('click'); + expect(container.querySelector('.lightbox')).toBeNull(); + fireEvent.click(getByRole('img')); - expect(wrapper.find('.lightbox')).toHaveLength(1); + expect(container.querySelector('.lightbox')).toBeInTheDocument(); }); it('Lightbox closes on click', () => { const source = 'That is an image URL: http://link.to/image.png'; - const wrapper = mount(); - wrapper.find('img').simulate('click'); - wrapper.find('.lightbox').simulate('click'); + const { getByRole, container } = render( + , + ); + fireEvent.click(getByRole('img')); + fireEvent.click(container.querySelector('.lightbox')); - expect(wrapper.find('.lightbox')).toHaveLength(0); + expect(container.querySelector('.lightbox')).toBeNull(); }); it('Lightbox closes on key press', () => { const source = 'That is an image URL: http://link.to/image.png'; - const wrapper = mount(); - wrapper.find('img').simulate('click'); + const { getByRole, container } = render( + , + ); + fireEvent.click(getByRole('img')); act(() => { window.document.dispatchEvent( new KeyboardEvent('keydown', { code: 'Escape' }), ); }); - wrapper.update(); - expect(wrapper.find('.lightbox')).toHaveLength(0); + expect(container.querySelector('.lightbox')).toBeNull(); }); }); diff --git a/translate/src/modules/history/components/HistoryTranslation.test.jsx b/translate/src/modules/history/components/HistoryTranslation.test.jsx index e6207ece67..8316de5765 100644 --- a/translate/src/modules/history/components/HistoryTranslation.test.jsx +++ b/translate/src/modules/history/components/HistoryTranslation.test.jsx @@ -1,13 +1,21 @@ -import { shallow } from 'enzyme'; import React from 'react'; import * as hookModule from '~/hooks/useTranslator'; import { HistoryTranslationBase } from './HistoryTranslation'; +import { fireEvent, render } from '@testing-library/react'; + +import { MockLocalizationProvider } from '~/test/utils'; beforeAll(() => { vitest.mock('~/hooks/useTranslator', () => ({ useTranslator: vi.fn(() => false), })); + + vi.mock('react-time-ago', () => { + return { + default: () => null, + }; + }); }); afterAll(() => { @@ -29,6 +37,7 @@ describe('', () => { user: '', username: 'michel', comments: [], + userBanner: [], }; const DEFAULT_USER = { @@ -38,6 +47,13 @@ describe('', () => { const DEFAULT_ENTITY = { format: 'gettext', }; + const WrapHistoryTranslationBase = (props) => { + return ( + + + + ); + }; describe('getStatus', () => { it('returns the correct status for approved translations', () => { @@ -45,15 +61,15 @@ describe('', () => { ...DEFAULT_TRANSLATION, ...{ approved: true }, }; - const wrapper = shallow( - , ); - expect(wrapper.find('.approved')).toHaveLength(1); + expect(container.querySelector('.approved')).toBeInTheDocument(); }); it('returns the correct status for rejected translations', () => { @@ -61,15 +77,15 @@ describe('', () => { ...DEFAULT_TRANSLATION, ...{ rejected: true }, }; - const wrapper = shallow( - , ); - expect(wrapper.find('.rejected')).toHaveLength(1); + expect(container.querySelector('.rejected')).toBeInTheDocument(); }); it('returns the correct status for pretranslated translations', () => { @@ -77,15 +93,15 @@ describe('', () => { ...DEFAULT_TRANSLATION, ...{ pretranslated: true }, }; - const wrapper = shallow( - , ); - expect(wrapper.find('.pretranslated')).toHaveLength(1); + expect(container.querySelector('.pretranslated')).toBeInTheDocument(); }); it('returns the correct status for fuzzy translations', () => { @@ -93,115 +109,135 @@ describe('', () => { ...DEFAULT_TRANSLATION, ...{ fuzzy: true }, }; - const wrapper = shallow( - , ); - expect(wrapper.find('.fuzzy')).toHaveLength(1); + expect(container.querySelector('.fuzzy')).toBeInTheDocument(); }); it('returns the correct status for unreviewed translations', () => { - const wrapper = shallow( - , ); - expect(wrapper.find('.unreviewed')).toHaveLength(1); + expect(container.querySelector('.unreviewed')).toBeInTheDocument(); }); }); describe('review title', () => { it('returns the correct review title when approved and approved user is available', () => { + const approvedTitle = 'test-approved'; const translation = { ...DEFAULT_TRANSLATION, ...{ approved: true, approvedUser: 'Cespenar' }, }; - const wrapper = shallow( - , - ); - expect(wrapper.find('[id="history-translation--approved"]')).toHaveLength( - 2, + const { getAllByTitle } = render( + + + , ); + + expect(getAllByTitle(approvedTitle)).toHaveLength(2); }); it('returns the correct review title when approved and approved user is not available', () => { + const approvedAnonymousTitle = 'test-approved-anonymous'; const translation = { ...DEFAULT_TRANSLATION, ...{ approved: true }, }; - const wrapper = shallow( - , + const { getAllByTitle } = render( + + + , ); - expect( - wrapper.find('[id="history-translation--approved-anonymous"]'), - ).toHaveLength(2); + expect(getAllByTitle(approvedAnonymousTitle)).toHaveLength(2); }); it('returns the correct review title when rejected and rejected user is available', () => { + const rejectedTitle = 'test-rejected'; const translation = { ...DEFAULT_TRANSLATION, ...{ rejected: true, rejectedUser: 'Bhaal' }, }; - const wrapper = shallow( - , + const { getAllByTitle } = render( + + + , ); - expect(wrapper.find('[id="history-translation--rejected"]')).toHaveLength( - 2, - ); + expect(getAllByTitle(rejectedTitle)).toHaveLength(2); }); it('returns the correct review title when rejected and rejected user is not available', () => { + const rejectedAnonymousTitle = 'test-rejected-anonymous'; const translation = { ...DEFAULT_TRANSLATION, ...{ rejected: true }, }; - const wrapper = shallow( - , + const { getAllByTitle } = render( + + + , ); - expect( - wrapper.find('[id="history-translation--rejected-anonymous"]'), - ).toHaveLength(2); + expect(getAllByTitle(rejectedAnonymousTitle)).toHaveLength(2); }); it('returns the correct approver title when neither approved or rejected', () => { - const wrapper = shallow( - , + const unreviewedTitle = 'test-unreviewed'; + const { getAllByTitle } = render( + + + , ); - - expect( - wrapper.find('[id="history-translation--unreviewed"]'), - ).toHaveLength(2); + expect(getAllByTitle(unreviewedTitle)).toHaveLength(2); }); }); @@ -211,191 +247,206 @@ describe('', () => { ...DEFAULT_TRANSLATION, ...{ uid: 1, username: 'id_Sarevok', user: 'Sarevok' }, }; - const wrapper = shallow( - , ); - const link = wrapper.find('User').dive().find('a'); - expect(link.props()).toMatchObject({ - children: 'Sarevok', - href: '/contributors/id_Sarevok', - }); + const link = getByRole('link', { name: translation.user }); + expect(link).toHaveAttribute( + 'href', + `/contributors/${translation.username}`, + ); }); it('returns no link when the author is not known', () => { - const wrapper = shallow( - , ); - const link = wrapper.find('User').dive().find('a'); - expect(link).toHaveLength(0); + expect(queryByRole('link', { name: translation.user })).toBeNull(); + getByText(translation.user); }); }); describe('status', () => { - it('shows the correct status for approved translations', () => { + const approve = 'Approve'; + const approved = 'Approved'; + const unapprove = 'Unapprove'; + const notApproved = 'Not approved'; + const reject = 'Reject'; + const unReject = 'Unreject'; + const notRejected = 'Not rejected'; + + it('shows the correct buttons for approved translations', () => { const translation = { ...DEFAULT_TRANSLATION, ...{ approved: true }, }; - const wrapper = shallow( - , ); - expect(wrapper.find('.approve')).toHaveLength(0); - expect(wrapper.find('.unapprove')).toHaveLength(1); - expect(wrapper.find('.reject')).toHaveLength(1); - expect(wrapper.find('.unreject')).toHaveLength(0); + expect(queryByRole('button', { name: approve })).toBeNull(); + expect(getByRole('button', { name: approved })).toBeDisabled(); + expect(getByRole('button', { name: notRejected })).toBeDisabled(); + expect(queryByRole('button', { name: unReject })).toBeNull(); }); - it('shows the correct status for rejected translations', () => { + it('shows the correct buttons for rejected translations', () => { const translation = { ...DEFAULT_TRANSLATION, ...{ rejected: true }, }; - const wrapper = shallow( - , ); - expect(wrapper.find('.approve')).toHaveLength(1); - expect(wrapper.find('.unapprove')).toHaveLength(0); - expect(wrapper.find('.reject')).toHaveLength(0); - expect(wrapper.find('.unreject')).toHaveLength(1); + expect(getByRole('button', { name: notApproved })).toBeDisabled(); + expect(queryByRole('button', { name: unapprove })).toBeNull(); + expect(queryByRole('button', { name: reject })).toBeNull(); + expect(getByRole('button', { name: unReject })).not.toBeDisabled(); }); - it('shows the correct status for unreviewed translations', () => { - const wrapper = shallow( - { + const { getByRole, queryByRole } = render( + , ); - - expect(wrapper.find('.approve')).toHaveLength(1); - expect(wrapper.find('.unapprove')).toHaveLength(0); - expect(wrapper.find('.reject')).toHaveLength(1); - expect(wrapper.find('.unreject')).toHaveLength(0); + expect(getByRole('button', { name: notApproved })).toBeDisabled(); + expect(queryByRole('button', { name: unapprove })).toBeNull(); + expect(getByRole('button', { name: reject })).not.toBeDisabled(); + expect(queryByRole('button', { name: unReject })).toBeNull(); }); }); describe('permissions', () => { + const rejectButton = 'Reject'; + const approveButton = 'Approve'; + const deleteButton = 'Delete'; + it('allows the user to reject their own unapproved translation', () => { - const wrapper = shallow( - , ); - expect(wrapper.find('.can-reject')).toHaveLength(1); - expect(wrapper.find('.can-approve')).toHaveLength(0); + getByRole('button', { name: rejectButton }); + expect(queryByRole('button', { name: approveButton })).toBeNull(); }); it('forbids the user to reject their own approved translation', () => { const translation = { ...DEFAULT_TRANSLATION, approved: true }; - const wrapper = shallow( - , ); - expect(wrapper.find('.can-reject')).toHaveLength(0); - expect(wrapper.find('.can-approve')).toHaveLength(0); + expect(queryByRole('button', { name: rejectButton })).toBeNull(); + expect(queryByRole('button', { name: approveButton })).toBeNull(); }); it('allows translators to review the translation', () => { hookModule.useTranslator.mockReturnValue(true); - const wrapper = shallow( - , ); - expect(wrapper.find('.can-reject')).toHaveLength(1); - expect(wrapper.find('.can-approve')).toHaveLength(1); + getByRole('button', { name: rejectButton }); + getByRole('button', { name: approveButton }); }); it('allows translators to delete the rejected translation', () => { hookModule.useTranslator.mockReturnValue(true); const translation = { ...DEFAULT_TRANSLATION, rejected: true }; - const wrapper = shallow( - , ); - expect(wrapper.find('.delete')).toHaveLength(1); + getByRole('button', { name: deleteButton }); }); it('forbids translators to delete non-rejected translation', () => { hookModule.useTranslator.mockReturnValue(true); const translation = { ...DEFAULT_TRANSLATION, rejected: false }; - const wrapper = shallow( - , ); - expect(wrapper.find('.delete')).toHaveLength(0); + expect(queryByRole('button', { name: deleteButton })).toBeNull(); }); it('allows the user to delete their own rejected translation', () => { const translation = { ...DEFAULT_TRANSLATION, rejected: true }; - const wrapper = shallow( - , ); - expect(wrapper.find('.delete')).toHaveLength(1); + getByRole('button', { name: deleteButton }); }); it('forbids the user to delete rejected translation of another user', () => { const translation = { ...DEFAULT_TRANSLATION, rejected: true }; - const wrapper = shallow( - , ); - expect(wrapper.find('.delete')).toHaveLength(0); + expect(queryByRole('button', { name: deleteButton })).toBeNull(); }); }); describe('DiffToggle', () => { it('shows default translation and no Show/Hide diff button for the first translation', () => { - const wrapper = shallow( - ', () => { />, ); - expect(wrapper.find('.default')).toHaveLength(1); - expect(wrapper.find('.diff-visible')).toHaveLength(0); + expect(container.querySelector('.default')).toBeInTheDocument(); + expect(container.querySelector('.diff-visible')).toBeNull(); - const toggle = wrapper.find('DiffToggle').dive(); - expect(toggle.find('.toggle.diff.off')).toHaveLength(0); - expect(toggle.find('.toggle.diff.on')).toHaveLength(0); + expect(queryByRole('button', { name: 'DIFF' })).toBeNull(); }); it('shows default translation and the Show diff button for a non-first translation', () => { - const wrapper = shallow( - ', () => { index={1} />, ); + expect(container.querySelector('.default')).toBeInTheDocument(); + expect(container.querySelector('.diff-visible')).toBeNull(); - expect(wrapper.find('.default')).toHaveLength(1); - expect(wrapper.find('.diff-visible')).toHaveLength(0); - - const toggle = wrapper.find('DiffToggle').dive(); - expect(toggle.find('.toggle.diff.off')).toHaveLength(1); - expect(toggle.find('.toggle.diff.on')).toHaveLength(0); + const diffToggle = getByRole('button', { name: 'DIFF' }); + expect(diffToggle).toHaveClass('off'); + expect(diffToggle).not.toHaveClass('on'); }); it('shows translation diff and the Hide diff button for a non-first translation if diff visible', () => { - const wrapper = shallow( - ', () => { index={1} />, ); + const diffToggle = getByRole('button', { name: 'DIFF' }); - wrapper.find('DiffToggle').props().toggleVisible(); + fireEvent.click(diffToggle); - expect(wrapper.find('.default')).toHaveLength(0); - expect(wrapper.find('.diff-visible')).toHaveLength(1); + expect(container.querySelector('.default')).toBeNull(); + expect(container.querySelector('.diff-visible')).toBeInTheDocument(); - const toggle = wrapper.find('DiffToggle').dive(); - expect(toggle.find('.toggle.diff.off')).toHaveLength(0); - expect(toggle.find('.toggle.diff.on')).toHaveLength(1); + expect(diffToggle).not.toHaveClass('off'); + expect(diffToggle).toHaveClass('on'); }); }); }); diff --git a/translate/src/test/store.jsx b/translate/src/test/store.jsx index e053a67199..5e3fab1436 100644 --- a/translate/src/test/store.jsx +++ b/translate/src/test/store.jsx @@ -28,10 +28,12 @@ export const createReduxStore = (initialState = {}) => preloadedState: initialState, }); -export const MockStore = ({ children, store, history = HISTORY }) => ( +export const MockStore = ({ children, store, history = HISTORY, resource }) => ( - {children} + + {children} + ); @@ -41,9 +43,10 @@ export const mountComponentWithStore = ( store, props = {}, history, + resource, ) => render( - + , ); diff --git a/translate/src/test/utils.jsx b/translate/src/test/utils.jsx index 2d78d023db..1c67f1617a 100644 --- a/translate/src/test/utils.jsx +++ b/translate/src/test/utils.jsx @@ -1,3 +1,4 @@ +import { FluentBundle, FluentResource } from '@fluent/bundle'; import { LocalizationProvider, Localized, @@ -5,7 +6,6 @@ import { } from '@fluent/react'; import { shallow } from 'enzyme'; import React from 'react'; -import { vi } from 'vitest'; /* * Taken from https://github.com/mozilla/addons-frontend/blob/58d1315409f1ad6dc9b979440794df44c1128455/tests/unit/helpers.js#L276 @@ -82,8 +82,13 @@ export function findLocalizedById(wrapper, id) { * Mock the @fluent/react LocalizationProvider, * which is required as a wrapper for Localization. */ -export function MockLocalizationProvider({ children }) { - const l10n = new ReactLocalization([], null); +export function MockLocalizationProvider({ children, resource }) { + const bundle = new FluentBundle('en-US'); + if (resource) { + const fluentResource = new FluentResource(resource); + bundle.addResource(fluentResource); + } + const l10n = new ReactLocalization([bundle], null); // https://github.com/projectfluent/fluent.js/issues/411 l10n.reportError = () => {};
+ +
- -