Skip to content
Closed
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ module.exports = [
| :------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------- | :------------------------------------------------------------------ | :-- |
| [await-async-events](docs/rules/await-async-events.md) | Enforce promises from async event methods are handled | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | 🔧 |
| [await-async-queries](docs/rules/await-async-queries.md) | Enforce promises from async queries to be handled | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | |
| [await-async-utils](docs/rules/await-async-utils.md) | Enforce promises from async utils to be awaited properly | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | |
| [await-async-utils](docs/rules/await-async-utils.md) | Enforce promises from async utils to be awaited properly | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | 🔧 |
| [consistent-data-testid](docs/rules/consistent-data-testid.md) | Ensures consistent usage of `data-testid` | | | |
| [no-await-sync-events](docs/rules/no-await-sync-events.md) | Disallow unnecessary `await` for sync events | ![badge-angular][] ![badge-dom][] ![badge-react][] | | |
| [no-await-sync-queries](docs/rules/no-await-sync-queries.md) | Disallow unnecessary `await` for sync queries | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | |
Expand Down
2 changes: 2 additions & 0 deletions docs/rules/await-async-utils.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

💼 This rule is enabled in the following configs: `angular`, `dom`, `marko`, `react`, `vue`.

🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).

<!-- end auto-generated rule header -->

Ensure that promises returned by async utils are handled properly.
Expand Down
11 changes: 11 additions & 0 deletions lib/rules/await-async-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
getFunctionName,
getInnermostReturningFunction,
getVariableReferences,
isMemberExpression,
isObjectPattern,
isPromiseHandled,
isProperty,
Expand Down Expand Up @@ -36,6 +37,7 @@ export default createTestingLibraryRule<Options, MessageIds>({
'Promise returned from {{ name }} wrapper over async util must be handled',
},
schema: [],
fixable: 'code',
},
defaultOptions: [],

Expand Down Expand Up @@ -149,6 +151,12 @@ export default createTestingLibraryRule<Options, MessageIds>({
data: {
name: node.name,
},
fix: (fixer) => {
if (isMemberExpression(node.parent)) {
return fixer.insertTextBefore(node.parent, 'await ');
}
return fixer.insertTextBefore(node, 'await ');
},
});
}
} else {
Expand All @@ -161,6 +169,9 @@ export default createTestingLibraryRule<Options, MessageIds>({
data: {
name: node.name,
},
fix: (fixer) => {
return fixer.insertTextBefore(referenceNode, 'await ');
},
});
return;
}
Expand Down
182 changes: 182 additions & 0 deletions tests/lib/rules/await-async-utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,13 @@ ruleTester.run(RULE_NAME, rule, {
data: { name: asyncUtil },
},
],
output: `
import { ${asyncUtil} } from '${testingFramework}';
test('${asyncUtil} util not waited is invalid', () => {
doSomethingElse();
await ${asyncUtil}(() => getByLabelText('email'));
});
`,
}) as const
),
...ASYNC_UTILS.map(
Expand All @@ -367,6 +374,13 @@ ruleTester.run(RULE_NAME, rule, {
data: { name: asyncUtil },
},
],
output: `
import { ${asyncUtil} } from '${testingFramework}';
test('${asyncUtil} util not waited is invalid', () => {
doSomethingElse();
const el = await ${asyncUtil}(() => getByLabelText('email'));
});
`,
}) as const
),
...ASYNC_UTILS.map(
Expand All @@ -387,6 +401,13 @@ ruleTester.run(RULE_NAME, rule, {
data: { name: asyncUtil },
},
],
output: `
import * as asyncUtil from '${testingFramework}';
test('asyncUtil.${asyncUtil} util not handled is invalid', () => {
doSomethingElse();
await asyncUtil.${asyncUtil}(() => getByLabelText('email'));
});
`,
}) as const
),
...ASYNC_UTILS.map(
Expand All @@ -407,6 +428,13 @@ ruleTester.run(RULE_NAME, rule, {
data: { name: asyncUtil },
},
],
output: `
import { ${asyncUtil} } from '${testingFramework}';
test('${asyncUtil} util promise saved not handled is invalid', () => {
doSomethingElse();
const aPromise = await ${asyncUtil}(() => getByLabelText('email'));
});
`,
}) as const
),
...ASYNC_UTILS.map(
Expand Down Expand Up @@ -434,6 +462,14 @@ ruleTester.run(RULE_NAME, rule, {
data: { name: asyncUtil },
},
],
output: `
import { ${asyncUtil} } from '${testingFramework}';
test('several ${asyncUtil} utils not handled are invalid', () => {
const aPromise = ${asyncUtil}(() => getByLabelText('username'));
doSomethingElse(await aPromise);
await ${asyncUtil}(() => getByLabelText('email'));
});
`,
}) as const
),
...ASYNC_UTILS.map(
Expand Down Expand Up @@ -461,6 +497,14 @@ ruleTester.run(RULE_NAME, rule, {
data: { name: asyncUtil },
},
],
output: `
import { ${asyncUtil} } from '${testingFramework}';
test('unhandled expression that evaluates to promise is invalid', () => {
const aPromise = ${asyncUtil}(() => getByLabelText('username'));
doSomethingElse(await aPromise);
await ${asyncUtil}(() => getByLabelText('email'));
});
`,
}) as const
),
...ASYNC_UTILS.map(
Expand All @@ -486,6 +530,18 @@ ruleTester.run(RULE_NAME, rule, {
data: { name: 'waitForSomethingAsync' },
},
],
output: `
import { ${asyncUtil}, render } from '${testingFramework}';

function waitForSomethingAsync() {
return ${asyncUtil}(() => somethingAsync())
}

test('unhandled promise from function wrapping ${asyncUtil} util is invalid', async () => {
render()
await waitForSomethingAsync()
});
`,
}) as const
),
...ASYNC_UTILS.map(
Expand All @@ -508,6 +564,15 @@ ruleTester.run(RULE_NAME, rule, {
data: { name: asyncUtil },
},
],
output: `
import { ${asyncUtil} } from 'some-other-library';
test(
'aggressive reporting - util "${asyncUtil}" which is not related to testing library is invalid',
async () => {
doSomethingElse();
await ${asyncUtil}();
});
`,
}) as const
),
...ASYNC_UTILS.map(
Expand All @@ -533,6 +598,18 @@ ruleTester.run(RULE_NAME, rule, {
data: { name: 'waitForSomethingAsync' },
},
],
output: `
import { ${asyncUtil}, render } from '${testingFramework}';

function waitForSomethingAsync() {
return ${asyncUtil}(() => somethingAsync())
}

test('unhandled promise from function wrapping ${asyncUtil} util is invalid', async () => {
render()
const el = await waitForSomethingAsync()
});
`,
}) as const
),

