新增 8 条:`sidebar-data-cache.test.ts`(命中不重拉 / 过期重拉一次 / 写操作后 拿到新标题并逐条锁住五个写路径 / 双账户不串 / 只存内存 / 401 清空)、 `sidebar-state.test.ts` +2(收起后重挂仍收起,含三种降级;移动端不读不写)、 `sidebar-contract.test.ts` +1(只读模式只少三样)、`chart-page-view.test.tsx` +1 (`(secondary)` layout 恰好挂一份只读侧栏,数据 hook 无写方法)。 改写 9 处既有断言,每处带「原值 / 新值 / 原因」三栏注释,均未削弱: `window.location.assign(path)` → `<SidebarMenuLink href>` 并追加反向断言; `onOpenReports` / `useRouter` 改成 doesNotMatch;`SecondaryShell` → `SecondaryHeader`; 导航顺序改在 `NAV_PAGES` 常量里量;两个 render 辅助改为裹 `SidebarProvider` (provider 上移到 layout);三处源码路径跟随路由组移动。 测试总数 3391 → 3399,失败清单与基线逐条一致(47 条均为无 Docker 的既有缺口)。 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0193vBv6w5MV2cifdTUu9H5P
106 lines
4.8 KiB
TypeScript
106 lines
4.8 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { readFileSync } from "node:fs";
|
|
import test from "node:test";
|
|
import {
|
|
defaultSidebarOpen,
|
|
readStoredSidebarOpen,
|
|
shouldHandleSidebarShortcut,
|
|
sidebarViewportForWidth,
|
|
writeStoredSidebarOpen,
|
|
} from "../src/lib/sidebar-state.ts";
|
|
import { beginSessionPageLoad, mergeSessionPage } from "../src/lib/session-groups.ts";
|
|
import { homeSurface } from "./home-surface.ts";
|
|
|
|
test("classifies the exact sidebar breakpoints", () => {
|
|
assert.equal(sidebarViewportForWidth(0), "mobile");
|
|
assert.equal(sidebarViewportForWidth(767), "mobile");
|
|
assert.equal(sidebarViewportForWidth(768), "tablet");
|
|
assert.equal(sidebarViewportForWidth(1023), "tablet");
|
|
assert.equal(sidebarViewportForWidth(1024), "desktop");
|
|
});
|
|
|
|
test("uses the approved reload defaults", () => {
|
|
assert.equal(defaultSidebarOpen("mobile"), false);
|
|
assert.equal(defaultSidebarOpen("tablet"), false);
|
|
assert.equal(defaultSidebarOpen("desktop"), true);
|
|
});
|
|
|
|
test("accepts Command or Control B only outside editable controls", () => {
|
|
const shortcut = { key: "b", metaKey: true, ctrlKey: false, altKey: false, shiftKey: false };
|
|
|
|
assert.equal(shouldHandleSidebarShortcut({ ...shortcut, target: null }), true);
|
|
assert.equal(shouldHandleSidebarShortcut({ ...shortcut, target: { tagName: "TEXTAREA" } }), false);
|
|
assert.equal(shouldHandleSidebarShortcut({ ...shortcut, target: { isContentEditable: true } }), false);
|
|
assert.equal(shouldHandleSidebarShortcut({ ...shortcut, key: "k", target: null }), false);
|
|
});
|
|
|
|
test("renaming a session does not bump updatedAt", () => {
|
|
assert.match(homeSurface, /const nextSession = \{ \.\.\.session, title \};/);
|
|
assert.doesNotMatch(homeSurface, /const nextSession = \{ \.\.\.session, title, updatedAt: timestamp\(\) \};/);
|
|
});
|
|
|
|
test("loadMoreSessions merges by id and only starts one in-flight request", () => {
|
|
const inFlight = { current: false };
|
|
assert.equal(beginSessionPageLoad(inFlight, "cursor-1"), true);
|
|
assert.equal(beginSessionPageLoad(inFlight, "cursor-1"), false);
|
|
const merged = mergeSessionPage(
|
|
[{ id: "keep", updatedAt: 900 }],
|
|
[{ id: "keep", updatedAt: 1 }, { id: "next", updatedAt: 2 }],
|
|
);
|
|
assert.deepEqual(merged.map((item) => item.id), ["keep", "next"]);
|
|
assert.equal(merged[0]?.updatedAt, 900);
|
|
});
|
|
|
|
/* T5 · the collapse state is remembered across pages (TASK-sidebar-unify D5).
|
|
localStorage, not the shadcn `sidebar_state` cookie: `/`, `/chart` and
|
|
`/ephemeris` are all static routes, and reading a cookie on the server would
|
|
opt every one of them out of static rendering. */
|
|
|
|
function fakeStorage(initial?: string) {
|
|
let value = initial;
|
|
return {
|
|
getItem: () => value ?? null,
|
|
setItem: (_key: string, next: string) => { value = next; },
|
|
read: () => value,
|
|
};
|
|
}
|
|
|
|
test("a collapsed desktop sidebar is still collapsed after a remount", () => {
|
|
const storage = fakeStorage();
|
|
|
|
writeStoredSidebarOpen("desktop", false, storage);
|
|
assert.equal(storage.read(), "false");
|
|
|
|
// What SidebarProvider resolves on the next page: stored wins over the
|
|
// breakpoint default, which for desktop would have been `true`.
|
|
assert.equal(readStoredSidebarOpen("desktop", storage) ?? defaultSidebarOpen("desktop"), false);
|
|
|
|
writeStoredSidebarOpen("desktop", true, storage);
|
|
assert.equal(readStoredSidebarOpen("desktop", storage) ?? defaultSidebarOpen("desktop"), true);
|
|
|
|
// Nothing stored, or something unreadable: the breakpoint default stands.
|
|
const empty = fakeStorage();
|
|
assert.equal(readStoredSidebarOpen("desktop", empty), null);
|
|
assert.equal(readStoredSidebarOpen("tablet", fakeStorage("banana")), null);
|
|
assert.equal(readStoredSidebarOpen("tablet", empty) ?? defaultSidebarOpen("tablet"), false);
|
|
});
|
|
|
|
test("the mobile drawer is not remembered, read or written", () => {
|
|
// Reopening a phone drawer on every navigation is not a preference anyone set.
|
|
const storage = fakeStorage();
|
|
writeStoredSidebarOpen("mobile", true, storage);
|
|
assert.equal(storage.read(), undefined);
|
|
assert.equal(readStoredSidebarOpen("mobile", fakeStorage("true")), null);
|
|
assert.equal(readStoredSidebarOpen("mobile", fakeStorage("true")) ?? defaultSidebarOpen("mobile"), false);
|
|
|
|
// Unavailable storage (private mode, blocked site data) degrades, never throws.
|
|
assert.equal(readStoredSidebarOpen("desktop", null), null);
|
|
assert.doesNotThrow(() => writeStoredSidebarOpen("desktop", true, null));
|
|
|
|
// And the provider applies it in the same effect that applied the breakpoint
|
|
// default, so a stored value costs no frame the default did not already cost.
|
|
const provider = readFileSync(new URL("../src/components/ui/sidebar.tsx", import.meta.url), "utf8");
|
|
assert.match(provider, /readStoredSidebarOpen\(viewport\) \?\? defaultSidebarOpen\(viewport\)/);
|
|
assert.match(provider, /writeStoredSidebarOpen\(viewport, nextOpen\)/);
|
|
});
|