diff --git a/.eslintrc.js b/.eslintrc.js
index cf2c75f3..356e7ed8 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -1,7 +1,8 @@
module.exports = {
extends: [
'./node_modules/kcd-scripts/eslint.js',
- 'plugin:vue/recommended',
+ 'plugin:vue/vue3-recommended',
+ 'plugin:testing-library/vue',
'prettier/vue',
],
plugins: ['vue'],
@@ -12,5 +13,6 @@ module.exports = {
'testing-library/no-dom-import': 'off',
'testing-library/prefer-screen-queries': 'off',
'testing-library/no-manual-cleanup': 'off',
+ 'testing-library/no-await-sync-events': 'off',
},
}
diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml
new file mode 100644
index 00000000..36e4130e
--- /dev/null
+++ b/.github/workflows/validate.yml
@@ -0,0 +1,81 @@
+name: validate
+on:
+ push:
+ branches:
+ [
+ '+([0-9])?(.{+([0-9]),x}).x',
+ 'master',
+ 'next',
+ 'next-major',
+ 'beta',
+ 'alpha',
+ '!all-contributors/**',
+ ]
+ pull_request:
+ branches-ignore: ['all-contributors/**']
+jobs:
+ main:
+ strategy:
+ matrix:
+ node: [10.13, 12, 14]
+ runs-on: ubuntu-latest
+ steps:
+ - name: ⬇️ Checkout repo
+ uses: actions/checkout@v2
+
+ - name: ⎔ Setup node
+ uses: actions/setup-node@v1
+ with:
+ node-version: ${{ matrix.node }}
+
+ - name: 📥 Download deps
+ uses: bahmutov/npm-install@v1
+ with:
+ useLockFile: false
+
+ - name: ▶️ Run validate script
+ run: npm run validate
+
+ - name: ⬆️ Upload coverage report
+ uses: codecov/codecov-action@v1
+
+ release:
+ needs: main
+ runs-on: ubuntu-latest
+ if:
+ ${{ github.repository == 'testing-library/vue-testing-library' &&
+ contains('refs/heads/master,refs/heads/beta,refs/heads/next,refs/heads/alpha',
+ github.ref) && github.event_name == 'push' }}
+ steps:
+ - name: ⬇️ Checkout repo
+ uses: actions/checkout@v2
+
+ - name: ⎔ Setup node
+ uses: actions/setup-node@v1
+ with:
+ node-version: 14
+
+ - name: 📥 Download deps
+ uses: bahmutov/npm-install@v1
+ with:
+ useLockFile: false
+
+ - name: 🏗 Run build script
+ run: npm run build
+
+ - name: 🚀 Release
+ uses: cycjimmy/semantic-release-action@v2
+ with:
+ semantic_version: 17
+ branches: |
+ [
+ '+([0-9])?(.{+([0-9]),x}).x',
+ 'master',
+ 'next',
+ 'next-major',
+ {name: 'beta', prerelease: true},
+ {name: 'alpha', prerelease: true}
+ ]
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 91989b6d..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,28 +0,0 @@
-sudo: false
-language: node_js
-cache: npm
-notifications:
- email: false
-node_js:
- - 10.18
- - 12
- - 14
- - 15
-before_install:
- - nvm install-latest-npm
-install: npm install
-script:
- - npm run validate
- - npx codecov@3
-branches:
- only:
- - master
- - beta
- - next
-
-jobs:
- include:
- - stage: release
- node_js: 14
- script: kcd-scripts travis-release
- if: fork = false
diff --git a/README.md b/README.md
index 735f0594..077ed5d3 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
-
Vue Testing Library
+
Vue Testing Library for Vue 3
@@ -32,7 +32,6 @@
[![GitHub version][github-badge]][github]
[![npm version][npm-badge]][npm]
[![Discord][discord-badge]][discord]
-
[![MIT License][license-badge]][license]
@@ -66,7 +65,7 @@ project's `devDependencies`:
npm install --save-dev @testing-library/vue
```
-This library has `peerDependencies` listings for `Vue` and
+This library has `peerDependencies` listings for `Vue 3` and
`vue-template-compiler`.
You may also be interested in installing `jest-dom` so you can use [the custom
@@ -76,10 +75,8 @@ Jest matchers][jest-dom].
```html
-
-
Times clicked: {{ count }}
-
increment
-
+ Times clicked: {{ count }}
+ increment
diff --git a/src/__tests__/components/Form.vue b/src/__tests__/components/Form.vue
index 809bf316..ffa729f2 100644
--- a/src/__tests__/components/Form.vue
+++ b/src/__tests__/components/Form.vue
@@ -1,44 +1,42 @@
-
-
Movie Review
-
-
+ Submit
+
diff --git a/src/__tests__/components/Select.vue b/src/__tests__/components/Select.vue
index a6bd39cb..c5b3c63d 100644
--- a/src/__tests__/components/Select.vue
+++ b/src/__tests__/components/Select.vue
@@ -18,8 +18,8 @@
export default {
data() {
return {
- selectedDino: 'dino1'
+ selectedDino: 'dino1',
}
- }
+ },
}
diff --git a/src/__tests__/debug.js b/src/__tests__/debug.js
index d870244f..c17d4cd2 100644
--- a/src/__tests__/debug.js
+++ b/src/__tests__/debug.js
@@ -57,3 +57,19 @@ test('debug pretty prints multiple nodes with the given parameter', () => {
expect.stringContaining('Lorem ipsum dolor sit amet'),
)
})
+
+test('allows same arguments as prettyDOM', () => {
+ const {debug, container} = render(HelloWorld)
+
+ // debug accepts a maxLength and an options parameters:
+ // https://testing-library.com/docs/dom-testing-library/api-helpers#prettydom
+ debug(container, 6, {highlight: false})
+
+ expect(console.log).toHaveBeenCalledTimes(1)
+ expect(console.log.mock.calls[0]).toMatchInlineSnapshot(`
+ Array [
+
+ ...,
+ ]
+ `)
+})
diff --git a/src/__tests__/fire-event.js b/src/__tests__/fire-event.js
index a9b6d537..7e18a512 100644
--- a/src/__tests__/fire-event.js
+++ b/src/__tests__/fire-event.js
@@ -121,6 +121,14 @@ const eventTypes = [
},
]
+beforeEach(() => {
+ jest.spyOn(console, 'warn').mockImplementation(() => {})
+})
+
+afterEach(() => {
+ console.warn.mockRestore()
+})
+
const capitalize = str => str.charAt(0).toUpperCase() + str.slice(1)
// For each event type, we assert that the right events are being triggered
@@ -184,6 +192,30 @@ test('calling `fireEvent` directly works too', async () => {
expect(emitted()).toHaveProperty('click')
})
+test.each(['input', 'change'])(
+ `fireEvent.%s prints a warning message to use fireEvent.update instead`,
+ async event => {
+ const {getByRole} = render({template: `
`})
+
+ await fireEvent[event](getByRole('textbox'), 'hello')
+
+ expect(console.warn).toHaveBeenCalledTimes(1)
+ expect(console.warn).toHaveBeenCalledWith(
+ `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({
template: `
Hi
`,
@@ -196,4 +228,6 @@ 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..58493bed 100644
--- a/src/__tests__/form.js
+++ b/src/__tests__/form.js
@@ -8,6 +8,8 @@ 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 +63,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 36e86ac9..b4e2eaf0 100644
--- a/src/vue-testing-library.js
+++ b/src/vue-testing-library.js
@@ -4,7 +4,7 @@ import merge from 'lodash.merge'
import {
getQueriesForElement,
- logDOM,
+ prettyDOM,
waitFor,
fireEvent as dtlFireEvent,
} from '@testing-library/dom'
@@ -74,8 +74,10 @@ function render(
return {
container,
baseElement,
- debug: (el = baseElement) =>
- Array.isArray(el) ? el.forEach(e => logDOM(e)) : logDOM(el),
+ debug: (el = baseElement, maxLength, options) =>
+ Array.isArray(el)
+ ? el.forEach(e => console.log(prettyDOM(e, maxLength, options)))
+ : console.log(prettyDOM(el, maxLength, options)),
unmount: () => wrapper.unmount(),
html: () => wrapper.html(),
emitted: () => wrapper.emitted(),
@@ -121,8 +123,20 @@ async function fireEvent(...args) {
await waitFor(() => {})
}
+function suggestUpdateIfNecessary(eventValue, eventKey) {
+ const changeOrInputEventCalledDirectly =
+ eventValue && (eventKey === 'change' || eventKey === 'input')
+
+ if (changeOrInputEventCalledDirectly) {
+ console.warn(
+ `Using fireEvent.${eventKey}() may lead to unexpected results. Please use fireEvent.update() instead.`,
+ )
+ }
+}
+
Object.keys(dtlFireEvent).forEach(key => {
fireEvent[key] = async (...args) => {
+ suggestUpdateIfNecessary(args[1], key)
dtlFireEvent[key](...args)
await waitFor(() => {})
}