From 2a38473347da6118f45c51195444312f61fc6d75 Mon Sep 17 00:00:00 2001 From: "Kent C. Dodds" Date: Wed, 4 Mar 2020 13:43:19 -0700 Subject: [PATCH 1/4] feat: update DOM Testing Library to latest BREAKING CHANGE: This updates @testing-library/dom, please check the changelog for the most recent version of that package to know whether you are impacted by these changes. --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 64b8520..2adac91 100644 --- a/package.json +++ b/package.json @@ -41,8 +41,8 @@ "author": "Kent C. Dodds (http://kentcdodds.com/)", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.8.4", - "@testing-library/dom": "^6.15.0", + "@babel/runtime": "^7.8.7", + "@testing-library/dom": "^7.0.2", "@types/testing-library__cypress": "^5.0.3" }, "devDependencies": { From b783cbf9625fe181adebe1b66e1650678b52de1b Mon Sep 17 00:00:00 2001 From: "Kent C. Dodds" Date: Wed, 4 Mar 2020 13:44:03 -0700 Subject: [PATCH 2/4] fix(node): Drop Node 8. Closes #113 BREAKING CHANGE: Node 10 or greater is now required to use this package --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2adac91..f686f4a 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "Simple and complete custom Cypress commands and utilities that encourage good testing practices.", "main": "dist/index.js", "engines": { - "node": ">=8" + "node": ">=10" }, "scripts": { "build": "kcd-scripts build", From 55fa553db9f67920a1438b4af5f427a10a8567a3 Mon Sep 17 00:00:00 2001 From: "Kent C. Dodds" Date: Wed, 4 Mar 2020 13:53:58 -0700 Subject: [PATCH 3/4] chore: cleanup repo (#123) --- .gitattributes | 3 +-- .gitignore | 4 ---- .prettierignore | 3 +-- .prettierrc | 11 ----------- .prettierrc.js | 1 + package.json | 14 +++++++------- 6 files changed, 10 insertions(+), 26 deletions(-) delete mode 100644 .prettierrc create mode 100644 .prettierrc.js diff --git a/.gitattributes b/.gitattributes index 391f0a4..6313b56 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1 @@ -* text=auto -*.js text eol=lf +* text=auto eol=lf diff --git a/.gitignore b/.gitignore index 11f40c6..dd6ebe2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,7 @@ node_modules coverage dist -.opt-in -.opt-out .DS_Store -.eslintcache -.idea/ # these cause more harm than good # when working with contributors diff --git a/.prettierignore b/.prettierignore index 30117ea..9c62828 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,4 +1,3 @@ -package.json node_modules -dist coverage +dist diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index f368519..0000000 --- a/.prettierrc +++ /dev/null @@ -1,11 +0,0 @@ -{ - "printWidth": 80, - "tabWidth": 2, - "useTabs": false, - "semi": false, - "singleQuote": true, - "trailingComma": "all", - "bracketSpacing": false, - "jsxBracketSameLine": false, - "proseWrap": "always" -} diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 0000000..4679d9b --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1 @@ +module.exports = require('kcd-scripts/prettier') diff --git a/package.json b/package.json index f686f4a..08a9187 100644 --- a/package.json +++ b/package.json @@ -9,15 +9,15 @@ "scripts": { "build": "kcd-scripts build", "lint": "kcd-scripts lint", + "setup": "npm install && npm run validate -s", "test": "npm-run-all --parallel test:unit test:cypress", + "test:cypress": "npm run test:cypress:run", + "test:cypress:open": "cypress open", + "test:cypress:run": "cypress run", "test:unit": "kcd-scripts test --no-watch", "test:unit:watch": "kcd-scripts test", - "test:cypress:run": "cypress run", - "test:cypress:open": "cypress open", - "test:cypress": "npm run test:cypress:run", "test:cypress:dev": "npm run test:cypress:open", - "validate": "kcd-scripts validate build,lint,test", - "setup": "npm install && npm run validate -s" + "validate": "kcd-scripts validate build,lint,test" }, "husky": { "hooks": { @@ -38,7 +38,7 @@ "end-to-end", "e2e" ], - "author": "Kent C. Dodds (http://kentcdodds.com/)", + "author": "Kent C. Dodds (https://kentcdodds.com)", "license": "MIT", "dependencies": { "@babel/runtime": "^7.8.7", @@ -68,7 +68,7 @@ ], "repository": { "type": "git", - "url": "https://github.com/kentcdodds/cypress-testing-library.git" + "url": "https://github.com/kentcdodds/cypress-testing-library" }, "bugs": { "url": "https://github.com/kentcdodds/cypress-testing-library/issues" From 436f51c7562bea7c8920041d9136c9104ce7854c Mon Sep 17 00:00:00 2001 From: Nicholas Boll Date: Thu, 12 Mar 2020 18:13:53 -0500 Subject: [PATCH 4/4] fix: remove deprecated `query*` queries (#130) BREAKING CHANGE: Remove `query*` queries. Throw an error instead. Fixing requires updating all `query*` to `find*` queries. In practice this means replacing `cy.query` with `cy.find` BREAKING CHANGE: Remove fallback that retries chaiined query that assumes no previous subject. In practice this means starting new chains if no previous subject is required. ```js // Before cy.findByText('Foo') .click() .findByText('Bar') // Element with 'Bar' text is not a child of an element with 'Foo' text .click() // After cy.findByText('Foo') .click() cy.findByText('Bar') .click() ``` --- README.md | 69 ++++---- cypress/integration/configure.spec.js | 18 -- cypress/integration/find.spec.js | 22 +-- cypress/integration/query.spec.js | 231 ++++---------------------- src/index.js | 89 ++++------ 5 files changed, 100 insertions(+), 329 deletions(-) delete mode 100644 cypress/integration/configure.spec.js diff --git a/README.md b/README.md index 2122935..e93538e 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,6 @@ This allows you to use all the useful - - [Installation](#installation) - [With TypeScript](#with-typescript) - [Usage](#usage) @@ -79,7 +78,8 @@ npm install --save-dev @testing-library/cypress ### With TypeScript -Typings are defined in `@types/testing-library__cypress` at [DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/testing-library__cypress), +Typings are defined in `@types/testing-library__cypress` at +[DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/testing-library__cypress), and should be added as follows in `tsconfig.json`: ```json @@ -100,11 +100,12 @@ Add this line to your project's `cypress/support/commands.js`: import '@testing-library/cypress/add-commands' ``` -You can now use all of `DOM Testing Library`'s `findBy`, `findAllBy`, `queryBy` -and `queryAllBy` commands. +You can now use all of `DOM Testing Library`'s `findBy` and `findAllBy` +commands. [See the `DOM Testing Library` docs for reference](https://testing-library.com) -You can find [all Library definitions here](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/testing-library__cypress/index.d.ts). +You can find +[all Library definitions here](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/testing-library__cypress/index.d.ts). To configure DOM Testing Library, use the following custom command: @@ -113,46 +114,43 @@ cy.configureCypressTestingLibrary(config) ``` To show some simple examples (from -[cypress/integration/query.spec.js](cypress/integration/query.spec.js) or [cypress/integration/find.spec.js](cypress/integration/find.spec.js)): +[cypress/integration/find.spec.js](cypress/integration/find.spec.js)): ```javascript -cy.queryAllByText('Button Text').should('exist') -cy.queryAllByText('Non-existing Button Text').should('not.exist') -cy.queryAllByLabelText('Label text', {timeout: 7000}).should('exist') -cy.findAllByText('Jackie Chan').click(); +cy.findAllByText('Button Text').should('exist') +cy.findAllByText('Non-existing Button Text').should('not.exist') +cy.findAllByLabelText('Label text', {timeout: 7000}).should('exist') +cy.findAllByText('Jackie Chan').click() // findAllByText _inside_ a form element -cy.get('form').within(() => { - cy.findAllByText('Button Text').should('exist') -}) -cy.get('form').then(subject => { - cy.findAllByText('Button Text', {container: subject}).should('exist') -}) -cy.get('form').findAllByText('Button Text').should('exist') +cy.get('form') + .findAllByText('Button Text') + .should('exist') ``` ### Differences from DOM Testing Library `Cypress Testing Library` supports both jQuery elements and DOM nodes. This is necessary because Cypress uses jQuery elements, while `DOM Testing Library` -expects DOM nodes. When you pass a jQuery element as `container`, it will get -the first DOM node from the collection and use that as the `container` parameter -for the `DOM Testing Library` functions. - -`get*` queries are disabled. `find*` queries do not use the Promise API of -`DOM Testing Library`, but instead forward to the `get*` queries and use Cypress' -built-in retryability using error messages from `get*` APIs to forward as error -messages if a query fails. `query*` also uses `get*` APIs, but disables retryability. - -`findAll*` can select more than one element and is closer in functionality to how -Cypress built-in commands work. `findAll*` is preferred to `find*` queries. -`find*` commands will fail if more than one element is found that matches the criteria -which is not how built-in Cypress commands work, but is provided for closer compatibility -to other Testing Libraries. - -Cypress handles actions when there is only one element found. For example, the following -will work without having to limit to only 1 returned element. The `cy.click` will -automatically fail if more than 1 element is returned by the `findAllByText`: +expects DOM nodes. When you chain a query, it will get the first DOM node from +`subject` of the collection and use that as the `container` parameter for the +`DOM Testing Library` functions. + +`get*` and `query*` queries are disabled. `find*` queries do not use the Promise +API of `DOM Testing Library`, but instead forward to the `get*` queries and use +Cypress' built-in retryability using error messages from `get*` APIs to forward +as error messages if a query fails. + +`findAll*` can select more than one element and is closer in functionality to +how Cypress built-in commands work. `find*` commands will fail if more than one +element is found that matches the criteria which is not how built-in Cypress +commands work, but is provided for closer compatibility to other Testing +Libraries. + +Cypress handles actions when there is only one element found. For example, the +following will work without having to limit to only 1 returned element. The +`cy.click` will automatically fail if more than 1 element is returned by the +`findAllByText`: ```javascript cy.findAllByText('Some Text').click() @@ -237,6 +235,7 @@ Thanks goes to these people ([emoji key][emojis]): + This project follows the [all-contributors][all-contributors] specification. diff --git a/cypress/integration/configure.spec.js b/cypress/integration/configure.spec.js deleted file mode 100644 index beb113e..0000000 --- a/cypress/integration/configure.spec.js +++ /dev/null @@ -1,18 +0,0 @@ -/// -describe('configuring fallback globally', () => { - beforeEach(() => { - cy.visit('cypress/fixtures/test-app/') - cy.configureCypressTestingLibrary({ fallbackRetryWithoutPreviousSubject: false }) - }) - - it('findByText with a previous subject', () => { - cy.get('#nested') - .findByText('Button Text 1') - .should('not.exist') - cy.get('#nested') - .findByText('Button Text 2') - .should('exist') - }) -}) - -/* global cy */ diff --git a/cypress/integration/find.spec.js b/cypress/integration/find.spec.js index 96fc2a0..d69443b 100644 --- a/cypress/integration/find.spec.js +++ b/cypress/integration/find.spec.js @@ -108,7 +108,7 @@ describe('find* dom-testing-library commands', () => { it('findByText with a previous subject', () => { cy.get('#nested') - .findByText('Button Text 1', {fallbackRetryWithoutPreviousSubject: false}) + .findByText('Button Text 1') .should('not.exist') cy.get('#nested') .findByText('Button Text 2') @@ -188,15 +188,6 @@ describe('find* dom-testing-library commands', () => { cy.findByText(/^Button Text/i, {timeout: 100}) }) - it('findByText should not break existing code', () => { - cy.window() - .findByText('Button Text 1') - .should('exist') - cy.location() - .findByText('Button Text 1') - .should('exist') - }) - it('findByText should show as a parent command if it starts a chain', () => { const assertLog = (attrs, log) => { if (log.get('name') === 'findByText') { @@ -218,17 +209,6 @@ describe('find* dom-testing-library commands', () => { cy.on('log:added', assertLog) cy.get('body').findByText('Button Text 1') }) - - it('should chain findBy* with subject different of document, element or window', () => { - cy.wrap(true) - .should('be.true') - .findByText('Error message') - .findByLabelText(/Required/i) - .type('something') - .findByText('Submit') - .queryByText('Error message') - .should('not.be.visible') - }) }) /* global cy */ diff --git a/cypress/integration/query.spec.js b/cypress/integration/query.spec.js index 24671d3..6075f89 100644 --- a/cypress/integration/query.spec.js +++ b/cypress/integration/query.spec.js @@ -1,208 +1,49 @@ -/// -describe('query* dom-testing-library commands', () => { +describe('get* queries should error', () => { beforeEach(() => { cy.visit('cypress/fixtures/test-app/') }) - // Test each of the types of queries: LabelText, PlaceholderText, Text, DisplayValue, AltText, Title, Role, TestId - - it('queryByLabelText', () => { - cy.queryByLabelText('Label 1') - .click() - .type('Hello Input Labelled By Id') - }) - - it('queryAllByLabelText', () => { - cy.queryAllByLabelText(/^Label \d$/).should('have.length', 2) - }) - - it('queryByPlaceholderText', () => { - cy.queryByPlaceholderText('Input 1') - .click() - .type('Hello Placeholder') - }) - - it('queryAllByPlaceholderText', () => { - cy.queryAllByPlaceholderText(/^Input \d$/).should('have.length', 2) - }) - - it('queryByText', () => { - cy.queryByText('Button Text 1') - .click() - .should('contain', 'Button Clicked') - }) - - it('queryAllByText', () => { - cy.queryAllByText(/^Button Text \d$/) - .should('have.length', 2) - .click({ multiple: true }) - .should('contain', 'Button Clicked') - }) - - it('queryByDisplayValue', () => { - cy.queryByDisplayValue('Display Value 1') - .click() - .clear() - .type('Some new text') - }) - - it('queryAllByDisplayValue', () => { - cy.queryAllByDisplayValue(/^Display Value \d$/) - .should('have.length', 2) - }) - - it('queryByAltText', () => { - cy.queryByAltText('Image Alt Text 1').click() - }) - - it('queryAllByAltText', () => { - cy.queryAllByAltText(/^Image Alt Text \d$/).should('have.length', 2) - }) - - it('queryByTitle', () => { - cy.queryByTitle('Title 1').click() - }) - - it('queryAllByTitle', () => { - cy.queryAllByTitle(/^Title \d$/).should('have.length', 2) - }) - - it('queryByRole', () => { - cy.queryByRole('dialog').click() - }) - - it('queryAllByRole', () => { - cy.queryAllByRole(/^dialog/).should('have.length', 2) - }) - - it('queryByTestId', () => { - cy.queryByTestId('image-with-random-alt-tag-1').click() - }) - - it('queryAllByTestId', () => { - cy.queryAllByTestId(/^image-with-random-alt-tag-\d$/).should('have.length', 2) - }) - - /* Test the behaviour around these queries */ - - it('queryByText should show a deprecation warning', () => { - let addedLog - function addLog (_, log) { - addedLog = log - cy.off('log:added', addLog) - } - cy.on('log:added', addLog) - - cy.queryByText('Button Text 1') - // query* doesn't retry more than once, but our log could be updated later depending on timing. - // the `cy.wrap` adds a retryable step in to deal with possible timing issues of the assertions. - cy.wrap(null).should(() => { - const attrs = addedLog.toJSON() - expect(attrs).to.have.property('state', 'failed') - expect(attrs).to.have.nested.property('err.message') - expect(attrs.err.message).to.contain(`@testing-library/cypress is deprecating all 'query*' commands.`) + const queryPrefixes = ['By', 'AllBy'] + const queryTypes = [ + 'LabelText', + 'PlaceholderText', + 'Text', + 'DisplayValue', + 'AltText', + 'Title', + 'Role', + 'TestId', + ] + + queryPrefixes.forEach(queryPrefix => { + queryTypes.forEach(queryType => { + const obsoleteQueryName = `query${queryPrefix + queryType}` + const preferredQueryName = `find${queryPrefix + queryType}` + it(`${obsoleteQueryName} should error and suggest using ${preferredQueryName}`, () => { + const errorMessage = `You used '${obsoleteQueryName}' which has been removed from Cypress Testing Library because it does not make sense in this context. Please use '${preferredQueryName}' instead.` + cy.on('fail', err => { + expect(err.message).to.eq(errorMessage) + }) + + cy[`${obsoleteQueryName}`]('Irrelevant') }) - }) - - it('queryByText with .should(\'not.exist\')', () => { - cy.queryAllByText(/^Button Text \d$/).should('exist') - cy.queryByText('Non-existing Button Text', {timeout: 100}).should('not.exist') - }) - - it('queryByText within', () => { - cy.get('#nested').within(() => { - cy.queryByText('Button Text 2').click() - }) - }) - - it('queryByText should set the Cypress element to the found element', (done) => { - // This test is a little strange since snapshots show what element - // is selected, but snapshots themselves don't give access to those - // elements. I had to make the implementation specific so that the `$el` - // is the `subject` when the log is added and the `$el` is the `value` - // when the log is changed. It would be better to extract the `$el` from - // each snapshot - - cy.on('log:changed', (attrs, log) => { - if (log.get('name') === 'queryByText') { - expect(log.get('$el')).to.have.text('Button Text 1') - done() - } - }) - cy.queryByText('Button Text 1') - }) - - it('query* will return immediately, and never retry', () => { - cy.queryByText('Next Page').click() + it(`${obsoleteQueryName} should not log more than once`, () => { + let logCount = 0 + cy.on('log:added', (attrs, log) => { + if (log.get('name') === obsoleteQueryName) { + logCount = logCount + 1 + } + }) - const errorMessage = `Unable to find an element with the text: New Page Loaded.` - cy.on('fail', err => { - expect(err.message).to.contain(errorMessage) - }) - - cy.queryByText('New Page Loaded', { timeout: 300 }).should('exist') - }) + cy.on('fail', _ => { + expect(logCount).to.equal(1) + cy.removeAllListeners('log:added') + }) - it('query* in container', () => { - return cy.get('#nested') - .then(subject => { - cy.queryByText(/^Button Text/, {container: subject}).click() + cy[`${obsoleteQueryName}`]('Irrelevant') }) - }) - - it('queryByText can return no result, and should not error', () => { - const text = 'Supercalifragilistic' - - cy.queryByText(text, {timeout: 100}) - .should('have.length', 0) - .and('not.exist') - }) - - it('queryAllByText can return no results message should not error', () => { - const text = 'Supercalifragilistic' - - cy.queryAllByText(text, {timeout: 100}) - .should('have.length', 0) - .and('not.exist') - }) - - it('queryAllByText should forward existence error message from @testing-library/dom', () => { - const text = 'Supercalifragilistic' - const errorMessage = `Unable to find an element with the text: Supercalifragilistic.` - cy.on('fail', err => { - expect(err.message).to.contain(errorMessage) }) - - cy.queryAllByText(text, {timeout: 100}).should('exist') - }) - - it('queryByLabelText should forward useful error messages from @testing-library/dom', () => { - const errorMessage = `Found a label with the text of: Label 3, however no form control was found associated to that label.` - cy.on('fail', err => { - expect(err.message).to.contain(errorMessage) - }) - - cy.queryByLabelText('Label 3', {timeout: 100}).should('exist') - }) - - it('queryAllByText should default to Cypress non-existence error message', () => { - const errorMessage = `Expected