Skip to content
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

feat: add new settings for prefer-user-event #251

Merged
merged 3 commits into from
Nov 9, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions lib/node-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,3 +253,22 @@ export function getImportModuleName(
return node.arguments[0].value;
}
}

export function getSpecifierFromImport(
node: ImportModuleNode,
specifierName: string
) {
if (isImportDeclaration(node)) {
const namedExport = node.specifiers.find(
(node) => isImportSpecifier(node) && node.imported.name === specifierName
);
// it is "import { foo } from 'baz'""
gndelia marked this conversation as resolved.
Show resolved Hide resolved
if (namedExport) {
return namedExport;
}
// it could be "import * as rtl from 'baz'"
return node.specifiers.find((n) => isImportNamespaceSpecifier(n));
} else {
// TODO make it work for require
}
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we move this function into the detect-import-testing-library utils, instead of passing node I could get it directly from the helper. Not sure which option is best.

I still have to make it work for both require scenarios mentioned

Copy link
Member

@Belco90 Belco90 Nov 5, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's a good idea to have this within detection module. I actually mentioned this helper when analyzing how to organize the helpers for detection module. There are difference scenarios as you have already figured in your draft, but there are more related to other Testing Library utils, specially for render.

Anyway, I think we should include this one within detection already, covering all different imports. Then we can improve it when necessary when refactoring other rules. I'd name it something like findImportedUtilSpecifier

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved it into the detection helper, and added the scenario for require

59 changes: 19 additions & 40 deletions lib/rules/prefer-user-event.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { ESLintUtils, TSESTree } from '@typescript-eslint/experimental-utils';
import { getDocsUrl, hasTestingLibraryImportModule } from '../utils';
import { TSESTree } from '@typescript-eslint/experimental-utils';
import { createTestingLibraryRule } from '../create-testing-library-rule';
import {
isImportSpecifier,
isIdentifier,
isMemberExpression,
getSpecifierFromImport,
} from '../node-utils';

export const RULE_NAME = 'prefer-user-event';
Expand Down Expand Up @@ -65,7 +65,7 @@ function buildErrorMessage(fireEventMethod: string) {

const fireEventMappedMethods = Object.keys(MappingToUserEvent);

export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({
export default createTestingLibraryRule<Options, MessageIds>({
name: RULE_NAME,
meta: {
type: 'suggestion',
Expand All @@ -90,59 +90,38 @@ export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({
},
defaultOptions: [{ allowedMethods: [] }],

create(context, [options]) {
create(context, [options], helpers) {
const { allowedMethods } = options;
const sourceCode = context.getSourceCode();
let hasNamedImportedFireEvent = false;
let hasImportedFireEvent = false;
let fireEventAlias: string | undefined;
let wildcardImportName: string | undefined;

return {
// checks if import has shape:
// import { fireEvent } from '@testing-library/dom';
ImportDeclaration(node: TSESTree.ImportDeclaration) {
if (!hasTestingLibraryImportModule(node)) {
return;
}
const fireEventImport = node.specifiers.find(
(node) =>
isImportSpecifier(node) && node.imported.name === 'fireEvent'
);
hasNamedImportedFireEvent = !!fireEventImport;
if (!hasNamedImportedFireEvent) {
['CallExpression > MemberExpression'](node: TSESTree.MemberExpression) {
if (!helpers.getIsTestingLibraryImported()) {
gndelia marked this conversation as resolved.
Show resolved Hide resolved
return;
}
fireEventAlias = fireEventImport.local.name;
},
const testingLibraryImportNode = helpers.getTestingLibraryImportNode();
const fireEventAliasOrWildcard = getSpecifierFromImport(
testingLibraryImportNode,
'fireEvent'
)?.local.name;

// checks if import has shape:
// import * as dom from '@testing-library/dom';
'ImportDeclaration ImportNamespaceSpecifier'(
node: TSESTree.ImportNamespaceSpecifier
) {
const importDeclarationNode = node.parent as TSESTree.ImportDeclaration;
if (!hasTestingLibraryImportModule(importDeclarationNode)) {
if (!fireEventAliasOrWildcard) {
// testing library was imported, but fireEvent was not imported
return;
}
hasImportedFireEvent = !!node.local.name;
wildcardImportName = node.local.name;
},
['CallExpression > MemberExpression'](node: TSESTree.MemberExpression) {
if (!hasImportedFireEvent && !hasNamedImportedFireEvent) {
return;
}
// check node is fireEvent or it's alias from the named import
const fireEventUsed =
isIdentifier(node.object) && node.object.name === fireEventAlias;
isIdentifier(node.object) &&
node.object.name === fireEventAliasOrWildcard;

const fireEventFromWildcardUsed =
isMemberExpression(node.object) &&
isIdentifier(node.object.object) &&
node.object.object.name === wildcardImportName &&
node.object.object.name === fireEventAliasOrWildcard &&
isIdentifier(node.object.property) &&
node.object.property.name === 'fireEvent';
gndelia marked this conversation as resolved.
Show resolved Hide resolved

if (!fireEventUsed && !fireEventFromWildcardUsed) {
// fireEvent was imported but it was not used
return;
}

Expand Down