Files
beacon-web/tests/lib/formatters.test.ts
T
MrAlders0n 39641b6339 ui: footer version from package.json plus a batch of interaction fixes
- footer read 'v0.1' forever; it's injected from package.json at build
  time now
- region picker shows 'Failed to load' instead of an eternal Loading
  when /iatas errors
- selecting text in a modal and releasing over the backdrop no longer
  closes it (close requires the press to start on the backdrop)
- Escape in a dropdown inside the mobile filter sheet closes just the
  dropdown, not the whole sheet
- Shift+Tab right after a modal opens wraps to the last control instead
  of escaping the focus trap
- formatCount rolls 999,999 over to 1M instead of printing 1000k
- TABS list moved to lib/constants so AppShell only exports components
2026-06-10 07:03:16 -04:00

106 lines
2.9 KiB
TypeScript

import { describe, it, expect } from "vitest";
import {
formatHex,
formatAbsolute,
timeAgoMs,
formatSnr,
snrLevel,
formatPropagation,
formatCount,
} from "../../src/lib/formatters";
describe("formatHex", () => {
it("truncates to 8 chars uppercase", () => {
expect(formatHex("9e9b7d6a91cab445")).toBe("9E9B7D6A");
});
it("handles short hashes", () => {
expect(formatHex("abcd")).toBe("ABCD");
});
});
describe("formatAbsolute", () => {
it("formats epoch ms as YYYY-MM-DD HH:MM:SS (no ms by default)", () => {
const result = formatAbsolute(1717689045123);
expect(result).toMatch(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/);
});
it("appends .mmm when ms is requested, preserving the millisecond component", () => {
const result = formatAbsolute(1717689045123, { ms: true });
expect(result).toMatch(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}$/);
expect(result.endsWith(".123")).toBe(true); // ms component is timezone-independent
});
});
describe("timeAgoMs", () => {
it("renders sub-minute as seconds and minutes/hours/days above that", () => {
const now = Date.now();
expect(timeAgoMs(now - 5_000)).toBe("5s");
expect(timeAgoMs(now - 5 * 60_000)).toBe("5m");
expect(timeAgoMs(now - 3 * 3_600_000)).toBe("3h");
expect(timeAgoMs(now - 2 * 86_400_000)).toBe("2d");
});
it("clamps future timestamps (clock skew) to 0s", () => {
expect(timeAgoMs(Date.now() + 60_000)).toBe("0s");
});
});
describe("snrLevel", () => {
it("returns good for SNR >= 10", () => {
expect(snrLevel(10.5)).toBe("good");
});
it("returns mid for SNR between 5 and 10", () => {
expect(snrLevel(7.2)).toBe("mid");
});
it("returns bad for SNR < 5", () => {
expect(snrLevel(3.1)).toBe("bad");
});
it("returns null for null input", () => {
expect(snrLevel(null)).toBeNull();
});
});
describe("formatSnr", () => {
it("formats to 2 decimal places", () => {
expect(formatSnr(10.756)).toBe("10.76");
});
it("returns dash for null", () => {
expect(formatSnr(null)).toBe("—");
});
});
describe("formatCount", () => {
it("passes small counts through and abbreviates k/M", () => {
expect(formatCount(932)).toBe("932");
expect(formatCount(14732)).toBe("14.7k");
expect(formatCount(8_900_000)).toBe("8.9M");
});
it("returns dash for null", () => {
expect(formatCount(null)).toBe("—");
});
it("rolls over to the next unit when rounding reaches 1000", () => {
// regression: 999_999 used to render as "1000k" instead of rolling to "1M"
expect(formatCount(999_949)).toBe("999.9k");
expect(formatCount(999_999)).toBe("1M");
expect(formatCount(999_999_999)).toBe("1B");
expect(formatCount(-999_999)).toBe("-1M");
});
});
describe("formatPropagation", () => {
it("formats ms to seconds string", () => {
expect(formatPropagation(1936)).toBe("1.936s");
});
it("returns dash for null", () => {
expect(formatPropagation(null)).toBe("—");
});
});