Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions frontend/src/__tests__/components/Navbar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ describe("Navbar", () => {

it("renders the logo / brand name", () => {
render(<Navbar />);
expect(screen.getByText(/Stellar Pulse/i)).toBeInTheDocument();
expect(screen.getByText(/StellarPulse/i)).toBeInTheDocument();
});

it("renders all navigation links", () => {
Expand All @@ -87,7 +87,7 @@ describe("Navbar", () => {

it("logo links to home page", () => {
render(<Navbar />);
const logo = screen.getByText(/Stellar Pulse/i);
const logo = screen.getByText(/StellarPulse/i);
expect(logo.closest("a")).toHaveAttribute("href", "/");
});

Expand Down
103 changes: 46 additions & 57 deletions frontend/src/__tests__/events.test.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,4 @@
import { describe, expect, it } from "vitest";
import { ledgerClosedAtToUnixSeconds } from "@/services/events";

describe("event timestamp parsing", () => {
it("normalizes ledgerClosedAt to Unix seconds for UI formatters", () => {
const closedAt = "2026-02-26T02:05:30.000Z";

expect(ledgerClosedAtToUnixSeconds(closedAt)).toBe(
Date.UTC(2026, 1, 26, 2, 5, 30) / 1000
);
});

it("preserves numeric Unix seconds instead of treating them as milliseconds", () => {
expect(ledgerClosedAtToUnixSeconds(1771985130)).toBe(1771985130);
import { describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";

const mocks = vi.hoisted(() => ({
getLatestLedger: vi.fn(),
Expand All @@ -38,66 +24,69 @@ import {
pollMarketEvents,
} from "@/services/events";

describe("ledgerClosedAtToUnixSeconds", () => {
const expected = Date.UTC(2026, 1, 26, 15, 4) / 1000;
describe("ledger close timestamp parsing", () => {
const expectedSeconds = Date.UTC(2026, 1, 26, 15, 4) / 1_000;

beforeEach(() => {
vi.clearAllMocks();
});

it.each([
["ISO string", "2026-02-26T15:04:00.000Z"],
["numeric milliseconds", Date.UTC(2026, 1, 26, 15, 4)],
["Date", new Date("2026-02-26T15:04:00.000Z")],
])("accepts a valid %s", (_label, value) => {
expect(ledgerClosedAtToUnixSeconds(value)).toBe(expected);
["numeric seconds", expectedSeconds],
["numeric milliseconds", expectedSeconds * 1_000],
["Date object", new Date("2026-02-26T15:04:00.000Z")],
])("normalizes a valid %s to Unix seconds", (_label, value) => {
expect(ledgerClosedAtToUnixSeconds(value)).toBe(expectedSeconds);
});

it("honors an explicit ISO timezone offset", () => {
expect(ledgerClosedAtToUnixSeconds("2026-02-26T10:04:00-05:00")).toBe(
expectedSeconds
);
});

it.each([
["invalid string", "not-a-date"],
["empty string", ""],
["whitespace string", " "],
["timezone-less string", "2026-02-26T15:04:00"],
["impossible date", "2026-02-30T15:04:00Z"],
["invalid hour", "2026-02-26T25:04:00Z"],
["invalid offset", "2026-02-26T15:04:00+24:00"],
["zero", 0],
["negative", -1],
["missing value", undefined],
["null", null],
["NaN", Number.NaN],
["positive infinity", Number.POSITIVE_INFINITY],
["negative infinity", Number.NEGATIVE_INFINITY],
])("throws RangeError for %s", (_label, value) => {
["invalid Date", new Date(Number.NaN)],
])("rejects %s", (_label, value) => {
expect(() =>
ledgerClosedAtToUnixSeconds(value as unknown as string)
).toThrow(new RangeError("Invalid ledger close timestamp"));
const expectedSeconds = Date.UTC(2026, 6, 18, 8, 30) / 1000;

it.each([
["ISO timestamp", "2026-07-18T08:30:00.000Z"],
["offset timestamp", "2026-07-18T16:30:00+08:00"],
["Date instance", new Date("2026-07-18T08:30:00.000Z")],
["Unix milliseconds", expectedSeconds * 1000],
["Unix seconds", expectedSeconds],
])("normalizes a valid %s", (_label, value) => {
expect(ledgerClosedAtToUnixSeconds(value)).toBe(expectedSeconds);
});

it.each([
["blank value", ""],
["invalid string", "not-a-date"],
["impossible calendar date", "2026-02-31T08:30:00Z"],
["timestamp without a time zone", "2026-07-18T08:30:00"],
["NaN", Number.NaN],
["positive infinity", Number.POSITIVE_INFINITY],
["invalid Date", new Date(Number.NaN)],
])("rejects %s", (_label, value) => {
expect(() => ledgerClosedAtToUnixSeconds(value)).toThrow(
new RangeError("Invalid ledger close timestamp")
);
});
});
it("keeps parsed MarketEvent timestamps in Unix seconds", async () => {
mocks.getLatestLedger.mockResolvedValue({ sequence: 100 });
mocks.getEvents.mockResolvedValue({
events: [
{
topic: ["market_cancelled", 7],
value: {},
ledgerClosedAt: "2026-02-26T15:04:00.000Z",
txHash: "valid-event",
},
],
});

describe("pollMarketEvents", () => {
it("discards an event with malformed ledgerClosedAt", async () => {
const malformedEvent = {
topic: ["market_cancelled", 7],
value: {},
ledgerClosedAt: "not-a-date",
txHash: "malformed-event",
};
await expect(pollMarketEvents()).resolves.toEqual([
expect.objectContaining({ timestamp: expectedSeconds }),
]);
});

mocks.getLatestLedger.mockResolvedValue({ sequence: 100 });
mocks.getEvents.mockResolvedValue({ events: [malformedEvent] });
it("drops an event with a malformed close timestamp", async () => {
it("discards an event whose close time violates the timestamp contract", async () => {
mocks.getLatestLedger.mockResolvedValue({ sequence: 100 });
mocks.getEvents.mockResolvedValue({
events: [
Expand Down
Loading