Skip to content

Commit

Permalink
feat: throw error if using inline snapshot inside of test.each or `…
Browse files Browse the repository at this point in the history
…describe.each` (#3360)

Co-authored-by: Vladimir <sleuths.slews0s@icloud.com>
  • Loading branch information
fenghan34 and sheremet-va committed May 25, 2023
1 parent b72ebdb commit 7c2f708
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 7 deletions.
18 changes: 13 additions & 5 deletions packages/runner/src/suite.ts
Expand Up @@ -53,7 +53,7 @@ export function createSuiteHooks() {
}

// implementations
function createSuiteCollector(name: string, factory: SuiteFactory = () => { }, mode: RunMode, concurrent?: boolean, shuffle?: boolean, suiteOptions?: TestOptions) {
function createSuiteCollector(name: string, factory: SuiteFactory = () => { }, mode: RunMode, concurrent?: boolean, shuffle?: boolean, each?: boolean, suiteOptions?: TestOptions) {
const tasks: (Test | TaskCustom | Suite | SuiteCollector)[] = []
const factoryQueue: (Test | Suite | SuiteCollector)[] = []

Expand All @@ -80,6 +80,7 @@ function createSuiteCollector(name: string, factory: SuiteFactory = () => { }, m
id: '',
type: 'test',
name,
each: this.each,
mode,
suite: undefined!,
fails: this.fails,
Expand Down Expand Up @@ -145,6 +146,7 @@ function createSuiteCollector(name: string, factory: SuiteFactory = () => { }, m
type: 'suite',
name,
mode,
each,
shuffle,
tasks: [],
}
Expand Down Expand Up @@ -198,11 +200,12 @@ function createSuite() {
options = { repeats: currentSuite.options.repeats, ...options }
}

return createSuiteCollector(name, factory, mode, this.concurrent, this.shuffle, options)
return createSuiteCollector(name, factory, mode, this.concurrent, this.shuffle, this.each, options)
}

suiteFn.each = function<T>(this: { withContext: () => SuiteAPI }, cases: ReadonlyArray<T>, ...args: any[]) {
suiteFn.each = function<T>(this: { withContext: () => SuiteAPI; setContext: (key: string, value: boolean | undefined) => SuiteAPI }, cases: ReadonlyArray<T>, ...args: any[]) {
const suite = this.withContext()
this.setContext('each', true)

if (Array.isArray(cases) && args.length)
cases = formatTemplateString(cases, args)
Expand All @@ -215,6 +218,8 @@ function createSuite() {
? suite(formatTitle(name, items, idx), () => fn(...items), options)
: suite(formatTitle(name, items, idx), () => fn(i), options)
})

this.setContext('each', undefined)
}
}

Expand All @@ -229,16 +234,17 @@ function createSuite() {

function createTest(fn: (
(
this: Record<'concurrent' | 'skip' | 'only' | 'todo' | 'fails', boolean | undefined>,
this: Record<'concurrent' | 'skip' | 'only' | 'todo' | 'fails' | 'each', boolean | undefined>,
title: string,
fn?: TestFunction,
options?: number | TestOptions
) => void
)) {
const testFn = fn as any

testFn.each = function<T>(this: { withContext: () => TestAPI }, cases: ReadonlyArray<T>, ...args: any[]) {
testFn.each = function<T>(this: { withContext: () => SuiteAPI; setContext: (key: string, value: boolean | undefined) => SuiteAPI }, cases: ReadonlyArray<T>, ...args: any[]) {
const test = this.withContext()
this.setContext('each', true)

if (Array.isArray(cases) && args.length)
cases = formatTemplateString(cases, args)
Expand All @@ -252,6 +258,8 @@ function createTest(fn: (
? test(formatTitle(name, items, idx), () => fn(...items), options)
: test(formatTitle(name, items, idx), () => fn(i), options)
})

this.setContext('each', undefined)
}
}

Expand Down
1 change: 1 addition & 0 deletions packages/runner/src/types/tasks.ts
Expand Up @@ -9,6 +9,7 @@ export interface TaskBase {
id: string
name: string
mode: RunMode
each?: boolean
concurrent?: boolean
shuffle?: boolean
suite?: Suite
Expand Down
3 changes: 3 additions & 0 deletions packages/runner/src/utils/chain.ts
Expand Up @@ -16,6 +16,9 @@ export function createChainable<T extends string, Args extends any[], R = any, E
}
Object.assign(chain, fn)
chain.withContext = () => chain.bind(context)
chain.setContext = (key: T, value: boolean | undefined) => {
context[key] = value
}
for (const key of keys) {
Object.defineProperty(chain, key, {
get() {
Expand Down
12 changes: 10 additions & 2 deletions packages/vitest/src/integrations/snapshot/chai.ts
Expand Up @@ -101,9 +101,12 @@ export const SnapshotPlugin: ChaiPlugin = (chai, utils) => {
chai.Assertion.prototype,
'toMatchInlineSnapshot',
function __INLINE_SNAPSHOT__(this: Record<string, unknown>, properties?: object, inlineSnapshot?: string, message?: string) {
const test = utils.flag(this, 'vitest-test')
const isInsideEach = test && (test.each || test.suite?.each)
if (isInsideEach)
throw new Error('InlineSnapshot cannot be used inside of test.each or describe.each')
const expected = utils.flag(this, 'object')
const error = utils.flag(this, 'error')
const test = utils.flag(this, 'vitest-test')
if (typeof properties === 'string') {
message = inlineSnapshot
inlineSnapshot = properties
Expand All @@ -112,6 +115,7 @@ export const SnapshotPlugin: ChaiPlugin = (chai, utils) => {
if (inlineSnapshot)
inlineSnapshot = stripSnapshotIndentation(inlineSnapshot)
const errorMessage = utils.flag(this, 'message')

getSnapshotClient().assert({
received: expected,
message,
Expand Down Expand Up @@ -144,11 +148,15 @@ export const SnapshotPlugin: ChaiPlugin = (chai, utils) => {
chai.Assertion.prototype,
'toThrowErrorMatchingInlineSnapshot',
function __INLINE_SNAPSHOT__(this: Record<string, unknown>, inlineSnapshot: string, message: string) {
const test = utils.flag(this, 'vitest-test')
const isInsideEach = test && (test.each || test.suite?.each)
if (isInsideEach)
throw new Error('InlineSnapshot cannot be used inside of test.each or describe.each')
const expected = utils.flag(this, 'object')
const error = utils.flag(this, 'error')
const test = utils.flag(this, 'vitest-test')
const promise = utils.flag(this, 'promise') as string | undefined
const errorMessage = utils.flag(this, 'message')

getSnapshotClient().assert({
received: getErrorString(expected, promise),
message,
Expand Down
27 changes: 27 additions & 0 deletions test/fails/fixtures/inline-snapshop-inside-each.test.ts
@@ -0,0 +1,27 @@
import { describe, expect, test } from 'vitest'

test.each([1])('', () => {
expect('').toMatchInlineSnapshot()
})

describe.each([1])('', () => {
test('', () => {
expect('').toMatchInlineSnapshot()
})

test.each([1])('', () => {
expect('').toMatchInlineSnapshot()
})

test('', () => {
expect(() => {
throw new Error('1')
}).toThrowErrorMatchingInlineSnapshot()
})

test.each([1])('', () => {
expect(() => {
throw new Error('1')
}).toThrowErrorMatchingInlineSnapshot()
})
})
8 changes: 8 additions & 0 deletions test/fails/test/__snapshots__/runner.test.ts.snap
Expand Up @@ -15,6 +15,14 @@ exports[`should fails > hooks-called.test.ts > hooks-called.test.ts 1`] = `
Error: before all"
`;
exports[`should fails > inline-snapshop-inside-each.test.ts > inline-snapshop-inside-each.test.ts 1`] = `
"Error: InlineSnapshot cannot be used inside of test.each or describe.each
Error: InlineSnapshot cannot be used inside of test.each or describe.each
Error: InlineSnapshot cannot be used inside of test.each or describe.each
Error: InlineSnapshot cannot be used inside of test.each or describe.each
Error: InlineSnapshot cannot be used inside of test.each or describe.each"
`;
exports[`should fails > mock-import-proxy-module.test.ts > mock-import-proxy-module.test.ts 1`] = `"Error: There are some problems in resolving the mocks API."`;
exports[`should fails > nested-suite.test.ts > nested-suite.test.ts 1`] = `"AssertionError: expected true to be false // Object.is equality"`;
Expand Down

0 comments on commit 7c2f708

Please sign in to comment.