Skip to content
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

fix: use exact match for data-testid #29

Merged
Show file tree
Hide file tree
Changes from 4 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
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ when a real user uses it.
* [`getByPlaceholderText(container: HTMLElement, text: TextMatch): HTMLElement`](#getbyplaceholdertextcontainer-htmlelement-text-textmatch-htmlelement)
* [`getByText(container: HTMLElement, text: TextMatch): HTMLElement`](#getbytextcontainer-htmlelement-text-textmatch-htmlelement)
* [`getByAltText(container: HTMLElement, text: TextMatch): HTMLElement`](#getbyalttextcontainer-htmlelement-text-textmatch-htmlelement)
* [`getByTestId(container: HTMLElement, text: TextMatch): HTMLElement`](#getbytestidcontainer-htmlelement-text-textmatch-htmlelement)
* [`wait`](#wait)
* [`waitForElement`](#waitforelement)
* [`fireEvent(node: HTMLElement, event: Event)`](#fireeventnode-htmlelement-event-event)
Expand Down Expand Up @@ -248,7 +249,7 @@ and [`<area>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/area)
const incrediblesPosterImg = getByAltText(container, /incredibles.*poster$/i)
```

#### `getByTestId(container: HTMLElement, text: TextMatch): HTMLElement`
### `getByTestId(container: HTMLElement, text: TextMatch): HTMLElement`

A shortcut to `` container.querySelector(`[data-testid="${yourId}"]`) `` (and it
also accepts a [`TextMatch`](#textmatch)).
Expand Down
15 changes: 15 additions & 0 deletions src/__tests__/element-queries.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,18 @@ test('get element by its alt text', () => {
expect(getByAltText(/fin.*nem.*poster$/i).src).toBe('/finding-nemo.png')
})

test('can get elements by data-testid attribute', () => {
const {queryByTestId} = render(`<div data-testid="firstName"></div>`)
expect(queryByTestId('firstName')).toBeInTheDOM()
expect(queryByTestId(/first/)).toBeInTheDOM()
expect(queryByTestId(testid => testid === 'firstName')).toBeInTheDOM()
// match should be exact, case-sensitive
expect(queryByTestId('firstname')).not.toBeInTheDOM()
expect(queryByTestId('first')).not.toBeInTheDOM()
expect(queryByTestId('firstNamePlusMore')).not.toBeInTheDOM()
expect(queryByTestId('first-name')).not.toBeInTheDOM()
})

test('getAll* matchers return an array', () => {
const {
getAllByAltText,
Expand Down Expand Up @@ -161,9 +173,12 @@ test('getAll* matchers throw for 0 matches', () => {
} = render(`
<div>
<label>No Matches Please</label>
<div data-testid="ABC"></div>
<div data-testid="a-b-c"></div>
</div>,
`)
expect(() => getAllByTestId('nope')).toThrow()
expect(() => getAllByTestId('abc')).toThrow()
expect(() => getAllByAltText('nope')).toThrow()
expect(() => getAllByLabelText('nope')).toThrow()
expect(() => getAllByLabelText('no matches please')).toThrow()
Expand Down
26 changes: 26 additions & 0 deletions src/__tests__/matches.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {matches, matchesExact} from '../'

// unit tests for text match utils

const node = null

test('matches should get fuzzy matches', () => {
// should not match
expect(matchesExact(null, node, 'abc')).toBe(false)
expect(matchesExact('', node, 'abc')).toBe(false)
// should match
expect(matches('ABC', node, 'abc')).toBe(true)
expect(matches('ABC', node, 'ABC')).toBe(true)
})

test('matchesExact should only get exact matches', () => {
// should not match
expect(matchesExact(null, node, null)).toBe(false)
expect(matchesExact(null, node, 'abc')).toBe(false)
expect(matchesExact('', node, 'abc')).toBe(false)
expect(matchesExact('ABC', node, 'abc')).toBe(false)
expect(matchesExact('ABC', node, 'A')).toBe(false)
expect(matchesExact('ABC', node, 'ABCD')).toBe(false)
// should match
expect(matchesExact('ABC', node, 'ABC')).toBe(true)
})
15 changes: 14 additions & 1 deletion src/matches.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,17 @@ function matches(textToMatch, node, matcher) {
}
}

export {matches}
function matchesExact(textToMatch, node, matcher) {
if (typeof textToMatch !== 'string') {
return false
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the only line not exercised by public api tests because the exact match is already pre-filtered with querySelectorAll in queryAllByAttribute. Added unit tests for the matchers instead to document match behavior.

}
if (typeof matcher === 'string') {
return textToMatch === matcher
} else if (typeof matcher === 'function') {
return matcher(textToMatch, node)
} else {
return matcher.test(textToMatch)
}
}

export {matches, matchesExact}
17 changes: 10 additions & 7 deletions src/queries.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {matches} from './matches'
import {matches, matchesExact} from './matches'
import {getNodeText} from './get-node-text'
import {prettyDOM} from './pretty-dom'

Expand Down Expand Up @@ -70,22 +70,25 @@ function queryByText(container, text, opts) {

// this is just a utility and not an exposed query.
// There are no plans to expose this.
function queryAllByAttribute(attribute, container, text) {
function queryAllByAttribute(attribute, container, text, {exact = false} = {}) {
const matcher = exact ? matchesExact : matches
return Array.from(container.querySelectorAll(`[${attribute}]`)).filter(node =>
matches(node.getAttribute(attribute), node, text),
matcher(node.getAttribute(attribute), node, text),
)
}

// this is just a utility and not an exposed query.
// There are no plans to expose this.
function queryByAttribute(attribute, container, text) {
return firstResultOrNull(queryAllByAttribute, attribute, container, text)
function queryByAttribute(...args) {
return firstResultOrNull(queryAllByAttribute, ...args)
}

const queryByPlaceholderText = queryByAttribute.bind(null, 'placeholder')
const queryAllByPlaceholderText = queryAllByAttribute.bind(null, 'placeholder')
const queryByTestId = queryByAttribute.bind(null, 'data-testid')
const queryAllByTestId = queryAllByAttribute.bind(null, 'data-testid')
const queryByTestId = (...args) =>
queryByAttribute('data-testid', ...args, {exact: true})
const queryAllByTestId = (...args) =>
queryAllByAttribute('data-testid', ...args, {exact: true})

function queryAllByAltText(container, alt) {
return Array.from(container.querySelectorAll('img,input,area')).filter(node =>
Expand Down