bun:test: format test.each titles with Jest's placeholder semantics
Closed
RO
8d ago+340+340+012345678901234567890123456789-140-140-012345678901234567890123456789
Fixes #37821
Problem
test.eachanddescribe.eachtitles diverge from Jest in three ways. A placeholder only substitutes when the argument already has the matching type (%sneeds a string,%d/%fa number,%ian Int52), otherwise the literal token stays and the argument is still consumed:test.each([[1, 2, 3]])("%s + %s = %s")gives%s + %s = %s.%#and%%stop working once the positional arguments run out, and an unknown token loses its%(%zprintsz). In the$varform, a null or undefined value,$#,${a}and$5each skip the character after the token (a=$a|b=$b|givesa=$ab=5|).- The cause is
format_labelinsrc/runtime/test_runner/jest.rs. The%arm was guarded byargs_idx < args.len(), each specifier had a type gate, the_arm dropped the%, and the$varfallback fell through to the loop'sidx += 1. - Titles feed
-tfiltering, JUnit names and snapshot keys, so a wrong title is not only cosmetic. A suite that had a wrong title recorded in a.snapfile gets a new key after this change.
Fix
%s,%d,%i,%fapply theutil.formatcoercions Jest uses:String(),Number(),parseInt(),parseFloat().%iand%fgo through JSC's own parsers (ParseInt.h, and theparseFloatlogic ofJSGlobalObjectFunctions.cpp), exposed asJSValue::parse_intandJSValue::parse_float. A BigInt prints as10n, a Symbol givesNaNfor the numeric specifiers.%j/%ostayJSON.stringify,%pand%Opretty-print.%%,%#and%$(1-based index, Jest 30) always apply. A%with no argument left, or with an unknown letter, stays as written.$#is the row index. A$that does not start an identifier is literal and the next character is kept. An existing null/undefined property renders asnull/undefined; a missing property stays literal.- Non-string values in a title print on one line (
{ a: 1 }), so a title never spans lines.docs/test/writing-tests.mdxlists%O,%$and$#. - Verified:
test/cli/test/bun-test.test.ts(two new tests, two existing expectations that encoded the eaten character are corrected). Stock bun fails 4 tests. Alsojest-each.test.ts,jest-each-gc-root.test.ts,isolation,test-shard.
Background
- Jest formats a title in
jest-each/src/table/array.ts:%%becomes%,%#/%$are replaced first, then each row value consumes the first remaining%[sdifjoOp]throughutil.format. Object rows useinterpolation.ts:$key[.path]renders the value,$#the index. - Self-reviewed: 5 concerns raised, 4 addressed (string quoting for
%p/%O, JSC-backed parsers, docs rows, lineage below). Not taken: hoisting Jest's template-vs-printf mode decision out of the per-row loop. Bun has always applied both forms in one pass, and changing that is a separate behaviour change.
Supersedes #38831 (the %s gate, by @deepshekhardas) and #34977 (the $var null/undefined case), and covers item 3 of #32817.
no test proof · iteration 2 · platform-specific test(s) that do not run on this machine, deferring to CI, which covers all platforms: test/cli/test/bun-test.test.ts