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

fix(require-tothrow-message): require throw messages on async functions #303

Merged
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/rules/require-tothrow-message.md
Expand Up @@ -18,6 +18,10 @@ The following patterns are considered warnings:
expect(() => a()).toThrow();

expect(() => a()).toThrowError();

await expect(a()).rejects.toThrow();

await expect(a()).rejects.toThrowError();
```

The following patterns are not considered warnings:
Expand All @@ -26,4 +30,8 @@ The following patterns are not considered warnings:
expect(() => a()).toThrow('a');

expect(() => a()).toThrowError('a');

await expect(a()).rejects.toThrow('a');

await expect(a()).rejects.toThrowError('a');
```
91 changes: 83 additions & 8 deletions src/rules/__tests__/require-tothrow-message.test.js
Expand Up @@ -5,28 +5,80 @@ const rule = require('../require-tothrow-message');

const ruleTester = new RuleTester({
parserOptions: {
ecmaVersion: 6,
ecmaVersion: 8,
},
});

ruleTester.run('require-tothrow-message', rule, {
valid: [
// String
"expect(() => { throw new Error('a'); }).toThrow('a');",
jeysal marked this conversation as resolved.
Show resolved Hide resolved
"expect(() => { throw new Error('a'); }).toThrowError('a');",
`test('string', async () => {
const error = new Error('a');
const throwErrorSync = () => { throw new Error(error) };
const throwErrorAsync = async () => { throw new Error(error) };

expect(() => throwErrorSync()).toThrow('a');
expect(() => throwErrorSync()).toThrowError('a');

await expect(throwErrorAsync()).rejects.toThrow('a');
await expect(throwErrorAsync()).rejects.toThrowError('a');
})`,

// Template literal
"const a = 'a'; expect(() => { throw new Error('a'); }).toThrow(`${a}`);",
`test('Template literal', async () => {
const a = 'a';

const error = new Error('a');
const throwErrorSync = () => { throw new Error(error) };
const throwErrorAsync = async () => { throw new Error(error) };

expect(() => throwErrorSync()).toThrow(\`\${a}\`);
expect(() => throwErrorSync()).toThrowError(\`\${a}\`);

await expect(throwErrorAsync()).rejects.toThrow(\`\${a}\`);
await expect(throwErrorAsync()).rejects.toThrowError(\`\${a}\`);
})`,

// Regex
"expect(() => { throw new Error('a'); }).toThrow(/^a$/);",
`test('Regex', async () => {
const error = new Error('a');
const throwErrorSync = () => { throw new Error(error) };
const throwErrorAsync = async () => { throw new Error(error) };

expect(() => throwErrorSync()).toThrow(/^a$/);
expect(() => throwErrorSync()).toThrowError(/^a$/);

await expect(throwErrorAsync()).rejects.toThrow(/^a$/);
await expect(throwErrorAsync()).rejects.toThrowError(/^a$/);
})`,

// Function
"expect(() => { throw new Error('a'); })" +
".toThrow((() => { return 'a'; })());",
`test('Function', async () => {
const error = new Error('a');
const throwErrorSync = () => { throw new Error(error) };
const throwErrorAsync = async () => { throw new Error(error) };

const fn = () => { return 'a'; };

expect(() => throwErrorSync()).toThrow(fn());
expect(() => throwErrorSync()).toThrowError(fn());

await expect(throwErrorAsync()).rejects.toThrow(fn());
await expect(throwErrorAsync()).rejects.toThrowError(fn());
})`,

// Allow no message for `not`.
"expect(() => { throw new Error('a'); }).not.toThrow();",
`test('Allow no message for "not"', async () => {
const error = new Error('a');
const throwErrorSync = () => { throw new Error(error) };
const throwErrorAsync = async () => { throw new Error(error) };

expect(() => throwErrorSync()).not.toThrow();
expect(() => throwErrorSync()).not.toThrowError();

await expect(throwErrorAsync()).resolves.not.toThrow();
await expect(throwErrorAsync()).resolves.not.toThrowError();
})`,
],

invalid: [
Expand Down Expand Up @@ -54,5 +106,28 @@ ruleTester.run('require-tothrow-message', rule, {
},
],
},

// Empty rejects.toThrow / rejects.toThrowError
{
code: `test('empty rejects.toThrow', async () => {
const throwErrorAsync = async () => { throw new Error('a') };
await expect(throwErrorAsync()).rejects.toThrow();
await expect(throwErrorAsync()).rejects.toThrowError();
})`,
errors: [
{
messageId: 'requireRethrow',
data: { propertyName: 'toThrow' },
column: 49,
line: 3,
},
{
messageId: 'requireRethrow',
data: { propertyName: 'toThrowError' },
column: 49,
line: 4,
},
],
},
],
});
11 changes: 8 additions & 3 deletions src/rules/require-tothrow-message.js
Expand Up @@ -19,17 +19,22 @@ module.exports = {
return;
}

const propertyName = method(node) && method(node).name;
let targetNode = method(node);
if (targetNode.name === 'rejects') {
targetNode = method(node.parent);
}

const propertyName = method(targetNode) && method(targetNode).name;

// Look for `toThrow` calls with no arguments.
if (
['toThrow', 'toThrowError'].includes(propertyName) &&
!argument(node)
!argument(targetNode)
) {
context.report({
messageId: 'requireRethrow',
data: { propertyName },
node: method(node),
node: targetNode,
});
}
},
Expand Down