Expand All @@ -556,6 +633,15 @@ ruleTester.run(RULE_NAME, rule, {
data: { name: asyncUtil },
},
],
output: `
import * as asyncUtils from 'some-other-library';
test(
'aggressive reporting - util "asyncUtils.${asyncUtil}" which is not related to testing library is invalid',
async () => {
doSomethingElse();
await asyncUtils.${asyncUtil}();
});
`,
}) as const
),
...ASYNC_UTILS.map(
Expand Down Expand Up @@ -586,6 +672,22 @@ ruleTester.run(RULE_NAME, rule, {
data: { name: 'waitForAsyncUtil' },
},
],
output: `
function setup() {
const utils = render(<MyComponent />);

const waitForAsyncUtil = () => {
return ${asyncUtil}(screen.queryByTestId('my-test-id'));
};

return { waitForAsyncUtil, ...utils };
}

test('unhandled promise from destructed property of async function wrapper is invalid', () => {
const { user, waitForAsyncUtil } = setup();
await waitForAsyncUtil();
});
`,
}) as const
),
...ASYNC_UTILS.map(
Expand Down Expand Up @@ -617,6 +719,23 @@ ruleTester.run(RULE_NAME, rule, {
data: { name: 'myAlias' },
},
],
output: `
function setup() {
const utils = render(<MyComponent />);

const waitForAsyncUtil = () => {
return ${asyncUtil}(screen.queryByTestId('my-test-id'));
};

return { waitForAsyncUtil, ...utils };
}

test('unhandled promise from destructed property of async function wrapper is invalid', () => {
const { user, waitForAsyncUtil } = setup();
const myAlias = waitForAsyncUtil;
await myAlias();
});
`,
}) as const
),
...ASYNC_UTILS.map(
Expand Down Expand Up @@ -647,6 +766,22 @@ ruleTester.run(RULE_NAME, rule, {
data: { name: 'waitForAsyncUtil' },
},
],
output: `
function setup() {
const utils = render(<MyComponent />);

const waitForAsyncUtil = () => {
return ${asyncUtil}(screen.queryByTestId('my-test-id'));
};

return { waitForAsyncUtil, ...utils };
}

test('unhandled promise from destructed property of async function wrapper is invalid', () => {
const { ...clone } = setup();
await clone.waitForAsyncUtil();
});
`,
}) as const
),
...ASYNC_UTILS.map(
Expand Down Expand Up @@ -677,6 +812,22 @@ ruleTester.run(RULE_NAME, rule, {
data: { name: 'myAlias' },
},
],
output: `
function setup() {
const utils = render(<MyComponent />);

const waitForAsyncUtil = () => {
return ${asyncUtil}(screen.queryByTestId('my-test-id'));
};

return { waitForAsyncUtil, ...utils };
}

test('unhandled promise from destructed property of async function wrapper is invalid', () => {
const { waitForAsyncUtil: myAlias } = setup();
await myAlias();
});
`,
}) as const
),
...ASYNC_UTILS.map(
Expand Down Expand Up @@ -706,6 +857,21 @@ ruleTester.run(RULE_NAME, rule, {
data: { name: 'waitForAsyncUtil' },
},
],
output: `
function setup() {
const utils = render(<MyComponent />);

const waitForAsyncUtil = () => {
return ${asyncUtil}(screen.queryByTestId('my-test-id'));
};

return { waitForAsyncUtil, ...utils };
}

test('unhandled promise from destructed property of async function wrapper is invalid', () => {
await setup().waitForAsyncUtil();
});
`,
}) as const
),
...ASYNC_UTILS.map(
Expand Down Expand Up @@ -736,6 +902,22 @@ ruleTester.run(RULE_NAME, rule, {
data: { name: 'myAlias' },
},
],
output: `
function setup() {
const utils = render(<MyComponent />);

const waitForAsyncUtil = () => {
return ${asyncUtil}(screen.queryByTestId('my-test-id'));
};

return { waitForAsyncUtil, ...utils };
}

test('unhandled promise from destructed property of async function wrapper is invalid', () => {
const myAlias = setup().waitForAsyncUtil;
await myAlias();
});
`,
}) as const
),
]),
Expand Down
Loading