From 1eecc59969705d16ef5a94258359e1bb32cee3d5 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Thu, 3 Mar 2022 19:38:03 -0330 Subject: [PATCH] Improve unit tests for `SrpInput` component (#13803) The library `@testing-library/user-event` has been updated to the latest beta version, so that our unit tests better model real user interactions. In particular, I found that previously the `paste` event was missing the `clipboardData` API, so it was impossible to implement any custom handling of paste events (which we will need in later PRs). See the `v14.0.0-beta.1` release notes for a list of all breaking changes [1]. The main change is that all methods now return Promises. The `paste` method has also been dramatically simplified. The unit tests have also been updated to reset all mocks before each test. These tests don't have any shared mocks, but this is generally a good practice, to ensure that tests don't develop accidental inter- dependencies. [1]: https://github.com/testing-library/user-event/releases/tag/v14.0.0-beta.1 --- package.json | 2 +- ui/components/app/srp-input/srp-input.test.js | 226 ++++++++---------- yarn.lock | 8 +- 3 files changed, 104 insertions(+), 132 deletions(-) diff --git a/package.json b/package.json index 7a1201ae5..25ca6d675 100644 --- a/package.json +++ b/package.json @@ -261,7 +261,7 @@ "@testing-library/jest-dom": "^5.11.10", "@testing-library/react": "^10.4.8", "@testing-library/react-hooks": "^3.2.1", - "@testing-library/user-event": "^13.5.0", + "@testing-library/user-event": "^14.0.0-beta.12", "@types/react": "^16.9.53", "addons-linter": "1.14.0", "babelify": "^10.0.0", diff --git a/ui/components/app/srp-input/srp-input.test.js b/ui/components/app/srp-input/srp-input.test.js index 9d528b002..af024119c 100644 --- a/ui/components/app/srp-input/srp-input.test.js +++ b/ui/components/app/srp-input/srp-input.test.js @@ -37,6 +37,10 @@ const poorlyFormattedInputs = [ ]; describe('srp-input', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + describe('onChange event', () => { it('should not fire event on render', async () => { const onChange = jest.fn(); @@ -58,7 +62,7 @@ describe('srp-input', () => { , ); getByLabelText(enLocale.secretRecoveryPhrase.message).focus(); - userEvent.keyboard(invalidInput); + await userEvent.keyboard(invalidInput); expect(onChange).toHaveBeenLastCalledWith(''); }); @@ -73,10 +77,8 @@ describe('srp-input', () => { const { getByLabelText } = renderWithLocalization( , ); - userEvent.paste( - getByLabelText(enLocale.secretRecoveryPhrase.message), - invalidInput, - ); + getByLabelText(enLocale.secretRecoveryPhrase.message).focus(); + await userEvent.paste(invalidInput); expect(onChange).toHaveBeenLastCalledWith(''); }); @@ -84,27 +86,27 @@ describe('srp-input', () => { }); describe('valid typed inputs', () => { - it('should fire event with a valid SRP', () => { + it('should fire event with a valid SRP', async () => { const onChange = jest.fn(); const { getByLabelText } = renderWithLocalization( , ); getByLabelText(enLocale.secretRecoveryPhrase.message).focus(); - userEvent.keyboard(correct); + await userEvent.keyboard(correct); expect(onChange).toHaveBeenLastCalledWith(correct); }); for (const poorlyFormattedInput of poorlyFormattedInputs) { - it(`should fire with formatted SRP when given poorly formatted valid SRP: '${poorlyFormattedInput}'`, () => { + it(`should fire with formatted SRP when given poorly formatted valid SRP: '${poorlyFormattedInput}'`, async () => { const onChange = jest.fn(); const { getByLabelText } = renderWithLocalization( , ); getByLabelText(enLocale.secretRecoveryPhrase.message).focus(); - userEvent.keyboard(poorlyFormattedInput); + await userEvent.keyboard(poorlyFormattedInput); expect(onChange).toHaveBeenLastCalledWith(correct); }); @@ -112,31 +114,27 @@ describe('srp-input', () => { }); describe('valid pasted inputs', () => { - it('should fire event with a valid SRP', () => { + it('should fire event with a valid SRP', async () => { const onChange = jest.fn(); const { getByLabelText } = renderWithLocalization( , ); - userEvent.paste( - getByLabelText(enLocale.secretRecoveryPhrase.message), - correct, - ); + getByLabelText(enLocale.secretRecoveryPhrase.message).focus(); + await userEvent.paste(correct); expect(onChange).toHaveBeenLastCalledWith(correct); }); for (const poorlyFormattedInput of poorlyFormattedInputs) { - it(`should fire with formatted SRP when given poorly formatted valid SRP: '${poorlyFormattedInput}'`, () => { + it(`should fire with formatted SRP when given poorly formatted valid SRP: '${poorlyFormattedInput}'`, async () => { const onChange = jest.fn(); const { getByLabelText } = renderWithLocalization( , ); - userEvent.paste( - getByLabelText(enLocale.secretRecoveryPhrase.message), - poorlyFormattedInput, - ); + getByLabelText(enLocale.secretRecoveryPhrase.message).focus(); + await userEvent.paste(poorlyFormattedInput); expect(onChange).toHaveBeenLastCalledWith(correct); }); @@ -158,93 +156,93 @@ describe('srp-input', () => { }); describe('typed', () => { - it('should show word requirement error if SRP has too few words', () => { + it('should show word requirement error if SRP has too few words', async () => { const onChange = jest.fn(); const { getByLabelText, queryByText } = renderWithLocalization( , ); getByLabelText(enLocale.secretRecoveryPhrase.message).focus(); - userEvent.keyboard(tooFewWords); + await userEvent.keyboard(tooFewWords); expect(queryByText(enLocale.seedPhraseReq.message)).not.toBeNull(); expect(queryByText(enLocale.invalidSeedPhrase.message)).toBeNull(); }); - it('should show word requirement error if SRP has too many words', () => { + it('should show word requirement error if SRP has too many words', async () => { const onChange = jest.fn(); const { getByLabelText, queryByText } = renderWithLocalization( , ); getByLabelText(enLocale.secretRecoveryPhrase.message).focus(); - userEvent.keyboard(tooManyWords); + await userEvent.keyboard(tooManyWords); expect(queryByText(enLocale.seedPhraseReq.message)).not.toBeNull(); expect(queryByText(enLocale.invalidSeedPhrase.message)).toBeNull(); }); - it('should show word requirement error if SRP has an unsupported word count above 12 but below 24', () => { + it('should show word requirement error if SRP has an unsupported word count above 12 but below 24', async () => { const onChange = jest.fn(); const { getByLabelText, queryByText } = renderWithLocalization( , ); getByLabelText(enLocale.secretRecoveryPhrase.message).focus(); - userEvent.keyboard(invalidWordCount); + await userEvent.keyboard(invalidWordCount); expect(queryByText(enLocale.seedPhraseReq.message)).not.toBeNull(); expect(queryByText(enLocale.invalidSeedPhrase.message)).toBeNull(); }); - it('should show invalid SRP error if SRP is correct length but has an invalid checksum', () => { + it('should show invalid SRP error if SRP is correct length but has an invalid checksum', async () => { const onChange = jest.fn(); const { getByLabelText, queryByText } = renderWithLocalization( , ); getByLabelText(enLocale.secretRecoveryPhrase.message).focus(); - userEvent.keyboard(invalidChecksum); + await userEvent.keyboard(invalidChecksum); expect(queryByText(enLocale.seedPhraseReq.message)).toBeNull(); expect(queryByText(enLocale.invalidSeedPhrase.message)).not.toBeNull(); }); - it('should show invalid SRP error if SRP is correct length and has correct checksum but has an invalid word', () => { + it('should show invalid SRP error if SRP is correct length and has correct checksum but has an invalid word', async () => { const onChange = jest.fn(); const { getByLabelText, queryByText } = renderWithLocalization( , ); getByLabelText(enLocale.secretRecoveryPhrase.message).focus(); - userEvent.keyboard(invalidWordCorrectChecksum); + await userEvent.keyboard(invalidWordCorrectChecksum); expect(queryByText(enLocale.seedPhraseReq.message)).toBeNull(); expect(queryByText(enLocale.invalidSeedPhrase.message)).not.toBeNull(); }); - it('should not show error for valid SRP', () => { + it('should not show error for valid SRP', async () => { const onChange = jest.fn(); const { getByLabelText, queryByText } = renderWithLocalization( , ); getByLabelText(enLocale.secretRecoveryPhrase.message).focus(); - userEvent.keyboard(correct); + await userEvent.keyboard(correct); expect(queryByText(enLocale.seedPhraseReq.message)).toBeNull(); expect(queryByText(enLocale.invalidSeedPhrase.message)).toBeNull(); }); for (const poorlyFormattedInput of poorlyFormattedInputs) { - it(`should not show error for poorly formatted valid SRP: '${poorlyFormattedInput}'`, () => { + it(`should not show error for poorly formatted valid SRP: '${poorlyFormattedInput}'`, async () => { const onChange = jest.fn(); const { getByLabelText, queryByText } = renderWithLocalization( , ); getByLabelText(enLocale.secretRecoveryPhrase.message).focus(); - userEvent.keyboard(poorlyFormattedInput); + await userEvent.keyboard(poorlyFormattedInput); expect(queryByText(enLocale.seedPhraseReq.message)).toBeNull(); expect(queryByText(enLocale.invalidSeedPhrase.message)).toBeNull(); @@ -253,107 +251,93 @@ describe('srp-input', () => { }); describe('pasted', () => { - it('should show word requirement error if SRP has too few words', () => { + it('should show word requirement error if SRP has too few words', async () => { const onChange = jest.fn(); const { getByLabelText, queryByText } = renderWithLocalization( , ); - userEvent.paste( - getByLabelText(enLocale.secretRecoveryPhrase.message), - tooFewWords, - ); + getByLabelText(enLocale.secretRecoveryPhrase.message).focus(); + await userEvent.paste(tooFewWords); expect(queryByText(enLocale.seedPhraseReq.message)).not.toBeNull(); expect(queryByText(enLocale.invalidSeedPhrase.message)).toBeNull(); }); - it('should show word requirement error if SRP has too many words', () => { + it('should show word requirement error if SRP has too many words', async () => { const onChange = jest.fn(); const { getByLabelText, queryByText } = renderWithLocalization( , ); - userEvent.paste( - getByLabelText(enLocale.secretRecoveryPhrase.message), - tooManyWords, - ); + getByLabelText(enLocale.secretRecoveryPhrase.message).focus(); + await userEvent.paste(tooManyWords); expect(queryByText(enLocale.seedPhraseReq.message)).not.toBeNull(); expect(queryByText(enLocale.invalidSeedPhrase.message)).toBeNull(); }); - it('should show word requirement error if SRP has an unsupported word count above 12 but below 24', () => { + it('should show word requirement error if SRP has an unsupported word count above 12 but below 24', async () => { const onChange = jest.fn(); const { getByLabelText, queryByText } = renderWithLocalization( , ); - userEvent.paste( - getByLabelText(enLocale.secretRecoveryPhrase.message), - invalidWordCount, - ); + getByLabelText(enLocale.secretRecoveryPhrase.message).focus(); + await userEvent.paste(invalidWordCount); expect(queryByText(enLocale.seedPhraseReq.message)).not.toBeNull(); expect(queryByText(enLocale.invalidSeedPhrase.message)).toBeNull(); }); - it('should show invalid SRP error if SRP is correct length but has an invalid checksum', () => { + it('should show invalid SRP error if SRP is correct length but has an invalid checksum', async () => { const onChange = jest.fn(); const { getByLabelText, queryByText } = renderWithLocalization( , ); - userEvent.paste( - getByLabelText(enLocale.secretRecoveryPhrase.message), - invalidChecksum, - ); + getByLabelText(enLocale.secretRecoveryPhrase.message).focus(); + await userEvent.paste(invalidChecksum); expect(queryByText(enLocale.seedPhraseReq.message)).toBeNull(); expect(queryByText(enLocale.invalidSeedPhrase.message)).not.toBeNull(); }); - it('should show invalid SRP error if SRP is correct length and has correct checksum but has an invalid word', () => { + it('should show invalid SRP error if SRP is correct length and has correct checksum but has an invalid word', async () => { const onChange = jest.fn(); const { getByLabelText, queryByText } = renderWithLocalization( , ); - userEvent.paste( - getByLabelText(enLocale.secretRecoveryPhrase.message), - invalidWordCorrectChecksum, - ); + getByLabelText(enLocale.secretRecoveryPhrase.message).focus(); + await userEvent.paste(invalidWordCorrectChecksum); expect(queryByText(enLocale.seedPhraseReq.message)).toBeNull(); expect(queryByText(enLocale.invalidSeedPhrase.message)).not.toBeNull(); }); - it('should not show error for valid SRP', () => { + it('should not show error for valid SRP', async () => { const onChange = jest.fn(); const { getByLabelText, queryByText } = renderWithLocalization( , ); - userEvent.paste( - getByLabelText(enLocale.secretRecoveryPhrase.message), - correct, - ); + getByLabelText(enLocale.secretRecoveryPhrase.message).focus(); + await userEvent.paste(correct); expect(queryByText(enLocale.seedPhraseReq.message)).toBeNull(); expect(queryByText(enLocale.invalidSeedPhrase.message)).toBeNull(); }); for (const poorlyFormattedInput of poorlyFormattedInputs) { - it(`should not show error for poorly formatted valid SRP: '${poorlyFormattedInput}'`, () => { + it(`should not show error for poorly formatted valid SRP: '${poorlyFormattedInput}'`, async () => { const onChange = jest.fn(); const { getByLabelText, queryByText } = renderWithLocalization( , ); - userEvent.paste( - getByLabelText(enLocale.secretRecoveryPhrase.message), - poorlyFormattedInput, - ); + getByLabelText(enLocale.secretRecoveryPhrase.message).focus(); + await userEvent.paste(poorlyFormattedInput); expect(queryByText(enLocale.seedPhraseReq.message)).toBeNull(); expect(queryByText(enLocale.invalidSeedPhrase.message)).toBeNull(); @@ -384,7 +368,7 @@ describe('srp-input', () => { , ); getByLabelText(enLocale.secretRecoveryPhrase.message).focus(); - userEvent.keyboard(correct); + await userEvent.keyboard(correct); expect(queryByText(correct)).toBeNull(); }); @@ -395,10 +379,8 @@ describe('srp-input', () => { const { getByLabelText, queryByText } = renderWithLocalization( , ); - userEvent.paste( - getByLabelText(enLocale.secretRecoveryPhrase.message), - correct, - ); + getByLabelText(enLocale.secretRecoveryPhrase.message).focus(); + await userEvent.paste(correct); expect(queryByText(correct)).toBeNull(); }); @@ -413,10 +395,10 @@ describe('srp-input', () => { getByRole, queryByText, } = renderWithLocalization(); - userEvent.click(getByRole('checkbox')); + await userEvent.click(getByRole('checkbox')); getByLabelText(enLocale.secretRecoveryPhrase.message).focus(); - userEvent.keyboard(correct); - userEvent.click(getByRole('checkbox')); + await userEvent.keyboard(correct); + await userEvent.click(getByRole('checkbox')); expect(queryByText(correct)).toBeNull(); }); @@ -429,36 +411,17 @@ describe('srp-input', () => { getByRole, queryByText, } = renderWithLocalization(); - userEvent.click(getByRole('checkbox')); - userEvent.paste( - getByLabelText(enLocale.secretRecoveryPhrase.message), - correct, - ); - userEvent.click(getByRole('checkbox')); + await userEvent.click(getByRole('checkbox')); + getByLabelText(enLocale.secretRecoveryPhrase.message).focus(); + await userEvent.paste(correct); + await userEvent.click(getByRole('checkbox')); expect(queryByText(correct)).toBeNull(); }); }); describe('shown after input', () => { - it('should show typed SRP', () => { - const onChange = jest.fn(); - - const { - getByLabelText, - getByRole, - queryByText, - } = renderWithLocalization(); - userEvent.paste( - getByLabelText(enLocale.secretRecoveryPhrase.message), - correct, - ); - userEvent.click(getByRole('checkbox')); - - expect(queryByText(correct)).not.toBeNull(); - }); - - it('should show pasted SRP', () => { + it('should show typed SRP', async () => { const onChange = jest.fn(); const { @@ -467,15 +430,30 @@ describe('srp-input', () => { queryByText, } = renderWithLocalization(); getByLabelText(enLocale.secretRecoveryPhrase.message).focus(); - userEvent.keyboard(correct); - userEvent.click(getByRole('checkbox')); + await userEvent.paste(correct); + await userEvent.click(getByRole('checkbox')); + + expect(queryByText(correct)).not.toBeNull(); + }); + + it('should show pasted SRP', async () => { + const onChange = jest.fn(); + + const { + getByLabelText, + getByRole, + queryByText, + } = renderWithLocalization(); + getByLabelText(enLocale.secretRecoveryPhrase.message).focus(); + await userEvent.keyboard(correct); + await userEvent.click(getByRole('checkbox')); expect(queryByText(correct)).not.toBeNull(); }); }); describe('shown before input', () => { - it('should show typed SRP', () => { + it('should show typed SRP', async () => { const onChange = jest.fn(); const { @@ -483,16 +461,14 @@ describe('srp-input', () => { getByRole, queryByText, } = renderWithLocalization(); - userEvent.click(getByRole('checkbox')); - userEvent.paste( - getByLabelText(enLocale.secretRecoveryPhrase.message), - correct, - ); + await userEvent.click(getByRole('checkbox')); + getByLabelText(enLocale.secretRecoveryPhrase.message).focus(); + await userEvent.paste(correct); expect(queryByText(correct)).not.toBeNull(); }); - it('should show pasted SRP', () => { + it('should show pasted SRP', async () => { const onChange = jest.fn(); const { @@ -500,9 +476,9 @@ describe('srp-input', () => { getByRole, queryByText, } = renderWithLocalization(); - userEvent.click(getByRole('checkbox')); + await userEvent.click(getByRole('checkbox')); getByLabelText(enLocale.secretRecoveryPhrase.message).focus(); - userEvent.keyboard(correct); + await userEvent.keyboard(correct); expect(queryByText(correct)).not.toBeNull(); }); @@ -510,7 +486,7 @@ describe('srp-input', () => { }); describe('clear after paste', () => { - it('should not clear clipboard after typing hidden SRP', () => { + it('should not clear clipboard after typing hidden SRP', async () => { const onChange = jest.fn(); const writeTextSpy = jest.spyOn(window.navigator.clipboard, 'writeText'); @@ -518,52 +494,48 @@ describe('srp-input', () => { , ); getByLabelText(enLocale.secretRecoveryPhrase.message).focus(); - userEvent.keyboard(correct); + await userEvent.keyboard(correct); expect(writeTextSpy).not.toHaveBeenCalled(); }); - it('should not clear clipboard after typing shown SRP', () => { + it('should not clear clipboard after typing shown SRP', async () => { const onChange = jest.fn(); const writeTextSpy = jest.spyOn(window.navigator.clipboard, 'writeText'); const { getByLabelText, getByRole } = renderWithLocalization( , ); - userEvent.click(getByRole('checkbox')); + await userEvent.click(getByRole('checkbox')); getByLabelText(enLocale.secretRecoveryPhrase.message).focus(); - userEvent.keyboard(correct); + await userEvent.keyboard(correct); expect(writeTextSpy).not.toHaveBeenCalled(); }); - it('should clear the clipboard after pasting hidden SRP', () => { + it('should clear the clipboard after pasting hidden SRP', async () => { const onChange = jest.fn(); const writeTextSpy = jest.spyOn(window.navigator.clipboard, 'writeText'); const { getByLabelText } = renderWithLocalization( , ); - userEvent.paste( - getByLabelText(enLocale.secretRecoveryPhrase.message), - correct, - ); + getByLabelText(enLocale.secretRecoveryPhrase.message).focus(); + await userEvent.paste(correct); expect(writeTextSpy).toHaveBeenCalledWith(''); }); - it('should clear the clipboard after pasting shown SRP', () => { + it('should clear the clipboard after pasting shown SRP', async () => { const onChange = jest.fn(); const writeTextSpy = jest.spyOn(window.navigator.clipboard, 'writeText'); const { getByLabelText, getByRole } = renderWithLocalization( , ); - userEvent.click(getByRole('checkbox')); - userEvent.paste( - getByLabelText(enLocale.secretRecoveryPhrase.message), - correct, - ); + await userEvent.click(getByRole('checkbox')); + getByLabelText(enLocale.secretRecoveryPhrase.message).focus(); + await userEvent.paste(correct); expect(writeTextSpy).toHaveBeenCalledWith(''); }); diff --git a/yarn.lock b/yarn.lock index c19d3128d..962bbf8b3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4176,10 +4176,10 @@ "@babel/runtime" "^7.10.3" "@testing-library/dom" "^7.17.1" -"@testing-library/user-event@^13.5.0": - version "13.5.0" - resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-13.5.0.tgz#69d77007f1e124d55314a2b73fd204b333b13295" - integrity sha512-5Kwtbo3Y/NowpkbRuSepbyMFkZmHgD+vPzYB/RJ4oxt5Gj/avFFBYjhw27cqSVPVw/3a67NK1PbiIr9k4Gwmdg== +"@testing-library/user-event@^14.0.0-beta.12": + version "14.0.0-beta.12" + resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.0.0-beta.12.tgz#8df662578e49371fd80d5923d92f3c378f7dd927" + integrity sha512-vFZQBBzO14bJseKAS78Ae/coSuJbgrs7ywRZw88Hc52Le8RJGehdxR4w25Oj7QVNpZZpz0R6q1zMVdYGtPbd2A== dependencies: "@babel/runtime" "^7.12.5"