Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
66 changes: 38 additions & 28 deletions src/create-testing-library-rule/detect-testing-library-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,9 @@ export function detectTestingLibraryUtils<
let userEventName: string | undefined;

if (userEvent) {
userEventName = userEvent.name;
userEventName = ASTUtils.isIdentifier(userEvent)
? userEvent.name
: userEvent.local.name;
} else if (isAggressiveModuleReportingEnabled()) {
userEventName = USER_EVENT_NAME;
}
Expand Down Expand Up @@ -574,7 +576,9 @@ export function detectTestingLibraryUtils<
let userEventName: string | undefined;

if (userEvent) {
userEventName = userEvent.name;
userEventName = ASTUtils.isIdentifier(userEvent)
? userEvent.name
: userEvent.local.name;
} else if (isAggressiveModuleReportingEnabled()) {
userEventName = USER_EVENT_NAME;
}
Expand Down Expand Up @@ -876,38 +880,44 @@ export function detectTestingLibraryUtils<
return findImportSpecifier(specifierName, node);
};

const findImportedUserEventSpecifier: () => TSESTree.Identifier | null =
() => {
if (!importedUserEventLibraryNode) {
return null;
const findImportedUserEventSpecifier: () =>
| TSESTree.Identifier
| TSESTree.ImportClause
| null = () => {
if (!importedUserEventLibraryNode) {
const customModuleNode = getCustomModuleImportNode();
if (customModuleNode) {
return findImportSpecifier(USER_EVENT_NAME, customModuleNode) ?? null;
}
return null;
}

if (isImportDeclaration(importedUserEventLibraryNode)) {
const userEventIdentifier =
importedUserEventLibraryNode.specifiers.find((specifier) =>
isImportDefaultSpecifier(specifier)
);

if (userEventIdentifier) {
return userEventIdentifier.local;
}
} else {
if (
!ASTUtils.isVariableDeclarator(importedUserEventLibraryNode.parent)
) {
return null;
}
if (isImportDeclaration(importedUserEventLibraryNode)) {
const userEventIdentifier =
importedUserEventLibraryNode.specifiers.find((specifier) =>
isImportDefaultSpecifier(specifier)
);

const requireNode = importedUserEventLibraryNode.parent;
if (!ASTUtils.isIdentifier(requireNode.id)) {
return null;
}
if (userEventIdentifier) {
return userEventIdentifier.local;
}
} else {
if (
!ASTUtils.isVariableDeclarator(importedUserEventLibraryNode.parent)
) {
return null;
}

return requireNode.id;
const requireNode = importedUserEventLibraryNode.parent;
if (!ASTUtils.isIdentifier(requireNode.id)) {
return null;
}

return null;
};
return requireNode.id;
}

return null;
};

const getTestingLibraryImportedUtilSpecifier = (
node: TSESTree.Identifier | TSESTree.MemberExpression
Expand Down
23 changes: 23 additions & 0 deletions tests/create-testing-library-rule.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,13 @@ ruleTester.run(rule.name, rule, {
code: `
import * as incorrect from '@testing-library/user-event'
userEvent.click()
`,
},
{
settings: { 'testing-library/utils-module': 'test-utils' },
code: `
import { userEvent } from 'somewhere-else'
userEvent.click(element)
`,
},

Expand Down Expand Up @@ -615,6 +622,22 @@ ruleTester.run(rule.name, rule, {
code: `
const renamed = require('@testing-library/user-event')
renamed.click(element)
`,
errors: [{ line: 3, column: 15, messageId: 'userEventError' }],
},
{
settings: { 'testing-library/utils-module': 'test-utils' },
code: `
import { userEvent } from 'test-utils'
userEvent.click(element)
`,
errors: [{ line: 3, column: 17, messageId: 'userEventError' }],
},
{
settings: { 'testing-library/utils-module': 'test-utils' },
code: `
import { userEvent as renamed } from 'test-utils'
renamed.click(element)
`,
errors: [{ line: 3, column: 15, messageId: 'userEventError' }],
},
Expand Down
90 changes: 90 additions & 0 deletions tests/rules/await-async-events.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -887,6 +887,96 @@ ruleTester.run(rule.name, rule, {
}) as const
),
]),
...USER_EVENT_ASYNC_FUNCTIONS.map(
(eventMethod) =>
({
settings: {
'testing-library/utils-module': 'test-utils',
},
code: `
import { userEvent } from 'test-utils'
test('unhandled promise from userEvent imported from custom module is invalid', () => {
userEvent.${eventMethod}(getByLabelText('username'))
})
`,
errors: [
{
line: 4,
column: 9,
endColumn: 19 + eventMethod.length,
messageId: 'awaitAsyncEvent',
data: { name: eventMethod },
},
],
options: [{ eventModule: 'userEvent' }],
output: `
import { userEvent } from 'test-utils'
test('unhandled promise from userEvent imported from custom module is invalid', async () => {
await userEvent.${eventMethod}(getByLabelText('username'))
})
`,
}) as const
),
...USER_EVENT_ASYNC_FUNCTIONS.map(
(eventMethod) =>
({
settings: {
'testing-library/utils-module': 'test-utils',
},
code: `
const { userEvent } = require('test-utils')
test('unhandled promise from userEvent required from custom module is invalid', () => {
userEvent.${eventMethod}(getByLabelText('username'))
})
`,
errors: [
{
line: 4,
column: 9,
endColumn: 19 + eventMethod.length,
messageId: 'awaitAsyncEvent',
data: { name: eventMethod },
},
],
options: [{ eventModule: 'userEvent' }],
output: `
const { userEvent } = require('test-utils')
test('unhandled promise from userEvent required from custom module is invalid', async () => {
await userEvent.${eventMethod}(getByLabelText('username'))
})
`,
}) as const
),
...USER_EVENT_ASYNC_FUNCTIONS.map(
(eventMethod) =>
({
settings: {
'testing-library/utils-module': 'test-utils',
},
code: `
import { userEvent as ue } from 'test-utils'
test('unhandled promise from aliased userEvent imported from custom module is invalid', () => {
ue.${eventMethod}(getByLabelText('username'))
})
`,
errors: [
{
line: 4,
column: 9,
endColumn: 12 + eventMethod.length,
messageId: 'awaitAsyncEvent',
data: { name: eventMethod },
},
],
options: [{ eventModule: 'userEvent' }],
output: `
import { userEvent as ue } from 'test-utils'
test('unhandled promise from aliased userEvent imported from custom module is invalid', async () => {
await ue.${eventMethod}(getByLabelText('username'))
})
`,
}) as const
),
...USER_EVENT_ASYNC_FRAMEWORKS.flatMap((testingFramework) => [
...USER_EVENT_ASYNC_FUNCTIONS.map(
(eventMethod) =>
Expand Down
69 changes: 69 additions & 0 deletions tests/rules/no-await-sync-events.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,18 @@ ruleTester.run(rule.name, rule, {
});
`,
})),
// userEvent from non-custom module should not be reported when custom module is set
{
settings: { 'testing-library/utils-module': 'test-utils' },
code: `
import { userEvent } from 'somewhere-else';

test('should not report userEvent from non-custom module', async() => {
await userEvent.type('foo', 'bar', { delay: 0 });
});
`,
options: [{ eventModules: ['user-event'] }],
},
],

invalid: [
Expand Down Expand Up @@ -387,6 +399,63 @@ ruleTester.run(rule.name, rule, {
},
],
},
{
settings: { 'testing-library/utils-module': 'test-utils' },
code: `
import { userEvent } from 'test-utils';

test('should report userEvent from custom module', async() => {
await userEvent.type('foo', 'bar', { delay: 0 });
});
`,
options: [{ eventModules: ['user-event'] }],
errors: [
{
line: 5,
column: 17,
messageId: 'noAwaitSyncEvents',
data: { name: 'userEvent.type' },
},
],
},
{
settings: { 'testing-library/utils-module': 'test-utils' },
code: `
const { userEvent } = require('test-utils');

test('should report userEvent required from custom module', async() => {
await userEvent.type('foo', 'bar', { delay: 0 });
});
`,
options: [{ eventModules: ['user-event'] }],
errors: [
{
line: 5,
column: 17,
messageId: 'noAwaitSyncEvents',
data: { name: 'userEvent.type' },
},
],
},
{
settings: { 'testing-library/utils-module': 'test-utils' },
code: `
import { userEvent as ue } from 'test-utils';

test('should report aliased userEvent from custom module', async() => {
await ue.type('foo', 'bar', { delay: 0 });
});
`,
options: [{ eventModules: ['user-event'] }],
errors: [
{
line: 5,
column: 17,
messageId: 'noAwaitSyncEvents',
data: { name: 'ue.type' },
},
],
},
{
code: `async() => {
const delay = 0
Expand Down
Loading