Add logging with filters

This commit is contained in:
Twirre
2025-11-12 14:35:38 +00:00
committed by Gerla, J. (Justin)
parent b7eb0cb5ec
commit 231d7a5ba1
22 changed files with 1899 additions and 68 deletions

View File

@@ -0,0 +1,156 @@
import {render, screen, act} from "@testing-library/react";
import "@testing-library/jest-dom";
import {type Cell, cell, useCell} from "../../src/utils/cellStore.ts";
describe("cell store (unit)", () => {
it("returns initial value with get()", () => {
const c = cell(123);
expect(c.get()).toBe(123);
});
it("updates value with set(next)", () => {
const c = cell("a");
c.set("b");
expect(c.get()).toBe("b");
});
it("gives previous value in set(updater)", () => {
const c = cell(1);
c.set((prev) => prev + 2);
expect(c.get()).toBe(3);
});
it("calls subscribe callback on set", () => {
const c = cell(0);
const cb = jest.fn();
const unsub = c.subscribe(cb);
c.set(1);
c.set(2);
expect(cb).toHaveBeenCalledTimes(2);
unsub();
});
it("stops notifications when unsubscribing", () => {
const c = cell(0);
const cb = jest.fn();
const unsub = c.subscribe(cb);
c.set(1);
unsub();
c.set(2);
expect(cb).toHaveBeenCalledTimes(1);
});
it("updates multiple listeners", () => {
const c = cell("x");
const a = jest.fn();
const b = jest.fn();
const ua = c.subscribe(a);
const ub = c.subscribe(b);
c.set("y");
expect(a).toHaveBeenCalledTimes(1);
expect(b).toHaveBeenCalledTimes(1);
ua();
ub();
});
});
describe("cell store (integration)", () => {
function View({c, label}: { c: Cell<any>; label: string }) {
const v = useCell(c);
// count renders to verify re-render behavior
(View as any).__renders = ((View as any).__renders ?? 0) + 1;
return <div data-testid={label}>{String(v)}</div>;
}
it("reads initial value and updates on set", () => {
const c = cell("hello");
render(<View c={c} label="value"/>);
expect(screen.getByTestId("value")).toHaveTextContent("hello");
act(() => {
c.set("world");
});
expect(screen.getByTestId("value")).toHaveTextContent("world");
});
it("triggers one re-render with set", () => {
const c = cell(1);
(View as any).__renders = 0;
render(<View c={c} label="num"/>);
const rendersAfterMount = (View as any).__renders;
act(() => {
c.set((prev: number) => prev + 1);
});
// exactly one extra render from the update
expect((View as any).__renders).toBe(rendersAfterMount + 1);
expect(screen.getByTestId("num")).toHaveTextContent("2");
});
it("unsubscribes on unmount (no errors on later sets)", () => {
const c = cell("a");
const {unmount} = render(<View c={c} label="value"/>);
unmount();
// should not throw even though there was a subscriber
expect(() =>
act(() => {
c.set("b");
})
).not.toThrow();
});
it("only re-renders components that use the cell", () => {
const a = cell("A");
const b = cell("B");
let rendersA = 0;
let rendersB = 0;
function A() {
const v = useCell(a);
rendersA++;
return <div data-testid="A">{v}</div>;
}
function B() {
const v = useCell(b);
rendersB++;
return <div data-testid="B">{v}</div>;
}
render(
<>
<A/>
<B/>
</>
);
const rendersAAfterMount = rendersA;
const rendersBAfterMount = rendersB;
act(() => {
a.set("A2"); // only A should update
});
expect(screen.getByTestId("A")).toHaveTextContent("A2");
expect(screen.getByTestId("B")).toHaveTextContent("B");
expect(rendersA).toBe(rendersAAfterMount + 1);
expect(rendersB).toBe(rendersBAfterMount); // unchanged
});
});

View File

