## Summary Restores green **Full Suite (non-blocking)** runs on `main`. Recent main merges left i18n key parity, schema baseline bookkeeping (0011→0012), heartbeat tool inventory (FN-8058 `fn_task_logs_read`), and merger whitespace-classification mocks (execFile `git diff -p -w :2: :3:`) out of date, so all four test shards failed. ## Root causes observed on main - **Shard 4 / `@fusion/i18n`**: missing `skipConfirmationDialogs*` + `reviewBudgetExhausted` in non-en locales; orphan `awaitingApprovalPlanReviewReplanCap` - **Shard 3 / `@fusion/core`**: `SCHEMA_BASELINE_VERSION` advanced to `0012` while tests still equated it with `OWNER_PROJECT_ID_SPLIT_VERSION` (`0011`) and omitted `0012` from applied-migration lists - **Shards 1–2 / `@fusion/engine`**: tool count/snapshot drift for `fn_task_logs_read`; merger tests still mocked `git diff-tree` for trivial classification after the execFile `:2:`/`:3:` cutover; mock provider `updateTask` arity drift ## Changes - Locale catalogs: add missing keys, drop orphan key - Schema applier tests: immutable 0011 identity + baseline 0012 lists - Heartbeat + gating snapshots: include `fn_task_logs_read` - Merger unit mocks: recognize `git diff -p -w :2:path :3:path` - Mock provider: accept optional third `updateTask` arg ## Test plan - [x] `pnpm --filter @fusion/i18n exec vitest run` — 23/23 - [x] `pnpm --filter @fusion/core exec vitest run src/__tests__/postgres/schema-applier.test.ts` (immutable + automation upgrade) — pass - [x] `pnpm --filter @fusion/core exec vitest run` project-identity + satellite-fusiondir — pass - [x] Engine suites from failed CI shards (file-scoped, hermes/openclaw/paperclip/grok, reliability post-finalize/mission, heartbeat, gating, merger recovery/prompt, mock-provider, etc.) — pass - [ ] Full Suite workflow green on merge to main <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Improved project data isolation across backend operations. - Added safer optional toast handling when UI components render outside the full application shell. - Added support for reading task logs during agent heartbeat sessions. - **Bug Fixes** - Prevented runtime probes from hanging and avoided scanning large binary files. - Improved path handling for workspaces with missing descendants. - Corrected task retry state resets and GitHub import/issue-close behavior. - **Style** - Improved chat, terminal, and settings spacing. - Added clearer accessibility labeling for the auto-merge control. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
156 lines
5.8 KiB
TypeScript
156 lines
5.8 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
import {
|
|
GLOBAL_STORAGE_KEYS,
|
|
PROJECT_STORAGE_KEYS,
|
|
getScopedItem,
|
|
removeScopedItem,
|
|
scopedKey,
|
|
setScopedItem,
|
|
} from "../projectStorage";
|
|
|
|
describe("projectStorage", () => {
|
|
beforeEach(() => {
|
|
localStorage.clear();
|
|
});
|
|
|
|
describe("scopedKey", () => {
|
|
it("returns scoped key when projectId is provided", () => {
|
|
expect(scopedKey("kb-dashboard-list-columns", "proj-abc")).toBe(
|
|
"kb:proj-abc:kb-dashboard-list-columns",
|
|
);
|
|
});
|
|
|
|
it("returns base key unchanged when projectId is undefined", () => {
|
|
expect(scopedKey("kb-dashboard-list-columns", undefined)).toBe("kb-dashboard-list-columns");
|
|
});
|
|
|
|
it("returns base key unchanged when projectId is omitted", () => {
|
|
expect(scopedKey("kb-dashboard-list-columns")).toBe("kb-dashboard-list-columns");
|
|
});
|
|
|
|
it("returns base key unchanged when projectId is empty", () => {
|
|
expect(scopedKey("kb-dashboard-list-columns", "")).toBe("kb-dashboard-list-columns");
|
|
});
|
|
|
|
it("returns base key unchanged when projectId is null", () => {
|
|
expect(scopedKey("kb-dashboard-list-columns", null as any)).toBe("kb-dashboard-list-columns");
|
|
});
|
|
});
|
|
|
|
it("uses scoped keys for get/set/remove with projectId", () => {
|
|
setScopedItem("kb-dashboard-list-columns", "value", "proj-abc");
|
|
|
|
expect(localStorage.getItem("kb:proj-abc:kb-dashboard-list-columns")).toBe("value");
|
|
expect(getScopedItem("kb-dashboard-list-columns", "proj-abc")).toBe("value");
|
|
|
|
removeScopedItem("kb-dashboard-list-columns", "proj-abc");
|
|
expect(localStorage.getItem("kb:proj-abc:kb-dashboard-list-columns")).toBeNull();
|
|
});
|
|
|
|
it("uses unscoped keys for get/set/remove without projectId", () => {
|
|
setScopedItem("kb-dashboard-list-columns", "value");
|
|
|
|
expect(localStorage.getItem("kb-dashboard-list-columns")).toBe("value");
|
|
expect(getScopedItem("kb-dashboard-list-columns")).toBe("value");
|
|
|
|
removeScopedItem("kb-dashboard-list-columns");
|
|
expect(localStorage.getItem("kb-dashboard-list-columns")).toBeNull();
|
|
});
|
|
|
|
it("includes all global storage keys", () => {
|
|
expect(GLOBAL_STORAGE_KEYS).toEqual(
|
|
expect.arrayContaining([
|
|
"kb-dashboard-theme-mode",
|
|
"kb-dashboard-color-theme",
|
|
"kb-dashboard-view-mode",
|
|
"kb-dashboard-current-project",
|
|
"kb-dashboard-recent-projects",
|
|
"fn-agent-log-markdown",
|
|
"fn-agent-log-tool-output",
|
|
]),
|
|
);
|
|
expect(GLOBAL_STORAGE_KEYS).toHaveLength(7);
|
|
});
|
|
|
|
it("includes all project-scoped storage keys", () => {
|
|
expect(PROJECT_STORAGE_KEYS).toEqual(
|
|
expect.arrayContaining([
|
|
"kb-dashboard-task-view",
|
|
"kb-dashboard-list-columns",
|
|
"kb-dashboard-hide-done",
|
|
"kb-dashboard-todo-hide-done",
|
|
"kb-dashboard-list-collapsed",
|
|
"kb-dashboard-selected-tasks",
|
|
"kb-dashboard-list-selected-task",
|
|
"kb-dashboard-list-sidebar-width",
|
|
"kb-dashboard-mailbox-sidebar-width",
|
|
"kb-dashboard-agents-sidebar-width",
|
|
"kb-dashboard-github-import-list-width",
|
|
"kb-dashboard-github-import-state",
|
|
"kb-quick-entry-text",
|
|
"kb-inline-create-text",
|
|
"fn-agent-view",
|
|
"kb-terminal-tabs",
|
|
"kb-planning-last-description",
|
|
"kb-subtask-last-description",
|
|
"kb-mission-last-goal",
|
|
"kb-usage-view-mode",
|
|
"kb-usage-hidden-windows",
|
|
"kb-usage-modal-size",
|
|
"kb-usage-provider-order",
|
|
"kb-chat-active-session",
|
|
"kb-dashboard-working-branch-filter",
|
|
"kb-dashboard-base-branch-filter",
|
|
"kb-capacity-risk-banner-dismissed",
|
|
"kb-github-setup-warning-missing-since",
|
|
"kb-files-line-numbers",
|
|
"kb-dashboard-dock-files-current",
|
|
"kb-dashboard-board-workflow-selection",
|
|
"fusion-plugin-dependency-graph:positions",
|
|
]),
|
|
);
|
|
/*
|
|
FNXC:ProjectStorage 2026-07-14-19:20:
|
|
Keep PROJECT_STORAGE_KEYS length lockstep with the source array (todo hide-done, github import state, github setup warning dismissals).
|
|
*/
|
|
expect(PROJECT_STORAGE_KEYS).toHaveLength(32);
|
|
});
|
|
|
|
it("stores branch filter values as scoped strings per project", () => {
|
|
setScopedItem("kb-dashboard-working-branch-filter", "feature/a", "proj-1");
|
|
setScopedItem("kb-dashboard-base-branch-filter", "__fusion:no-branch__", "proj-1");
|
|
setScopedItem("kb-dashboard-working-branch-filter", "feature/b", "proj-2");
|
|
|
|
expect(getScopedItem("kb-dashboard-working-branch-filter", "proj-1")).toBe("feature/a");
|
|
expect(getScopedItem("kb-dashboard-base-branch-filter", "proj-1")).toBe("__fusion:no-branch__");
|
|
expect(getScopedItem("kb-dashboard-working-branch-filter", "proj-2")).toBe("feature/b");
|
|
expect(getScopedItem("kb-dashboard-working-branch-filter", "proj-3")).toBeNull();
|
|
});
|
|
|
|
it("getScopedItem returns null when localStorage.getItem is unavailable", () => {
|
|
vi.stubGlobal("window", { localStorage: {} });
|
|
expect(getScopedItem("kb-dashboard-list-columns", "proj-abc")).toBeNull();
|
|
vi.unstubAllGlobals();
|
|
});
|
|
|
|
it("setScopedItem is a no-op when localStorage.setItem is unavailable", () => {
|
|
vi.stubGlobal("window", { localStorage: {} });
|
|
expect(() => setScopedItem("kb-dashboard-list-columns", "value", "proj-abc")).not.toThrow();
|
|
vi.unstubAllGlobals();
|
|
});
|
|
|
|
it("removeScopedItem is a no-op when localStorage.removeItem is unavailable", () => {
|
|
vi.stubGlobal("window", { localStorage: {} });
|
|
expect(() => removeScopedItem("kb-dashboard-list-columns", "proj-abc")).not.toThrow();
|
|
vi.unstubAllGlobals();
|
|
});
|
|
|
|
it("has no overlap between global and project-scoped keys", () => {
|
|
const globalSet = new Set(GLOBAL_STORAGE_KEYS);
|
|
const overlap = PROJECT_STORAGE_KEYS.filter((key) => globalSet.has(key));
|
|
|
|
expect(overlap).toEqual([]);
|
|
});
|
|
});
|