-
Notifications
You must be signed in to change notification settings - Fork 148
fix: add findby variable declaration to prefer-find-by when auto fixing #197
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,34 @@ | ||
import { InvalidTestCase } from '@typescript-eslint/experimental-utils/dist/ts-eslint' | ||
import { InvalidTestCase, ValidTestCase } from '@typescript-eslint/experimental-utils/dist/ts-eslint' | ||
import { createRuleTester } from '../test-utils'; | ||
import { ASYNC_QUERIES_COMBINATIONS, SYNC_QUERIES_COMBINATIONS } from '../../../lib/utils'; | ||
import rule, { WAIT_METHODS, RULE_NAME } from '../../../lib/rules/prefer-find-by'; | ||
import rule, { WAIT_METHODS, RULE_NAME, getFindByQueryVariant, MessageIds } from '../../../lib/rules/prefer-find-by'; | ||
|
||
const ruleTester = createRuleTester({ | ||
ecmaFeatures: { | ||
jsx: true, | ||
}, | ||
}); | ||
|
||
function buildFindByMethod(queryMethod: string) { | ||
return `${getFindByQueryVariant(queryMethod)}${queryMethod.split('By')[1]}` | ||
} | ||
|
||
function createScenario<T extends ValidTestCase<[]> | InvalidTestCase<MessageIds, []>>(callback: (waitMethod: string, queryMethod: string) => T) { | ||
return WAIT_METHODS.reduce((acc: T[], waitMethod) => | ||
acc.concat( | ||
SYNC_QUERIES_COMBINATIONS | ||
.map((queryMethod) => callback(waitMethod, queryMethod)) | ||
) | ||
, []) | ||
} | ||
|
||
ruleTester.run(RULE_NAME, rule, { | ||
valid: [ | ||
...ASYNC_QUERIES_COMBINATIONS.map((queryMethod) => ({ | ||
code: `const submitButton = await ${queryMethod}('foo')` | ||
code: ` | ||
const { ${queryMethod} } = setup() | ||
const submitButton = await ${queryMethod}('foo') | ||
` | ||
})), | ||
...ASYNC_QUERIES_COMBINATIONS.map((queryMethod) => ({ | ||
code: `const submitButton = await screen.${queryMethod}('foo')` | ||
|
@@ -60,35 +76,117 @@ ruleTester.run(RULE_NAME, rule, { | |
} | ||
], | ||
invalid: [ | ||
// using reduce + concat 'cause flatMap is not available in node10.x | ||
...WAIT_METHODS.reduce((acc: InvalidTestCase<'preferFindBy', []>[], waitMethod) => acc | ||
.concat( | ||
SYNC_QUERIES_COMBINATIONS.map((queryMethod: string) => ({ | ||
code: `const submitButton = await ${waitMethod}(() => ${queryMethod}('foo', { name: 'baz' }))`, | ||
errors: [{ | ||
messageId: 'preferFindBy', | ||
data: { | ||
queryVariant: queryMethod.includes('All') ? 'findAllBy': 'findBy', | ||
queryMethod: queryMethod.split('By')[1], | ||
fullQuery: `${waitMethod}(() => ${queryMethod}('foo', { name: 'baz' }))`, | ||
}, | ||
}], | ||
output: `const submitButton = await ${queryMethod.includes('All') ? 'findAllBy': 'findBy'}${queryMethod.split('By')[1]}('foo', { name: 'baz' })` | ||
})) | ||
).concat( | ||
SYNC_QUERIES_COMBINATIONS.map((queryMethod: string) => ({ | ||
code: `const submitButton = await ${waitMethod}(() => screen.${queryMethod}('foo', { name: 'baz' }))`, | ||
errors: [{ | ||
messageId: 'preferFindBy', | ||
data: { | ||
queryVariant: queryMethod.includes('All') ? 'findAllBy': 'findBy', | ||
queryMethod: queryMethod.split('By')[1], | ||
fullQuery: `${waitMethod}(() => screen.${queryMethod}('foo', { name: 'baz' }))`, | ||
} | ||
}], | ||
output: `const submitButton = await screen.${queryMethod.includes('All') ? 'findAllBy': 'findBy'}${queryMethod.split('By')[1]}('foo', { name: 'baz' })` | ||
})) | ||
), | ||
[]) | ||
...createScenario((waitMethod: string, queryMethod: string) => ({ | ||
code: ` | ||
const { ${queryMethod} } = render() | ||
const submitButton = await ${waitMethod}(() => ${queryMethod}('foo', { name: 'baz' })) | ||
`, | ||
errors: [{ | ||
messageId: 'preferFindBy', | ||
data: { | ||
queryVariant: getFindByQueryVariant(queryMethod), | ||
queryMethod: queryMethod.split('By')[1], | ||
fullQuery: `${waitMethod}(() => ${queryMethod}('foo', { name: 'baz' }))`, | ||
}, | ||
}], | ||
output: ` | ||
const { ${queryMethod}, ${buildFindByMethod(queryMethod)} } = render() | ||
const submitButton = await ${buildFindByMethod(queryMethod)}('foo', { name: 'baz' }) | ||
` | ||
})), | ||
...createScenario((waitMethod: string, queryMethod: string) => ({ | ||
code: `const submitButton = await ${waitMethod}(() => screen.${queryMethod}('foo', { name: 'baz' }))`, | ||
errors: [{ | ||
messageId: 'preferFindBy', | ||
data: { | ||
queryVariant: getFindByQueryVariant(queryMethod), | ||
queryMethod: queryMethod.split('By')[1], | ||
fullQuery: `${waitMethod}(() => screen.${queryMethod}('foo', { name: 'baz' }))`, | ||
} | ||
}], | ||
output: `const submitButton = await screen.${buildFindByMethod(queryMethod)}('foo', { name: 'baz' })` | ||
})), | ||
// // this scenario verifies it works when the render function is defined in another scope | ||
...WAIT_METHODS.map((waitMethod: string) => ({ | ||
code: ` | ||
const { getByText, queryByLabelText, findAllByRole } = customRender() | ||
it('foo', async () => { | ||
const submitButton = await ${waitMethod}(() => getByText('baz', { name: 'button' })) | ||
}) | ||
`, | ||
errors: [{ | ||
messageId: 'preferFindBy', | ||
data: { | ||
queryVariant: 'findBy', | ||
queryMethod: 'Text', | ||
fullQuery: `${waitMethod}(() => getByText('baz', { name: 'button' }))`, | ||
} | ||
}], | ||
output: ` | ||
const { getByText, queryByLabelText, findAllByRole, findByText } = customRender() | ||
it('foo', async () => { | ||
const submitButton = await findByText('baz', { name: 'button' }) | ||
}) | ||
` | ||
})), | ||
// // this scenario verifies when findBy* were already defined (because it was used elsewhere) | ||
...WAIT_METHODS.map((waitMethod: string) => ({ | ||
code: ` | ||
const { getAllByRole, findAllByRole } = customRender() | ||
describe('some scenario', () => { | ||
it('foo', async () => { | ||
const submitButton = await ${waitMethod}(() => getAllByRole('baz', { name: 'button' })) | ||
}) | ||
}) | ||
`, | ||
errors: [{ | ||
messageId: 'preferFindBy', | ||
data: { | ||
queryVariant: 'findAllBy', | ||
queryMethod: 'Role', | ||
fullQuery: `${waitMethod}(() => getAllByRole('baz', { name: 'button' }))`, | ||
} | ||
}], | ||
output: ` | ||
const { getAllByRole, findAllByRole } = customRender() | ||
describe('some scenario', () => { | ||
it('foo', async () => { | ||
const submitButton = await findAllByRole('baz', { name: 'button' }) | ||
}) | ||
}) | ||
` | ||
})), | ||
// invalid code, as we need findBy* to be defined somewhere, but required for getting 100% coverage | ||
{ | ||
code: `const submitButton = await waitFor(() => getByText('baz', { name: 'button' }))`, | ||
errors: [{ | ||
messageId: 'preferFindBy', | ||
data: { | ||
queryVariant: 'findBy', | ||
queryMethod: 'Text', | ||
fullQuery: `waitFor(() => getByText('baz', { name: 'button' }))` | ||
} | ||
}], | ||
output: `const submitButton = await findByText('baz', { name: 'button' })` | ||
}, | ||
// this code would be invalid too, as findByRole is not defined anywhere. | ||
{ | ||
code: ` | ||
const getByRole = render().getByRole | ||
const submitButton = await waitFor(() => getByRole('baz', { name: 'button' })) | ||
`, | ||
errors: [{ | ||
messageId: 'preferFindBy', | ||
data: { | ||
queryVariant: 'findBy', | ||
queryMethod: 'Role', | ||
fullQuery: `waitFor(() => getByRole('baz', { name: 'button' }))` | ||
} | ||
}], | ||
output: ` | ||
const getByRole = render().getByRole | ||
const submitButton = await findByRole('baz', { name: 'button' }) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would turn runnable code into breaking code, right? If that's the case I would consider this a bad experience as it could potentially break a lot of code. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it would. Maybe we went too far by making this rule autofixable? Not sure. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In addition to that, I am not sure how often that would be a realistic scenario. I'd think that devs use destructuring or call the function from screen or the object returned from the render function. But not quite sure. I'm open to debate it. I still want to enforce this PR does not introduce this scenario, but rather, fixes one of the many possible scenarios where There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Got it, thanks for the clarification. I do agree that most devs will use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I hope no one uses Testing Library this way to be honest 😅 I think we are fine by covering all the other cases. |
||
` | ||
} | ||
], | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not a fan of doing this kind of "importing the function calculating something to check the same in the tests". This can lead to silent errors, so I prefer to hardcode or reimplement this kind of behaviors in testing side. Anyway, I think for this case it's fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I generally, agree, but I was using that function everywhere and it was so small... I couldn't resist the temptation (?)