diff --git a/package.json b/package.json index 6c977fd9..e0e805fc 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "@babel/plugin-transform-runtime": "^7.11.5", "@testing-library/jest-dom": "^5.11.6", "@types/estree": "0.0.45", + "@testing-library/user-event": "^12.1.10", "apollo-boost": "^0.4.9", "apollo-cache-inmemory": "^1.6.6", "axios": "^0.20.0", diff --git a/src/__tests__/components/Form.vue b/src/__tests__/components/Form.vue index 78b83ca9..09f0c5e5 100644 --- a/src/__tests__/components/Form.vue +++ b/src/__tests__/components/Form.vue @@ -1,73 +1,73 @@ - - - Movie Review - - Title of the movie - - - Your review - - - - - Wonderful - - - - Average - - - - Awful - - - Would you recommend this movie? - - - - Submit - - - - - - + + + Movie Review + + Title of the movie + + + Your review + + + + + Wonderful + + + + Average + + + + Awful + + + + Would you recommend this movie? + + + + Submit + + + + + diff --git a/src/__tests__/fire-event.js b/src/__tests__/fire-event.js index bced573f..221ca549 100644 --- a/src/__tests__/fire-event.js +++ b/src/__tests__/fire-event.js @@ -119,6 +119,13 @@ const eventTypes = [ elementType: 'div', }, ] +beforeEach(() => { + jest.spyOn(console, 'warn').mockImplementation(() => {}) +}) + +afterEach(() => { + console.warn.mockRestore() +}) // For each event type, we assert that the right events are being triggered // when the associated fireEvent method is called. @@ -181,6 +188,32 @@ test('calling `fireEvent` directly works too', async () => { expect(emitted()).toHaveProperty('click') }) +const typingEvents = ['input', 'change'] +typingEvents.forEach(event => { + test(`fireEvent.${event} prints a warning message to use fireEvent.update instead`, async () => { + const {getByTestId} = render({ + template: ``, + }) + + await fireEvent[event](getByTestId(`test-${event}`), 'hello') + + expect(console.warn).toHaveBeenCalledTimes(1) + expect(console.warn).toHaveBeenCalledWith( + expect.stringContaining( + `Using "fireEvent.${event} may lead to unexpected results. Please use fireEvent.update() instead.`, + ), + ) + }) +}) +test('fireEvent.update does not trigger warning messages', async () => { + const {getByTestId} = render({ + template: ``, + }) + + await fireEvent.update(getByTestId('test-update'), 'hello') + + expect(console.warn).not.toHaveBeenCalled() +}) test('fireEvent.update does not crash if non-input element is passed in', async () => { const {getByText} = render({ @@ -194,4 +227,5 @@ test('fireEvent.update does not crash if non-input element is passed in', async Hi `) + expect(console.warn).not.toHaveBeenCalled() }) diff --git a/src/__tests__/form.js b/src/__tests__/form.js index 130c2a16..8b48ec01 100644 --- a/src/__tests__/form.js +++ b/src/__tests__/form.js @@ -8,6 +8,7 @@ import Form from './components/Form' // Read 'What queries should I use?' for additional context: // https://testing-library.com/docs/guide-which-query test('Review form submits', async () => { + jest.spyOn(console, 'warn').mockImplementation(() => {}) const fakeReview = { title: 'An Awesome Movie', review: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', @@ -61,4 +62,5 @@ test('Review form submits', async () => { // Assert the right event has been emitted. expect(emitted()).toHaveProperty('submit') expect(emitted().submit[0][0]).toMatchObject(fakeReview) + expect(console.warn).not.toHaveBeenCalled() }) diff --git a/src/__tests__/user-event.js b/src/__tests__/user-event.js new file mode 100644 index 00000000..21e123e6 --- /dev/null +++ b/src/__tests__/user-event.js @@ -0,0 +1,72 @@ +import '@testing-library/jest-dom' +import {render} from '@testing-library/vue' +import userEvent from '@testing-library/user-event' +import Form from './components/Form' +import Select from './components/Select' + +beforeEach(() => { + jest.spyOn(console, 'warn').mockImplementation(() => {}) +}) + +afterEach(() => { + console.warn.mockRestore() +}) + +test('User events in a form', async () => { + const fakeReview = { + title: 'An Awesome Movie', + review: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + rating: '3', + } + const {getByText, getByLabelText, emitted} = render(Form) + + const submitButton = getByText('Submit') + expect(submitButton).toBeDisabled() + + const titleInput = getByLabelText(/title of the movie/i) + await userEvent.type(titleInput, fakeReview.title) + expect(titleInput.value).toEqual(fakeReview.title) + + const textArea = getByLabelText(/Your review/i) + await userEvent.type(textArea, 'The t-rex went insane!') + expect(textArea.value).toEqual('The t-rex went insane!') + + await userEvent.clear(textArea) + expect(textArea.value).toEqual('') + await userEvent.type(textArea, fakeReview.review) + expect(textArea.value).toEqual(fakeReview.review) + + const initialSelectedRating = getByLabelText(/Awful/i) + const wonderfulRadioInput = getByLabelText(/Wonderful/i) + expect(initialSelectedRating).toBeChecked() + expect(wonderfulRadioInput).not.toBeChecked() + + await userEvent.click(wonderfulRadioInput) + expect(wonderfulRadioInput).toBeChecked() + expect(initialSelectedRating).not.toBeChecked() + + const recommendInput = getByLabelText(/Would you recommend this movie?/i) + await userEvent.click(recommendInput) + expect(recommendInput).toBeChecked() + + userEvent.tab() + expect(submitButton).toHaveFocus() + expect(submitButton).toBeEnabled() + await userEvent.type(submitButton, '{enter}') + expect(emitted().submit[0][0]).toMatchObject(fakeReview) + + expect(console.warn).not.toHaveBeenCalled() +}) + +test('selecting option with user events', async () => { + const {getByDisplayValue} = render(Select) + const select = getByDisplayValue('Tyrannosaurus') + expect(select.value).toBe('dino1') + + await userEvent.selectOptions(select, 'dino2') + expect(select.value).toBe('dino2') + + await userEvent.selectOptions(select, 'dino3') + expect(select.value).not.toBe('dino2') + expect(select.value).toBe('dino3') +}) diff --git a/src/vue-testing-library.js b/src/vue-testing-library.js index a9425f8e..def669ed 100644 --- a/src/vue-testing-library.js +++ b/src/vue-testing-library.js @@ -112,9 +112,16 @@ async function fireEvent(...args) { dtlFireEvent(...args) await waitFor(() => {}) } +const changeOrInputEventCalledDirectly = (eventValue, eventKey) => + eventValue && (eventKey === 'change' || eventKey === 'input') Object.keys(dtlFireEvent).forEach(key => { fireEvent[key] = async (...args) => { + if (changeOrInputEventCalledDirectly(args[1], key)) { + console.warn( + `Using "fireEvent.${key} may lead to unexpected results. Please use fireEvent.update() instead.`, + ) + } dtlFireEvent[key](...args) await waitFor(() => {}) }