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

feat: throw error if using inline snapshot inside of test.each or describe.each #3360

Merged
merged 9 commits into from May 25, 2023
20 changes: 13 additions & 7 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,11 @@ 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[]) {
const suite = this.withContext()
suiteFn.each = function<T>(this: { withContext: (entries: Record<string, boolean | undefined>) => SuiteAPI }, cases: ReadonlyArray<T>, ...args: any[]) {
const suite = this.withContext({ each: true })

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

this.withContext({ each: undefined })
sheremet-va marked this conversation as resolved.
Show resolved Hide resolved
}
}

Expand All @@ -229,16 +233,16 @@ 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[]) {
const test = this.withContext()
testFn.each = function<T>(this: { withContext: (entries: Record<string, boolean | undefined>) => TestAPI }, cases: ReadonlyArray<T>, ...args: any[]) {
const test = this.withContext({ each: true })

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

this.withContext({ 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
2 changes: 1 addition & 1 deletion packages/runner/src/utils/chain.ts
Expand Up @@ -15,7 +15,7 @@ export function createChainable<T extends string, Args extends any[], R = any, E
return fn.apply(context, args)
}
Object.assign(chain, fn)
chain.withContext = () => chain.bind(context)
chain.withContext = (entries?: Record<T, boolean | undefined>) => chain.bind(Object.assign(context, entries))
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