diff --git a/docs/api/index.md b/docs/api/index.md index 729ca7576b3d..c28b003f4d60 100644 --- a/docs/api/index.md +++ b/docs/api/index.md @@ -330,16 +330,29 @@ test.each([ // ✓ add(2, 1) -> 3 ``` -You can also access object properties with `$` prefix, if you are using objects as arguments: +You can also access object properties and array elements with `$` prefix: - ```ts - test.each([ - { a: 1, b: 1, expected: 2 }, - { a: 1, b: 2, expected: 3 }, - { a: 2, b: 1, expected: 3 }, - ])('add($a, $b) -> $expected', ({ a, b, expected }) => { - expect(a + b).toBe(expected) - }) +```ts +test.each([ + { a: 1, b: 1, expected: 2 }, + { a: 1, b: 2, expected: 3 }, + { a: 2, b: 1, expected: 3 }, +])('add($a, $b) -> $expected', ({ a, b, expected }) => { + expect(a + b).toBe(expected) +}) + +// this will return +// ✓ add(1, 1) -> 2 +// ✓ add(1, 2) -> 3 +// ✓ add(2, 1) -> 3 + +test.each([ + [1, 1, 2], + [1, 2, 3], + [2, 1, 3], +])('add($0, $1) -> $2', (a, b, expected) => { + expect(a + b).toBe(expected) +}) // this will return // ✓ add(1, 1) -> 2 diff --git a/packages/runner/src/suite.ts b/packages/runner/src/suite.ts index bd12467b454b..a20bfbd64a10 100644 --- a/packages/runner/src/suite.ts +++ b/packages/runner/src/suite.ts @@ -809,16 +809,21 @@ function formatTitle(template: string, items: any[], idx: number) { } let formatted = format(template, ...items.slice(0, count)) - if (isObject(items[0])) { - formatted = formatted.replace( - /\$([$\w.]+)/g, - // https://github.com/chaijs/chai/pull/1490 - (_, key) => - objDisplay(objectAttr(items[0], key), { - truncate: runner?.config?.chaiConfig?.truncateThreshold, - }) as unknown as string, - ) - } + const isObjectItem = isObject(items[0]) + formatted = formatted.replace( + /\$([$\w.]+)/g, + (_, key: string) => { + const isArrayKey = /^\d+$/.test(key) + if (!isObjectItem && !isArrayKey) { + return `$${key}` + } + const arrayElement = isArrayKey ? objectAttr(items, key) : undefined + const value = isObjectItem ? objectAttr(items[0], key, arrayElement) : arrayElement + return objDisplay(value, { + truncate: runner?.config?.chaiConfig?.truncateThreshold, + }) as unknown as string + }, + ) return formatted } diff --git a/test/reporters/fixtures/test-for-title.test.ts b/test/reporters/fixtures/test-for-title.test.ts new file mode 100644 index 000000000000..353889928799 --- /dev/null +++ b/test/reporters/fixtures/test-for-title.test.ts @@ -0,0 +1,50 @@ +import { expect, test } from "vitest" + +test.for([ + { 0: 'a', 1: {}, 2: { te: "st" } }, + { "0": 'b', "1": [], "2": ["test"] }, +])('test.for object : 0 = $0, 2 = $2', () => {}); + +test.each([ + { 0: 'a', 1: {}, 2: { te: "st" } }, + { "0": 'b', "1": [], "2": ["test"] }, +])('test.each object : 0 = $0, 2 = $2 ', () => {}); + +test.for([ + ['a', {}, { te: "st" }], + ['b', [], [ "test" ]], +])('test.for array : 0 = $0, 2 = $2', () => {}); + +test.each([ + ['a', {}, { te: "st" }], + ['b', [], [ "test" ]], +])('test.each array : 0 = $0, 2 = $2', () => {}); + +test.each([ + { a: 1, b: 1, expected: 2 }, + { a: 1, b: 2, expected: 3 }, + { a: 2, b: 1, expected: 3 }, +])('object : add($a, $b) -> $expected', ({ a, b, expected }) => { + expect(a + b).toBe(expected) +}) + +test.each([ + [1, 1, 2], + [1, 2, 3], + [2, 1, 3], +])('array : add($0, $1) -> $2', (a, b, expected) => { + expect(a + b).toBe(expected) +}) + +test.for([ + [{ k1: "v1" }, { k2: "v2" }], +])('first array element is object: 0 = $0, 1 = $1, k1 = $k1, k2 = $k2', () => {}) + +test.for([ + ["foo", "bar"], +])('first array element is not object: 0 = $0, 1 = $1, k = $k', () => {}) + +test.for([ + { k: "v1" }, + { k: "v2" }, +])('not array: 0 = $0, 1 = $1, k = $k', () => {}) diff --git a/test/reporters/tests/default.test.ts b/test/reporters/tests/default.test.ts index 4075d1620764..df243d298511 100644 --- a/test/reporters/tests/default.test.ts +++ b/test/reporters/tests/default.test.ts @@ -177,4 +177,36 @@ describe('default reporter', async () => { expect(stdout).toContain('1 passed') expect(stdout).toContain('✓ repeat couple of times (repeat x3)') }) + + test('test.each/for title format', async () => { + const { stdout } = await runVitest({ + include: ['fixtures/test-for-title.test.ts'], + reporters: [['default', { isTTY: true, summary: false }]], + config: false, + }) + expect( + [...stdout.matchAll(/(✓ .*)$/gm)].map(v => v[0]).filter(v => !v.includes('ms')), + ).toMatchInlineSnapshot(` + [ + "✓ test.for object : 0 = 'a', 2 = { te: 'st' }", + "✓ test.for object : 0 = 'b', 2 = [ 'test' ]", + "✓ test.each object : 0 = 'a', 2 = { te: 'st' } ", + "✓ test.each object : 0 = 'b', 2 = [ 'test' ] ", + "✓ test.for array : 0 = 'a', 2 = { te: 'st' }", + "✓ test.for array : 0 = 'b', 2 = [ 'test' ]", + "✓ test.each array : 0 = 'a', 2 = { te: 'st' }", + "✓ test.each array : 0 = 'b', 2 = [ 'test' ]", + "✓ object : add(1, 1) -> 2", + "✓ object : add(1, 2) -> 3", + "✓ object : add(2, 1) -> 3", + "✓ array : add(1, 1) -> 2", + "✓ array : add(1, 2) -> 3", + "✓ array : add(2, 1) -> 3", + "✓ first array element is object: 0 = { k1: 'v1' }, 1 = { k2: 'v2' }, k1 = 'v1', k2 = undefined", + "✓ first array element is not object: 0 = 'foo', 1 = 'bar', k = $k", + "✓ not array: 0 = { k: 'v1' }, 1 = undefined, k = 'v1'", + "✓ not array: 0 = { k: 'v2' }, 1 = undefined, k = 'v2'", + ] + `) + }) }, 120000)