@@ -0,0 +1,53 @@
import formatDuration from "../../src/utils/formatDuration.ts";
describe("formatting durations (unit)", () => {
it("does one millisecond", () => {
const result = formatDuration(1);
expect(result).toBe("00:00:00.001");
});
it("does one-hundred twenty-three milliseconds", () => {
const result = formatDuration(123);
expect(result).toBe("00:00:00.123");
});
it("does one second", () => {
const result = formatDuration(1*1000);
expect(result).toBe("00:00:01.000");
});
it("does thirteen seconds", () => {
const result = formatDuration(13*1000);
expect(result).toBe("00:00:13.000");
});
it("does one minute", () => {
const result = formatDuration(60*1000);
expect(result).toBe("00:01:00.000");
});
it("does thirteen minutes", () => {
const result = formatDuration(13*60*1000);
expect(result).toBe("00:13:00.000");
});
it("does one hour", () => {
const result = formatDuration(60*60*1000);
expect(result).toBe("01:00:00.000");
});
it("does thirteen hours", () => {
const result = formatDuration(13*60*60*1000);
expect(result).toBe("13:00:00.000");
});
it("does negative one millisecond", () => {
const result = formatDuration(-1);
expect(result).toBe("-00:00:00.001");
});
it("does large negative durations", () => {
const result = formatDuration(-(123*60*60*1000 + 59*60*1000 + 59*1000 + 123));
expect(result).toBe("-123:59:59.123");
});
});

View File

@@ -0,0 +1,81 @@
import {applyPriorityPredicates, type PriorityFilterPredicate} from "../../src/utils/priorityFiltering";
const makePred = <T>(priority: number, fn: (el: T) => boolean | null): PriorityFilterPredicate<T> => ({
priority,
predicate: jest.fn(fn),
});
describe("applyPriorityPredicates (unit)", () => {
beforeEach(() => jest.clearAllMocks());
it("returns true when there are no predicates", () => {
expect(applyPriorityPredicates(123, [])).toBe(true);
});
it("behaves like a normal predicate with only one predicate", () => {
const even = makePred<number>(1, (n) => n % 2 === 0);
expect(applyPriorityPredicates(2, [even])).toBe(true);
expect(applyPriorityPredicates(3, [even])).toBe(false);
});
it("determines the result only listening to the highest priority predicates", () => {
const lowFail = makePred<number>(1, (_) => false);
const lowPass = makePred<number>(1, (_) => true);
const highPass = makePred<number>(10, (n) => n > 0);
const highFail = makePred<number>(10, (n) => n < 0);
expect(applyPriorityPredicates(5, [lowFail, highPass])).toBe(true);
expect(applyPriorityPredicates(5, [lowPass, highFail])).toBe(false);
});
it("uses all predicates at the highest priority", () => {
const high1 = makePred<number>(5, (n) => n % 2 === 0);
const high2 = makePred<number>(5, (n) => n > 2);
expect(applyPriorityPredicates(4, [high1, high2])).toBe(true);
expect(applyPriorityPredicates(2, [high1, high2])).toBe(false);
});
it("is order independent (later higher positive clears earlier lower negative)", () => {
const lowFalse = makePred<number>(1, (_) => false);
const highTrue = makePred<number>(9, (n) => n === 7);
// Higher priority appears later → should reset and decide by highest only
expect(applyPriorityPredicates(7, [lowFalse, highTrue])).toBe(true);
// Same set, different order → same result
expect(applyPriorityPredicates(7, [highTrue, lowFalse])).toBe(true);
});
it("handles many priorities: only max matters", () => {
const p1 = makePred<number>(1, (_) => false);
const p3 = makePred<number>(3, (_) => false);
const p5 = makePred<number>(5, (n) => n > 0);
expect(applyPriorityPredicates(1, [p1, p3, p5])).toBe(true);
});
it("skips predicates that return null", () => {
const high = makePred<number>(10, (n) => n === 0 ? true : null);
const low = makePred<number>(1, (_) => false);
expect(applyPriorityPredicates(0, [high, low])).toBe(true);
expect(applyPriorityPredicates(1, [high, low])).toBe(false);
});
});
describe("(integration) filter with applyPriorityPredicates", () => {
it("filters an array using only highest-priority predicates", () => {
const elems = [1, 2, 3, 4, 5];
const low = makePred<number>(0, (_) => false);
const high1 = makePred<number>(5, (n) => n % 2 === 0);
const high2 = makePred<number>(5, (n) => n > 2);
const result = elems.filter((e) => applyPriorityPredicates(e, [low, high1, high2]));
expect(result).toEqual([4]);
});
it("filters an array using only highest-priority predicates", () => {
const elems = [1, 2, 3, 4, 5];
const low = makePred<number>(0, (_) => false);
const high = makePred<number>(5, (n) => n === 3 ? true : null);
const result = elems.filter((e) => applyPriorityPredicates(e, [low, high]));
expect(result).toEqual([3]);
});
});