This commit is contained in:
2026-10-16 16:34:15 +08:00
parent 7a3769670b
commit ff366a4daf
80 changed files with 9961 additions and 888 deletions

View File

@@ -0,0 +1,56 @@
// P2-9(audit C-4)配套:createFileOps 不得构造时缓存 editor。
//
// 背景:app.js 顶层不再 new MarkdownEditor(CM6 走 dynamic import,bootstrap
// 里才实例化),所以 createFileOps 在模块 eval 时拿到的 controls.editor 是
// null。file-ops.js 若构造时 `const { editor } = controls` 解构缓存,之后
// bootstrap 给 editor 赋值也传不进来 —— deleteFile 等路径 syncDirtyFromEditor
// (state, null) 直接 TypeError。
//
// 契约:controls.editor 必须运行时现取(app.js 侧用对象字面量 getter 提供
// 活视图),createFileOps 之后赋值也生效。
// @vitest-environment jsdom
import { describe, it, expect } from 'vitest';
import { createFileOps } from '../../src/file-ops.js';
describe('createFileOps editor 惰性解析(P2-9)', () => {
it('createFileOps 之后才赋值的 editor 也能被 deleteFile 用到', async () => {
let lateEditor = null;
const calls = [];
const controls = {
fileList: {},
contextMenu: {},
viewer: {},
// app.js 实际形态:对象字面量 getter,运行时才读模块级 let editor
get editor() { return lateEditor; },
};
const state = { currentFile: null, isDirty: false, deletingPath: null };
const ops = createFileOps({
state,
controls,
api: { deleteFile: async () => ({ ok: true }) },
callbacks: {
openFile() {},
updateWindowTitle() {},
clearCurrentFile() {},
updateStatusStats() {},
syncSaveButton() {},
applyEditorMode() {},
},
});
// bootstrap 完成:editor 才实例化(模拟 app.js 的时序)
lateEditor = {
isDirty: () => { calls.push('isDirty'); return false; },
getContent: () => 'content',
};
await ops.deleteFile({ path: '/tmp/a.md', name: 'a.md' });
// deleteFile 第一行就 syncDirtyFromEditor(state, editor) —— 旧实现这里
// 拿到构造时缓存的 null,editor.isDirty() 直接 TypeError
expect(calls).toContain('isDirty');
expect(state.deletingPath).toBe(null); // finally 清守卫,流程走完了
});
});