// tests/unit/renderer/components/standalone-editor-note-moved.test.tsx // // 守 audit 2026-10-21(P0 数据丢失):「笔记被搬走 / 改名」时,编辑器里**未落盘** // 的内容必须落到**新 id** 上,而不是主进程已经删掉的旧 id。 // // 旧行为(两条路都丢字,且**永久**丢): // 1. 主进程的笔记 id = relPath 的哈希(standalone.ts repin 规则)⇒ 搬移/改名 // 后同一个文件换了 id,旧 id 在 byId 里被 delete ⇒ 成了**死键**; // 2. 编辑器判「id 变了 = 换了另一篇笔记」用的是 relPath(r192 的漂移判据) // ⇒ 搬移/改名**必然**改 relPath ⇒ 必然被判成「用户切了笔记」; // 3. 于是按**旧 id** flush ⇒ 主进程 NOT_FOUND ⇒ 内容只进 failedDrafts // (**旧 id 键**)⇒ 而笔记现在的新 id 取不到那份草稿 ⇒ 这段字永久消失。 // // 本测锁(行为级,只锁用户可见的行为): // M1. 搬移:flush 必须打到**新 id**;本地正文不得被搬移前的快照回滚。 // M2. 改名(full 为 null ⇒ 编辑器整条链卸载):flush 同样打到**新 id**。 // M3. 对照:真的切到**另一篇**笔记(没有重映射)⇒ 仍然照旧 flush 旧 id。 // M4. store 层:搬移后 notes[] 里不得留着死 id(否则右侧渲染「笔记已不可用」, // 而且编辑器被卸载 ⇒ 未落盘内容只能走卸载 flush)。 // M5. store 层:搬移后的保存不得带 expectedMtime —— 主进程 repin 重写了文件, // 磁盘 mtime 必然变,带搬移前的旧 mtime 必报**假 CONFLICT**(「文件被外部 // 修改」,其实是我们自己搬的)。 import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest'; import { render, fireEvent, screen, act, cleanup } from '@testing-library/react'; // i18n:只关心交互,不关心文案 —— key 原样返回 vi.mock('react-i18next', () => ({ useTranslation: () => ({ t: (k: string) => k }), })); // workspace store:编辑器只用到 pendingSaveActiveNote.version(⌘S 通道) // + consumeSaveActiveNoteRequest();本测不走 ⌘S,后者恒 false。 // 注意:编辑器是**当 hook 用**的(useWorkspaceStore(selector))⇒ mock 必须是 // 可调用的,不能只给 getState。 vi.mock('../../../../src/renderer/src/store/workspace', () => { const state = { pendingSaveActiveNote: { version: 0 }, consumeSaveActiveNoteRequest: () => false, }; const useWorkspaceStore = ((selector?: (s: unknown) => unknown) => selector ? selector(state) : state) as unknown as { (selector?: (s: unknown) => unknown): unknown; getState: () => unknown; }; useWorkspaceStore.getState = () => state; return { useWorkspaceStore }; }); // toast:store 的 CONFLICT 分支会弹 toast,本测不让它真的弹。 vi.mock('../../../../src/renderer/src/lib/toast', () => ({ useToast: { getState: () => ({ show: vi.fn() }) }, })); // MarkdownEditor 是 CodeMirror(jsdom 里不可测)⇒ 换成普通 textarea, // 行为等价(受控 value + onChange(string))。 vi.mock('../../../../src/renderer/src/components/editor/MarkdownEditor', () => ({ MarkdownEditor: (props: { value: string; onChange: (v: string) => void }) => (