From 1d94f212723afbe9fb9de9e54a525b1544e1c249 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 7 Sep 2026 23:16:49 +0200 Subject: [PATCH] test(client): move the formatter benchmark onto the vitest 5 bench API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `bench` is no longer a top-level export; it is a test-context fixture whose registrations `bench.compare` runs together. That reads better here than the old shape did: every block in this file pairs a `formatters.ts` entry point against a bare-`Intl` floor, which is the comparison `compare` exists to report. This was the only real typecheck failure of the thirteen the vitest 5 bump produced. `apps/client/tsconfig.app.json` includes `src/**/*.ts`, so this file is part of the client app project, and a file that does not compile takes the project's declaration emit down with it. Everything referencing those declarations then degraded, which is where the other twelve came from: `Parameters[0]` collapsed to `unknown` in six specs, `UniverLocaleSource` to `{}`, `contextMenu.show` and `executeBulkActions` lost their signatures, and `Api` widened until build-docs' `@ts-expect-error` had nothing left to suppress. All twelve go away with this one file fixed; none of them needed a change of their own. The imports are bound to locals because vitest 5 reports this file hitting module-export getters per iteration. The overhead is asymmetric — the `formatters.ts` side pays it and the floor, a local function, does not — so it lands on the ratio the file exists to watch. Measured before and after, the pairs stay at 1.24-1.28x, so the ~1.3x the header documents still holds. The remaining warning is `options.ts`, read from inside `formatters.ts` itself. Co-Authored-By: Claude Opus 5 (1M context) --- apps/client/src/utils/formatters.bench.ts | 98 +++++++++++++---------- 1 file changed, 54 insertions(+), 44 deletions(-) diff --git a/apps/client/src/utils/formatters.bench.ts b/apps/client/src/utils/formatters.bench.ts index 0887c80c78..28ce455fe7 100644 --- a/apps/client/src/utils/formatters.bench.ts +++ b/apps/client/src/utils/formatters.bench.ts @@ -26,10 +26,16 @@ vi.mock("../services/i18n", async () => { }; }); -import { bench, describe, vi } from "vitest"; +import { test, vi } from "vitest"; import options from "../services/options"; -import { formatDateNumeric, formatDateTime } from "./formatters"; +import { formatDateNumeric as importedFormatDateNumeric, formatDateTime as importedFormatDateTime } from "./formatters"; + +// Vitest serves an import through a module-export getter, so naming one inside a benchmark pays for +// a getter on every iteration while the floor, a local function, pays nothing — which lands on the +// side of the pair being measured. Bind them once so the comparison is about formatting. +const formatDateTime = importedFormatDateTime; +const formatDateNumeric = importedFormatDateNumeric; const LOCALE = "en-GB"; const DATE = new Date("2026-02-11T14:23:45Z"); @@ -41,50 +47,54 @@ options.set("formattingLocale", LOCALE); const formatterCache = new Map(); -describe("formatDateTime — date and time", () => { - bench("formatters.ts", () => { - formatDateTime(DATE, "short", "short"); - }); - - bench("floor: bare cached Intl.DateTimeFormat", () => { - cachedFormatter(`${LOCALE}|short|short`, { dateStyle: "short", timeStyle: "short" }).format(DATE); - }); -}); - -describe("formatDateTime — date only", () => { - bench("formatters.ts", () => { - formatDateTime(DATE, "short", "none"); - }); - - bench("floor: bare cached Intl.DateTimeFormat", () => { - cachedFormatter(`${LOCALE}|short|none`, { dateStyle: "short" }).format(DATE); - }); -}); - -describe("formatDateNumeric — with time (also probes the locale's hour cycle)", () => { - bench("formatters.ts", () => { - formatDateNumeric(DATE, true); - }); - - bench("floor: bare cached Intl.DateTimeFormat", () => { - cachedFormatter(`${LOCALE}|numeric|withTime`, { - year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit" - }).format(DATE); - }); -}); - -describe(`board redraw — ${BOARD_CARDS} cards, one dated label each`, () => { - bench("formatters.ts", () => { - for (let i = 0; i < BOARD_CARDS; i++) { +test("formatDateTime — date and time", async ({ bench }) => { + await bench.compare( + bench("formatters.ts", () => { formatDateTime(DATE, "short", "short"); - } - }); - - bench("floor: bare cached Intl.DateTimeFormat", () => { - for (let i = 0; i < BOARD_CARDS; i++) { + }), + bench("floor: bare cached Intl.DateTimeFormat", () => { cachedFormatter(`${LOCALE}|short|short`, { dateStyle: "short", timeStyle: "short" }).format(DATE); - } - }); + }) + ); +}); + +test("formatDateTime — date only", async ({ bench }) => { + await bench.compare( + bench("formatters.ts", () => { + formatDateTime(DATE, "short", "none"); + }), + bench("floor: bare cached Intl.DateTimeFormat", () => { + cachedFormatter(`${LOCALE}|short|none`, { dateStyle: "short" }).format(DATE); + }) + ); +}); + +test("formatDateNumeric — with time (also probes the locale's hour cycle)", async ({ bench }) => { + await bench.compare( + bench("formatters.ts", () => { + formatDateNumeric(DATE, true); + }), + bench("floor: bare cached Intl.DateTimeFormat", () => { + cachedFormatter(`${LOCALE}|numeric|withTime`, { + year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit" + }).format(DATE); + }) + ); +}); + +test(`board redraw — ${BOARD_CARDS} cards, one dated label each`, async ({ bench }) => { + await bench.compare( + bench("formatters.ts", () => { + for (let i = 0; i < BOARD_CARDS; i++) { + formatDateTime(DATE, "short", "short"); + } + }), + bench("floor: bare cached Intl.DateTimeFormat", () => { + for (let i = 0; i < BOARD_CARDS; i++) { + cachedFormatter(`${LOCALE}|short|short`, { dateStyle: "short", timeStyle: "short" }).format(DATE); + } + }) + ); }); /**