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
16 changes: 9 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?: number | TestOptions) {
function createSuiteCollector(name: string, factory: SuiteFactory = () => { }, mode: RunMode, concurrent?: boolean, shuffle?: boolean, each?: boolean, suiteOptions?: number | TestOptions) {
const tasks: (Test | TaskCustom | Suite | SuiteCollector)[] = []
const factoryQueue: (Test | Suite | SuiteCollector)[] = []

Expand All @@ -79,6 +79,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 @@ -143,6 +144,7 @@ function createSuiteCollector(name: string, factory: SuiteFactory = () => { }, m
type: 'suite',
name,
mode,
each,
shuffle,
tasks: [],
}
Expand Down Expand Up @@ -186,11 +188,11 @@ function createSuite() {
function suiteFn(this: Record<string, boolean | undefined>, name: string, factory?: SuiteFactory, options?: number | TestOptions) {
checkVersion()
const mode: RunMode = this.only ? 'only' : this.skip ? 'skip' : this.todo ? 'todo' : 'run'
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 @@ -217,16 +219,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 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
5 changes: 5 additions & 0 deletions packages/snapshot/src/client.ts
Expand Up @@ -32,6 +32,7 @@ interface AssertOptions {
name?: string
message?: string
isInline?: boolean
isInsideEach?: boolean
sheremet-va marked this conversation as resolved.
Show resolved Hide resolved
properties?: object
inlineSnapshot?: string
error?: Error
Expand Down Expand Up @@ -95,6 +96,7 @@ export class SnapshotClient {
name = this.name,
message,
isInline = false,
isInsideEach = false,
properties,
inlineSnapshot,
error,
Expand All @@ -106,6 +108,9 @@ export class SnapshotClient {
if (!filepath)
throw new Error('Snapshot cannot be used outside of test')

if (isInline && isInsideEach)
throw new Error('InlineSnapshot cannot be used inside of test.each or describe.each')

if (typeof properties === 'object') {
if (typeof received !== 'object' || !received)
throw new Error('Received value must be an object when the matcher has properties')
Expand Down
4 changes: 4 additions & 0 deletions packages/vitest/src/integrations/snapshot/chai.ts
Expand Up @@ -112,10 +112,12 @@ export const SnapshotPlugin: ChaiPlugin = (chai, utils) => {
if (inlineSnapshot)
inlineSnapshot = stripSnapshotIndentation(inlineSnapshot)
const errorMessage = utils.flag(this, 'message')

getSnapshotClient().assert({
received: expected,
message,
isInline: true,
isInsideEach: test && (test.each || test.suite?.each),
properties,
inlineSnapshot,
error,
Expand Down Expand Up @@ -149,11 +151,13 @@ export const SnapshotPlugin: ChaiPlugin = (chai, utils) => {
const test = utils.flag(this, 'vitest-test')
const promise = utils.flag(this, 'promise') as string | undefined
const errorMessage = utils.flag(this, 'message')
const isInsideEach = test && (test.each || test.suite?.each)
getSnapshotClient().assert({
received: getErrorString(expected, promise),
message,
inlineSnapshot,
isInline: true,
isInsideEach,
error,
errorMessage,
...getTestNames(test),
Expand Down
15 changes: 15 additions & 0 deletions test/fails/fixtures/inline-snapshop-inside-each.test.ts
@@ -0,0 +1,15 @@
import { describe, expect, test } from 'vitest'

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

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

test.each([1])('', () => {
expect('').toMatchInlineSnapshot()
})
})
6 changes: 6 additions & 0 deletions test/fails/test/__snapshots__/runner.test.ts.snap
Expand Up @@ -15,6 +15,12 @@ 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"
`;

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