From c5f228066ce5a16282d9bc3f0ba05a46d367c967 Mon Sep 17 00:00:00 2001 From: Senja Jarva Date: Wed, 7 Sep 2022 14:40:58 +0300 Subject: [PATCH] fix: no-await-sync-events false positive Check if delay is declared or assigned a value elsewhere than in the call expression's arguments. Add test cases that have declarations and assignments. Ref: #403 --- lib/rules/no-await-sync-events.ts | 43 +++++++++++- tests/lib/rules/no-await-sync-events.test.ts | 73 ++++++++++++++++++++ 2 files changed, 114 insertions(+), 2 deletions(-) diff --git a/lib/rules/no-await-sync-events.ts b/lib/rules/no-await-sync-events.ts index 8c09b05f..ebb0b5bc 100644 --- a/lib/rules/no-await-sync-events.ts +++ b/lib/rules/no-await-sync-events.ts @@ -56,6 +56,7 @@ export default createTestingLibraryRule({ create(context, [options], helpers) { const { eventModules = VALID_EVENT_MODULES } = options; + let hasDelayDeclarationOrAssignmentGTZero: boolean; // userEvent.type() and userEvent.keyboard() are exceptions, which returns a // Promise. But it is only necessary to wait when delay option other than 0 @@ -63,6 +64,30 @@ export default createTestingLibraryRule({ // - userEvent.type(element, 'abc', {delay: 1234}) // - userEvent.keyboard('abc', {delay: 1234}) return { + VariableDeclaration(node: TSESTree.VariableDeclaration) { + // Case delay has been declared outside of call expression's arguments + // Let's save the info if it is greater than zero + hasDelayDeclarationOrAssignmentGTZero = node.declarations.some( + (property) => + ASTUtils.isIdentifier(property.id) && + property.id.name === 'delay' && + isLiteral(property.init) && + property.init.value && + property.init.value > 0 + ); + }, + AssignmentExpression(node: TSESTree.AssignmentExpression) { + // Case delay has been assigned or re-assigned outside of call expression's arguments + // Let's save the info if it is greater than zero + if ( + ASTUtils.isIdentifier(node.left) && + node.left.name === 'delay' && + isLiteral(node.right) && + node.right.value !== null + ) { + hasDelayDeclarationOrAssignmentGTZero = node.right.value > 0; + } + }, 'AwaitExpression > CallExpression'(node: TSESTree.CallExpression) { const simulateEventFunctionIdentifier = getDeepestIdentifierNode(node); @@ -91,7 +116,20 @@ export default createTestingLibraryRule({ const lastArg = node.arguments[node.arguments.length - 1]; - const hasDelay = + // Checking if there's a delay property + // Note: delay's value may have declared or assigned somewhere else (as a variable declaration or as an assignment expression) + // or right after this (as a literal) + const hasDelayProperty = + isObjectExpression(lastArg) && + lastArg.properties.some( + (property) => + isProperty(property) && + ASTUtils.isIdentifier(property.key) && + property.key.name === 'delay' + ); + + // In case delay's value has been declared as a literal + const hasDelayLiteralGTZero = isObjectExpression(lastArg) && lastArg.properties.some( (property) => @@ -107,7 +145,8 @@ export default createTestingLibraryRule({ if ( USER_EVENT_ASYNC_EXCEPTIONS.includes(simulateEventFunctionName) && - hasDelay + hasDelayProperty && + (hasDelayDeclarationOrAssignmentGTZero || hasDelayLiteralGTZero) ) { return; } diff --git a/tests/lib/rules/no-await-sync-events.test.ts b/tests/lib/rules/no-await-sync-events.test.ts index a3bd6e65..8145138e 100644 --- a/tests/lib/rules/no-await-sync-events.test.ts +++ b/tests/lib/rules/no-await-sync-events.test.ts @@ -148,6 +148,28 @@ ruleTester.run(RULE_NAME, rule, { code: `() => { await userEvent.keyboard('foo', {delay: 1234}) } + `, + }, + { + code: `async() => { + const delay = 10 + await userEvent.keyboard('foo', {delay}) + } + `, + }, + { + code: `async() => { + const delay = 10 + await userEvent.type(element, text, {delay}) + } + `, + }, + { + code: `async() => { + let delay = 0 + delay = 10 + await userEvent.type(element, text, {delay}) + } `, }, { @@ -369,5 +391,56 @@ ruleTester.run(RULE_NAME, rule, { }, ], }, + { + code: `async() => { + const delay = 0 + await userEvent.type('foo', { delay }); + } + `, + errors: [ + { + line: 3, + column: 17, + messageId: 'noAwaitSyncEvents', + data: { name: 'userEvent.type' }, + }, + ], + }, + { + code: `async() => { + const delay = 0 + const somethingElse = true + const skipHover = true + await userEvent.type('foo', { delay, skipHover }); + } + `, + errors: [ + { + line: 5, + column: 17, + messageId: 'noAwaitSyncEvents', + data: { name: 'userEvent.type' }, + }, + ], + }, + { + code: `async() => { + let delay = 0 + const somethingElse = true + const skipHover = true + delay = 15 + delay = 0 + await userEvent.type('foo', { delay, skipHover }); + } + `, + errors: [ + { + line: 7, + column: 17, + messageId: 'noAwaitSyncEvents', + data: { name: 'userEvent.type' }, + }, + ], + }, ], });