louie-vibecoding_time-recorder/tests/settingsMigration.test.ts
罗逸 09116f5f24 feat: flash-note button — one tap to the end of your quick-notes file
- New  闪记 button in the punch-in grid footer + 'Flash note' command:
  opens the configured note with the cursor at the very end, ready to type
  (switches reading view to edit mode; scrolls into view; focuses editor)
- Target note is configurable in settings (below template path); empty means
  unconfigured — first click opens a fuzzy picker (vault .md files, most
  recently modified first) and the chosen file is saved, so guidance IS setup
- Stale/missing target falls back to the picker with a notice
- settings schema: new flashNotePath field ("" = unset); migration keeps
  old data.json valid without marking it recovered; tests cover keep/missing/
  wrong-type cases; all strings bilingual (zh/en)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UAe1irQiaK4asnA5pQQjuy
2026-07-22 14:20:51 +08:00

148 lines
6 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { describe, it, expect } from "vitest";
import { migrateSettings, CURRENT_SETTINGS_VERSION } from "../src/settingsMigration";
import { DEFAULT_CATEGORIES, DEFAULT_SETTINGS } from "../src/settings";
describe("migrateSettings", () => {
it("首次安装null/undefined→ 默认设置、不算回退", () => {
for (const input of [null, undefined]) {
const { settings, recovered } = migrateSettings(input);
expect(recovered).toBe(false);
expect(settings.version).toBe(CURRENT_SETTINGS_VERSION);
expect(settings.recordsFolder).toBe(DEFAULT_SETTINGS.recordsFolder);
expect(settings.categories).toHaveLength(DEFAULT_CATEGORIES.length);
}
});
it("顶层是数组 → 默认设置、recovered=true", () => {
const { settings, recovered } = migrateSettings([1, 2, 3]);
expect(recovered).toBe(true);
expect(settings.categories).toHaveLength(DEFAULT_CATEGORIES.length);
});
it("顶层是字符串/数字 → 默认设置、recovered=true", () => {
expect(migrateSettings("oops").recovered).toBe(true);
expect(migrateSettings(42).recovered).toBe(true);
expect(migrateSettings("oops").settings.categories).toHaveLength(
DEFAULT_CATEGORIES.length,
);
});
it("正常完整 v1 → 原样保留、recovered=false", () => {
const input = {
version: 1,
recordsFolder: "我的/记录",
templatePath: "我的/模板.md",
categories: [{ id: "study", name: "学习", emoji: "📚", aliases: ["阅读"] }],
};
const { settings, recovered } = migrateSettings(input);
expect(recovered).toBe(false);
expect(settings.recordsFolder).toBe("我的/记录");
expect(settings.templatePath).toBe("我的/模板.md");
expect(settings.categories).toEqual([
{ id: "study", name: "学习", emoji: "📚", aliases: ["阅读"] },
]);
});
it("缺 templatePath → 回填默认、recovered=false", () => {
const input = {
version: 1,
recordsFolder: "我的/记录",
categories: [{ id: "study", name: "学习", emoji: "📚", aliases: [] }],
};
const { settings, recovered } = migrateSettings(input);
expect(recovered).toBe(false);
expect(settings.templatePath).toBe(DEFAULT_SETTINGS.templatePath);
});
it("已配置 flashNotePath → 原样保留、recovered=false", () => {
const input = {
version: 1,
recordsFolder: "我的/记录",
templatePath: "我的/模板.md",
flashNotePath: "收集箱/闪记.md",
categories: [{ id: "study", name: "学习", emoji: "📚", aliases: [] }],
};
const { settings, recovered } = migrateSettings(input);
expect(recovered).toBe(false);
expect(settings.flashNotePath).toBe("收集箱/闪记.md");
});
it("旧版 data.json 缺 flashNotePath / 类型错 → 回填空串(= 未配置、recovered=false", () => {
const base = {
version: 1,
categories: [{ id: "study", name: "学习", emoji: "📚", aliases: [] }],
};
const missing = migrateSettings(base);
expect(missing.recovered).toBe(false);
expect(missing.settings.flashNotePath).toBe("");
const wrongType = migrateSettings({ ...base, flashNotePath: 42 });
expect(wrongType.recovered).toBe(false);
expect(wrongType.settings.flashNotePath).toBe("");
});
it("categories 缺失 → 默认分类、recovered=true", () => {
const { settings, recovered } = migrateSettings({ version: 1, recordsFolder: "x" });
expect(recovered).toBe(true);
expect(settings.categories).toHaveLength(DEFAULT_CATEGORIES.length);
});
it("categories 非数组 → 默认分类、recovered=true", () => {
const { settings, recovered } = migrateSettings({ categories: "nope" });
expect(recovered).toBe(true);
expect(settings.categories).toHaveLength(DEFAULT_CATEGORIES.length);
});
it("分类条目缺 aliases → 回填 []、recovered=false", () => {
const input = { categories: [{ id: "study", name: "学习", emoji: "📚" }] };
const { settings, recovered } = migrateSettings(input);
expect(recovered).toBe(false);
expect(settings.categories[0].aliases).toEqual([]);
});
it("分类条目缺必需字段 → 丢弃该条、其余保留、recovered=false", () => {
const input = {
categories: [
{ id: "study", name: "学习", emoji: "📚", aliases: [] },
{ id: "", name: "坏", emoji: "x", aliases: [] },
{ name: "无id", emoji: "y", aliases: [] },
],
};
const { settings, recovered } = migrateSettings(input);
expect(recovered).toBe(false);
expect(settings.categories).toHaveLength(1);
expect(settings.categories[0].id).toBe("study");
});
it("所有条目都无效 → 默认分类、recovered=true", () => {
const input = { categories: [{ id: "", name: "", emoji: "" }, "garbage"] };
const { settings, recovered } = migrateSettings(input);
expect(recovered).toBe(true);
expect(settings.categories).toHaveLength(DEFAULT_CATEGORIES.length);
});
it("version 比当前新 → 尽力合并使用、recovered=false", () => {
const input = {
version: CURRENT_SETTINGS_VERSION + 5,
recordsFolder: "未来/记录",
templatePath: "未来/模板.md",
categories: [{ id: "study", name: "学习", emoji: "📚", aliases: ["阅读"] }],
};
const { settings, recovered } = migrateSettings(input);
expect(recovered).toBe(false);
expect(settings.recordsFolder).toBe("未来/记录");
expect(settings.categories[0].id).toBe("study");
});
it("任意输入 → 输出 version 都戳成当前版本", () => {
expect(migrateSettings(null).settings.version).toBe(CURRENT_SETTINGS_VERSION);
expect(migrateSettings({ version: 99 }).settings.version).toBe(CURRENT_SETTINGS_VERSION);
expect(migrateSettings([]).settings.version).toBe(CURRENT_SETTINGS_VERSION);
});
it("不与默认对象共享引用(深拷贝,改返回值不污染 DEFAULT_CATEGORIES", () => {
const { settings } = migrateSettings(null);
settings.categories[0].aliases.push("污染");
expect(DEFAULT_CATEGORIES[0].aliases).not.toContain("污染");
});
});