-
Notifications
You must be signed in to change notification settings - Fork 150
docs: new utils commenting their approaches #217
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 | ||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -181,3 +181,48 @@ export function isRenderVariableDeclarator( | |||||||||||||||||||||||||
|
||||||||||||||||||||||||||
return false; | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
type GetTestingLibraryUtilImportArgs = { | ||||||||||||||||||||||||||
context: any, | ||||||||||||||||||||||||||
utilName: string, | ||||||||||||||||||||||||||
extraModules?: string[], | ||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
export function getTestingLibraryUtilImport(options: GetTestingLibraryUtilImportArgs): null | string { | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
/* Here we need to: | ||||||||||||||||||||||||||
- look for imports of shape: import { <utilName> } from '@testing-library/whatever'; | ||||||||||||||||||||||||||
- look for imports of shape: import { <utilName> as <alias> } from '@testing-library/whatever'; | ||||||||||||||||||||||||||
- look for imports of shape: import * as testingLibrary from '@testing-library/whatever'; | ||||||||||||||||||||||||||
- look for imports of shape: const render = require('@testing-library/whatever').render; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
From 3rd case, this method can't return what's the imported name for `utilName` as it's contained within an object. | ||||||||||||||||||||||||||
What should we do in that case? Return an object or array to indicate if `utilName` has been imported directly or | ||||||||||||||||||||||||||
under a namespace? | ||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||
Comment on lines
+193
to
+202
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. Here you can see the first problem. With the current implementation I had, I found this: Working fine if name imported from testing library // node
import { screen } from '@testing-library/framework';
// ...
// rule implementation
const importedScreen = getTestingLibraryUtilImport({ context, utilName: 'screen' });
console.log(importedScreen) // 'screen' Working fine if rename imported from testing library // node
import { screen as testingScreen } from '@testing-library/framework';
// ...
// rule implementation
const importedScreen = getTestingLibraryUtilImport({ context, utilName: 'screen' });
console.log(importedScreen) // 'testingScreen' But I don't know what to do here // node
import * as testingLibrary from '@testing-library/framework';
// ...
// rule implementation
const importedScreen = getTestingLibraryUtilImport({ context, utilName: 'screen' });
console.log(importedScreen) // null For the 3rd case, this method must return something else to indicate 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. 🤔 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. Yeah, I think this is probably the best option. It'll return an object including the imported name and the context of the import. I need to think what to use for the context of the import. I guess I can use same types from AST, so from the example before it would return: // node
import { screen as testingScreen } from '@testing-library/framework';
// ...
// rule implementation
const importedScreen = getTestingLibraryUtilImport({ context, utilName: 'screen' });
console.log(importedScreen) // { Identifier: 'testingScreen', type: 'ImportSpecifier' } // node
import * as testingLibrary from '@testing-library/framework';
// ...
// rule implementation
const importedScreen = getTestingLibraryUtilImport({ context, utilName: 'screen' });
console.log(importedScreen) // { Identifier: 'testingLibrary', type: 'ImportNamespaceSpecifier' } Thoughts? |
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
/* | ||||||||||||||||||||||||||
Would be nice to be able to find everything from `context.getSourceCode()` but I haven't done that before. | ||||||||||||||||||||||||||
This way, we don't have to look for different import methods in the rule implementation, and just call this method | ||||||||||||||||||||||||||
before returning the object with corresponding AST selectors. | ||||||||||||||||||||||||||
Comment on lines
+206
to
+208
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. At the beginning I was thinking to receive I'm using this |
||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
return null; | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
export function getTestingLibraryRenderImport(context: any): null | string { | ||||||||||||||||||||||||||
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 is a necessary wrapper around The implementation should work fine already, though it can be improved of course. |
||||||||||||||||||||||||||
const customRenders = context.settings['testing-library/custom-renders'] || []; | ||||||||||||||||||||||||||
const possibleRenderOptions = ['render', ...customRenders]; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
let importedRenderName = null; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
possibleRenderOptions.forEach((renderOption) => { | ||||||||||||||||||||||||||
const importedRenderOption = getTestingLibraryUtilImport({ context, utilName: renderOption}); | ||||||||||||||||||||||||||
if (importedRenderOption) { | ||||||||||||||||||||||||||
importedRenderName = importedRenderOption | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
}) | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
return importedRenderName | ||||||||||||||||||||||||||
Comment on lines
+218
to
+227
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. a really minor suggestion, but if we switch from
Suggested change
the only difference is that in case of multiple matches (are we considering that? should we guard or care about that scenario?) 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. Ah of course. Much better approach. About multiple matches: I thought about that possibility. Technically it could happen, but there are so many things I had to consider that I'm gonna just return first match I find. It could happen with something like: import { render } from '@testing-library/foo';
import { render as customRender } from 'test-utils'; For now we will consider "render" as the single found util method. We can see in the future other alternatives to handle this, but I don't think this is an usual use case. |
||||||||||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
import { ESLintUtils, TSESTree } from '@typescript-eslint/experimental-utils'; | ||
import { getDocsUrl, hasTestingLibraryImportModule } from '../utils'; | ||
import { | ||
getTestingLibraryRenderImport, | ||
isCallExpression, | ||
isIdentifier, | ||
isImportSpecifier, | ||
|
@@ -51,9 +52,12 @@ export default ESLintUtils.RuleCreator(getDocsUrl)({ | |
let renderAlias: string | undefined; | ||
let wildcardImportName: string | undefined; | ||
|
||
const testingLibraryRenderName = getTestingLibraryRenderImport(context); | ||
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 is how I'm planning to use the new methods, so you can just do this: create(context) {
const testingLibraryRenderName = getTestingLibraryRenderImport(context);
// now you have 'render' within this var if render found and imported from related Testing Library module
return {
[`VariableDeclarator CallExpression[callee.name=${testingLibraryRenderName}]`](node) {
// here you don't need to check anything manually about render alias or imported properly anymore!
// just implement the actual checks for the rule
}
}
} 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 really like this 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. Right? I hope I can implement this version, so it would be really easy to focus on specific rules implementations. This is why is taking me so long basically, I'm trying to 1) find proper approach and 2) figure how to implement it. There are couple of things I need to try with |
||
|
||
return { | ||
// check named imports | ||
ImportDeclaration(node: TSESTree.ImportDeclaration) { | ||
// <-- this check would disappear | ||
if (!hasTestingLibraryImportModule(node)) { | ||
return; | ||
} | ||
|
@@ -69,6 +73,7 @@ export default ESLintUtils.RuleCreator(getDocsUrl)({ | |
}, | ||
// check wildcard imports | ||
'ImportDeclaration ImportNamespaceSpecifier'( | ||
// <-- this check would disappear | ||
node: TSESTree.ImportNamespaceSpecifier | ||
) { | ||
if ( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -97,11 +97,24 @@ ruleTester.run(RULE_NAME, rule, { | |
const button = screen.getByText('some button'); | ||
}); | ||
`, | ||
options: [ | ||
{ | ||
renderFunctions: ['customRender'], | ||
}, | ||
], | ||
settings: { | ||
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. Tests updated with the new ESLint shared settings. |
||
'testing-library/custom-renders': ['customRender'], | ||
'testing-library/custom-modules': ['test-utils'], | ||
}, | ||
}, | ||
{ | ||
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. There is a case not covered in tests which I forgot to bring here. What about these 2 cases?
On the first example: "custom-renders" option could be understood as user wants to report On the second example: "custom-renders" is setup but there is no "custom-modules" setup. Our new util methods won't be aware of this "customRender" as there is no custom module where it could be possibly imported. I think this is fine. |
||
import { render, screen } from 'test-utils'; | ||
|
||
test('should not report straight destructured render result from custom module', () => { | ||
const { unmount } = render(<SomeComponent />); | ||
const button = screen.getByText('some button'); | ||
}); | ||
`, | ||
settings: { | ||
'testing-library/custom-renders': ['customRender'], | ||
'testing-library/custom-modules': ['test-utils'], | ||
}, | ||
}, | ||
{ | ||
code: ` | ||
|
@@ -112,11 +125,23 @@ ruleTester.run(RULE_NAME, rule, { | |
await view.findByRole('button'); | ||
}); | ||
`, | ||
options: [ | ||
{ | ||
renderFunctions: ['customRender'], | ||
}, | ||
], | ||
settings: { | ||
'testing-library/custom-renders': ['customRender'], | ||
'testing-library/custom-modules': ['test-utils'], | ||
}, | ||
}, | ||
{ | ||
code: ` | ||
import { render } from 'test-utils'; | ||
|
||
test('should not report render result called "view" from custom module', async () => { | ||
const view = render(); | ||
await view.findByRole('button'); | ||
}); | ||
`, | ||
settings: { | ||
'testing-library/custom-modules': ['test-utils'], | ||
}, | ||
}, | ||
{ | ||
code: ` | ||
|
@@ -127,11 +152,23 @@ ruleTester.run(RULE_NAME, rule, { | |
await utils.findByRole('button'); | ||
}); | ||
`, | ||
options: [ | ||
{ | ||
renderFunctions: ['customRender'], | ||
}, | ||
], | ||
settings: { | ||
'testing-library/custom-renders': ['customRender'], | ||
'testing-library/custom-modules': ['test-utils'], | ||
}, | ||
}, | ||
{ | ||
code: ` | ||
import { render } from 'test-utils'; | ||
|
||
test('should not report render result called "utils" from custom module', async () => { | ||
const utils = render(); | ||
await utils.findByRole('button'); | ||
}); | ||
`, | ||
settings: { | ||
'testing-library/custom-modules': ['test-utils'], | ||
}, | ||
}, | ||
{ | ||
code: ` | ||
|
@@ -327,11 +364,33 @@ ruleTester.run(RULE_NAME, rule, { | |
const button = wrapper.getByText('some button'); | ||
}); | ||
`, | ||
options: [ | ||
settings: { | ||
'testing-library/custom-renders': ['customRender'], | ||
'testing-library/custom-modules': ['test-utils'], | ||
}, | ||
errors: [ | ||
{ | ||
renderFunctions: ['customRender'], | ||
messageId: 'invalidRenderResultName', | ||
data: { | ||
varName: 'wrapper', | ||
}, | ||
line: 5, | ||
column: 17, | ||
}, | ||
], | ||
}, | ||
{ | ||
code: ` | ||
import { render } from 'test-utils'; | ||
|
||
test('should report from custom render module ', () => { | ||
const wrapper = render(<SomeComponent />); | ||
const button = wrapper.getByText('some button'); | ||
}); | ||
`, | ||
settings: { | ||
'testing-library/custom-modules': ['test-utils'], | ||
}, | ||
errors: [ | ||
{ | ||
messageId: 'invalidRenderResultName', | ||
|
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.
This is the method to find any testing library import from
@testing-library/framework
or custom module defined in ESLint shared settings. It just needs 3 args:context.getSourceCode()
to find all imports in different formats, and to getcontext.settings
with plugin options.