Files
OmniRoute/tests/unit/format-reset-countdown.test.ts
Diego Rodrigues de Sa e Souza 5ca28f2ff7 fix(dashboard): resolve broken Card import breaking next build (base-red from #6061) (#6155)
* fix(dashboard): resolve broken Card import breaking next build (base-red from #6061)

CoolingConnectionsPanel imported `Card` from `@/components/ui/card`, a path
that does not exist in this repo (there is no shadcn-style `src/components/ui/`).
The PR->release fast-gates do not run `next build`, so the broken import slipped
in and `next build` failed with:

  Module not found: Can't resolve '@/components/ui/card'

Fix: the <Card> here was only a styled container, so replace it with a <div>
carrying the equivalent Tailwind classes (border/bg/padding + rounded-card
shadow-sm). Also normalize the file from CRLF to LF (it shipped with CRLF).

Adds a vitest/jsdom regression test (tests/unit/ui/CoolingConnectionsPanel.test.tsx)
that fails-without-fix (Vite: 'Failed to resolve import @/components/ui/card')
and passes with it, plus renders/empty-state coverage. Rule #18.

* fix(dashboard): stop client CoolingConnectionsPanel dragging server DB barrel into browser bundle

Second base-red from #6061, surfaced once the broken Card import was fixed:

  ./node_modules/ioredis/built/connectors/StandaloneConnector.js
  Module not found: Can't resolve 'net'
  Import trace: ioredis <- rateLimiter.ts <- apiKeys.ts <- @/lib/localDb
                <- CoolingConnectionsPanel.tsx (a "use client" component)

The client panel imported `formatResetCountdown` from `@/lib/localDb` — the
server-side DB re-export barrel — which transitively pulls better-sqlite3/ioredis
(node:net) into the browser bundle. That violates the CLAUDE.md rule 'never
barrel-import from localDb'.

`formatResetCountdown` is a pure date-formatting function, so move its
implementation to the client-safe `@/shared/utils/formatting` (alongside
formatTime/formatDuration) and re-export it from db/providers/rateLimit.ts for the
existing server callers + barrel. The panel now imports it directly from the
shared util — no server code in the client bundle.

Tests (Rule #18):
- tests/unit/format-reset-countdown.test.ts (node:test, blocking test:unit) —
  pure-function coverage: null/past/invalid, s, m+s, h+m, ISO string.
- tests/unit/ui/CoolingConnectionsPanel.test.tsx mock updated to the new module.
2026-07-04 06:35:07 -03:00

39 lines
1.5 KiB
TypeScript

import { test } from "node:test";
import assert from "node:assert/strict";
import { formatResetCountdown } from "@/shared/utils/formatting";
// Guards both the pure formatting behavior and the client-safe home of this
// helper: it MUST live in @/shared/utils/formatting (not db/providers/rateLimit)
// so client components can import it without pulling the server-only DB barrel
// (better-sqlite3/ioredis → node:net) into the browser bundle. See PR #6155.
test("returns null for missing / past / invalid reset times", () => {
assert.equal(formatResetCountdown(null), null);
assert.equal(formatResetCountdown(undefined), null);
assert.equal(formatResetCountdown(0), null);
assert.equal(formatResetCountdown("not-a-date"), null);
assert.equal(formatResetCountdown(Date.now() - 60_000), null);
});
test("formats seconds-only remaining", () => {
const out = formatResetCountdown(Date.now() + 30_000);
assert.match(out ?? "", /^\d+s$/);
});
test("formats minutes + seconds", () => {
const out = formatResetCountdown(Date.now() + 5 * 60_000 + 30_000);
assert.match(out ?? "", /^\d+m \d+s$/);
});
test("formats hours + minutes", () => {
const out = formatResetCountdown(Date.now() + 2 * 3_600_000 + 35 * 60_000);
assert.match(out ?? "", /^\d+h \d+m$/);
assert.ok((out ?? "").startsWith("2h"));
});
test("accepts an ISO string as well as an epoch number", () => {
const iso = new Date(Date.now() + 90_000).toISOString();
assert.match(formatResetCountdown(iso) ?? "", /^(1m \d+s|\d+s)$/);
});