diff --git a/packages/vitest/src/runtime/suite.ts b/packages/vitest/src/runtime/suite.ts index f211a97eb25a..598ea413dd55 100644 --- a/packages/vitest/src/runtime/suite.ts +++ b/packages/vitest/src/runtime/suite.ts @@ -1,5 +1,5 @@ import { format } from 'util' -import type { File, RunMode, Suite, SuiteAPI, SuiteCollector, SuiteFactory, SuiteHooks, Test, TestAPI, TestFunction } from '../types' +import type { File, MutableArray, RunMode, Suite, SuiteAPI, SuiteCollector, SuiteFactory, SuiteHooks, Test, TestAPI, TestFunction } from '../types' import { isObject, noop, toArray } from '../utils' import { createChainable } from './chain' import { collectTask, context, normalizeTest, runWithSuite } from './context' @@ -145,8 +145,8 @@ function createSuite() { }, ) as SuiteAPI - suite.each = (cases: T[]) => { - return (name: string, fn: (...args: T extends any[] ? T : [T]) => void) => { + suite.each = (cases: T[] | readonly T[]) => { + return (name: string, fn: (...args: T extends any[] | readonly any[] ? MutableArray : [T]) => void) => { cases.forEach((i) => { const items = toArray(i) as any suite(formatTitle(name, items), () => fn(...items)) @@ -163,8 +163,8 @@ function createTest(fn: ((this: Record<'concurrent'| 'skip'| 'only'| 'todo'| 'fa fn, ) as TestAPI - test.each = (cases: T[]) => { - return (name: string, fn: (...args: T extends any[] ? T : [T]) => void) => { + test.each = (cases: T[] | readonly T[]) => { + return (name: string, fn: (...args: T extends any[] | readonly any[] ? MutableArray : [T]) => void) => { cases.forEach((i) => { const items = toArray(i) as any test(formatTitle(name, items), () => fn(...items)) diff --git a/packages/vitest/src/types/general.ts b/packages/vitest/src/types/general.ts index edaedd9c7897..44a5bf3198a2 100644 --- a/packages/vitest/src/types/general.ts +++ b/packages/vitest/src/types/general.ts @@ -18,6 +18,8 @@ export type DeepMerge = MergeInsertions<{ : never; }> +export type MutableArray = { -readonly [k in keyof T]: T[k] } + export interface Constructable { new (...args: any[]): any } diff --git a/packages/vitest/src/types/tasks.ts b/packages/vitest/src/types/tasks.ts index 0f58de67d437..2542934986d6 100644 --- a/packages/vitest/src/types/tasks.ts +++ b/packages/vitest/src/types/tasks.ts @@ -1,5 +1,5 @@ import type { ChainableFunction } from '../runtime/chain' -import type { Awaitable, ErrorWithDiff } from './general' +import type { Awaitable, ErrorWithDiff, MutableArray } from './general' import type { UserConsoleLog } from '.' export type RunMode = 'run' | 'skip' | 'only' | 'todo' @@ -44,7 +44,7 @@ export type Task = Test | Suite | File export type DoneCallback = (error?: any) => void export type TestFunction = (done: DoneCallback) => Awaitable -export type EachFunction = (cases: T[]) => (name: string, fn: (...args: T extends any[] ? T : [T]) => void) => void +export type EachFunction = (cases: T[] | readonly T[]) => (name: string, fn: (...args: T extends any[] | readonly any[] ? MutableArray : [T]) => void) => void export type TestAPI = ChainableFunction< 'concurrent' | 'only' | 'skip' | 'todo' | 'fails', diff --git a/test/core/test/each.test.ts b/test/core/test/each.test.ts index cb61dd1d8067..79b16365b85c 100644 --- a/test/core/test/each.test.ts +++ b/test/core/test/each.test.ts @@ -26,6 +26,20 @@ describe.each([ }) }) +describe.each([ + [1, 'a', '1a'], + [1, 'b', '1b'], + [2, 'c', '2c'], +] as const)('describe concatenate(%i, %s)', (a, b, expected) => { + test(`returns ${expected}`, () => { + // This will fail typechecking if const is not used and/or types for a,b are merged into a union + const typedA: number = a + const typedB: string = b + + expect(`${typedA}${typedB}`).toBe(expected) + }) +}) + describe.each([ { a: 1, b: 1, expected: 2 }, { a: 1, b: 2, expected: 3 },