Skip to content

Commit 879bf94

Browse files
RoystonSKent C. Dodds
authored and
Kent C. Dodds
committedNov 29, 2018
feat(configureTestIdAttribute): add ability to override data-testid [generic config] (#164)
N.B. This is one of two alternative PRs with slightly differing configuration APIs. This one contains a generic configuration API exposing one configuration function taking an object which could be extended for future use. That may desirable or not, hence the two PRs. **What**: Introduce an API to allow `data-testid` to be overridden without consumers needing to construct and maintain a set of higher level decorator functions to override the various 'xxxByTestId' functions as exposed in various places. <!-- Why are these changes necessary? --> **Why**: The existing API, where `data-testid` cannot be changed at the low level, requires a significant amount of effort to customise the higher-level APIs. See issue #162 for more discussion. **How**: Ultimately we needed to parameterise what was previously some hardcoded strings in `queries.js`. I would have preferred to keep the changes local to `queries.js`, but we assume elsewhere that all exports from `queries.js` are queries, and I didn't want to alter that. So I added a simple `config.js` module to store the actual configuration, and `queries.js` references that and `index.js` exposes the tiny configuration API. <!-- Have you done all of these things? --> **Checklist**: - [x] Documentation - [x] Tests - [x] Ready to be merged <!-- In your opinion, is this ready to be merged as soon as it's reviewed? --> - [x] Added myself to contributors table <!-- this is optional, see the contributing guidelines for instructions --> <!-- feel free to add additional comments -->
1 parent 4b1363f commit 879bf94

9 files changed

+170
-14
lines changed
 

‎.all-contributorsrc

+12
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,18 @@
419419
"contributions": [
420420
"code"
421421
]
422+
},
423+
{
424+
"login": "RoystonS",
425+
"name": "Royston Shufflebotham",
426+
"avatar_url": "https://avatars0.githubusercontent.com/u/19773?v=4",
427+
"profile": "https://github.com/RoystonS",
428+
"contributions": [
429+
"bug",
430+
"code",
431+
"doc",
432+
"test"
433+
]
422434
}
423435
]
424436
}

‎README.md

+29-1
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,21 @@
1010

1111
<hr />
1212

13+
<!-- prettier-ignore-start -->
1314
[![Build Status][build-badge]][build]
1415
[![Code Coverage][coverage-badge]][coverage]
1516
[![version][version-badge]][package]
1617
[![downloads][downloads-badge]][npmtrends]
1718
[![MIT License][license-badge]][license]
1819

