Skip to content

chore: remove deprecated query* queries and prevSubject fallback code #130

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 34 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ This allows you to use all the useful
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->


- [Installation](#installation)
- [With TypeScript](#with-typescript)
- [Usage](#usage)
Expand All @@ -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
Expand All @@ -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:

Expand All @@ -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()
Expand Down Expand Up @@ -223,6 +221,7 @@ Thanks goes to these people ([emoji key][emojis]):

<!-- markdownlint-enable -->
<!-- prettier-ignore-end -->

<!-- ALL-CONTRIBUTORS-LIST:END -->

This project follows the [all-contributors][all-contributors] specification.
Expand Down
18 changes: 0 additions & 18 deletions cypress/integration/configure.spec.js

This file was deleted.

22 changes: 1 addition & 21 deletions cypress/integration/find.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -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') {
Expand All @@ -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 */
203 changes: 28 additions & 175 deletions cypress/integration/query.spec.js
Original file line number Diff line number Diff line change
@@ -1,190 +1,43 @@
/// <reference types="cypress" />
describe('query* dom-testing-library commands', () => {
describe('get* queries should error', () => {
beforeEach(() => {
cy.visit('cypress/fixtures/test-app/')
cy.visit('cypress/fixtures/test-app/')
})

// Test each of the types of queries: LabelText, PlaceholderText, Text, DisplayValue, AltText, Title, Role, TestId
const queryPrefixes = ['By', 'AllBy']
const queryTypes = ['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')
})
queryPrefixes.forEach(queryPrefix => {
queryTypes.forEach(queryType => {
const obsoleteQueryName = `query${queryPrefix + queryType}`;
const preferredQueryName = `find${queryPrefix + queryType}`;
it(`${obsoleteQueryName} should error and suggest using ${preferredQueryName}`, () => {

it('queryAllByPlaceholderText', () => {
cy.queryAllByPlaceholderText(/^Input \d$/).should('have.length', 2)
})
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)
})

it('queryByText', () => {
cy.queryByText('Button Text 1')
.click()
.should('contain', 'Button Clicked')
})
cy[`${obsoleteQueryName}`]('Irrelevant')
})

it('queryAllByText', () => {
cy.queryAllByText(/^Button Text \d$/)
.should('have.length', 2)
.click({ multiple: true })
.should('contain', 'Button Clicked')
})
it(`${obsoleteQueryName} should not log more than once`, () => {

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)
})
let logCount = 0
cy.on('log:added', (attrs, log) => {
if (log.get('name') === obsoleteQueryName) {
logCount = logCount + 1
}
})

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 with .should(\'not.exist\')', () => {
cy.queryAllByText(/^Button Text \d$/).should('exist')
cy.queryByText('Non-existing Button Text', {timeout: 100}).should('not.exist')
})
cy.on('fail', _ => {
expect(logCount).to.equal(1)
cy.removeAllListeners('log:added')
})

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()

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')
})

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 <button> not to exist in the DOM, but it was continuously found.`
cy.on('fail', err => {
expect(err.message).to.contain(errorMessage)
})

cy.queryAllByText('Button Text 1', {timeout: 100})
.should('not.exist')
})

it('queryByText finding multiple items should error', () => {
const errorMessage = `Found multiple elements with the text: /^Button Text/i\n\n(If this is intentional, then use the \`*AllBy*\` variant of the query (like \`queryAllByText\`, \`getAllByText\`, or \`findAllByText\`)).`
cy.on('fail', err => {
expect(err.message).to.contain(errorMessage)
})

cy.queryByText(/^Button Text/i)
})
})

/* global cy */
Loading