Files
peoplelib/src/_test/reader-window.test.js
T
lofyerandfactory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> 3ccd044527 feat: 内置阅读器、批注笔记与 AI 助手,发布 1.3.0
新增 PDF/EPUB/MOBI/AZW 内置阅读器,PDF 分段读取支持超大文件,
批注、读书与画布笔记、封面生成与本地导入。AI 助手支持三种协议、
图像上下文与安全 Markdown 渲染,上下文范围改为 选中/当前页/全文,
页面与全文无需选中文本即可发送,全文会提示可能超出模型限制。

便携版输出目录固定为 PeopleLib-windows-x64,不再随版本号变化,
避免升级后 data/ 被遗留在旧目录。

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
2026-08-03 12:13:02 +08:00

101 lines
3.8 KiB
JavaScript

const test = require('node:test');
const assert = require('node:assert');
const EventEmitter = require('node:events');
const Module = require('node:module');
test('阅读器全局只创建一个窗口,新书与删除请求路由到标签事件', () => {
const instances = [];
let webContentsId = 0;
class FakeWindow extends EventEmitter {
constructor(options) {
super();
this.options = options;
this.destroyed = false;
this.focused = 0;
this.webContents = new EventEmitter();
this.webContents.id = ++webContentsId;
this.webContents.loading = true;
this.webContents.sent = [];
this.webContents.setWindowOpenHandler = (handler) => { this.webContents.windowOpenHandler = handler; };
this.webContents.isLoadingMainFrame = () => this.webContents.loading;
this.webContents.send = (channel, payload) => this.webContents.sent.push([channel, payload]);
instances.push(this);
}
loadFile(file, options) {
this.loaded = { file, options };
}
isDestroyed() { return this.destroyed; }
isMinimized() { return false; }
focus() { this.focused += 1; }
close() {
const event = { prevented: false, preventDefault() { this.prevented = true; } };
this.emit('close', event);
if (!event.prevented) {
this.destroyed = true;
this.emit('closed');
}
}
destroy() {
this.destroyed = true;
this.emit('closed');
}
}
const originalLoad = Module._load;
Module._load = function mock(request, parent, isMain) {
if (request === 'electron') return { BrowserWindow: FakeWindow };
return originalLoad.call(this, request, parent, isMain);
};
const modulePath = require.resolve('../reader/window.js');
delete require.cache[modulePath];
let windows;
try {
windows = require(modulePath);
} finally {
Module._load = originalLoad;
}
const firstLocator = { kind: 'pdf', page: 4 };
const secondLocator = { kind: 'epub', chapter: 2, offset: 180 };
const first = windows.open('book-a', 'C:\\app', 0, firstLocator);
const second = windows.open('book-b', 'C:\\app', 1, secondLocator);
assert.strictEqual(first, second);
assert.strictEqual(instances.length, 1);
assert.deepStrictEqual(first.loaded.options.query, {
entryId: 'book-a',
fileIndex: '0',
locator: JSON.stringify(firstLocator)
});
assert.deepStrictEqual(first.webContents.sent, [], '加载完成前不应丢失事件或过早发送');
assert.deepStrictEqual(first.webContents.windowOpenHandler(), { action: 'deny' });
const navigation = { prevented: false, preventDefault() { this.prevented = true; } };
first.webContents.emit('will-navigate', navigation, 'https://untrusted.example/');
assert.strictEqual(navigation.prevented, true);
first.webContents.loading = false;
assert.strictEqual(windows.markReady(first.webContents), true);
assert.strictEqual(windows.isReady(first), true);
assert.deepStrictEqual(first.webContents.sent[0], [
'reader:openEntry',
{ entryId: 'book-b', fileIndex: 1, locator: secondLocator }
]);
windows.closeFor('book-a');
assert.deepStrictEqual(first.webContents.sent[1], ['reader:closeEntry', 'book-a']);
windows.purgeFor('book-b', 'purge-1');
assert.deepStrictEqual(first.webContents.sent[2], [
'reader:purgeEntry',
{ entryId: 'book-b', requestId: 'purge-1' }
]);
assert.strictEqual(first.destroyed, false, '删除一个条目不应关闭整个阅读器窗口');
assert.strictEqual(windows.fromWebContents(first.webContents), 'reader');
assert.deepStrictEqual(windows.all(), [first]);
first.close();
assert.strictEqual(first.destroyed, false, '关闭前应等待渲染器排空保存队列');
assert.deepStrictEqual(first.webContents.sent[3], ['reader:prepareClose', null]);
assert.strictEqual(windows.shutdownReady(first.webContents), true);
assert.strictEqual(first.destroyed, true);
});