Coder Social home page Coder Social logo

testing-library / dom-testing-library Goto Github PK

View Code? Open in Web Editor NEW
3.2K 32.0 459.0 1.73 MB

πŸ™ Simple and complete DOM testing utilities that encourage good testing practices.

Home Page: https://testing-library.com/dom

License: MIT License

JavaScript 77.09% TypeScript 22.91%
javascript testing

dom-testing-library's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

dom-testing-library's Issues

Jest async matchers

There seems to be an ongoing effort in the jest library to add support for async matchers. I wonder how that affects the need to use something like this library's wait utility.

fireEvent.click() performs click on disabled input field

  • dom-testing-library version: 3.4.0
  • react version: -NA-
  • node version: 9.11.1
  • npm (or yarn) version: -NA-

Relevant code or config:

fireEvent.click() on disabled elements.

What you did:

I was expecting fireEvent.click() not to have dispatched click event on disabled elements.

What happened:

fireEvent is ignoring the disabled attribute on the element.

Reproduction:

The following test case events.js is failing.

describe('Disabled element', () => {
  it(`should not fire any event on disabled element`, () => {
    const node = document.createElement('input')
    const disabledAttr = document.createAttribute('disabled')
    node.setAttributeNode(disabledAttr);
    const spy = jest.fn()
    node.addEventListener('click', spy)
    fireEvent.click(node)
    expect(spy).toHaveBeenCalledTimes(0)
  })
});

Problem description:

Currently, the fireEvent is ignoring the disabled attribute in elements.

Suggested solution:

A form control that is disabled must prevent any click events that are queued on the user interaction task source from being dispatched on the element.

β€” The HTML Spec

fireEvent can check whether the element has disabled attribute and skip dispatching the event.

However I'm not sure whether to ignore all MouseEvents (like Chrome/Safari) or just ignore click events.

Doesn't work with custom jsdom

  • dom-testing-library version: 3.5.1
  • react version: 16.4.1
  • node version: 10.4.1
  • npm (or yarn) version: yarn 1.7.0

Relevant code or config:

Usual installation,

"testEnvironment": "node",

as jest configuration, then something like following in tests:

import { getByText } from 'dom-testing-library'

it('Suggests cities to search by', async () => {
  const container = createJSDOM(...)
  getByText(container, 'foobar')
})

What you did:

Tried to use dom-testing-library with node environment in Jest + creating own jsdom instance in a helper and passing it to dom-testing-library.

What happened:

● Test suite failed to run

ReferenceError: window is not defined

  at Object.<anonymous> (node_modules/mutationobserver-shim/dist/mutationobserver.min.js:3:1)
  at Object.<anonymous> (node_modules/dom-testing-library/dist/wait-for-element.js:8:1)
  at Object.<anonymous> (node_modules/dom-testing-library/dist/index.js:32:23)

Reproduction:

As above, enable node jest environment and try to use dom-testing-library with some custom container

Suggested solution:

check if shim is loaded on given container and install it only then? generally something to support no global window object in jest environment

Make waitForElement always resolve to a truthy value.

What?

I'd like waitForElement to always require a callback, and always resolve to a truthy value (an element).

Why?

As of today, this is the type definition for waitForElement:

export function waitForElement<T>(
  callback?: () => T,
  options?: {
    container?: HTMLElement
    timeout?: number
    mutationObserverOptions?: MutationObserverInit
  },
): Promise<T | undefined>

I question why the promise resolves to T | undefined.

The reason for the above type definition probably is that callback is optional. and when a callback function is not passed, the promise indeed resolves to undefined. But what's the point of calling waitForElement if we're never going to pass a callback?

Suggested implementation

Refactor the waitForElement implementation to require the callback or fail. And to not resolve to undefined in its absence. Update the type definitions to reflect this, and the promise resolve type to not allow undefined.

Describe alternatives you've considered:

In Javascript this is no big deal. Because you know, if you provided a callback, that by definition the promise will only resolves when this callback returns a truthy value (i.e. an element). But for TypeScript users, it can be cumbersome to have to explicitly handle the non-truths case that will actually never happen:

const table = await waitForElement(() => container.querySelector('table'));
if (!table) {
  // this will never happen
}
// ...

Admittedly, there's a more concise way, using table! from that point on. But again, why the need?

Teachability, Documentation, Adoption, Migration Strategy:

There's practically nothing to do here. Using waitForElement without a callback should not be a normal use case. So this would impact very few people.

Even the type definitions shown in the README for waitForElement do not match the promise resolve type in the actual type definitions. So in terms of documentation, this is already covered as it should've been in the code.

waitForElement gets into infinite setTimeout loop

  • dom-testing-library version: 3.5.1
  • node version: 8.11.2
  • npm (or yarn) version: 5.6.0

Relevant code or config:

const { waitForElement } = require("dom-testing-library");

it("shouldn't hang after succeeding", async () => {
    let didMakeMutation = false;
    const createElement = () => document.createElement("div");
    const container = createElement();
    const myCallback = () => {
        return didMakeMutation;
    };
    const result = waitForElement(myCallback, { container });
    container.appendChild(createElement());
    didMakeMutation = true;
    await new Promise((resolve) => setTimeout(resolve, 50));
});

What you did:

I'm trying to use waitForElement to wait until an element is created in the dom.

What happened:

I found that my test is passing, but the test runner (mocha) doesn't exit - it gets into infinite setTimeout loop.

Reproduction:

Repo: https://github.com/grigasp/dom-testing-library-waitforelement-issue

npm install
npm run test

Problem description:

The test succeeds, but the test runner (mocha) doesn't ever exit, because waitForElement gets into infinite setTimeout loop.

I think the problem is that waitForElement calls observer.disconnect() from the onMutation() -> onDone() callback. MutationObserver, after calling the onMutation() callback, immediately recreates the listener, and then gets into the infinite setTimeout loop.

Suggested solution:

waitForElement should somehow call disconnect() after MutationObserver recreates the listener.

waitForElement not responding to text-only updates to the markup.

  • react-testing-library version: 4.1.2
  • react version: 16.1.0
  • node version: 8.11.3
  • npm (or yarn) version: 5.6.0

Relevant code or config:

Test

test('render error message if products fail to load', async () => {
  axios.get.mockReturnValue(new Promise((resolve, reject) => reject('some error')));

  const { queryByText, getByText, debug } = renderComponent();

  expect(queryByText('Loading…')).toBeInTheDOM();
  expect(queryByText('Failed to load products')).not.toBeInTheDOM();

  await waitForElement(() => getByText('Failed to load products'));
  expect(queryByText('Failed to load products')).toBeInTheDOM();
});

React component segment

