From 7a4aaa80f857657d0a583f4fae91b766170a1498 Mon Sep 17 00:00:00 2001 From: Florent Tapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Fri, 31 Jul 2026 18:08:06 +0200 Subject: [PATCH] fix: polymarket-us hub row links to external site instead of 404 products page --- src/components/pm-venues-leaderboard.tsx | 16 ++- src/lib/compare-compute.test.ts | 158 +++++++++++++++++++++++ src/lib/downsample.test.ts | 89 +++++++++++++ src/lib/format.test.ts | 151 ++++++++++++++++++++++ src/lib/pm-stats.ts | 11 +- src/lib/ranking.test.ts | 114 ++++++++++++++++ 6 files changed, 533 insertions(+), 6 deletions(-) create mode 100644 src/lib/compare-compute.test.ts create mode 100644 src/lib/downsample.test.ts create mode 100644 src/lib/format.test.ts create mode 100644 src/lib/ranking.test.ts diff --git a/src/components/pm-venues-leaderboard.tsx b/src/components/pm-venues-leaderboard.tsx index fe36c143..b9164957 100644 --- a/src/components/pm-venues-leaderboard.tsx +++ b/src/components/pm-venues-leaderboard.tsx @@ -131,10 +131,15 @@ export function PmVenuesLeaderboard({ rows }: { rows: PmVenueRow[] }) { - {filtered.map((r, i) => ( + {filtered.map((r, i) => { + const href = r.benched === false && r.externalUrl + ? r.externalUrl + : `/products/${r.slug}`; + const isExternal = r.benched === false; + return ( router.push(`/products/${r.slug}`)} + onClick={() => isExternal ? window.open(href, "_blank") : router.push(href)} className="border-t border-ink/5 hover:bg-paper-soft/40 transition-colors cursor-pointer" > @@ -142,7 +147,9 @@ export function PmVenuesLeaderboard({ rows }: { rows: PmVenueRow[] }) { e.stopPropagation()} > @@ -182,7 +189,8 @@ export function PmVenuesLeaderboard({ rows }: { rows: PmVenueRow[] }) { {fmtMs(r.p50ApiLatencyMs)} {fmtCount(r.marketsAbove1m)} - ))} + ); + })} {filtered.length === 0 && ( { + test("returns the most recent ISO string from a list", () => { + const items = [ + { ts: "2026-01-01T00:00:00.000Z" }, + { ts: "2026-06-15T12:00:00.000Z" }, + { ts: "2026-03-10T00:00:00.000Z" }, + ]; + expect(latestIso(items, (x) => x.ts)).toBe("2026-06-15T12:00:00.000Z"); + }); + + test("returns null for empty list", () => { + expect(latestIso([], (x: { ts: string }) => x.ts)).toBeNull(); + }); + + test("skips null/undefined picks", () => { + const items = [ + { ts: null as string | null }, + { ts: "2026-05-01T00:00:00.000Z" }, + { ts: undefined as string | undefined }, + ]; + expect(latestIso(items, (x) => x.ts)).toBe("2026-05-01T00:00:00.000Z"); + }); + + test("returns null when all picks are null", () => { + const items = [{ ts: null }, { ts: null }]; + expect(latestIso(items, (x) => x.ts)).toBeNull(); + }); + + test("single item returns that item's timestamp", () => { + expect( + latestIso([{ ts: "2026-07-01T00:00:00.000Z" }], (x) => x.ts), + ).toBe("2026-07-01T00:00:00.000Z"); + }); + + test("works with plain string array via identity pick", () => { + const strs = [ + "2025-12-31T23:59:59.000Z", + "2026-01-01T00:00:00.000Z", + "2025-06-15T00:00:00.000Z", + ]; + expect(latestIso(strs, (s) => s)).toBe("2026-01-01T00:00:00.000Z"); + }); + + test("two equal timestamps returns either (both same)", () => { + const ts = "2026-07-15T10:00:00.000Z"; + expect(latestIso([{ ts }, { ts }], (x) => x.ts)).toBe(ts); + }); +}); + +describe("fmtTs", () => { + test("formats a valid ISO string as UTC", () => { + const out = fmtTs("2026-07-15T14:30:00.000Z"); + expect(out).not.toBeNull(); + expect(out).toContain("UTC"); + expect(out).toContain("2026"); + }); + + test("returns null for null input", () => { + expect(fmtTs(null)).toBeNull(); + }); + + test("returns null for undefined input", () => { + expect(fmtTs(undefined)).toBeNull(); + }); + + test("returns null for empty string", () => { + expect(fmtTs("")).toBeNull(); + }); + + test("output replaces GMT with UTC", () => { + const out = fmtTs("2026-01-01T00:00:00.000Z"); + expect(out).not.toContain("GMT"); + expect(out).toContain("UTC"); + }); +}); + +describe("decideWinner", () => { + test("tie when p50s are equal", () => { + expect(decideWinner(100, 100, false)).toBe("tie"); + expect(decideWinner(100, 100, true)).toBe("tie"); + }); + + test("lower-is-better: lower p50 wins", () => { + expect(decideWinner(50, 100, false)).toBe("a"); + expect(decideWinner(100, 50, false)).toBe("b"); + }); + + test("higher-is-better: higher p50 wins", () => { + expect(decideWinner(200, 100, true)).toBe("a"); + expect(decideWinner(100, 200, true)).toBe("b"); + }); + + test("zero vs positive: zero loses in lower-is-better", () => { + expect(decideWinner(0, 100, false)).toBe("a"); + }); + + test("zero vs positive: zero loses in higher-is-better", () => { + expect(decideWinner(0, 100, true)).toBe("b"); + }); +}); + +describe("parseAdHocSlug", () => { + test("parses a simple a-vs-b slug", () => { + expect(parseAdHocSlug("ethereum-vs-solana")).toEqual({ a: "ethereum", b: "solana" }); + }); + + test("handles hyphenated provider names", () => { + expect(parseAdHocSlug("helius-rpc-vs-alchemy")).toEqual({ + a: "helius-rpc", + b: "alchemy", + }); + }); + + test("returns null when delimiter is absent", () => { + expect(parseAdHocSlug("no-delimiter-here")).toBeNull(); + }); + + test("returns null when delimiter is at position 0 (empty a)", () => { + expect(parseAdHocSlug("-vs-something")).toBeNull(); + }); + + test("returns null when b is empty after delimiter", () => { + expect(parseAdHocSlug("something-vs-")).toBeNull(); + }); + + test("returns null when a and b are identical", () => { + expect(parseAdHocSlug("ethereum-vs-ethereum")).toBeNull(); + }); + + test("returns null for empty string", () => { + expect(parseAdHocSlug("")).toBeNull(); + }); +}); + +describe("canonicalisationTarget", () => { + test("returns canonical (sorted) slug when out of order", () => { + expect(canonicalisationTarget("solana-vs-ethereum")).toBe("ethereum-vs-solana"); + }); + + test("returns null when slug is already canonical", () => { + expect(canonicalisationTarget("ethereum-vs-solana")).toBeNull(); + }); + + test("returns null for invalid pair slug", () => { + expect(canonicalisationTarget("not-a-pair")).toBeNull(); + }); + + test("returns null for same-provider slug", () => { + expect(canonicalisationTarget("eth-vs-eth")).toBeNull(); + }); + + test("alphabetical ordering determines canonical form", () => { + expect(canonicalisationTarget("zcash-vs-aptos")).toBe("aptos-vs-zcash"); + }); +}); diff --git a/src/lib/downsample.test.ts b/src/lib/downsample.test.ts new file mode 100644 index 00000000..63e6fc59 --- /dev/null +++ b/src/lib/downsample.test.ts @@ -0,0 +1,89 @@ +import { describe, expect, test } from "bun:test"; +import { downsample, MINI_CHART_POINTS } from "./downsample"; + +describe("downsample", () => { + test("returns input as-is (filtered) when length <= target", () => { + expect(downsample([1, 2, 3], 10)).toEqual([1, 2, 3]); + expect(downsample([1, 2, 3], 3)).toEqual([1, 2, 3]); + }); + + test("filters nulls out when no downsampling needed", () => { + expect(downsample([1, null, 3, null, 5], 10)).toEqual([1, 3, 5]); + }); + + test("reduces to target number of points when no nulls", () => { + const input = Array.from({ length: 100 }, (_, i) => i + 1); + const out = downsample(input, 10); + expect(out.length).toBe(10); + }); + + test("output may be less than target when buckets are all-null", () => { + // 4 values → 2 buckets; second all-null → 1 output point + const out = downsample([10, 20, null, null], 2); + expect(out.length).toBeLessThan(2); + }); + + test("target=1 returns mean of all values", () => { + const out = downsample([1, 2, 3, 4, 5], 1); + expect(out).toEqual([3]); + }); + + test("target=0 returns empty array", () => { + expect(downsample([1, 2, 3], 0)).toEqual([]); + }); + + test("bucket mean: each output point is the mean of its bucket", () => { + // 4 values → 2 buckets of 2: [1,2]=1.5, [3,4]=3.5 + const out = downsample([1, 2, 3, 4], 2); + expect(out).toEqual([1.5, 3.5]); + }); + + test("nulls inside a bucket are excluded from the mean", () => { + // 6 values, target 2 → bucketSize=3 + // bucket 0: [10, null, 20] → mean of [10,20] = 15 + // bucket 1: [30, null, 60] → mean of [30,60] = 45 + const out = downsample([10, null, 20, 30, null, 60], 2); + expect(out[0]).toBe(15); + expect(out[1]).toBe(45); + }); + + test("bucket with all nulls is omitted from output", () => { + // 4 values → 2 buckets; second bucket all null → only 1 output + const out = downsample([10, 20, null, null], 2); + expect(out).toHaveLength(1); + expect(out[0]).toBe(15); + }); + + test("empty array returns empty", () => { + expect(downsample([], 10)).toEqual([]); + }); + + test("all-null array returns empty", () => { + expect(downsample([null, null, null], 10)).toEqual([]); + }); + + test("single value passthrough", () => { + expect(downsample([42], 5)).toEqual([42]); + }); + + test("preserves shape: monotone series stays monotone after downsampling", () => { + const input = Array.from({ length: 200 }, (_, i) => i * 2); + const out = downsample(input, 20); + for (let i = 1; i < out.length; i++) { + expect(out[i]).toBeGreaterThanOrEqual(out[i - 1]); + } + }); + + test("uniform series: all output points equal the same value", () => { + const input = Array.from({ length: 50 }, () => 7); + const out = downsample(input, 10); + expect(out.every((v) => v === 7)).toBe(true); + }); +}); + +describe("MINI_CHART_POINTS constant", () => { + test("is a positive integer", () => { + expect(MINI_CHART_POINTS).toBeGreaterThan(0); + expect(Number.isInteger(MINI_CHART_POINTS)).toBe(true); + }); +}); diff --git a/src/lib/format.test.ts b/src/lib/format.test.ts new file mode 100644 index 00000000..9943315c --- /dev/null +++ b/src/lib/format.test.ts @@ -0,0 +1,151 @@ +import { describe, expect, test } from "bun:test"; +import { + fmtUnit, + fmtValue, + fmtAsOfUtc, + unitSuffix, + valueInDeclaredUnit, +} from "./format"; + +describe("fmtUnit — ms (default latency unit)", () => { + test("integer ms", () => expect(fmtUnit(150, "ms")).toBe("150 ms")); + test("zero ms", () => expect(fmtUnit(0, "ms")).toBe("0 ms")); + test("sub-millisecond keeps one decimal", () => expect(fmtUnit(0.5, "ms")).toBe("0.5 ms")); + test("auto-flips to s at 1000ms", () => expect(fmtUnit(1000, "ms")).toBe("1.00 s")); + test("auto-flips to s at 5000ms", () => expect(fmtUnit(5000, "ms")).toBe("5.00 s")); + test("auto-flips to min at 60000ms", () => expect(fmtUnit(60000, "ms")).toBe("1.0 min")); + test("auto-flips to min at 120000ms", () => expect(fmtUnit(120000, "ms")).toBe("2.0 min")); + test("non-finite returns dash", () => expect(fmtUnit(NaN, "ms")).toBe("-")); + test("Infinity returns dash", () => expect(fmtUnit(Infinity, "ms")).toBe("-")); +}); + +describe("fmtUnit — s (latency stored as ms, displayed as seconds)", () => { + test("sub-10s shows one decimal", () => expect(fmtUnit(5000, "s")).toBe("5.0 s")); + test("0ms edge case", () => expect(fmtUnit(0, "s")).toBe("<1 s")); + test("exactly 60s flips to min", () => expect(fmtUnit(60_000, "s")).toBe("1.0 min")); + test("10s+ shows integer", () => expect(fmtUnit(15_000, "s")).toBe("15 s")); + test("large value in minutes", () => expect(fmtUnit(600_000, "s")).toBe("10.0 min")); +}); + +describe("fmtUnit — sec (true seconds gauge)", () => { + test("sub-minute shows seconds", () => expect(fmtUnit(45, "sec")).toBe("45.0 s")); + test("sub-0.1s shows <0.1 s", () => expect(fmtUnit(0.05, "sec")).toBe("<0.1 s")); + test("flips to min at 60s", () => expect(fmtUnit(90, "sec")).toBe("1.5 min")); + test("flips to h at 3600s", () => expect(fmtUnit(7200, "sec")).toBe("2.0 h")); + test("flips to d at 172800s", () => expect(fmtUnit(172800, "sec")).toBe("2.0 d")); + test("large hours no decimal", () => expect(fmtUnit(36001, "sec")).toBe("10 h")); +}); + +describe("fmtUnit — pct", () => { + test("0%", () => expect(fmtUnit(0, "pct")).toBe("0%")); + test("large pct one decimal", () => expect(fmtUnit(12.5, "pct")).toBe("12.5%")); + test("medium pct two decimals", () => expect(fmtUnit(1.5, "pct")).toBe("1.50%")); + test("small pct three decimals", () => expect(fmtUnit(0.033, "pct")).toBe("0.033%")); + test("very small pct four decimals", () => expect(fmtUnit(0.005, "pct")).toBe("0.0050%")); +}); + +describe("fmtUnit — bps (legacy, converts to percent)", () => { + test("100 bps = 1%", () => expect(fmtUnit(100, "bps")).toBe("1.00%")); + test("1000 bps = 10%", () => expect(fmtUnit(1000, "bps")).toBe("10.0%")); +}); + +describe("fmtUnit — bp (basis points as-is)", () => { + test("0 bps", () => expect(fmtUnit(0, "bp")).toBe("0 bps")); + test("near-zero shows ~0 bps", () => expect(fmtUnit(0.001, "bp")).toBe("~0 bps")); + test("small value two decimals", () => expect(fmtUnit(1.5, "bp")).toBe("1.50 bps")); + test("mid value one decimal", () => expect(fmtUnit(50.5, "bp")).toBe("50.5 bps")); + test("large value integer", () => expect(fmtUnit(200, "bp")).toBe("200 bps")); + test("negative value", () => expect(fmtUnit(-0.74, "bp")).toBe("-0.74 bps")); +}); + +describe("fmtUnit — slots", () => { + test("0 slots", () => expect(fmtUnit(0, "slots")).toBe("0 slots")); + test("1.0 slot (singular)", () => expect(fmtUnit(1, "slots")).toBe("1.0 slot")); + test("2.5 slots", () => expect(fmtUnit(2.5, "slots")).toBe("2.5 slots")); + test("large integer slots", () => expect(fmtUnit(100, "slots")).toBe("100 slots")); +}); + +describe("fmtUnit — count", () => { + test("zero", () => expect(fmtUnit(0, "count")).toBe("0")); + test("integer count", () => expect(fmtUnit(1234, "count")).toBe("1,234")); + test("float noise rounded", () => expect(fmtUnit(32.01, "count")).toBe("32")); + test("near-zero sub-0.01 shows ~0", () => expect(fmtUnit(0.001, "count")).toBe("~0")); + test("sub-1 shows 3 decimals", () => expect(fmtUnit(0.5, "count")).toBe("0.500")); + test("10k+ compacts to K", () => expect(fmtUnit(10_000, "count")).toBe("10.0K")); + test("1M+ compacts to M", () => expect(fmtUnit(1_500_000, "count")).toBe("1.50M")); + test("1B+ compacts to B", () => expect(fmtUnit(2_000_000_000, "count")).toBe("2.00B")); +}); + +describe("fmtUnit — usd", () => { + test("zero", () => expect(fmtUnit(0, "usd")).toBe("$0")); + test("sub-cent precision", () => expect(fmtUnit(0.005, "usd")).toBe("$0.00500")); + test("sub-dollar", () => expect(fmtUnit(0.5, "usd")).toBe("$0.5000")); + test("dollars", () => expect(fmtUnit(12.5, "usd")).toBe("$12.5")); + test("thousands locale formatted", () => expect(fmtUnit(1_500, "usd")).toBe("$1,500")); + test("large compact", () => expect(fmtUnit(2_000_000, "usd")).toBe("$2.00M")); + test("very small exponent", () => { + const out = fmtUnit(0.0000001, "usd"); + expect(out).toMatch(/^\$.*e/); + }); +}); + +describe("fmtUnit — gwei", () => { + test("zero", () => expect(fmtUnit(0, "gwei")).toBe("0 gwei")); + test("near-zero shows ~0 gwei", () => expect(fmtUnit(1e-10, "gwei")).toBe("~0 gwei")); + test("sub-1 three decimals", () => expect(fmtUnit(0.5, "gwei")).toBe("0.500 gwei")); + test("integer gwei", () => expect(fmtUnit(25, "gwei")).toBe("25 gwei")); + test("large compact", () => expect(fmtUnit(15_000, "gwei")).toBe("15.0K gwei")); +}); + +describe("valueInDeclaredUnit", () => { + test("unit s divides by 1000 (ms stored, s declared)", () => { + expect(valueInDeclaredUnit(5000, "s")).toBe(5); + }); + test("other units pass through unchanged", () => { + expect(valueInDeclaredUnit(150, "ms")).toBe(150); + expect(valueInDeclaredUnit(0.5, "pct")).toBe(0.5); + expect(valueInDeclaredUnit(100, "usd")).toBe(100); + }); +}); + +describe("fmtAsOfUtc", () => { + test("formats a valid ISO string", () => { + expect(fmtAsOfUtc("2026-07-15T14:30:00.000Z")).toBe("2026-07-15 14:30 UTC"); + }); + test("zero-pads month and day", () => { + expect(fmtAsOfUtc("2026-01-05T09:05:00.000Z")).toBe("2026-01-05 09:05 UTC"); + }); + test("returns null for invalid input", () => { + expect(fmtAsOfUtc("not-a-date")).toBeNull(); + expect(fmtAsOfUtc("")).toBeNull(); + }); +}); + +describe("unitSuffix", () => { + test("pct suffix", () => expect(unitSuffix("pct")).toBe(" %")); + test("bps suffix", () => expect(unitSuffix("bps")).toBe(" %")); + test("bp suffix", () => expect(unitSuffix("bp")).toBe(" bps")); + test("slots suffix", () => expect(unitSuffix("slots")).toBe(" slots")); + test("count no suffix", () => expect(unitSuffix("count")).toBe("")); + test("usd no suffix", () => expect(unitSuffix("usd")).toBe("")); + test("gwei suffix", () => expect(unitSuffix("gwei")).toBe(" gwei")); + test("ms stays ms below 1000ms", () => expect(unitSuffix("ms", 500)).toBe(" ms")); + test("ms flips to s at 1000ms", () => expect(unitSuffix("ms", 1000)).toBe(" s")); + test("ms flips to min at 60000ms", () => expect(unitSuffix("ms", 60000)).toBe(" min")); + test("s unit flips to min above 60s worth of ms", () => expect(unitSuffix("s", 60_000)).toBe(" min")); + test("s unit stays s below 60s", () => expect(unitSuffix("s", 5_000)).toBe(" s")); + test("sec unit stays s below 60", () => expect(unitSuffix("sec", 30)).toBe(" s")); + test("sec unit flips to min at 60+", () => expect(unitSuffix("sec", 90)).toBe(" min")); + test("sec unit flips to h at 3600+", () => expect(unitSuffix("sec", 3600)).toBe(" h")); + test("sec unit flips to d at 172800+", () => expect(unitSuffix("sec", 172800)).toBe(" d")); + test("unknown unit defaults to ms", () => expect(unitSuffix("whatever")).toBe(" ms")); +}); + +describe("fmtValue (number only, no unit word)", () => { + test("strips trailing ms", () => expect(fmtValue(150, "ms")).toBe("150")); + test("strips trailing s", () => expect(fmtValue(5000, "ms")).toBe("5.00")); + test("strips trailing min", () => expect(fmtValue(60000, "ms")).toBe("1.0")); + test("keeps $ prefix for usd", () => expect(fmtValue(100, "usd")).toBe("$100")); + test("keeps K/M compact notation", () => expect(fmtValue(1_500_000, "count")).toBe("1.50M")); + test("strips % from pct", () => expect(fmtValue(5, "pct")).toBe("5.00")); +}); diff --git a/src/lib/pm-stats.ts b/src/lib/pm-stats.ts index 69a162d6..2815465f 100644 --- a/src/lib/pm-stats.ts +++ b/src/lib/pm-stats.ts @@ -27,6 +27,9 @@ export type PmVenueRow = { name: string; type: PmVenueType; chain?: string; + /** false = hub-only venue (volume tracked but no bench probes; no /products page) */ + benched: boolean; + externalUrl?: string; volume30d: number | null; volume24h: number | null; openInterest: number | null; @@ -64,6 +67,8 @@ type VenueSeed = { name: string; type: PmVenueType; chain?: string; + benched?: boolean; + externalUrl?: string; }; type DataFeedSeed = { @@ -75,7 +80,7 @@ type DataFeedSeed = { const PM_VENUES: VenueSeed[] = [ { slug: "polymarket", name: "Polymarket", type: "onchain", chain: "polygon" }, - { slug: "polymarket-us", name: "Polymarket US", type: "offchain" }, + { slug: "polymarket-us", name: "Polymarket US", type: "offchain", benched: false, externalUrl: "https://polymarketexchange.com" }, { slug: "kalshi", name: "Kalshi", type: "offchain" }, { slug: "limitless", name: "Limitless", type: "onchain", chain: "base" }, { slug: "myriad", name: "Myriad", type: "onchain", chain: "abstract" }, @@ -164,6 +169,8 @@ export async function fetchPmCohortFresh(): Promise { name: v.name, type: v.type, chain: v.chain, + benched: v.benched ?? true, + externalUrl: v.externalUrl, volume30d: null, volume24h: null, openInterest: null, @@ -177,7 +184,7 @@ export async function fetchPmCohortFresh(): Promise { const apply = ( series: { labels: Record; value: number }[] | null, - field: keyof Omit, + field: keyof Omit, ) => { for (const s of series ?? []) { const v = s.labels.venue; diff --git a/src/lib/ranking.test.ts b/src/lib/ranking.test.ts new file mode 100644 index 00000000..1eaa1e25 --- /dev/null +++ b/src/lib/ranking.test.ts @@ -0,0 +1,114 @@ +import { describe, expect, test } from "bun:test"; +import { rankResults, leader } from "./ranking"; +import type { Benchmark, ProviderResult } from "@/types/benchmark"; + +function r(slug: string, p50: number, p99 = p50): ProviderResult { + return { + slug, + name: slug, + ms: { p50, p90: (p50 + p99) / 2, p99, mean: p50 }, + successRate: 100, + availability: "live", + }; +} + +function bench(results: ProviderResult[], higherIsBetter = false): Benchmark { + return { + slug: "test", + number: "001", + title: "Test", + subtitle: "", + lastRunAt: "", + status: "live", + editorialStatus: "live", + sampleSize: 0, + abstract: "", + metric: "Latency", + unit: "ms", + higherIsBetter, + category: "RPCs", + results, + findings: [], + methodology: [], + source: "", + extras: { series24h: {}, regions: {} }, + }; +} + +describe("rankResults", () => { + test("lower-is-better sorts ascending by p50 (best first)", () => { + const input = [r("c", 300), r("a", 100), r("b", 200)]; + const out = rankResults(input, false); + expect(out.map((x) => x.slug)).toEqual(["a", "b", "c"]); + }); + + test("higher-is-better sorts descending by p50 (best first)", () => { + const input = [r("c", 300), r("a", 100), r("b", 200)]; + const out = rankResults(input, true); + expect(out.map((x) => x.slug)).toEqual(["c", "b", "a"]); + }); + + test("does not mutate the original array", () => { + const input = [r("b", 200), r("a", 100)]; + const copy = [...input]; + rankResults(input, false); + expect(input).toEqual(copy); + }); + + test("returns empty array for empty input", () => { + expect(rankResults([], false)).toEqual([]); + expect(rankResults([], true)).toEqual([]); + }); + + test("single provider returns same array contents", () => { + const input = [r("solo", 50)]; + expect(rankResults(input, false)).toEqual(input); + }); + + test("tie on p50 preserves original relative order (stable sort)", () => { + const input = [r("a", 100), r("b", 100), r("c", 100)]; + const out = rankResults(input, false); + expect(out.map((x) => x.slug)).toEqual(["a", "b", "c"]); + }); + + test("works with zero p50", () => { + const input = [r("a", 0), r("b", 50)]; + expect(rankResults(input, false)[0].slug).toBe("a"); + }); + + test("works with very large p50 values", () => { + const input = [r("slow", 999_999), r("fast", 1)]; + expect(rankResults(input, false)[0].slug).toBe("fast"); + expect(rankResults(input, true)[0].slug).toBe("slow"); + }); +}); + +describe("leader", () => { + test("returns the provider with the lowest p50 for lower-is-better", () => { + const b = bench([r("slow", 300), r("fast", 50), r("mid", 150)]); + expect(leader(b)?.slug).toBe("fast"); + }); + + test("returns the provider with the highest p50 for higher-is-better", () => { + const b = bench([r("low", 10), r("high", 500), r("mid", 250)], true); + expect(leader(b)?.slug).toBe("high"); + }); + + test("returns undefined for bench with no results", () => { + const b = bench([]); + expect(leader(b)).toBeUndefined(); + }); + + test("returns the only provider when there is one", () => { + const b = bench([r("solo", 99)]); + expect(leader(b)?.slug).toBe("solo"); + }); + + test("leader slug matches first entry of rankResults output", () => { + const results = [r("c", 300), r("a", 100), r("b", 200)]; + const b = bench(results); + const top = leader(b); + const ranked = rankResults(results, false); + expect(top?.slug).toBe(ranked[0].slug); + }); +});