Skip to content

Commit

Permalink
feat(vitest/require-local-test-context-for-concurrent-snapshots): add…
Browse files Browse the repository at this point in the history
… rule (#315)
  • Loading branch information
Haberkamp committed Dec 10, 2023
1 parent 78864d2 commit ffce7e1
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 1 deletion.
33 changes: 33 additions & 0 deletions docs/rules/require-local-test-context-for-concurrent-snapshots.md
@@ -0,0 +1,33 @@
# Require local Test Context for concurrent snapshot tests (`vitest/require-local-test-context-for-concurrent-snapshots`)

💼 This rule is enabled in the ✅ `recommended` config.

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

## Rule details

Examples of **incorrect** code for this rule:

```js
test.concurrent('myLogic', () => {
expect(true).toMatchSnapshot();
})

describe.concurrent('something', () => {
test('myLogic', () => {
expect(true).toMatchInlineSnapshot();
})
})
```

Examples of **correct** code for this rule:

```js
test.concurrent('myLogic', ({ expect }) => {
expect(true).toMatchSnapshot();
})

test.concurrent('myLogic', (context) => {
context.expect(true).toMatchSnapshot();
}
```
6 changes: 5 additions & 1 deletion src/index.ts
Expand Up @@ -43,6 +43,7 @@ import validDescribeCallback, { RULE_NAME as validDescribeCallbackName } from '.
import requireTopLevelDescribe, { RULE_NAME as requireTopLevelDescribeName } from './rules/require-top-level-describe'
import requireToThrowMessage, { RULE_NAME as requireToThrowMessageName } from './rules/require-to-throw-message'
import requireHook, { RULE_NAME as requireHookName } from './rules/require-hook'
import requireLocalTestContextForConcurrentSnapshots, { RULE_NAME as requireLocalTestContextForConcurrentSnapshotsName } from './rules/require-local-test-context-for-concurrent-snapshots'
import preferTodo, { RULE_NAME as preferTodoName } from './rules/prefer-todo'
import preferSpyOn, { RULE_NAME as preferSpyOnName } from './rules/prefer-spy-on'
import preferComparisonMatcher, { RULE_NAME as preferComparisonMatcherName } from './rules/prefer-comparison-matcher'
Expand Down Expand Up @@ -98,6 +99,7 @@ const allRules = {
[requireTopLevelDescribeName]: 'warn',
[requireToThrowMessageName]: 'warn',
[requireHookName]: 'warn',
[requireLocalTestContextForConcurrentSnapshotsName]: 'warn',
[preferTodoName]: 'warn',
[preferSpyOnName]: 'warn',
[preferComparisonMatcherName]: 'warn',
Expand All @@ -112,7 +114,8 @@ const recommended = {
[noCommentedOutTestsName]: 'error',
[validTitleName]: 'error',
[validExpectName]: 'error',
[validDescribeCallbackName]: 'error'
[validDescribeCallbackName]: 'error',
[requireLocalTestContextForConcurrentSnapshotsName]: 'error',
}

export default {
Expand Down Expand Up @@ -156,6 +159,7 @@ export default {
[preferEachName]: preferEach,
[preferHooksOnTopName]: preferHooksOnTop,
[preferHooksInOrderName]: preferHooksInOrder,
[requireLocalTestContextForConcurrentSnapshotsName]: requireLocalTestContextForConcurrentSnapshots,
[preferMockPromiseShortHandName]: preferMockPromiseShorthand,
[preferSnapshotHintName]: preferSnapshotHint,
[validDescribeCallbackName]: validDescribeCallback,
Expand Down
56 changes: 56 additions & 0 deletions src/rules/require-local-test-context-for-concurrent-snapshots.ts
@@ -0,0 +1,56 @@
import { AST_NODE_TYPES, TSESTree } from "@typescript-eslint/utils";
import { createEslintRule, getNodeName, isSupportedAccessor } from "../utils";
import { isTypeOfVitestFnCall } from "../utils/parseVitestFnCall";

export const RULE_NAME = "require-local-test-context-for-concurrent-snapshots";

export default createEslintRule({
name: RULE_NAME,
meta: {
docs: {
description: "Require local Test Context for concurrent snapshot tests",
recommended: "error",
},
messages: {
requireLocalTestContext: "Use local Test Context instead",
},
type: "problem",
schema: [],
},
defaultOptions: [],
create(context) {
return {
CallExpression(node) {
const isNotAnAssertion = !isTypeOfVitestFnCall(node, context, ['expect'])
if (isNotAnAssertion) return;

const isNotASnapshotAssertion = ![
'toMatchSnapshot',
'toMatchInlineSnapshot'
].includes(node.callee.property.name);

if (isNotASnapshotAssertion) return;

const isInsideSequentialDescribeOrTest = !context.getAncestors().some((ancestor) => {
if (ancestor.type !== AST_NODE_TYPES.CallExpression) return false;

const isNotInsideDescribeOrTest = !isTypeOfVitestFnCall(ancestor, context, ["describe", "test"]);
if (isNotInsideDescribeOrTest) return false;

const isTestRunningConcurrently =
ancestor.callee.type === AST_NODE_TYPES.MemberExpression &&
isSupportedAccessor(ancestor.callee.property, "concurrent");

return isTestRunningConcurrently
});

if (isInsideSequentialDescribeOrTest) return;

context.report({
node,
messageId: "requireLocalTestContext"
})
},
};
},
});
58 changes: 58 additions & 0 deletions tests/require-local-test-context-for-concurrent-snapshots.test.ts
@@ -0,0 +1,58 @@
import rule, { RULE_NAME } from '../src/rules/require-local-test-context-for-concurrent-snapshots'
import { ruleTester } from "./ruleTester";

ruleTester.run(RULE_NAME, rule, {
valid: [
'it("something", () => { expect(true).toBe(true) })',
'it.concurrent("something", () => { expect(true).toBe(true) })',
'it("something", () => { expect(1).toMatchSnapshot() })',
'it.concurrent("something", ({ expect }) => { expect(1).toMatchSnapshot() })',
'it.concurrent("something", ({ expect }) => { expect(1).toMatchInlineSnapshot("1") })',
'describe.concurrent("something", () => { it("something", () => { expect(true).toBe(true) }) })',
'describe.concurrent("something", () => { it("something", ({ expect }) => { expect(1).toMatchSnapshot() }) })',
'describe.concurrent("something", () => { it("something", ({ expect }) => { expect(1).toMatchInlineSnapshot() }) })',
'describe("something", () => { it("something", ({ expect }) => { expect(1).toMatchInlineSnapshot() }) })',
'describe("something", () => { it("something", (context) => { expect(1).toMatchInlineSnapshot() }) })',
'describe("something", () => { it("something", (context) => { context.expect(1).toMatchInlineSnapshot() }) })',
'describe("something", () => { it("something", (context) => { expect(1).toMatchInlineSnapshot() }) })',
'it.concurrent("something", (context) => { context.expect(1).toMatchSnapshot() })',
],
invalid: [
{
code: 'it.concurrent("should fail", () => { expect(true).toMatchSnapshot() })',
errors: [{ messageId: 'requireLocalTestContext' }]
},
{
code: 'it.concurrent("should fail", () => { expect(true).toMatchInlineSnapshot("true") })',
errors: [{ messageId: 'requireLocalTestContext' }]
},
{
code: 'describe.concurrent("failing", () => { it("should fail", () => { expect(true).toMatchSnapshot() }) })',
errors: [{ messageId: 'requireLocalTestContext' }]
},
{
code: 'describe.concurrent("failing", () => { it("should fail", () => { expect(true).toMatchInlineSnapshot("true") }) })',
errors: [{ messageId: 'requireLocalTestContext' }]
},
{
code: 'it.concurrent("something", (context) => { expect(true).toMatchSnapshot() })',
errors: [{ messageId: 'requireLocalTestContext' }]
},
{
code: `it.concurrent("something", () => {
expect(true).toMatchSnapshot();
expect(true).toMatchSnapshot();
})`,
errors: [{ messageId: 'requireLocalTestContext' }, { messageId: 'requireLocalTestContext' }]
},
{
code: `it.concurrent("something", () => {
expect(true).toBe(true);
expect(true).toMatchSnapshot();
})`,
errors: [{ messageId: 'requireLocalTestContext' }]
},
],
})

0 comments on commit ffce7e1

Please sign in to comment.