Skip to content

Commit ea6d732

Browse files
authored
fix: avoid recursively applying $ and % formatting to test.for/each title (#8557)
1 parent 25fd32b commit ea6d732

File tree

4 files changed

+73
-10
lines changed

4 files changed

+73
-10
lines changed

packages/runner/src/suite.ts

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import type {
1717
TestFunction,
1818
TestOptions,
1919
} from './types/tasks'
20-
import { format, objDisplay } from '@vitest/utils/display'
20+
import { format, formatRegExp, objDisplay } from '@vitest/utils/display'
2121
import {
2222
isNegativeNaN,
2323
isObject,
@@ -865,11 +865,9 @@ function formatTitle(template: string, items: any[], idx: number) {
865865
})
866866
}
867867

868-
let formatted = format(template, ...items.slice(0, count))
869868
const isObjectItem = isObject(items[0])
870-
formatted = formatted.replace(
871-
/\$([$\w.]+)/g,
872-
(_, key: string) => {
869+
function formatAttribute(s: string) {
870+
return s.replace(/\$([$\w.]+)/g, (_, key: string) => {
873871
const isArrayKey = /^\d+$/.test(key)
874872
if (!isObjectItem && !isArrayKey) {
875873
return `$${key}`
@@ -878,10 +876,50 @@ function formatTitle(template: string, items: any[], idx: number) {
878876
const value = isObjectItem ? objectAttr(items[0], key, arrayElement) : arrayElement
879877
return objDisplay(value, {
880878
truncate: runner?.config?.chaiConfig?.truncateThreshold,
881-
}) as unknown as string
879+
})
880+
})
881+
}
882+
883+
let output = ''
884+
let i = 0
885+
handleRegexMatch(
886+
template,
887+
formatRegExp,
888+
// format "%"
889+
(match) => {
890+
if (i < count) {
891+
output += format(match[0], items[i++])
892+
}
893+
else {
894+
output += match[0]
895+
}
896+
},
897+
// format "$"
898+
(nonMatch) => {
899+
output += formatAttribute(nonMatch)
882900
},
883901
)
884-
return formatted
902+
return output
903+
}
904+
905+
// based on https://github.com/unocss/unocss/blob/2e74b31625bbe3b9c8351570749aa2d3f799d919/packages/autocomplete/src/parse.ts#L11
906+
function handleRegexMatch(
907+
input: string,
908+
regex: RegExp,
909+
onMatch: (match: RegExpMatchArray) => void,
910+
onNonMatch: (nonMatch: string) => void,
911+
) {
912+
let lastIndex = 0
913+
for (const m of input.matchAll(regex)) {
914+
if (lastIndex < m.index) {
915+
onNonMatch(input.slice(lastIndex, m.index))
916+
}
917+
onMatch(m)
918+
lastIndex = m.index + m[0].length
919+
}
920+
if (lastIndex < input.length) {
921+
onNonMatch(input.slice(lastIndex))
922+
}
885923
}
886924

887925
function formatTemplateString(cases: any[], args: any[]): any[] {

packages/utils/src/display.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ export function stringify(
7878
: result
7979
}
8080

81-
const formatRegExp = /%[sdjifoOc%]/g
81+
export const formatRegExp: RegExp = /%[sdjifoOc%]/g
8282

8383
export function format(...args: unknown[]): string {
8484
if (typeof args[0] !== 'string') {

test/reporters/fixtures/test-for-title.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,22 @@ test.for([
4848
{ k: "v1" },
4949
{ k: "v2" },
5050
])('not array: 0 = $0, 1 = $1, k = $k', () => {})
51+
52+
53+
test.each([[343434, "$343,434.00"]])(
54+
"handles whole numbers: %s as %s",
55+
(input, expected) => {
56+
expect(
57+
new Intl.NumberFormat("en-US", {
58+
style: "currency",
59+
currency: "USD",
60+
}).format(input),
61+
).toBe(expected);
62+
},
63+
);
64+
65+
test.each([{ a: "$b", b: "yay" }])("%o", () => {});
66+
test.each([{ a: "%o" }])("$a", () => {});
67+
test.each([{ a: "%o" }])("%o", () => {});
68+
test.each([{ a: "%o" }])("$a %o", () => {});
69+
test.each([{ a: "%o" }])("%o $a", () => {});

test/reporters/tests/default.test.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ describe('default reporter', async () => {
237237
})
238238

239239
expect(trimReporterOutput(stdout)).toMatchInlineSnapshot(`
240-
"✓ fixtures/test-for-title.test.ts (18 tests) [...]ms
240+
"✓ fixtures/test-for-title.test.ts (24 tests) [...]ms
241241
✓ test.for object : 0 = 'a', 2 = { te: 'st' } [...]ms
242242
✓ test.for object : 0 = 'b', 2 = [ 'test' ] [...]ms
243243
✓ test.each object : 0 = 'a', 2 = { te: 'st' } [...]ms
@@ -255,7 +255,13 @@ describe('default reporter', async () => {
255255
✓ first array element is object: 0 = { k1: 'v1' }, 1 = { k2: 'v2' }, k1 = 'v1', k2 = undefined [...]ms
256256
✓ first array element is not object: 0 = 'foo', 1 = 'bar', k = $k [...]ms
257257
✓ not array: 0 = { k: 'v1' }, 1 = undefined, k = 'v1' [...]ms
258-
✓ not array: 0 = { k: 'v2' }, 1 = undefined, k = 'v2' [...]ms"
258+
✓ not array: 0 = { k: 'v2' }, 1 = undefined, k = 'v2' [...]ms
259+
✓ handles whole numbers: 343434 as $343,434.00 [...]ms
260+
✓ { a: '$b', b: 'yay' } [...]ms
261+
✓ '%o' [...]ms
262+
✓ { a: '%o' } [...]ms
263+
✓ '%o' { a: '%o' } [...]ms
264+
✓ { a: '%o' } '%o' [...]ms"
259265
`)
260266
})
261267

0 commit comments

Comments
 (0)