Add logging with filters
This commit is contained in:
committed by
Gerla, J. (Justin)
parent
b7eb0cb5ec
commit
231d7a5ba1
156
test/utils/cellStore.test.tsx
Normal file
156
test/utils/cellStore.test.tsx
Normal 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
|
||||
});
|
||||
});
|
||||
53
test/utils/formatDuration.test.ts
Normal file
53
test/utils/formatDuration.test.ts
Normal 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");
|
||||
});
|
||||
});
|
||||
81
test/utils/priorityFiltering.test.ts
Normal file
81
test/utils/priorityFiltering.test.ts
Normal 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]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user