diff --git a/README.md b/README.md index eb672608..cf790132 100644 --- a/README.md +++ b/README.md @@ -76,12 +76,14 @@ 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: ExactTextMatch): HTMLElement`](#getbytestidcontainer-htmlelement-text-exacttextmatch-htmlelement) * [`wait`](#wait) * [`waitForElement`](#waitforelement) * [`fireEvent(node: HTMLElement, event: Event)`](#fireeventnode-htmlelement-event-event) * [Custom Jest Matchers](#custom-jest-matchers) * [Using other assertion libraries](#using-other-assertion-libraries) * [`TextMatch`](#textmatch) + * [ExactTextMatch](#exacttextmatch) * [`query` APIs](#query-apis) * [`queryAll` and `getAll` APIs](#queryall-and-getall-apis) * [`bindElementToQueries`](#bindelementtoqueries) @@ -248,10 +250,10 @@ and [``](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: ExactTextMatch): HTMLElement` A shortcut to `` container.querySelector(`[data-testid="${yourId}"]`) `` (and it -also accepts a [`TextMatch`](#textmatch)). +also accepts an [`ExactTextMatch`](#exacttextmatch)). ```javascript // @@ -477,6 +479,25 @@ getByText(container, (content, element) => { }) ``` +### ExactTextMatch + +Some APIs use ExactTextMatch, which is the same as TextMatch but case-sensitive +and does not match substrings; however, regexes and functions are also accepted +for custom matching. + +```js +// + +// all of the following will find the button +getByTestId(container, 'submit-button') // exact match +getByTestId(container, /submit*/) // regex match +getByTestId(container, content => content.startsWith('submit')) // function + +// all of the following will NOT find the button +getByTestId(container, 'submit-') // no substrings +getByTestId(container, 'Submit-Button') // case-sensitive +``` + ## `query` APIs Each of the `get` APIs listed in [the 'Usage'](#usage) section above have a diff --git a/src/__tests__/element-queries.js b/src/__tests__/element-queries.js index 1cf4e915..2d646d77 100644 --- a/src/__tests__/element-queries.js +++ b/src/__tests__/element-queries.js @@ -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(`
`) + 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, @@ -161,9 +173,12 @@ test('getAll* matchers throw for 0 matches', () => { } = render(`
+
+
, `) expect(() => getAllByTestId('nope')).toThrow() + expect(() => getAllByTestId('abc')).toThrow() expect(() => getAllByAltText('nope')).toThrow() expect(() => getAllByLabelText('nope')).toThrow() expect(() => getAllByLabelText('no matches please')).toThrow() diff --git a/src/__tests__/matches.js b/src/__tests__/matches.js new file mode 100644 index 00000000..abef386d --- /dev/null +++ b/src/__tests__/matches.js @@ -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) +}) diff --git a/src/matches.js b/src/matches.js index 387e9fe6..96c8f2b3 100644 --- a/src/matches.js +++ b/src/matches.js @@ -12,4 +12,17 @@ function matches(textToMatch, node, matcher) { } } -export {matches} +function matchesExact(textToMatch, node, matcher) { + if (typeof textToMatch !== 'string') { + return false + } + if (typeof matcher === 'string') { + return textToMatch === matcher + } else if (typeof matcher === 'function') { + return matcher(textToMatch, node) + } else { + return matcher.test(textToMatch) + } +} + +export {matches, matchesExact} diff --git a/src/queries.js b/src/queries.js index 49d14223..97b035f7 100644 --- a/src/queries.js +++ b/src/queries.js @@ -1,4 +1,4 @@ -import {matches} from './matches' +import {matches, matchesExact} from './matches' import {getNodeText} from './get-node-text' import {prettyDOM} from './pretty-dom' @@ -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 =>