新增 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>
101 lines
3.8 KiB
JavaScript
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);
|
|
});
|