Files
MrAlders0n ad4e68f817 packets: highlight timer leak, analyzer close desync, dead code
- fresh-row highlights leaked under a steady stream (each new batch
  cancelled the previous batch's clear timer); per-batch timers in a
  small useFreshHashes hook now
- closing the analyzer drops ?hash and the row selection, so the same
  row reopens on the next click and a closed analyzer stays closed
  after reload/share
- sf=path / sf=payload from the URL fall back to hash search (only
  field matchesFilters implements; the UI options were already disabled)
- remove the summary preview line no backend path ever populates
- channel message rows key on packetHash (live WS messages carry no id)
2026-06-10 07:03:16 -04:00

88 lines
2.8 KiB
TypeScript

import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { renderHook, act } from "@testing-library/react";
import { useFreshHashes } from "../../../src/features/packets/useFreshHashes";
import type { PacketSummary } from "../../../src/types/api";
function pkt(hash: string): PacketSummary {
return {
packetHash: hash,
payloadType: 2,
payloadTypeName: "TEXT_MESSAGE",
routeType: 0,
routeTypeName: "FLOOD",
firstHeardAt: 0,
lastHeardAt: 0,
observationCount: 1,
};
}
beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
});
describe("useFreshHashes", () => {
it("does not mark the initial batch fresh", () => {
const { result } = renderHook(({ packets }) => useFreshHashes(packets), {
initialProps: { packets: [pkt("a"), pkt("b")] },
});
expect(result.current.size).toBe(0);
});
it("marks prepended hashes fresh and clears them after the delay", () => {
const { result, rerender } = renderHook(({ packets }) => useFreshHashes(packets), {
initialProps: { packets: [pkt("a")] },
});
rerender({ packets: [pkt("b"), pkt("a")] });
expect(result.current.has("b")).toBe(true);
act(() => vi.advanceTimersByTime(1100));
expect(result.current.has("b")).toBe(false);
});
it("clears each batch on its own delay during a steady stream", () => {
const { result, rerender } = renderHook(({ packets }) => useFreshHashes(packets), {
initialProps: { packets: [pkt("a")] },
});
// t=0: b arrives
rerender({ packets: [pkt("b"), pkt("a")] });
expect(result.current.has("b")).toBe(true);
// t=500: c arrives while b's highlight is still pending
act(() => vi.advanceTimersByTime(500));
rerender({ packets: [pkt("c"), pkt("b"), pkt("a")] });
expect(result.current.has("c")).toBe(true);
expect(result.current.has("b")).toBe(true);
// t=1100: b is past its 1s window and must clear even though c arrived after it
act(() => vi.advanceTimersByTime(600));
expect(result.current.has("b")).toBe(false);
expect(result.current.has("c")).toBe(true);
// t=1600: c clears too
act(() => vi.advanceTimersByTime(500));
expect(result.current.has("c")).toBe(false);
});
it("a packets change with no new hashes does not cancel a pending clear", () => {
const { result, rerender } = renderHook(({ packets }) => useFreshHashes(packets), {
initialProps: { packets: [pkt("a")] },
});
rerender({ packets: [pkt("b"), pkt("a")] });
expect(result.current.has("b")).toBe(true);
// same content, new array identity — e.g. a filter re-evaluation
act(() => vi.advanceTimersByTime(100));
rerender({ packets: [pkt("b"), pkt("a")] });
act(() => vi.advanceTimersByTime(1000));
expect(result.current.has("b")).toBe(false);
});
});