Skip to content

Commit a029772

Browse files
authored
feat: return more data from getSuggestedQuery to support tooling (#608)
* feat: expose suggestion data to support tooling I've added more data to the returned object, to support the development of tooling. Before this change, the returned value would be something like `{ queryName: 'Role' }`, and only by calling the `toString` method, the caller would get more information (the full formatted expression). With this change, the caller gets more useful data, to build tooling around. Example result: ``` { queryName: 'Role', queryMethod: 'getByRole', queryArgs: ['button', { name: /submit/i }], variant: 'get' } ``` The variant now defaults to `get`, to fix a bug that was causing suggestions like `undefinedByRole(…)` to be generated. * feat: update query style to match docs * chore: add some tests for getSuggestedQuery
1 parent fb012de commit a029772

File tree

2 files changed

+90
-34
lines changed

2 files changed

+90
-34
lines changed

src/__tests__/suggestions.js

Lines changed: 72 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {configure} from '../config'
2-
import {screen} from '..'
3-
import {renderIntoDocument} from './helpers/test-utils'
2+
import {screen, getSuggestedQuery} from '..'
3+
import {renderIntoDocument, render} from './helpers/test-utils'
44

55
beforeAll(() => {
66
configure({throwSuggestions: true})
@@ -72,7 +72,7 @@ test(`should not suggest if the suggestion would give different results`, () =>
7272
test('should suggest by label over title', () => {
7373
renderIntoDocument(`<label><span>bar</span><input title="foo" /></label>`)
7474

75-
expect(() => screen.getByTitle('foo')).toThrowError(/getByLabelText\("bar"\)/)
75+
expect(() => screen.getByTitle('foo')).toThrowError(/getByLabelText\('bar'\)/)
7676
})
7777

7878
test('should not suggest if there would be mixed suggestions', () => {
@@ -99,7 +99,7 @@ test('should suggest getByRole when used with getBy', () => {
9999

100100
expect(() => screen.getByTestId('foo')).toThrowErrorMatchingInlineSnapshot(`
101101
"A better query is available, try this:
102-
getByRole("button", {name: /submit/i})
102+
getByRole('button', { name: /submit/i })
103103
104104
105105
<body>
@@ -120,7 +120,7 @@ test('should suggest getAllByRole when used with getAllByTestId', () => {
120120
expect(() => screen.getAllByTestId('foo'))
121121
.toThrowErrorMatchingInlineSnapshot(`
122122
"A better query is available, try this:
123-
getAllByRole("button", {name: /submit/i})
123+
getAllByRole('button', { name: /submit/i })
124124
125125
126126
<body>
@@ -148,18 +148,18 @@ test('should suggest findByRole when used with findByTestId', async () => {
148148
`)
149149

150150
await expect(screen.findByTestId('foo')).rejects.toThrowError(
151-
/findByRole\("button", \{name: \/submit\/i\}\)/,
151+
/findByRole\('button', \{ name: \/submit\/i \}\)/,
152152
)
153153
await expect(screen.findAllByTestId(/foo/)).rejects.toThrowError(
154-
/findAllByRole\("button", \{name: \/submit\/i\}\)/,
154+
/findAllByRole\('button', \{ name: \/submit\/i \}\)/,
155155
)
156156
})
157157

158158
test('should suggest img role w/ alt text', () => {
159159
renderIntoDocument(`<img data-testid="img" alt="Incredibles 2 Poster" />`)
160160

161161
expect(() => screen.getByAltText('Incredibles 2 Poster')).toThrowError(
162-
/getByRole\("img", \{name: \/incredibles 2 poster\/i\}\)/,
162+
/getByRole\('img', \{ name: \/incredibles 2 poster\/i \}\)/,
163163
)
164164
})
165165

@@ -169,7 +169,7 @@ test('escapes regular expressions in suggestion', () => {
169169
)
170170

171171
expect(() => screen.getByTestId('foo')).toThrowError(
172-
/getByRole\("img", \{name: \/the problem \\\(picture of a question mark\\\)\/i\}\)/,
172+
/getByRole\('img', \{ name: \/the problem \\\(picture of a question mark\\\)\/i \}\)/,
173173
)
174174
})
175175

@@ -178,7 +178,7 @@ test('should suggest getByLabelText when no role available', () => {
178178
`<label for="foo">Username</label><input data-testid="foo" id="foo" />`,
179179
)
180180
expect(() => screen.getByTestId('foo')).toThrowError(
181-
/getByLabelText\("Username"\)/,
181+
/getByLabelText\('Username'\)/,
182182
)
183183
})
184184

@@ -191,7 +191,7 @@ test(`should suggest getByLabel on non form elements`, () => {
191191
`)
192192

193193
expect(() => screen.getByTestId('foo')).toThrowError(
194-
/getByLabelText\("Section One"\)/,
194+
/getByLabelText\('Section One'\)/,
195195
)
196196
})
197197

@@ -203,24 +203,24 @@ test.each([
203203
renderIntoDocument(html)
204204

205205
expect(() => screen.getByLabelText('Username')).toThrowError(
206-
/getByRole\("textbox", \{name: \/username\/i\}\)/,
206+
/getByRole\('textbox', \{ name: \/username\/i \}\)/,
207207
)
208208
expect(() => screen.getAllByLabelText('Username')).toThrowError(
209-
/getAllByRole\("textbox", \{name: \/username\/i\}\)/,
209+
/getAllByRole\('textbox', \{ name: \/username\/i \}\)/,
210210
)
211211

212212
expect(() => screen.queryByLabelText('Username')).toThrowError(
213-
/queryByRole\("textbox", \{name: \/username\/i\}\)/,
213+
/queryByRole\('textbox', \{ name: \/username\/i \}\)/,
214214
)
215215
expect(() => screen.queryAllByLabelText('Username')).toThrowError(
216-
/queryAllByRole\("textbox", \{name: \/username\/i\}\)/,
216+
/queryAllByRole\('textbox', \{ name: \/username\/i \}\)/,
217217
)
218218

219219
await expect(screen.findByLabelText('Username')).rejects.toThrowError(
220-
/findByRole\("textbox", \{name: \/username\/i\}\)/,
220+
/findByRole\('textbox', \{ name: \/username\/i \}\)/,
221221
)
222222
await expect(screen.findAllByLabelText(/Username/)).rejects.toThrowError(
223-
/findAllByRole\("textbox", \{name: \/username\/i\}\)/,
223+
/findAllByRole\('textbox', \{ name: \/username\/i \}\)/,
224224
)
225225
})
226226

@@ -230,23 +230,23 @@ test(`should suggest label over placeholder text`, () => {
230230
)
231231

232232
expect(() => screen.getByPlaceholderText('Username')).toThrowError(
233-
/getByLabelText\("Username"\)/,
233+
/getByLabelText\('Username'\)/,
234234
)
235235
})
236236

237237
test(`should suggest getByPlaceholderText`, () => {
238238
renderIntoDocument(`<input data-testid="foo" placeholder="Username" />`)
239239

240240
expect(() => screen.getByTestId('foo')).toThrowError(
241-
/getByPlaceholderText\("Username"\)/,
241+
/getByPlaceholderText\('Username'\)/,
242242
)
243243
})
244244

245245
test(`should suggest getByText for simple elements`, () => {
246246
renderIntoDocument(`<div data-testid="foo">hello there</div>`)
247247

248248
expect(() => screen.getByTestId('foo')).toThrowError(
249-
/getByText\("hello there"\)/,
249+
/getByText\('hello there'\)/,
250250
)
251251
})
252252

@@ -256,7 +256,7 @@ test(`should suggest getByDisplayValue`, () => {
256256
document.getElementById('lastName').value = 'Prine' // RIP John Prine
257257

258258
expect(() => screen.getByTestId('lastName')).toThrowError(
259-
/getByDisplayValue\("Prine"\)/,
259+
/getByDisplayValue\('Prine'\)/,
260260
)
261261
})
262262

@@ -269,10 +269,10 @@ test(`should suggest getByAltText`, () => {
269269
`)
270270

271271
expect(() => screen.getByTestId('input')).toThrowError(
272-
/getByAltText\("last name"\)/,
272+
/getByAltText\('last name'\)/,
273273
)
274274
expect(() => screen.getByTestId('area')).toThrowError(
275-
/getByAltText\("Computer"\)/,
275+
/getByAltText\('Computer'\)/,
276276
)
277277
})
278278

@@ -285,25 +285,67 @@ test(`should suggest getByTitle`, () => {
285285
</svg>`)
286286

287287
expect(() => screen.getByTestId('delete')).toThrowError(
288-
/getByTitle\("Delete"\)/,
288+
/getByTitle\('Delete'\)/,
289289
)
290290
expect(() => screen.getAllByTestId('delete')).toThrowError(
291-
/getAllByTitle\("Delete"\)/,
291+
/getAllByTitle\('Delete'\)/,
292292
)
293293
expect(() => screen.queryByTestId('delete')).toThrowError(
294-
/queryByTitle\("Delete"\)/,
294+
/queryByTitle\('Delete'\)/,
295295
)
296296
expect(() => screen.queryAllByTestId('delete')).toThrowError(
297-
/queryAllByTitle\("Delete"\)/,
297+
/queryAllByTitle\('Delete'\)/,
298298
)
299299
expect(() => screen.queryAllByTestId('delete')).toThrowError(
300-
/queryAllByTitle\("Delete"\)/,
300+
/queryAllByTitle\('Delete'\)/,
301301
)
302302
expect(() => screen.queryAllByTestId('delete')).toThrowError(
303-
/queryAllByTitle\("Delete"\)/,
303+
/queryAllByTitle\('Delete'\)/,
304304
)
305305

306306
// Since `ByTitle` and `ByText` will both return the <title> element
307307
// `getByText` will always be the suggested query as it is higher up the list.
308-
expect(() => screen.getByTestId('svg')).toThrowError(/getByText\("Close"\)/)
308+
expect(() => screen.getByTestId('svg')).toThrowError(/getByText\('Close'\)/)
309+
})
310+
311+
test('getSuggestedQuery handles `variant` and defaults to `get`', () => {
312+
const button = render(`<button>submit</button>`).container.firstChild
313+
314+
expect(getSuggestedQuery(button).toString()).toMatch(/getByRole/)
315+
expect(getSuggestedQuery(button, 'get').toString()).toMatch(/getByRole/)
316+
expect(getSuggestedQuery(button, 'getAll').toString()).toMatch(/getAllByRole/)
317+
expect(getSuggestedQuery(button, 'query').toString()).toMatch(/queryByRole/)
318+
expect(getSuggestedQuery(button, 'queryAll').toString()).toMatch(
319+
/queryAllByRole/,
320+
)
321+
expect(getSuggestedQuery(button, 'find').toString()).toMatch(/findByRole/)
322+
expect(getSuggestedQuery(button, 'findAll').toString()).toMatch(
323+
/findAllByRole/,
324+
)
325+
})
326+
327+
test('getSuggestedQuery returns rich data for tooling', () => {
328+
const button = render(`<button>submit</button>`).container.firstChild
329+
330+
expect(getSuggestedQuery(button)).toMatchObject({
331+
queryName: 'Role',
332+
queryMethod: 'getByRole',
333+
queryArgs: ['button', {name: /submit/i}],
334+
variant: 'get',
335+
})
336+
337+
expect(getSuggestedQuery(button).toString()).toEqual(
338+
`getByRole('button', { name: /submit/i })`,
339+
)
340+
341+
const div = render(`<a>cancel</a>`).container.firstChild
342+
343+
expect(getSuggestedQuery(div)).toMatchObject({
344+
queryName: 'Text',
345+
queryMethod: 'getByText',
346+
queryArgs: ['cancel'],
347+
variant: 'get',
348+
})
349+
350+
expect(getSuggestedQuery(div).toString()).toEqual(`getByText('cancel')`)
309351
})

src/suggestions.js

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,28 @@ function getLabelTextFor(element) {
2929
function escapeRegExp(string) {
3030
return string.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&') // $& means the whole matched string
3131
}
32-
function makeSuggestion(queryName, content, {variant, name}) {
32+
33+
function makeSuggestion(queryName, content, {variant = 'get', name}) {
34+
const queryArgs = [content]
35+
36+
if (name) {
37+
queryArgs.push({name: new RegExp(escapeRegExp(name.toLowerCase()), 'i')})
38+
}
39+
40+
const queryMethod = `${variant}By${queryName}`
41+
3342
return {
3443
queryName,
44+
queryMethod,
45+
queryArgs,
46+
variant,
3547
toString() {
36-
const options = name
37-
? `, {name: /${escapeRegExp(name.toLowerCase())}/i}`
48+
const options = queryArgs[1]
49+
? `, { ${Object.entries(queryArgs[1])
50+
.map(([k, v]) => `${k}: ${v}`)
51+
.join(', ')} }`
3852
: ''
39-
return `${variant}By${queryName}("${content}"${options})`
53+
return `${queryMethod}('${content}'${options})`
4054
},
4155
}
4256
}

0 commit comments

Comments
 (0)