render() {
    const {
      loading,
      error,
      products,
      favourites,
      sortedBy
    } = this.props;
    if (loading) return <div data-test-id="loading-products">Loading…</div>
    if (error) return <div data-test-id="products-error">Failed to load product</div>

What you did:

Testing a component error state

What happened:

When test is executed the component remains in loading state, even when error flips to true and loading false (confirmed by console logging).

However when I wrap the text "Failed to load product" inside of an additional element such as a <span /> the test passes.

It appears to me that waitForElement is expecting a change in the markup shape rather than just text in this instance…

Reproduction:

Repository
run yarn test:reactTestingLibrary

Watch all the tests pass.
Then remove the <span /> from Products.js L39
The test should fail.

Problem description:

I'm having to change the shape of the returned JSX in order to satisfy tests.

Expressing user actions at a higher level

Describe the feature you'd like:

Following the discussion in testing-library/jest-dom#53 and testing-library/jest-dom#59, it is becoming apparent the need to express user actions on a web page using a higher-level abstraction than fireEvent.

This issue is to bring the discussion to this repo, where the potential solution actually lies.

Suggested implementation

See this comment from @kentcdodds for details on how the api could look like.

I'd also like to add that we could take a look at similar cypress APIs. I wonder even if having something like what the above linked comment suggests, would be duplicating what cypress already provides. Of course this project is independent from cypress and it could well provide similar functionality.

Please publish v2.4.0 :-

Hey guys, looks like 2.4.0 is the latest release here but npm still has 2.3.2, any chance for publishing?

Add a way to access <select> elements given their text value

Describe the feature you'd like:

Accessing <select> elements by the selected option's text
In my experience using dom-testing-library, I've had no issues accessing and interacting with DOM elements in the ways that a user would, except for <select> elements. Unless a label is associated with <select> elements, they are a headache to access, and even harder to do so in a way the software is used.

I would like to get a conversation started for how we can better support <select> elements moving forward.

Here's an example of a select element that would be difficult to access and use:

<select>
	<option value="">State</option>
	<option value="AL">Alabama</option>
	<option value="AK">Alaska</option>
	<option value="AZ">Arizona</option>
	<option value="AR">Arkansas</option>
</select>

The first <option> is commonly used in <select> elements, like a placeholder attribute is used for text inputs. What the user sees is the text value of the first option, but it's not possible to access the <select> element given that text value.

What I have been doing on my projects has been to create a getSelectByText() function, though I would hate to have specific, one-use functions or options added to dom-testing-library.

Maybe a user should be able to select a <select> element by using getByText() using the selector option. I can't think of any really elegant solutions, but would be curious to hear from other users of the project.

Describe alternatives you've considered:

For the time being, I've been using this to access <select> values based on the innerText of the selected option:

function getSelectByText(text) {
  const selectElems = document.querySelectorAll('select')
  for (let i = 0; i < selectElems.length; i++) {
    const selectInstance = selectElems[i]
    const selectedText = selectInstance.selectedOptions[0].innerText
    if (selectedText === text) return selectInstance
  }
  throw new Error(`Unable to find a select element with the value: ${text}`)
}

The automated release is failing 🚨

🚨 The automated release from the master branch failed. 🚨

I recommend you give this issue a high priority, so other packages depending on you could benefit from your bug fixes and new features.

You can find below the list of errors reported by semantic-release. Each one of them has to be resolved in order to automatically publish your package. I’m sure you can resolve this πŸ’ͺ.

Errors are usually caused by a misconfiguration or an authentication problem. With each error reported below you will find explanation and guidance to help you to resolve it.

Once all the errors are resolved, semantic-release will release your package the next time you push a commit to the master branch. You can also manually restart the failed CI job that runs semantic-release.

If you are not sure how to resolve this, here is some links that can help you:

If those don’t help, or if this issue is reporting something you think isn’t right, you can always ask the humans behind semantic-release.


The push permission to the Git repository is required.

semantic-release cannot push the version tag to the branch master on remote Git repository.

Please refer to the authentication configuration documentation to configure the Git credentials on your CI environment.


Good luck with your project ✨

Your semantic-release bot πŸ“¦πŸš€

.toHaveClass custom matcher

At risk of sounding like I want to bloat the set of custom matchers provided by this lib, I'd like to propose .toHaveClass('dropdown-toggle'). The class attribute in html is designed to have multiple classes encoded in it, and it's often nice to be able to reliably check if an element has a certain class while ignoring the rest of what's encoded in the class attribute value.

// ...
// <button data-testid="delete-button" type="submit" class="btn btn-danger">
//   Delete item
// </button>
expect(getByTestId('delete-button')).toHaveClass('btn-danger')

Raising it as an issue first to see if it's considered useful for this lib, and also to discuss potential names:

  • toHaveClass
  • toIncludeClass
  • toContainClass
  • something else?

Or all of the above but ending with *ClassName instead of just *Class.

<select> doesn't reflect new "select text" in response to change event

  • dom-testing-library version: 3.8.2
  • react version: N/A
  • node version: v10.11.0 (but it doesn't matter)
  • npm (or yarn) version: yarn 1.10.1 (but it doesn't matter)

Relevant code or config:

See the reproduction section below.

What you did:

  1. Created a <div> containing a <select> with two <option>s.
  2. Fetched the <select> using getBySelectText
  3. Fired a change event on the fetched <select> to select another valid option
  4. Verified that the value was changed
  5. Attempted to fetch the <select> from the <div> again using the newly selected option's label (child element)

What happened:

The <select> cannot be fetched by its new "select text":

 FAIL  ./index.test.js
  βœ• should respond to changes (4520ms)

  ● should respond to changes

    Unable to find a <select> element with the selected option's text: Option 2

    <div>
      <select>


        <option
          value="option-1"
        >
          Option 1
        </option>


        <option
          value="option-2"
        >
          Option 2
        </option>


      </select>
    </div>

      22 |     // but this times out because the text hasn't been updated.
      23 |     // No changes are reflected in the DOM, which is probably why…
    > 24 |     return waitForElement(() => getBySelectText(container, 'Option 2'));
         |                                 ^
      25 | });
      26 |

      at getElementError (node_modules/dom-testing-library/dist/query-helpers.js:29:10)
      at getAllBySelectText (node_modules/dom-testing-library/dist/queries.js:375:45)
      at firstResultOrNull (node_modules/dom-testing-library/dist/query-helpers.js:37:30)
      at getBySelectText (node_modules/dom-testing-library/dist/queries.js:385:42)
      at getBySelectText (index.test.js:24:33)
      at onMutation (node_modules/dom-testing-library/dist/wait-for-element.js:48:22)
      at node_modules/dom-testing-library/dist/wait-for-element.js:66:7
      at waitForElement (node_modules/dom-testing-library/dist/wait-for-element.js:26:10)
      at Object.waitForElement (index.test.js:24:12)

Reproduction:

const { getBySelectText, fireEvent, wait, waitForElement } = require('dom-testing-library');

it('should respond to changes', () => {
    const select = document.createElement('select');
    select.innerHTML = `
        <option value="option-1">Option 1</option>
        <option value="option-2">Option 2</option>
    `;

    const container = document.createElement('div');
    container.appendChild(select);

    const foundSelectBeforeEvent = getBySelectText(container, 'Option 1');
    expect(foundSelectBeforeEvent.value).toBe('option-1');

    fireEvent.change(foundSelectBeforeEvent, { target: { value: 'option-2' }});

    // It has been updated, and this passes
    expect(foundSelectBeforeEvent.value).toBe('option-2');

    // I would then expect to be able to get the <select> again by the new text,
    // but this times out because the text hasn't been updated.
    // No changes are reflected in the DOM, which is probably why…
    return waitForElement(() => getBySelectText(container, 'Option 2'));
});

I've tried (among other things):

  • waiting for a tick to let updates happen
  • Firing events for focus and blur before and after firing the change to force a rerender
  • Not wrapping the fetching in waitForElement (it just fails in the same way, but faster)

Problem description:

The <select> element doesn't seem to update its displayed value, so it behaves differently from what a user experiences in a browser.

Suggested solution:

The <select> should be selectable using the label of the selected option. I don't know how to fix it, though πŸ˜„

event.currentTarget not populated when fireevent.change happens for an input.

  • dom-testing-library version: 2.9.1
  • react version:
  • node version:
  • npm (or yarn) version:

Relevant code or config:

import React, { Component } from 'react';

export class Input extends Component {

  constructor(props) {
    super();

    const {
      value
    } = props;

    this.state = {
      value: value || ''
    };
  }

  handleChange = (evt) => {
    const {
      onChange
    } = this.props;

    this.setState(prevState => ({
      value: evt.target.value
    }));

    if (onChange) {
      onChange( evt.target.value);
    }

  }

  render() {
    const {
      value
    } = this.state;

    return (
      <input onChange={this.handleChange} value={value} data-testid="input" />
    )
  }
}



// test

test('will call onChange prop if changes occur', async () => {
	const spy = jest.fn();
  const {getByText, getByTestId, container} = render(<Input value="test" onChange={spy} />);

  expect(getByTestId('input')).toHaveAttribute('value', 'test');

  const input = getByTestId('input');

  input.value = 'testing';

  fireEvent.change(
    input
  );

	expect(getByTestId('input')).toHaveAttribute('value', 'testing');
	expect(spy).toHaveBeenCalled();
  // expect(container.firstChild).toMatchSnapshot();
});

What you did:

writing a test for an input.

What happened:

component was using event.currentTarget.value, but got an error in the test because currentTarget was not defined.

Reproduction:

change the code above to use current target instead.

Problem description:

Suggested solution:

Remove extend-expect

People should know their abstractions and it doesn't make sense to me that we have this here. Instead people should import 'jest-dom/extend-expect'.

Doing this will help people know where they should go to learn about that abstraction.

This will be a breaking change.

Utilities for dispatching events?

I'm thinking there could be room for a utility for dispatching events in elements. Similar to react's test utils Simulate utilities except with actual browser events. What should the API look like?

Add support to specify the type of node which is selected

So I ran into an issue when I was working with cypress. I wanted to .click() a button which had the exact same wording as the headline right above. getByTextalways took the headline instead of the button so I had to use .get('button:contains("[...]")') to be able to get the button.

screen shot 2018-06-01 at 08 45 35

So I thought to be able to always use getByText it would be nice to be able to specify the type of node I want to get as part of the options maybe something like getByText(container, 'text', { node: 'button' }).

Of course I would volunteer to do it if it was discussed and approved.

getByLabelText does not match when id has a "." in the string.

I had an element that looks like this:

<label for="activity.username">Username</label>
<input id="activity.username" />

and calling: getByLabelText('Username') throws because it won't find the with an id of activity.username. Having a period in the id is valid HTML.

The reason is that on this line:

https://github.com/kentcdodds/dom-testing-library/blob/4bf26d70eda829ca0470c79952f53f5d51ab423b/src/queries.js#L27

the CSS selector # needs to be escaped for a characters that are not valid CSS labels. In this case it is trying to run container.querySelector('#activity.username') but that needs to be escaped to container.querySelector('#activity\\.username'). (double slash to escape in javascript, so the actual string would only have one slash) (see MDN docs).

An alternative would be to use the [id='activity.shift'] selector, as that finds the element without escaping:

So the whole line would be:

return container.querySelector(`[id='${label.getAttribute('for')}']`)

Update docs to remove toBeInTheDOM

Regarding the deprecation of toBeInTheDOM we should either find better assertions or use toBeInTheDocument: testing-library/jest-dom#40

Anyone wanna try that?

For dom-testing-library, most of the examples aren't actually attached to document.body so toBeInTheDocument wont work. We'll have to think of something better on a case-by-case basis I think.

"Safe" click event helper that warns when clicking non-interactive elements

Describe the feature you'd like:

I was recently debugging an issue where I passed "container" to fireEvent.click() instead of a button reference. What if there was a variant of fireEvent that warns/errors when firing events on elements that aren't normally interactable? button and a would be safe, plus any semantic/aria- elements.

API ideas:

fireEvent.clickSafe(element, options)
fireEvent.accessibleClick(element, options)
fireEvent.click(element, { allowAny: true })

Suggested implementation:

Detect target element tag name and attributes when firing events

Describe alternatives you've considered:

Teachability, Documentation, Adoption, Migration Strategy:

This would be a non-breaking change

Baking-in `closest()`

Hi Kent! Thanks so much for this and RTL, it's made testing really quite easy πŸ˜€

Describe the feature you'd like:

If I have this...

<button>
  <span>testing</span>
</button>

...I have found myself having to use .closest() when trying to find the button, where the span isn't guaranteed to exist, and this works really well.

getByText('testing').closest('button')

However, I thought this would be what the selector option was supposed to provide but that didn't seem to work for me. I get:

Unable to find an element with the text: testing. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.

Therefore I'm not really sure if this is a bug report or feature request. If...

getByText('testing', { selector: 'button'})

... should find the button, then this is a bug report. If it's not, then maybe...

getByText('testing', { closest: 'button'})

...could be useful and some docs to explain this situation? Or maybe this is all overkill, what are your thoughts?

Happy to help, just want to know if I'm barking up the correct tree to begin with.

wait-for-expect dependency throws Unknown plugin "@babel/proposal-class-properties"

  • dom-testing-library version: 2.2.0
  • react-testing-library version: 3.0.2
  • react version: 16.3.1
  • node version: v8.9.4
  • npm (or yarn) version: 6.0.0

What you did:

Upon updating to the latest version of react-testing-library I now receive the following error when running npm test

What happened:

    ReferenceError: Unknown plugin "@babel/proposal-class-properties" specified in "/Users/ec/Payload/payload/node_modules/dom-testing-library/node_modules/wait-for-expect/.babelrc" at 0, attempted to resolve relative to "/Users/ec/Payload/payload/node_modules/dom-testing-library/node_modules/wait-for-expect"

      at node_modules/babel-core/lib/transformation/file/options/option-manager.js:180:17
          at Array.map (<anonymous>)
      at Function.normalisePlugins (node_modules/babel-core/lib/transformation/file/options/option-manager.js:158:20)
      at OptionManager.mergeOptions (node_modules/babel-core/lib/transformation/file/options/option-manager.js:234:36)
      at OptionManager.init (node_modules/babel-core/lib/transformation/file/options/option-manager.js:368:12)
      at File.initOptions (node_modules/babel-core/lib/transformation/file/index.js:212:65)
      at new File (node_modules/babel-core/lib/transformation/file/index.js:135:24)
      at Pipeline.transform (node_modules/babel-core/lib/transformation/pipeline.js:46:16)

Problem description:

I'm not entirely sure if this a config issue on my end or with the wait-for-expect package within dom-testing-library, but I decided to ask here first.

prettyDOM error when container is document

  • dom-testing-library version: 2.3.2
  • cypress-testing-library version: 2.0.0
  • cypress version: 2.1.0
  • react version: 16.3.2
  • node version: 8.4.0
  • npm version: 5.3.0

Relevant code or config:

cy.getByLabelText("something not on the page");

What you did:

I'm selecting an element using getByLabelText, but my code isn't setting the htmlFor correctly, so it couldn't find it.

What happened:

  1. cypress-testing-library passes the window.document as container to the query.
  2. Since getByLabelText can't find the element, it wants to log a friendly error message using prettyDOM.
  3. prettyDOM uses outerHTML.length to determine whether to truncate. However, document.outerHTML is undefined resulting in the error.
TypeError: Cannot read property 'length' of undefined
    at prettyDOM (http://localhost:3001/__cypress/tests?p=cypress\support\index.js-147:898:59)
    at debugDOM (http://localhost:3001/__cypress/tests?p=cypress\support\index.js-147:920:35)
    at getAllByLabelText (http://localhost:3001/__cypress/tests?p=cypress\support\index.js-147:1181:216)
    at firstResultOrNull (http://localhost:3001/__cypress/tests?p=cypress\support\index.js-147:932:30)
    at getByLabelText (http://localhost:3001/__cypress/tests?p=cypress\support\index.js-147:1194:28)
    at container (http://localhost:3001/__cypress/tests?p=cypress\support\index.js-147:82:28)
    at onMutation (http://localhost:3001/__cypress/tests?p=cypress\support\index.js-147:1309:22)
    at http://localhost:3001/__cypress/tests?p=cypress\support\index.js-147:1327:7
    at new Promise (<anonymous>)
    at waitForElement (http://localhost:3001/__cypress/tests?p=cypress\support\index.js-147:1289:10)
From previous event:
    at Context.thenFn (http://localhost:3001/__cypress/runner/cypress_runner.js:56945:26)
    at Context.then (http://localhost:3001/__cypress/runner/cypress_runner.js:57207:21)
    at Context.<anonymous> (http://localhost:3001/__cypress/runner/cypress_runner.js:63338:21)
    at http://localhost:3001/__cypress/runner/cypress_runner.js:63053:33
From previous event:
    at runCommand (http://localhost:3001/__cypress/runner/cypress_runner.js:63043:14)
    at next (http://localhost:3001/__cypress/runner/cypress_runner.js:63125:14)
    at http://localhost:3001/__cypress/runner/cypress_runner.js:63137:18

Reproduction:

I've spent a little bit of time to try and test this, but with JSDOM, this doesn't seem to be easy. prettyDOM(document) crashes before it hits the outerHTML.length part due to pretty-format package (RangeError: Invalid string length).

Full `pretty-format` error

  RangeError: Invalid string length
      at printListItems (node_modules/pretty-format/build/collections.js:105:33)
      at printComplexValue (node_modules/pretty-format/build/index.js:176:141)
      at printer (node_modules/pretty-format/build/index.js:236:10)
      at printObjectProperties (node_modules/pretty-format/build/collections.js:140:21)
      at printComplexValue (node_modules/pretty-format/build/index.js:187:169)
      at printer (node_modules/pretty-format/build/index.js:236:10)
      at printListItems (node_modules/pretty-format/build/collections.js:105:35)
      at printComplexValue (node_modules/pretty-format/build/index.js:176:141)
      at printer (node_modules/pretty-format/build/index.js:236:10)
      at printObjectProperties (node_modules/pretty-format/build/collections.js:140:21)
      at printComplexValue (node_modules/pretty-format/build/index.js:187:169)
      at printer (node_modules/pretty-format/build/index.js:236:10)
      at printListItems (node_modules/pretty-format/build/collections.js:105:35)
      at printComplexValue (node_modules/pretty-format/build/index.js:176:141)
      at printer (node_modules/pretty-format/build/index.js:236:10)
      at printObjectProperties (node_modules/pretty-format/build/collections.js:140:21)
      at printComplexValue (node_modules/pretty-format/build/index.js:187:169)
      at printer (node_modules/pretty-format/build/index.js:236:10)
      at printListItems (node_modules/pretty-format/build/collections.js:105:35)
      at printComplexValue (node_modules/pretty-format/build/index.js:176:141)
      at printer (node_modules/pretty-format/build/index.js:236:10)
      at printObjectProperties (node_modules/pretty-format/build/collections.js:140:21)
      at printComplexValue (node_modules/pretty-format/build/index.js:187:169)
      at printer (node_modules/pretty-format/build/index.js:236:10)
      at printListItems (node_modules/pretty-format/build/collections.js:105:35)
      at printComplexValue (node_modules/pretty-format/build/index.js:176:141)
      at printer (node_modules/pretty-format/build/index.js:236:10)
      at printObjectProperties (node_modules/pretty-format/build/collections.js:140:21)
      at printComplexValue (node_modules/pretty-format/build/index.js:187:169)
      at printer (node_modules/pretty-format/build/index.js:236:10)
      at printListItems (node_modules/pretty-format/build/collections.js:105:35)
      at printComplexValue (node_modules/pretty-format/build/index.js:176:141)
      at printer (node_modules/pretty-format/build/index.js:236:10)
      at printObjectProperties (node_modules/pretty-format/build/collections.js:140:21)
      at printComplexValue (node_modules/pretty-format/build/index.js:187:169)
      at printer (node_modules/pretty-format/build/index.js:236:10)
      at printListItems (node_modules/pretty-format/build/collections.js:105:35)
      at printComplexValue (node_modules/pretty-format/build/index.js:176:141)
      at printer (node_modules/pretty-format/build/index.js:236:10)
      at printObjectProperties (node_modules/pretty-format/build/collections.js:140:21)
      at printComplexValue (node_modules/pretty-format/build/index.js:187:169)
      at printer (node_modules/pretty-format/build/index.js:236:10)
      at printListItems (node_modules/pretty-format/build/collections.js:105:35)
      at printComplexValue (node_modules/pretty-format/build/index.js:176:141)
      at printer (node_modules/pretty-format/build/index.js:236:10)
      at printObjectProperties (node_modules/pretty-format/build/collections.js:140:21)
      at printComplexValue (node_modules/pretty-format/build/index.js:187:169)
      at printer (node_modules/pretty-format/build/index.js:236:10)
      at printListItems (node_modules/pretty-format/build/collections.js:105:35)
      at printComplexValue (node_modules/pretty-format/build/index.js:176:141)
      at printer (node_modules/pretty-format/build/index.js:236:10)
      at printObjectProperties (node_modules/pretty-format/build/collections.js:140:21)
      at printComplexValue (node_modules/pretty-format/build/index.js:187:169)
      at printer (node_modules/pretty-format/build/index.js:236:10)
      at printListItems (node_modules/pretty-format/build/collections.js:105:35)
      at printComplexValue (node_modules/pretty-format/build/index.js:176:141)
      at printer (node_modules/pretty-format/build/index.js:236:10)
      at printObjectProperties (node_modules/pretty-format/build/collections.js:140:21)
      at printComplexValue (node_modules/pretty-format/build/index.js:187:169)
      at printer (node_modules/pretty-format/build/index.js:236:10)
      at printListItems (node_modules/pretty-format/build/collections.js:105:35)
      at printComplexValue (node_modules/pretty-format/build/index.js:176:141)
      at printer (node_modules/pretty-format/build/index.js:236:10)
      at printObjectProperties (node_modules/pretty-format/build/collections.js:140:21)
      at printComplexValue (node_modules/pretty-format/build/index.js:187:169)
      at printer (node_modules/pretty-format/build/index.js:236:10)
      at printObjectProperties (node_modules/pretty-format/build/collections.js:140:21)
      at printComplexValue (node_modules/pretty-format/build/index.js:187:169)
      at printer (node_modules/pretty-format/build/index.js:236:10)
      at printObjectProperties (node_modules/pretty-format/build/collections.js:140:21)
      at printComplexValue (node_modules/pretty-format/build/index.js:187:169)
      at printer (node_modules/pretty-format/build/index.js:236:10)
      at printObjectProperties (node_modules/pretty-format/build/collections.js:140:21)
      at printComplexValue (node_modules/pretty-format/build/index.js:187:169)
      at printer (node_modules/pretty-format/build/index.js:236:10)
      at printListItems (node_modules/pretty-format/build/collections.js:105:35)
      at printComplexValue (node_modules/pretty-format/build/index.js:176:141)
      at printer (node_modules/pretty-format/build/index.js:236:10)
      at printObjectProperties (node_modules/pretty-format/build/collections.js:140:21)
      at printComplexValue (node_modules/pretty-format/build/index.js:187:169)
      at printer (node_modules/pretty-format/build/index.js:236:10)
      at printListItems (node_modules/pretty-format/build/collections.js:105:35)
      at printComplexValue (node_modules/pretty-format/build/index.js:176:141)
      at printer (node_modules/pretty-format/build/index.js:236:10)
      at printObjectProperties (node_modules/pretty-format/build/collections.js:140:21)
      at printComplexValue (node_modules/pretty-format/build/index.js:187:169)
      at printer (node_modules/pretty-format/build/index.js:236:10)
      at printObjectProperties (node_modules/pretty-format/build/collections.js:140:21)
      at printComplexValue (node_modules/pretty-format/build/index.js:187:169)
      at printer (node_modules/pretty-format/build/index.js:236:10)
      at printObjectProperties (node_modules/pretty-format/build/collections.js:140:21)
      at printComplexValue (node_modules/pretty-format/build/index.js:187:169)
      at printer (node_modules/pretty-format/build/index.js:236:10)
      at printListItems (node_modules/pretty-format/build/collections.js:105:35)
      at printComplexValue (node_modules/pretty-format/build/index.js:176:141)
      at printer (node_modules/pretty-format/build/index.js:236:10)
      at printObjectProperties (node_modules/pretty-format/build/collections.js:140:21)
      at printComplexValue (node_modules/pretty-format/build/index.js:187:169)
      at printer (node_modules/pretty-format/build/index.js:236:10)
      at printObjectProperties (node_modules/pretty-format/build/collections.js:140:21)


I believe this can be reproduced by just using cypress-testing-library and selecting something that isn't on the page? I'll do some more testing in the weekend.

Problem description:

prettyDOM function shouldn't assume outerHTML is defined.

Maybe a more accurate description would be: I don't know how to write a test case for this within dom-testing-library to test a pull request.

Suggested solution:

I'm not sure why outerHTML is being used, since it's the debugContent that is being truncated. So my suggestion would be to check debugContent.length instead. Otherwise add an && htmlContent.outerHTML before the length check.

Add support for `<title>` element in SVG with `getByTitle`

Describe the feature you'd like:

There is a <title> element that can be rendered within an SVG element, see MDN, and would be nice to be considered when doing a getByTitle assertion. Currently it does work with getByText, but that might be incorrect considering it behaves more like a title attribute than text content.

If accepted, I will work up a PR adding support for it.

Suggested implementation:

Would need to find the <title> element and ensure it's parent is a <svg> element and allow that to be included in the assertion.

Describe alternatives you've considered:

We currently use getByText as a selector.

queryByText failing when querying a component that returns a string

  • dom-testing-library version: 2.4.0
  • react version: 16.3.2

Relevant code or config:

import "dom-testing-library/extend-expect"

import React from "react"
import { render } from "react-testing-library"

const App = () => "Hello World!"

test("App renders without crashing", async () => {
  const { queryByText } = render(<App />)
  expect(queryByText("Hello World!")).toBeInTheDOM()
})

What you did:

I ran the test.

What happened:

It returned an error:

Cannot read property 'match' of null

TypeError: Cannot read property 'match' of null
    at module.exports.str (https://j4rkx7jmlv.codesandbox.io/node_modules/strip-indent/index.js:3:20)

Reproduction:

https://codesandbox.io/s/j4rkx7jmlv

Problem description:

I expect queryByText() to work with components that return a string.

Suggested solution:

Improve queryByText() so it supports components that return a string.

Matcher import path is broken in get-queries-for-element typings file

The Matcher import in typings/get-queries-for-element.d.ts is incorrect.

  • dom-testing-library version: 2.3.1
  • react version: 16.3.1
  • node version: 8.11.1
  • npm (or yarn) version: 6.0.1

Relevant code or config:

The current (incorrect) code is:

import {Matcher} from 'matches'

The correct import is as follows:

import {Matcher} from './matches'

What you did:

Attempt to call ReactTesting.renderIntoDocument(...) in a foo.test.tsx file fails.
Unfortunately the code is in a private GitHub repo that can't be shared here.

What happened:

TypeScript compilation of the app fails and returns the following error message:

node_modules/dom-testing-library/typings/get-queries-for-element.d.ts
(1,23): Cannot find module 'matches'.

getAllBy* matchers?

  • dom-testing-library version: 2.1.1

It looks like all the matchers are designed to return the first element matching a query. Would it make sense to also export matchers that return all of the matching nodes as well (e.g., document.querySelectorAll vs document.querySelector)? Some use cases are testing the length or sort order of lists, handling pagination, etc.

Helper for testing style rules

Describe the feature you'd like:

Would you be interested in adding a helper for checking style rules? I taught a workshop on testing using the library and ended up with students stuck trying to check (for example) a button's background-colour had correctly changed.

Getting a DOM element's style rules is kind of gross, it so might be nice to expose a util like getStyleValue.

Suggested implementation:

It could be as simple as:

function getStyleValue(element, property) {
  return window.getComputedStyle(element).getPropertyValue(property);
}

If this is beyond the scope/doesn't fit the philosophy of the library then feel free to close ❀️

`toBeInTheDOM` matcher only ensures `instanceof HTMLElement`, expected to check that the element is attached to a full DOM tree

  • dom-testing-library version: 1.1.0
  • node version: N/A
  • npm (or yarn) version: N/A

Relevant code or config

https://github.com/kentcdodds/dom-testing-library/blob/f77f943a1887756372993eaf6ee3b054f2f58b91/src/jest-extensions.js#L18-L26

https://github.com/kentcdodds/dom-testing-library/blob/f77f943a1887756372993eaf6ee3b054f2f58b91/src/jest-extensions.js#L63-L85

(copy of the linked code)
function checkHtmlElement(htmlElement) {
  if (!(htmlElement instanceof HTMLElement)) {
    throw new Error(
      `The given subject is a ${getDisplayName(
        htmlElement,
      )}, not an HTMLElement`,
    )
  }
}

// ...

const extensions = {
  toBeInTheDOM(received) {
    if (received) {
      checkHtmlElement(received)
    }
    return {
      pass: !!received,
      message: () => {
        const to = this.isNot ? 'not to' : 'to'
        return getMessage(
          matcherHint(
            `${this.isNot ? '.not' : ''}.toBeInTheDOM`,
            'element',
            '',
          ),
          'Expected',
          `element ${to} be present`,
          'Received',
          received,
        )
      },
    }
  },

What you did:

  1. Used .toBeInTheDOM() in a test with an element disconnected from the DOM, like here; the test passed, surprisingly.
  2. Reviewed the source code of jest-extensions.js

What happened:

Discovered that toBeInTheDOM does not check that the element is attached to a full DOM tree.

Reproduction repository:

This repository's tests.

Problem description:

toBeInTheDOM, according to its name, has to ensure that the given element is in the DOM, i.e. attached to the full DOM tree, not hanging as a standalone element or in a standalone subtree.

Suggested solution:

Find out if the element is actually attached to a DOM document via element.ownerDocument.contains(element), and report an assertion failure if it's not.

Incorrect Environment Detection When Running with Ember Test Server

  • dom-testing-library version: 2.6.3
  • react version: n/a
  • node version: 8.4.0
  • npm (or yarn) version: 6.1.0

Relevant code or config:

Using any dom-testing-library functionality within a running ember project (ember s) and viewing tests at "/tests";

What you did:

Attempted to use dom-testing-library along side standard Ember tooling for testing. When assertions failed (elements not found, etc) the debug output is produced.

What happened:

DTL outputs entire container in the browser window. It appears to be formatted for CLI and is unreadable.

Reproduction:

Sorry, I wasn't sure how to pull dom-testing-library into ember-twiddle. Unpkg didn't seem to work.

Problem description:

This makes it almost impossible to debug. This is especially true if using document as your container.

Suggested solution:

Issue possibly around here with detecting environment (thanks @kentcdodds):
https://github.com/kentcdodds/dom-testing-library/blob/ba44c14f93b4def2b1f8ce0eea8cbaf5c777c735/src/queries.js#L15

I found that in an Ember test

const inNode = typeof module !== 'undefined' && module.exports

will be undefined because, while typeof module is 'object', there's still no module.exports.

I'm not sure what's making DTL think it's in Node or if that is even the true issue.

Add new getByRole queryselector.

Describe the feature you'd like:

A new querySelector to select elements based on role. I have a dialog that while I can query inside the dialog just fine I cannot first a click to close the dialog because all the current queries target elements inside the dialog so the out of bounds click handlers do not fire.

Suggested implementation:

const queryAllByRole = queryAllByAttribute.bind(null, 'role')

const queryByRole = queryByAttribute.bind(null, 'role')
const queryAllByRole = queryAllByAttribute.bind(null, 'role')

function getAllByRole(container, id, ...rest) {
  const els = queryAllByRole(container, id, ...rest)
  if (!els.length) {
    throw getElementError(
      `Unable to find an element by role=${id}`,
      container,
    )
  }
  return els
}

function getByRole(...args) {
  return firstResultOrNull(getAllByRole, ...args)
}

Describe alternatives you've considered:

I'm using Material UI where there is a lot of abstraction in DOM that I can't put data-testid on the outer dialog. So targeting it by testid won't work for triggering the onClose event.

The only thing I've considered is writing the
const dialogConstainer = container.querySelector('[role="dialog"]')
And then writing assertions from there.

Teachability, Documentation, Adoption, Migration Strategy:

This is a non breaking change as it is only an addition to the existing query set.


getByRole

getByRole(
  container: HTMLElement,
  text: TextMatch,
  options?: {
    exact?: boolean = true,
    collapseWhitespace?: boolean = false,
    trim?: boolean = true,
  }): HTMLElement`

A shortcut to container.querySelector(`[role="${yourRole}"]`) (and it
also accepts a TextMatch).

// <div role="dialog">...</div>
const dialogContainer = getByTestrole(container, 'dialog')

Add a function for printing a simple DOM representation for debugging

Would you be interested in a function like this? The only annoying thing I've found with using jsdom is that it's hard to debug if you don't know what's going on or are confused. In the browser you can just look at it, but you can't with jsdom. This gives me most of what I want:

function prettyDOM(node, indent = 0) {
  let str = '';
  if (node) {
    str += ' '.repeat(indent) + (node.tagName || node.textContent) + '\n';
    for (let child of node.childNodes) {
      str += debugDOM(child, indent + 2);
    }
  }
  return str;
}

You can now do console.log(prettyDOM(node)) and get an output like this in the terminal:

screen shot 2018-04-18 at 9 36 57 pm

getByPlaceholderText Should Also Return Type HTMLInputElement?

  • dom-testing-library version: 2.7.0
  • react version: 16.4.1
  • redux-form version: 7.4.2
  • node version: 8.11.3
  • npm (or yarn) version: 6.1.0

Relevant code or config:

import React from "react";
import { renderWithRedux, generate } from "test-utils";
import { cleanup, fireEvent } from "react-testing-library";

import LoginFormContainer from "../LoginFormContainer";

afterEach(cleanup);

describe("LoginFormContainer", () => {
  test("Submits Login with email and password", () => {
    //Arrange--------------
    const fakeUser = generate.loginForm();

    let submitLogin = jest.fn();
    const props = { submitLogin };

    const { getByText, getByPlaceholderText } = renderWithRedux(
      <LoginFormContainer {...props} />
    );

    const emailNode = getByPlaceholderText("E-mail address");
    const passwordNode = getByPlaceholderText("Password");

    //Act--------------
    emailNode.value = fakeUser.email;
              ^^^^^ [ts] Property 'value' does not exist on type 'HTMLElement'.
    passwordNode.value = fakeUser.password;
                 ^^^^^ [ts] Property 'value' does not exist on type 'HTMLElement'.

    fireEvent.change(emailNode);
    fireEvent.change(passwordNode);

    fireEvent.click(getByText("Login"));

    //Assert--------------
    expect(submitLogin).toHaveBeenCalledTimes(1);
    expect(submitLogin).toHaveBeenCalledWith(fakeUser);
  });
});

renderWithRedux is the same as the examples from react-testing-library

import React from "react";
import { createStore } from "redux";
import { Provider } from "react-redux";
import { render } from "react-testing-library";

import reducer from "../services/reducer";

export default function renderWithRedux(
  ui: React.ReactNode,
  { initialState, store = createStore(reducer, initialState) } = {}
) {
  return {
    ...render(<Provider store={store}>{ui}</Provider>),
    // adding `store` to the returned utilities to allow us
    // to reference it in our tests (just try to avoid using
    // this to test implementation details).
    store
  };
}

What you did:

I am using redux-form for my forms.

I want to:

  • modify the input values (email / password)
  • click "Login"
  • verify that submitLogin is fired

react-testing-library: fireEvent If you want to trigger the onChange handler of a controlled component with a different event.target.value, sending value through eventProperties won't work like it does with Simulate.
You need to change the element's value property, then use fireEvent to fire a change DOM event.

What happened:

I get TypeScript errors from the returned Nodes from getByPlaceholderText, because it returns a type HTMLElement.

    const emailNode = getByPlaceholderText("E-mail address");
    const passwordNode = getByPlaceholderText("Password");

    emailNode.value = fakeUser.email;
              ^^^^^ [ts] Property 'value' does not exist on type 'HTMLElement'.
    passwordNode.value = fakeUser.password;
                 ^^^^^ [ts] Property 'value' does not exist on type 'HTMLElement'.

Reproduction:

Problem description:

Suggested solution:

I'm not very well versed with TypeScript, but I'm wondering if updating the type definition for queries.d.ts, so that type GetByAttribute returns an intersection of HTMLElement & HTMLInputElement makes sense?

// queries.d.ts

export type GetByAttribute = (
  container: HTMLElement,
  id: Matcher,
  options?: MatcherOptions,
) => HTMLElement & HTMLInputElement
                   ^^^^^^^^^^^^^^^^^ Add intersection type?

export const getByPlaceholderText: GetByAttribute

In the meantime, I'm just typecasting:

    const emailNode = getByPlaceholderText("E-mail address") as HTMLInputElement;
    const passwordNode = getByPlaceholderText("Password") as HTMLInputElement;

InputEvent is not supported Error on previously passing tests

  • react-testing-library version: 5.2.0
  • dom-testing-library version: 3.8.3
  • react version:16.5.2
  • node version: v8.12.0
  • npm version: 6.4.1

Relevant code or config:

Running via create-react-app v1 through react-testing-library.

What you did:

Ran tests in CI. Once I saw they were broken, cleared my local node_modules and started having the same issues

What happened:

1/5th of our tests started breaking in our CI about an hour ago when 3.8.3 came out with the following errors:

InputEvent is not supported
      at Function.fireEvent.(anonymous function) [as change] (node_modules/dom-testing-library/dist/events.js:344:13)
      at Object.it ([redacted]/index.test.js:42:36)
          at <anonymous>

The index.test.js line the error points to is

const firstNameField = await waitForElement(() =>
  getByLabelText('First Name')
);

fireEvent.change(firstNameField, { // line 42
  target: { value: 'testFirstName' },
});

Reproduction:

Run previously working tests.

Problem description:

This was just a bugfix release so shouldn't cause this many issues, right?

Suggested solution:

Still digging into what changed and is causing the issue.

Allow testing whether an element doesn't exist

I am trying to test a component where based on the selection of a SELECT it should shows a specific component. The component has a input field. In my unit test I want to check if the expected input field is shown and not the other one. As this is what the user would expect. Now I am using getByTestId to get item:

<input data-testid="entry-weight" type="text" id="itemWeight" name="itemWeight" value={itemWeight} onChange={e => this.updateState(e.target.name, e.target.value)} />

I am using the following code for that: getByTestId('calculator-weight') this works fine in the unit test I am doing the following to verify it's not available:

        const onFormSubmit = jest.fn()
        const BasicForm = (
            <Formik initialValues={TEST_DATA} onSubmit={onFormSubmit}><DataEntryWizardRowItem item={TEST_DATA} index={0} /></Formik>
        )

        const { getByLabelText, getByTestId, debug, ...other } = render(BasicForm)
        const measuremeentTypeField = getByLabelText('Measurement')
        fireEvent.change(measuremeentTypeField, {target: {value: 'quantity'}})

        const calculatorField = getByTestId('calculator-weight') // use testId because both components end up having the same field label
        try {
            const weightField = getByTestId('entry-weight')
            expect(weightField).toBeUndefined() // fail the test if we get here
        } catch(error) {
            expect(error).toBeDefined()
        }

I am not sure if the try-catch is the right way to do? Maybe it would be useful to check if an item doesn't exist? Currently, when it doesn't exist it's throwing an error which breaks the unit test.

Support querying by aria-labelledby for tags that aren't <label>

Describe the feature you'd like:

https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_aria-labelledby_attribute

The aria-labelledby attribute doesn't require the labeling element to be a <label> tag, but dom-testing-lib only searches for labels in queryByLabelText.

I'd like to add a way to query for anything by ARIA label, which would allow doing things like selecting a card item by its heading text and then doing scoped queries for related elements like an "edit" button.

This would largely replace the need for workarounds like getByText -> .$closest() parent testid.

Suggested implementation:

Describe alternatives you've considered:

Teachability, Documentation, Adoption, Migration Strategy:

Merge wait and waitForElement

To simplify the wait() API I think we could merge wait() and waitForElement().

The implementation would be something like:

const isElement = obj => obj && obj.nodeType === Node.ELEMENT_NODE 

function wait(callback = () => {}) {
  const result = callback()
  return isElement(result) ? waitForElement(callback) : waitForExpect(callback)
}

Problem with typings

  • dom-testing-library version: 3.8.1

Problem description:

Typings for query by role are named with a T inside (queryByTRole, queryAllByTRole, getByTRole, getAllByTRole) which makes TS compiler fail.

Suggested solution:

Rename exports in typings/queries.d.ts.

`getByText` scoping and preference order when the text is not unique across the page

  • dom-testing-library version: 1.4.0
  • node version: N/A
  • npm (or yarn) version: N/A

Relevant code or config

cy.getByText('Create')

What you did:

Used getByText with a text that is not unique across the page (which is often the case).

What happened:

Wrong element was found.

Reproduction repository:

N/A

Problem description:

When attempting to find an element by text, label, or other visual cues,

  1. some scoping is required (e.g. find an element with this text that is around the top right corner of the viewport; or, find an element below some other element)
  2. some ordering is required if there is still more than one element found after scoping

Suggested solution:

This issue is here to start a larger discussion, I've got no complete coded solution yet. It's closely related to Cypress but visual element finding is DOM-specific, not specific to Cypress, as proven by my proof-of-concept powered by the same Guiding Principles that uses Puppeteer.

In the mentioned proof-of-concept I experimented with visual element finding and came to an API that allows finding more than one element around some viewport point and filter them after that (potentially, with a text matcher, but the filter is generic). https://github.com/sompylasar/puppeye/blob/d01aa117d3bacf8a458b03dffebef081380b54d9/test/smoke.test.ts#L145-L183

        const checkboxLabelCenterLeftPagePoint = {
          x: checkboxLabelElement.pageRect.left,
          y: checkboxLabelElement.centerPagePoint.y,
        } as puppeye.TPagePoint;
        const [checkboxElement] = await puppeye.findElements(
          pageModel,
          puppeye.makeElementsAroundFinder({
            viewportPoint: puppeye.convertPagePointToViewportPoint(
              pageModel.getPageState(),
              checkboxLabelCenterLeftPagePoint,
            ),
            filter: (el) =>
              el.viewportRect.area > 10 * 10 &&
              el.viewportRect.area < 40 * 40 &&
              el.viewportRect.ratio > 0.9,
            maxDistancePx: 100,
          }),
        );

        await puppeye.moveMouseIntoElementRect(pageModel, checkboxElement);

        await puppeye.clickElement(pageModel, checkboxElement);

        const [continueButton] = await puppeye.findElements(
          pageModel,
          puppeye.makeElementsBelowFinder({
            viewportPoint: puppeye.convertPagePointToViewportPoint(
              pageModel.getPageState(),
              checkboxLabelElement.centerPagePoint,
            ),
            filter: (el) =>
              el.isInteractive && el.textNormalized === puppeye.normalizeText('Continue'),
          }),
        );
        expect(continueButton).toMatchObject({
          text: 'Continue',
        });

        await puppeye.clickElement(pageModel, continueButton);

Cypress has some heuristics for ordering for the similar functionality they have (contains) described here: https://docs.cypress.io/api/commands/contains.html#Preferences – but the only scoping they have is based on DOM selectors. Cypress has something similar, coordinate-based, for the click action (click different parts of rectangles) but doesn't have that for scoping and ordering of elements.

data-original-title support for getByTitle

Describe the feature you'd like:

Many tooltip libraries remove the title attribute and add data-original-title with the same value. I'm using tippy.js. Bootstrap does this too.

Is this something that getByTitle should support out of the box?

Teachability, Documentation, Adoption, Migration Strategy:

// <span data-original-title="Tooltip message" id="2" />
const elementWithTooltip = getByTitle(container, 'Tooltip message')

Allow for different data-testid formatting in `getByTestId`

Describe the feature you'd like:

I'd like to introduce the possibility of using React Testing Library at my place of work, however the standard format this is baked into all of our codebases is data-test-id. This is used by teams of testers and it would be almost impossible to get them to change this.
As it stands we cannot use the getByTestId function in our projects.
This is how we'd currently have to use the library:

const product = container.querySelector('[data-test-id="product"]');

Suggested implementation:

I'm not sure how this would be possible to implement in a sensible manner looking at the codebase given that it directly uses the string data-testid that gets passed into querySelector().

Perhaps we could look at some global config as I am sure there are other teams who are formatting data-testids differently.

Improve the 'byText' queries capability.

Describe the feature you'd like:

'byText' queires, getByText(), for example, works greate in writing tests that closely resembles users actions, but it has a drawback, expecially on integration tests, which usually requires many components to be rendered.

Sometimes a user might be interested in a particular text, but only on a prticular section of the page. Take the bellow screenshot for example.

chat-screen1

So the user sends a 'lol' and 'today' message and wants to make sure the message is sent. She'll have to look for the message "byText" 'lol' to see when/if the message has been markded as sent. It is only the "byText" queries that can help us do this job, but here's the problem: There are many 'lol' and 'today' texts on the page, but our user is only looking at or interested in the 'lol' text INSIDE-THE-MESSAGES-SECTION.

getByText in it's current form will not help us in such case, because it will grab the text 'lol' anywhere it founds it on the page, which in turn require our tests to rely on DOM structure to get the text where're interested in.

Suggested API:

Provide an option to tell the 'byText' queries where-in-the-document they should get the text. Something like this:

getByText('some text', {in: node})

Suggested implementation:

I think this could be easily implemented by some minor editting the queryAllByText fucntion.

We could simply check to see if the in option is present, then use it as the baseElement of the querySelectorAll(). We only need to touch too lines in the function. Something like this:

function queryAllByText(
  container,
  text,
  {selector = '*', exact = true, collapseWhitespace = true, trim = true, ...options} = {}, 
) {
  const matcher = exact ? matches : fuzzyMatches
  const matchOpts = {collapseWhitespace, trim}
  // determin what the baseElement should be
  const baseElement = options.in ? options.in : container;
  return Array.from(baseElement.querySelectorAll(selector)).filter(node =>
    matcher(getNodeText(node), node, text, matchOpts),
  )
}

Describe alternatives you've considered:

N/A

Teachability, Documentation, Adoption, Migration Strategy:

To use this feature, user will need to first grab the in node, then use it as the in option. Like this:

const messageArea = getByTestId('message-area');
getByText('some text', {in: messageArea})

`queryByTestId`, `getByTestId` use partial case-insensitive text match by default, which is unexpected

  • dom-testing-library version: 1.1.0
  • node version: N/A
  • npm (or yarn) version: N/A

Relevant code or config

import { getByTestId } from "dom-testing-library";

document.getElementById("test").innerHTML = `
<div data-testid="supertest-123"></div>
`;

document.getElementById("output").innerHTML = `
${getByTestId(document, "tEsT")
  .outerHTML.replace(/</g, "&lt;")
  .replace(/>/g, "&gt;")}
`;

What you did:

  1. Added an element with data-testid="supertest-123" to an empty DOM.
  2. Queried the DOM via getByTestId by string "tEsT".

What happened:

The element with data-testid="supertest-123" was found.

Reproduction repository:

https://codesandbox.io/s/pwrpl1yy9x?module=%2Fsrc%2Findex.js

Problem description:

The current behavior is unexpected, the identifiers aren't supposed to be matched partially and case-insensitively.

Suggested solution:

Use case-sensitive full-string match in queryByTestId, getByTestId.

The automated release is failing 🚨

🚨 The automated release from the master branch failed. 🚨

I recommend you give this issue a high priority, so other packages depending on you could benefit from your bug fixes and new features.

You can find below the list of errors reported by semantic-release. Each one of them has to be resolved in order to automatically publish your package. I’m sure you can resolve this πŸ’ͺ.

Errors are usually caused by a misconfiguration or an authentication problem. With each error reported below you will find explanation and guidance to help you to resolve it.

Once all the errors are resolved, semantic-release will release your package the next time you push a commit to the master branch. You can also manually restart the failed CI job that runs semantic-release.

If you are not sure how to resolve this, here is some links that can help you:

If those don’t help, or if this issue is reporting something you think isn’t right, you can always ask the humans behind semantic-release.


The push permission to the Git repository is required.

semantic-release cannot push the version tag to the branch master on remote Git repository.

Please refer to the authentication configuration documentation to configure the Git credentials on your CI environment.


Good luck with your project ✨

Your semantic-release bot πŸ“¦πŸš€

getByAltText doesn't work with space in text

  • dom-testing-library version: current
  • react version: 16.4.1
  • node version: 9.11.1
  • npm (or yarn) version: 1.6.0

Relevant code or config:

const Button = () => (
	<button
	  alt="Menu Toggle"
	>
	  <span aria-hidden="true" />
	  <span aria-hidden="true" />
	  <span aria-hidden="true" />
	</button>
)
test('hamburger toggles menu', () => {
  const { getByAltText } = render(
    <Button />
  );
  expect(getByAltText('Menu Toggle')).toBeDefined();
});

What you did:

What happened: Cannot find by alt text containing space

Reproduction: Use above code

Problem description: Should be able to find by alt text with spaces.

getByAltText('Menu Toggle') doesn't find the element
getByAltText('Menu') finds the element

Suggested solution: Match by whole string as well

JSS style blocks overwhelm prettyDOM / debug output

Describe the feature you'd like:

The automatic debug output / prettyDOM is hard to pick through with JSS, since a rather large style block is added to the DOM with JSS classes. There should be a way to avoid JSS output (the material-ui library in our case dumps out 10,000 lines of CSS, which is thought-provoking in itself, but here we are).

Suggested implementation:

I was thinking of an option to prune style blocks from the output; @kentcdodds suggested that defaulting the debug output to the container instead of the documentElement might be a better idea.

Breaking change proposal: Use exact string match by default

Same as testing-library/react-testing-library#76 which would also expose this API

Describe the feature you'd like:

Make exact match the default instead of partial case-insensitive match. Regex would be recommended for those cases instead.

  • partial: /mystring/
  • case-insensitive partial: /mystring/i
  • prefix: /^mystring/
  • postfix: /mystring$/
  • case-insensitive full string: /^mystring$/i

One thing that would more involved to replicate with inline regexes is that the current implementation trims the string and replaces symbols with spaces:

- const normalizedText = textToMatch.trim().replace(/\s+/g, ' ')

See testing-library/react-testing-library#74 (comment)

Suggested implementation:

I could see implementing this by exposing the final options argument to the getAllByAttribute helper to the public methods that use it so that exact: true could toggled.

https://github.com/kentcdodds/dom-testing-library/blob/master/src/queries.js#L73

Describe alternatives you've considered:

Leave the matcher alone

Teachability, Documentation, Adoption, Migration Strategy:

Show common regex patterns like the ones above^ in the docs.

Is this a breaking change?

Yes

getByCSSSelector or something like that

Describe the feature you'd like:

Ability to find elements from a rendered DOM by some sort of CSS selector, a la jQuery.

For example, I have this DOM:

<button disabled="" type="button" class="ant-btn ant-btn-primary ant-btn-circle ant-btn-icon-only"><i class="anticon anticon-plus"><svg viewBox="64 64 896 896" class="" data-icon="plus" width="1em" height="1em" fill="currentColor" aria-hidden="true"><path snip></path></svg></i></button>

It's a <Button/> component from Ant Design but not important.

Now of the existing methods allow me to get this DOM element. I could modify the main code and inject a data-testid="add-button" and then use getByTestId but then it feels like the code has to work for the tests rather than the other way around.

Suggested implementation:

const { getByCSSSelector } = render(<Button disabled={false} icon="plus"/>)
fireEvent(getByCSSSelector('button')).click()

// or...
console.log(getByCSSSelector('button i.anticon'))
// or...
console.log(getByCSSSelector('button i svg'))

Describe alternatives you've considered:

Another challenge I found was I was trying to see if a DOM element existed based on it's classList. The DOM element, when rendered would (depending on business logic) set (or not set) className="spin-animation".

Then I'd be able to do:

const { getByCSSSelector } = render(<MyComponent key={value}/>)
expect(getByCSSSelector('div.spin-animation')).toBeInTheDocument()

Teachability, Documentation, Adoption, Migration Strategy:

No migration needed. A better name that CSSSelector maybe. Perhaps just querySelector or getBySelector.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.