19-
[![All Contributors](https://img.shields.io/badge/all_contributors-42-orange.svg?style=flat-square)](#contributors)
20+
[![All Contributors](https://img.shields.io/badge/all_contributors-43-orange.svg?style=flat-square)](#contributors)
2021
[![PRs Welcome][prs-badge]][prs]
2122
[![Code of Conduct][coc-badge]][coc]
2223

2324
[![Watch on GitHub][github-watch-badge]][github-watch]
2425
[![Star on GitHub][github-star-badge]][github-star]
2526
[![Tweet][twitter-badge]][twitter]
27+
<!-- prettier-ignore-end -->
2628

2729
<div align="center">
2830
<a href="https://testingjavascript.com">
@@ -102,6 +104,7 @@ when a real user uses it.
102104
- [`within` and `getQueriesForElement` APIs](#within-and-getqueriesforelement-apis)
103105
- [Debugging](#debugging)
104106
- [`prettyDOM`](#prettydom)
107+
- [Configuration](#configuration)
105108
- [Implementations](#implementations)
106109
- [Using Without Jest](#using-without-jest)
107110
- [FAQ](#faq)
@@ -456,6 +459,17 @@ const usernameInputElement = getByTestId(container, 'username-input')
456459
> `data-testid`s from the blog post
457460
> ["Making your UI tests resilient to change"][data-testid-blog-post]
458461

462+
#### Overriding `data-testid`
463+
464+
The `...ByTestId` functions in `dom-testing-library` use the attribute `data-testid`
465+
by default, following the precedent set by
466+
[React Native Web](https://github.com/kentcdodds/react-testing-library/issues/1)
467+
which uses a `testID` prop to emit a `data-testid` attribute on the element,
468+
and we recommend you adopt that attribute where possible.
469+
But if you already have an existing codebase that uses a different attribute
470+
for this purpose, you can override this value via
471+
`configure({testIdAttribute: 'data-my-test-attribute'})`.
472+
459473
### `wait`
460474
461475
```typescript
@@ -928,6 +942,19 @@ console.log(prettyDOM(div))
928942

929943
This function is what also powers [the automatic debugging output described above](#debugging).
930944

945+
## Configuration
946+
947+
The library can be configured via the `configure` function, which accepts:
948+
949+
- a plain JS object; this will be merged into the existing configuration. e.g. `configure({testIdAttribute: 'not-data-testid'})`
950+
- a function; the function will be given the existing configuration, and should return a plain JS object which will be merged as above, e.g.
951+
`configure(existingConfig => ({something: [...existingConfig.something, 'extra value for the something array']}))`
952+
953+
Configuration options:
954+
955+
`testIdAttribute`: The attribute used by `getByTestId` and related queries.
956+
Defaults to `data-testid`. See [`getByTestId`](#getbytestid).
957+
931958
## Implementations
932959

933960
This library was not built to be used on its own. The original implementation
@@ -1115,6 +1142,7 @@ Thanks goes to these people ([emoji key][emojis]):
11151142
| [<img src="https://avatars2.githubusercontent.com/u/21689428?v=4" width="100px;"/><br /><sub><b>Jonathan Stoye</b></sub>](http://jonathanstoye.de)<br />[📖](https://github.com/kentcdodds/dom-testing-library/commits?author=JonathanStoye "Documentation") | [<img src="https://avatars2.githubusercontent.com/u/4126644?v=4" width="100px;"/><br /><sub><b>Sanghyeon Lee</b></sub>](https://github.com/yongdamsh)<br />[💡](#example-yongdamsh "Examples") | [<img src="https://avatars3.githubusercontent.com/u/8015514?v=4" width="100px;"/><br /><sub><b>Justice Mba </b></sub>](https://github.com/Dajust)<br />[💻](https://github.com/kentcdodds/dom-testing-library/commits?author=Dajust "Code") [📖](https://github.com/kentcdodds/dom-testing-library/commits?author=Dajust "Documentation") [🤔](#ideas-Dajust "Ideas, Planning, & Feedback") | [<img src="https://avatars3.githubusercontent.com/u/340761?v=4" width="100px;"/><br /><sub><b>Wayne Crouch</b></sub>](https://github.com/wgcrouch)<br />[💻](https://github.com/kentcdodds/dom-testing-library/commits?author=wgcrouch "Code") | [<img src="https://avatars1.githubusercontent.com/u/4996462?v=4" width="100px;"/><br /><sub><b>Ben Elliott</b></sub>](http://benjaminelliott.co.uk)<br />[💻](https://github.com/kentcdodds/dom-testing-library/commits?author=benelliott "Code") | [<img src="https://avatars3.githubusercontent.com/u/577921?v=4" width="100px;"/><br /><sub><b>Ruben Costa</b></sub>](http://nuances.co)<br />[💻](https://github.com/kentcdodds/dom-testing-library/commits?author=rubencosta "Code") | [<img src="https://avatars2.githubusercontent.com/u/4982001?v=4" width="100px;"/><br /><sub><b>Robert Smith</b></sub>](http://rbrtsmith.com/)<br />[🐛](https://github.com/kentcdodds/dom-testing-library/issues?q=author%3Arbrtsmith "Bug reports") [🤔](#ideas-rbrtsmith "Ideas, Planning, & Feedback") [📖](https://github.com/kentcdodds/dom-testing-library/commits?author=rbrtsmith "Documentation") |
11161143
| [<img src="https://avatars3.githubusercontent.com/u/881986?v=4" width="100px;"/><br /><sub><b>dadamssg</b></sub>](https://github.com/dadamssg)<br />[💻](https://github.com/kentcdodds/dom-testing-library/commits?author=dadamssg "Code") | [<img src="https://avatars1.githubusercontent.com/u/186971?v=4" width="100px;"/><br /><sub><b>Neil Kistner</b></sub>](https://neilkistner.com/)<br />[💻](https://github.com/kentcdodds/dom-testing-library/commits?author=wyze "Code") | [<img src="https://avatars3.githubusercontent.com/u/1448597?v=4" width="100px;"/><br /><sub><b>Ben Chauvette</b></sub>](http://bdchauvette.net/)<br />[💻](https://github.com/kentcdodds/dom-testing-library/commits?author=bdchauvette "Code") | [<img src="https://avatars2.githubusercontent.com/u/777527?v=4" width="100px;"/><br /><sub><b>Jeff Baumgardt</b></sub>](https://github.com/JeffBaumgardt)<br />[💻](https://github.com/kentcdodds/dom-testing-library/commits?author=JeffBaumgardt "Code") [📖](https://github.com/kentcdodds/dom-testing-library/commits?author=JeffBaumgardt "Documentation") | [<img src="https://avatars0.githubusercontent.com/u/4658208?v=4" width="100px;"/><br /><sub><b>Matan Kushner</b></sub>](http://matchai.me)<br />[💻](https://github.com/kentcdodds/dom-testing-library/commits?author=matchai "Code") [📖](https://github.com/kentcdodds/dom-testing-library/commits?author=matchai "Documentation") [🤔](#ideas-matchai "Ideas, Planning, & Feedback") [⚠️](https://github.com/kentcdodds/dom-testing-library/commits?author=matchai "Tests") | [<img src="https://avatars2.githubusercontent.com/u/5779538?v=4" width="100px;"/><br /><sub><b>Alex Wendte</b></sub>](http://www.wendtedesigns.com/)<br />[💻](https://github.com/kentcdodds/dom-testing-library/commits?author=themostcolm "Code") [📖](https://github.com/kentcdodds/dom-testing-library/commits?author=themostcolm "Documentation") [⚠️](https://github.com/kentcdodds/dom-testing-library/commits?author=themostcolm "Tests") | [<img src="https://avatars0.githubusercontent.com/u/2196208?v=4" width="100px;"/><br /><sub><b>Tamas Fodor</b></sub>](https://github.com/ruffle1986)<br />[📖](https://github.com/kentcdodds/dom-testing-library/commits?author=ruffle1986 "Documentation") |
11171144
| [<img src="https://avatars3.githubusercontent.com/u/14793495?v=4" width="100px;"/><br /><sub><b>Benjamin Eckardt</b></sub>](https://github.com/BenjaminEckardt)<br />[💻](https://github.com/kentcdodds/dom-testing-library/commits?author=BenjaminEckardt "Code") | [<img src="https://avatars3.githubusercontent.com/u/205752?v=4" width="100px;"/><br /><sub><b>Ryan Campbell</b></sub>](https://github.com/campbellr)<br />[📖](https://github.com/kentcdodds/dom-testing-library/commits?author=campbellr "Documentation") | [<img src="https://avatars2.githubusercontent.com/u/1335519?v=4" width="100px;"/><br /><sub><b>Taylor Briggs</b></sub>](https://taylor-briggs.com)<br />[⚠️](https://github.com/kentcdodds/dom-testing-library/commits?author=TaylorBriggs "Tests") | [<img src="https://avatars2.githubusercontent.com/u/132233?v=4" width="100px;"/><br /><sub><b>John Gozde</b></sub>](https://github.com/jgoz)<br />[💻](https://github.com/kentcdodds/dom-testing-library/commits?author=jgoz "Code") | [<img src="https://avatars2.githubusercontent.com/u/3382565?v=4" width="100px;"/><br /><sub><b>C. T. Lin</b></sub>](https://github.com/chentsulin)<br />[📖](https://github.com/kentcdodds/dom-testing-library/commits?author=chentsulin "Documentation") | [<img src="https://avatars3.githubusercontent.com/u/5312329?v=4" width="100px;"/><br /><sub><b>Terrence Wong</b></sub>](http://terrencewwong.com)<br />[💻](https://github.com/kentcdodds/dom-testing-library/commits?author=terrencewwong "Code") | [<img src="https://avatars0.githubusercontent.com/u/12230408?v=4" width="100px;"/><br /><sub><b>Soo Jae Hwang</b></sub>](https://www.ossfinder.com)<br />[💻](https://github.com/kentcdodds/dom-testing-library/commits?author=misoguy "Code") |
1145+
| [<img src="https://avatars0.githubusercontent.com/u/19773?v=4" width="100px;"/><br /><sub><b>Royston Shufflebotham</b></sub>](https://github.com/RoystonS)<br />[🐛](https://github.com/kentcdodds/dom-testing-library/issues?q=author%3ARoystonS "Bug reports") [💻](https://github.com/kentcdodds/dom-testing-library/commits?author=RoystonS "Code") [📖](https://github.com/kentcdodds/dom-testing-library/commits?author=RoystonS "Documentation") [⚠️](https://github.com/kentcdodds/dom-testing-library/commits?author=RoystonS "Tests") |
11181146

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

‎src/__tests__/config.js

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import {configure, getConfig} from '../config'
2+
3+
describe('configuration API', () => {
4+
let originalConfig
5+
beforeEach(() => {
6+
// Grab the existing configuration so we can restore
7+
// it at the end of the test
8+
configure(existingConfig => {
9+
originalConfig = existingConfig
10+
// Don't change the existing config
11+
return {}
12+
})
13+
})
14+
afterEach(() => {
15+
configure(originalConfig)
16+
})
17+
18+
beforeEach(() => {
19+
configure({other: 123})
20+
})
21+
22+
describe('getConfig', () => {
23+
test('returns existing configuration', () => {
24+
const conf = getConfig()
25+
expect(conf.testIdAttribute).toEqual('data-testid')
26+
})
27+
})
28+
29+
describe('configure', () => {
30+
test('merges a delta rather than replacing the whole config', () => {
31+
const conf = getConfig()
32+
expect(conf).toMatchObject({testIdAttribute: 'data-testid'})
33+
})
34+
35+
test('overrides existing values', () => {
36+
configure({testIdAttribute: 'new-id'})
37+
const conf = getConfig()
38+
expect(conf.testIdAttribute).toEqual('new-id')
39+
})
40+
41+
test('passes existing config out to config function', () => {
42+
// Create a new config key based on the value of an existing one
43+
configure(existingConfig => ({
44+
testIdAttribute: `${existingConfig.testIdAttribute}-derived`,
45+
}))
46+
const conf = getConfig()
47+
48+
// The new value should be there, and existing values should be
49+
// untouched
50+
expect(conf).toMatchObject({
51+
testIdAttribute: 'data-testid-derived',
52+
})
53+
})
54+
})
55+
})

‎src/__tests__/element-queries.js

+29-10
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'jest-dom/extend-expect'
2+
import {configure} from '../config'
23
import {render, renderIntoDocument} from './helpers/test-utils'
34
import document from './helpers/document'
45

@@ -258,16 +259,34 @@ test('query/get select by text with multiple options selected', () => {
258259
expect(queryBySelectText('Alaska').id).toEqual('state-select')
259260
})
260261

261-
test('can get elements by data-testid attribute', () => {
262-
const {queryByTestId} = render(`<div data-testid="firstName"></div>`)
263-
expect(queryByTestId('firstName')).toBeTruthy()
264-
expect(queryByTestId(/first/)).toBeTruthy()
265-
expect(queryByTestId(testid => testid === 'firstName')).toBeTruthy()
266-
// match should be exact, case-sensitive
267-
expect(queryByTestId('firstname')).not.toBeTruthy()
268-
expect(queryByTestId('first')).not.toBeTruthy()
269-
expect(queryByTestId('firstNamePlusMore')).not.toBeTruthy()
270-
expect(queryByTestId('first-name')).not.toBeTruthy()
262+
describe('query by test id', () => {
263+
test('can get elements by test id', () => {
264+
const {queryByTestId} = render(`<div data-testid="firstName"></div>`)
265+
expect(queryByTestId('firstName')).toBeTruthy()
266+
expect(queryByTestId(/first/)).toBeTruthy()
267+
expect(queryByTestId(testid => testid === 'firstName')).toBeTruthy()
268+
// match should be exact, case-sensitive
269+
expect(queryByTestId('firstname')).not.toBeTruthy()
270+
expect(queryByTestId('first')).not.toBeTruthy()
271+
expect(queryByTestId('firstNamePlusMore')).not.toBeTruthy()
272+
expect(queryByTestId('first-name')).not.toBeTruthy()
273+
})
274+
275+
test('can override test id attribute', () => {
276+
const {queryByTestId} = render(`<div data-my-test-id="theTestId"></div>`)
277+
278+
configure({testIdAttribute: 'data-my-test-id'})
279+
expect(queryByTestId('theTestId')).toBeTruthy()
280+
281+
configure({testIdAttribute: 'something-else'})
282+
expect(queryByTestId('theTestId')).toBeFalsy()
283+
})
284+
285+
afterEach(() => {
286+
// Restore the default test id attribute
287+
// even if these tests failed
288+
configure({testIdAttribute: 'data-testid'})
289+
})
271290
})
272291

273292
test('getAll* matchers return an array', () => {

‎src/config.js

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// It would be cleaner for this to live inside './queries', but
2+
// other parts of the code assume that all exports from
3+
// './queries' are query functions.
4+
let config = {
5+
testIdAttribute: 'data-testid',
6+
}
7+
8+
export function configure(newConfig) {
9+
if (typeof newConfig === 'function') {
10+
// Pass the existing config out to the provided function
11+
// and accept a delta in return
12+
newConfig = newConfig(config)
13+
}
14+
15+
// Merge the incoming config delta
16+
config = {
17+
...config,
18+
...newConfig,
19+
}
20+
}
21+
22+
export function getConfig() {
23+
return config
24+
}

‎src/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export * from './events'
1212
export * from './get-queries-for-element'
1313
export * from './query-helpers'
1414
export * from './pretty-dom'
15+
export {configure} from './config'
1516

1617
export {
1718
// The original name of bindElementToQueries was weird

‎src/queries.js

+10-3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
queryAllByAttribute,
77
queryByAttribute,
88
} from './query-helpers'
9+
import {getConfig} from './config'
910

1011
// Here are the queries for the library.
1112
// The queries here should only be things that are accessible to both users who are using a screen reader
@@ -148,10 +149,16 @@ function queryBySelectText(...args) {
148149
return firstResultOrNull(queryAllBySelectText, ...args)
149150
}
150151

152+
function getTestIdAttribute() {
153+
return getConfig().testIdAttribute
154+
}
155+
151156
const queryByPlaceholderText = queryByAttribute.bind(null, 'placeholder')
152157
const queryAllByPlaceholderText = queryAllByAttribute.bind(null, 'placeholder')
153-
const queryByTestId = queryByAttribute.bind(null, 'data-testid')
154-
const queryAllByTestId = queryAllByAttribute.bind(null, 'data-testid')
158+
const queryByTestId = (...args) =>
159+
queryByAttribute(getTestIdAttribute(), ...args)
160+
const queryAllByTestId = (...args) =>
161+
queryAllByAttribute(getTestIdAttribute(), ...args)
155162
const queryByValue = queryByAttribute.bind(null, 'value')
156163
const queryAllByValue = queryAllByAttribute.bind(null, 'value')
157164
const queryByRole = queryByAttribute.bind(null, 'role')
@@ -182,7 +189,7 @@ function getAllByTestId(container, id, ...rest) {
182189
const els = queryAllByTestId(container, id, ...rest)
183190
if (!els.length) {
184191
throw getElementError(
185-
`Unable to find an element by: [data-testid="${id}"]`,
192+
`Unable to find an element by: [${getTestIdAttribute()}="${id}"]`,
186193
container,
187194
)
188195
}

‎typings/config.d.ts

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export interface IConfig {
2+
testIdAttribute: string
3+
}
4+
5+
export interface IConfigFn {
6+
(existingConfig: IConfig): Partial<IConfig>
7+
}
8+
9+
export function configure(configDelta: Partial<IConfig> | IConfigFn): void

‎typings/index.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ export * from './get-node-text'
1515
export * from './events'
1616
export * from './get-queries-for-element'
1717
export * from './pretty-dom'
18+
export * from './config'

0 commit comments

Comments
 (0)
Please sign in to comment.