102 lines
4.7 KiB
TypeScript
102 lines
4.7 KiB
TypeScript
// tests/unit/shared/sidebar-collapsed-persistence.test.ts
|
|
//
|
|
// 守 r242「折叠 / 展开左侧栏」的**持久化与接线**契约。
|
|
//
|
|
// 背景:左栏折叠态存在 settings.json 的 workspaceLayout.sidebarCollapsed。
|
|
// 这条链路上有三个「漏一环就静默失效」的坑,本测逐个钉住:
|
|
//
|
|
// ① **ZodWorkspaceLayout 是 .strict()** —— schema 漏加 sidebarCollapsed,
|
|
// renderer 写回的 workspaceLayout 被判「未声明字段」⇒ 整个
|
|
// settings.update 被拒 ⇒ 折叠态永远存不下来(用户每次重启都要
|
|
// 再点一次折叠按钮,且可能看到「保存失败」类报错)。
|
|
// ② **主进程 sanitizeWorkspaceLayout 有白名单** —— 只放行
|
|
// statusBarHidden / fontFamily / themePreset;漏加 sidebarCollapsed
|
|
// ⇒ 字段在主进程被剥掉 ⇒ 同 ① 的后果(且没有报错,更难查)。
|
|
// ③ **主进程菜单 → renderer 命令 id** —— View 菜单的「折叠 / 展开
|
|
// 左侧栏」点下去是 IPC 推 id 给 dispatcher;id 拼错 ⇒ 菜单项点了
|
|
// 没反应(dispatcher 只 warn「unknown command」,用户看不到原因)。
|
|
//
|
|
// 本测锁:
|
|
// A. ZodWorkspaceLayout 接受 sidebarCollapsed:boolean,拒绝非 boolean,
|
|
// 仍然 strict(未知字段照拒)。
|
|
// B. 主进程 sanitize 保留 sidebarCollapsed、剥掉未知字段。
|
|
// C. 主进程 View 菜单有该项(accelerator CmdOrCtrl+B)且发的是
|
|
// 'view.toggleSidebar'。
|
|
// D. 菜单里出现的每个命令 id 都能在 renderer 命令注册表里找到
|
|
// (防「菜单项点了没反应」这一类死链)。
|
|
import { describe, expect, it } from 'vitest';
|
|
import { readFileSync } from 'fs';
|
|
import { join } from 'path';
|
|
import { ZodWorkspaceLayout } from '../../../src/shared/schemas/settings';
|
|
import { sanitizeLoadedSettings } from '../../../src/main/settings/validate';
|
|
import { buildCommands, type CommandActions } from '../../../src/renderer/src/command/registry';
|
|
|
|
const ROOT = process.cwd();
|
|
const MENU_FILE = join(ROOT, 'src/main/menu.ts');
|
|
|
|
describe('左栏折叠态的持久化与接线(r242)', () => {
|
|
it('A1. ZodWorkspaceLayout 接受 sidebarCollapsed:boolean', () => {
|
|
const r = ZodWorkspaceLayout.safeParse({ sidebarCollapsed: true });
|
|
expect(r.success, 'schema 漏加 sidebarCollapsed ⇒ 折叠态存不下来').toBe(true);
|
|
const r2 = ZodWorkspaceLayout.safeParse({});
|
|
expect(r2.success, '未设置折叠态的新用户必须能过 schema').toBe(true);
|
|
});
|
|
|
|
it('A2. ZodWorkspaceLayout 拒绝非 boolean 的 sidebarCollapsed', () => {
|
|
expect(ZodWorkspaceLayout.safeParse({ sidebarCollapsed: 'yes' }).success).toBe(false);
|
|
expect(ZodWorkspaceLayout.safeParse({ sidebarCollapsed: 1 }).success).toBe(false);
|
|
});
|
|
|
|
it('A3. ZodWorkspaceLayout 仍然 strict(未知字段照拒)', () => {
|
|
expect(ZodWorkspaceLayout.safeParse({ somethingElse: true }).success).toBe(false);
|
|
});
|
|
|
|
it('B. 主进程 sanitize 保留 sidebarCollapsed、剥掉未知字段', () => {
|
|
const out = sanitizeLoadedSettings({
|
|
workspaceLayout: { statusBarHidden: true, sidebarCollapsed: true, bogusField: 1 },
|
|
});
|
|
expect(out.workspaceLayout).toMatchObject({
|
|
statusBarHidden: true,
|
|
sidebarCollapsed: true,
|
|
});
|
|
expect(out.workspaceLayout).not.toHaveProperty('bogusField');
|
|
});
|
|
|
|
it('C. 主进程 View 菜单有「折叠 / 展开左侧栏」且发 view.toggleSidebar', () => {
|
|
const src = readFileSync(MENU_FILE, 'utf8');
|
|
expect(src, "View 菜单缺少 toggleSidebar 项").toContain("menu.view.toggleSidebar");
|
|
expect(src, "toggleSidebar 菜单项没发 view.toggleSidebar 命令 id").toContain(
|
|
"send('view.toggleSidebar')",
|
|
);
|
|
expect(src, 'toggleSidebar 菜单项的 accelerator 应为 CmdOrCtrl+B').toContain(
|
|
"accelerator: 'CmdOrCtrl+B'",
|
|
);
|
|
});
|
|
|
|
it('D. 菜单里出现的每个命令 id 都能在 renderer 命令注册表里找到', () => {
|
|
const src = readFileSync(MENU_FILE, 'utf8');
|
|
const sent = [...src.matchAll(/send\(\s*'([A-Za-z0-9_.]+)'\s*\)/g)].map((m) => m[1]);
|
|
expect(sent.length, '菜单里一个 send() 都没扫到 —— 扫点失效,本测空过').toBeGreaterThan(3);
|
|
|
|
const actions = {
|
|
setActive: () => {},
|
|
toggleStatusBar: () => {},
|
|
toggleSidebar: () => {},
|
|
setFontFamily: () => {},
|
|
setThemePreset: () => {},
|
|
toggleTheme: async () => {},
|
|
openSettings: () => {},
|
|
openSearch: () => {},
|
|
saveActiveNote: () => {},
|
|
newNote: () => {},
|
|
} satisfies CommandActions;
|
|
const ids = new Set(buildCommands(actions).map((c) => c.id));
|
|
|
|
const orphan = sent.filter((id) => !ids.has(id));
|
|
expect(
|
|
orphan,
|
|
`菜单项的命令 id 在 renderer 注册表里不存在 ⇒ 点了没反应:\n${orphan.join('\n')}`,
|
|
).toEqual([]);
|
|
});
|
|
});
|