// tests/unit/renderer/notes/editor-preview-toggle.test.tsx // // 守 audit-r133「原始文本 ↔ 渲染显示」双模式切换的**用户可见**行为契约 // (src/renderer/src/components/notes/StandaloneNoteEditorCore.tsx 头注 // 指向本文件,原先标 🔒(守卫缺失))。 // // 背景:2026-09-06 的 Typora 原地渲染(装饰层自动隐藏非活动行标记)被用户报障 // 「显示存在很大问题」(符号忽隐忽现 / 内容跳动 / 表格任务列表装饰缺失)⇒ // 装饰层整体删除,改成「默认原始文本 + 头栏按钮切到只读渲染视图」。 // // 本测锁的契约(全部从用户可见结果出发): // 1. 默认是**原始文本**编辑器,不是渲染视图; // 2. 头栏 toggle 按一次 → 只读渲染视图(markdown-it 真渲染);再按 → 回原始文本; // 3. **切换往返不丢内容**:body 由 Core 持有,不经过编辑器实例 ⇒ 往返后 // 正文一字不差,「未保存」状态也还在; // 4. 预览显示的是 Core 持有的**最新** body,不是 note.body 快照; // 5. 新建 / 拖入笔记(initialFocus='body')时,即便上一轮停在渲染视图, // 也要切回原始文本 —— 否则用户没有地方打字。 // // 退化后的用户可见后果: // - 3 退化 ⇒ 用户切一次视图,刚写的一段字消失(丢字级别); // - 4 退化 ⇒ 预览显示旧正文,用户以为改动没生效; // - 5 退化 ⇒ 新建笔记后光标无处落,只能再手动点一次 toggle。 // // 变异思路(哪几处改动会红): // - previewOn 初值改 true / toggle 语义反转 ⇒ 例 1、2 红; // - 改成 source={note?.body} ⇒ 例 4 红; // - toggle 时 setBody('') 或每次重挂编辑器实例 ⇒ 例 3 红; // - 删掉 initialFocus effect 里的 setPreviewOn(false) ⇒ 例 5 红。 import { describe, expect, it, vi, afterEach } from 'vitest'; import { render, fireEvent, screen, cleanup, waitFor } from '@testing-library/react'; vi.mock('react-i18next', () => ({ useTranslation: () => ({ t: (k: string) => k }), })); // workspace store:编辑器只用到 pendingSaveActiveNote.version(⌘S 通道) // + consumeSaveActiveNoteRequest();本测不走 ⌘S,后者恒 false。 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 }; }); // notes store:编辑器只在两处读它 —— // 1) 切笔记 flush 前查「旧笔记还在不在列表里」; // 2) ⌘S 通道读 selectedId(本测不走)。 vi.mock('../../../../src/renderer/src/store/notes', () => { const state = { notes: [{ id: 'n1', relPath: 'a.md' }], selectedId: 'n1', }; return { useNotesStore: { getState: () => state, }, }; }); // MarkdownEditor 是 CodeMirror(jsdom 里不可测)⇒ 换成普通 textarea, // 行为等价(受控 value + onChange(string))。 vi.mock('../../../../src/renderer/src/components/editor/MarkdownEditor', () => ({ MarkdownEditor: (props: { value: string; onChange: (v: string) => void }) => (