feat(history): update the open view live

This commit is contained in:
Sucukdeluxe
2026-08-21 00:48:42 +02:00
parent 18fa58002c
commit 3a95136ef1
10 changed files with 176 additions and 19 deletions
+51
View File
@@ -0,0 +1,51 @@
import { beforeAll, describe, expect, it, vi } from "vitest";
import { IPC_CHANNELS } from "../src/shared/ipc";
import type { ElectronApi } from "../src/shared/preload-api";
import type { HistoryEntry } from "../src/shared/types";
const electron = vi.hoisted(() => ({
api: undefined as ElectronApi | undefined,
listeners: new Map<string, (...args: unknown[]) => void>(),
on: vi.fn((channel: string, listener: (...args: unknown[]) => void) => {
electron.listeners.set(channel, listener);
}),
removeListener: vi.fn((channel: string, listener: (...args: unknown[]) => void) => {
if (electron.listeners.get(channel) === listener) {
electron.listeners.delete(channel);
}
})
}));
vi.mock("electron", () => ({
contextBridge: {
exposeInMainWorld: (_name: string, api: ElectronApi) => {
electron.api = api;
}
},
ipcRenderer: {
invoke: vi.fn(),
on: electron.on,
removeListener: electron.removeListener,
send: vi.fn()
}
}));
describe("history preload contract", () => {
beforeAll(async () => {
await import("../src/preload/preload");
});
it("subscribes to saved history entries and removes the exact listener", () => {
const received: HistoryEntry[] = [];
const entry = { id: "history-live" } as HistoryEntry;
const unsubscribe = electron.api?.onHistoryEntryAdded((value) => received.push(value));
const listener = electron.listeners.get(IPC_CHANNELS.HISTORY_ENTRY_ADDED);
expect(listener).toBeTypeOf("function");
listener?.({}, entry);
expect(received).toEqual([entry]);
unsubscribe?.();
expect(electron.removeListener).toHaveBeenCalledWith(IPC_CHANNELS.HISTORY_ENTRY_ADDED, listener);
});
});
+30 -5
View File
@@ -11,8 +11,9 @@ import {
createHistoryTableColumnWidths,
getHistoryTableGridTemplate,
getHistoryTableMinWidth,
HISTORY_PAGE_SIZE,
paginateHistoryRows,
HISTORY_PAGE_SIZE,
mergeLiveHistoryEntry,
paginateHistoryRows,
pruneHistoryIds,
resizeHistoryTableColumn,
selectVisibleHistoryIds,
@@ -106,8 +107,20 @@ const entries: HistoryViewEntry[] = [
entry({ id: "older", name: "Altes Paket", completedAt: weekStart - 1, status: "failed", provider: null, outputDir: "D:\\Archiv\\Alt", urls: ["https://sub.example.test/a"] })
];
describe("history model", () => {
it("separates today, previous six calendar days, older and status filters at exact boundaries", () => {
describe("history model", () => {
it("prepends live entries without duplicates and applies retention limits", () => {
const incoming = entry({ id: "live", name: "Live", completedAt: now }) as HistoryEntry;
const existing: HistoryEntry[] = [
entry({ id: "current", name: "Current", completedAt: now - 1_000 }) as HistoryEntry,
entry({ id: "live", name: "Old live", completedAt: now - 2_000 }) as HistoryEntry,
entry({ id: "expired", name: "Expired", completedAt: now - 3 * 86_400_000 }) as HistoryEntry
];
expect(mergeLiveHistoryEntry(existing, incoming, { maxEntries: 2, maxAgeDays: 2 }, now).map((item) => item.id))
.toEqual(["live", "current"]);
});
it("separates today, previous six calendar days, older and status filters at exact boundaries", () => {
const expected: Record<HistoryFilter, string[]> = {
all: ["today", "week-edge", "week", "older"],
today: ["today"],
@@ -613,7 +626,19 @@ describe("visual history states", () => {
expect(setup).toBeGreaterThan(effectStart);
expect(setup).toBeLessThan(firstRequest);
expect(cleanup).toBeGreaterThan(firstRequest);
});
});
it("merges pushed history entries only while the history tab is open", () => {
const source = readFileSync(new URL("../src/renderer/App.tsx", import.meta.url), "utf8").replaceAll("\r\n", "\n");
const listenerStart = source.indexOf("window.rd.onHistoryEntryAdded");
const listenerEnd = source.indexOf("return unsubscribeHistoryEntryAdded", listenerStart);
const listener = source.slice(listenerStart, listenerEnd);
expect(listenerStart).toBeGreaterThan(-1);
expect(listener).toContain('activeTabRef.current !== "history"');
expect(listener).toContain("mergeLiveHistoryEntry");
expect(listener).not.toContain("getHistory()");
});
it("keeps bootstrap deterministic before exposing loading and error responses to the opened history view", async () => {
const loadingApi = createVisualElectronApi(createVisualFixture("dense"), "?history-state=loading");
+1
View File
@@ -449,6 +449,7 @@ export function createVisualElectronApi(
stateUpdateListeners.add(callback);
return () => stateUpdateListeners.delete(callback);
},
onHistoryEntryAdded: () => stableNoopUnsubscribe,
onClipboardDetected: () => stableNoopUnsubscribe,
onUpdateInstallProgress: () => stableNoopUnsubscribe
};