155 lines
5.6 KiB
JavaScript
155 lines
5.6 KiB
JavaScript
const test = require('node:test');
|
|
const assert = require('node:assert');
|
|
const mangaEpub = require('../library/manga-epub');
|
|
const zip = require('../zip');
|
|
|
|
function img(byte, ext) {
|
|
return { ext: ext || 'jpg', data: Buffer.from([byte, byte, byte]) };
|
|
}
|
|
|
|
test('createMangaEpub: 产出的 zip 含标准 EPUB 骨架与章节旁路索引', () => {
|
|
const { bytes, chapterCount, pageCount } = mangaEpub.createMangaEpub({
|
|
title: '测试漫画',
|
|
mangaId: 'manga-1',
|
|
originalLanguage: 'ja',
|
|
chapterId: 'ch-1',
|
|
label: '第 1 话',
|
|
volume: '1',
|
|
chapter: '1',
|
|
translatedLanguage: 'zh',
|
|
group: '汉化组',
|
|
images: [img(1), img(2), img(3)]
|
|
});
|
|
assert.strictEqual(chapterCount, 1);
|
|
assert.strictEqual(pageCount, 3);
|
|
|
|
const entries = zip.readZip(bytes);
|
|
assert.ok(entries.has('mimetype'));
|
|
assert.strictEqual(entries.get('mimetype').toString('utf8'), 'application/epub+zip');
|
|
assert.ok(entries.has('META-INF/container.xml'));
|
|
assert.ok(entries.has('content.opf'));
|
|
assert.ok(entries.has('nav.xhtml'));
|
|
assert.ok(entries.has('images/ch-1/0001.jpg'));
|
|
assert.ok(entries.has('chapters/ch-1/p0001.xhtml'));
|
|
|
|
const meta = JSON.parse(entries.get('peoplelib/chapters.json').toString('utf8'));
|
|
assert.strictEqual(meta.mangaId, 'manga-1');
|
|
assert.strictEqual(meta.chapters.length, 1);
|
|
assert.strictEqual(meta.chapters[0].chapterId, 'ch-1');
|
|
assert.strictEqual(meta.chapters[0].pageCount, 3);
|
|
});
|
|
|
|
test('appendMangaChapter: 合并新章节,保留旧章节图片字节不变,按卷/话排序', () => {
|
|
const first = mangaEpub.createMangaEpub({
|
|
title: '测试漫画',
|
|
mangaId: 'manga-1',
|
|
originalLanguage: 'ja',
|
|
chapterId: 'ch-2',
|
|
label: '第 2 话',
|
|
volume: '1',
|
|
chapter: '2',
|
|
images: [img(10)]
|
|
});
|
|
const { bytes } = mangaEpub.appendMangaChapter(first.bytes, {
|
|
title: '测试漫画',
|
|
mangaId: 'manga-1',
|
|
originalLanguage: 'ja',
|
|
chapterId: 'ch-1',
|
|
label: '第 1 话',
|
|
volume: '1',
|
|
chapter: '1',
|
|
images: [img(20), img(21)]
|
|
});
|
|
|
|
const entries = zip.readZip(bytes);
|
|
assert.ok(entries.has('images/ch-1/0001.jpg'));
|
|
assert.ok(entries.has('images/ch-2/0001.jpg'));
|
|
// 旧章节图片字节原样保留,未被重新编码
|
|
assert.deepStrictEqual([...entries.get('images/ch-2/0001.jpg')], [10, 10, 10]);
|
|
|
|
const list = mangaEpub.listChapters(bytes);
|
|
assert.strictEqual(list.length, 2);
|
|
// 按 volume/chapter 排序:话 1 应排在话 2 前面,尽管是后追加的
|
|
assert.strictEqual(list[0].chapterId, 'ch-1');
|
|
assert.strictEqual(list[1].chapterId, 'ch-2');
|
|
});
|
|
|
|
test('appendMangaChapter: 追加同一章节 ID 时抛出可读错误', () => {
|
|
const first = mangaEpub.createMangaEpub({
|
|
title: '测试漫画', mangaId: 'manga-1', originalLanguage: 'ja',
|
|
chapterId: 'ch-1', label: '第 1 话', images: [img(1)]
|
|
});
|
|
assert.throws(
|
|
() => mangaEpub.appendMangaChapter(first.bytes, {
|
|
title: '测试漫画', mangaId: 'manga-1', originalLanguage: 'ja',
|
|
chapterId: 'ch-1', label: '重复章节', images: [img(2)]
|
|
}),
|
|
/该章节已存在/
|
|
);
|
|
});
|
|
|
|
test('appendMangaChapter: 追加到不同漫画的 EPUB 会被拒绝', () => {
|
|
const first = mangaEpub.createMangaEpub({
|
|
title: '测试漫画', mangaId: 'manga-1', originalLanguage: 'ja',
|
|
chapterId: 'ch-1', label: '第 1 话', images: [img(1)]
|
|
});
|
|
assert.throws(
|
|
() => mangaEpub.appendMangaChapter(first.bytes, {
|
|
title: '另一部漫画', mangaId: 'manga-2', originalLanguage: 'ja',
|
|
chapterId: 'ch-9', label: '第 9 话', images: [img(2)]
|
|
}),
|
|
/不是同一部漫画/
|
|
);
|
|
});
|
|
|
|
test('appendMangaChapter: 目标不是漫画 EPUB(缺少旁路索引)时给出可操作提示', () => {
|
|
const plain = zip.writeZip([{ name: 'mimetype', data: Buffer.from('application/epub+zip') }]);
|
|
assert.throws(
|
|
() => mangaEpub.appendMangaChapter(plain, {
|
|
title: '测试漫画', mangaId: 'manga-1', originalLanguage: 'ja',
|
|
chapterId: 'ch-1', label: '第 1 话', images: [img(1)]
|
|
}),
|
|
/请改为新建条目/
|
|
);
|
|
});
|
|
|
|
test('hasChapter: 命中已收录章节,未收录或非法数据都返回 false', () => {
|
|
const { bytes } = mangaEpub.createMangaEpub({
|
|
title: '测试漫画', mangaId: 'manga-1', originalLanguage: 'ja',
|
|
chapterId: 'ch-1', label: '第 1 话', images: [img(1)]
|
|
});
|
|
assert.strictEqual(mangaEpub.hasChapter(bytes, 'ch-1'), true);
|
|
assert.strictEqual(mangaEpub.hasChapter(bytes, 'ch-missing'), false);
|
|
assert.strictEqual(mangaEpub.hasChapter(Buffer.from('not a zip'), 'ch-1'), false);
|
|
});
|
|
|
|
test('compareChapters: 缺省卷号/话号排到最后', () => {
|
|
const withNum = { volume: '1', chapter: '1' };
|
|
const withoutNum = { volume: '', chapter: '' };
|
|
assert.ok(mangaEpub.compareChapters(withNum, withoutNum) < 0);
|
|
assert.ok(mangaEpub.compareChapters(withoutNum, withNum) > 0);
|
|
});
|
|
|
|
test('漫画 EPUB 用 sourceId 隔离不同站点的同名 ID', () => {
|
|
const created = mangaEpub.createMangaEpub({
|
|
title: '中文漫画',
|
|
sourceId: 'copymanga',
|
|
mangaId: 'same-id',
|
|
originalLanguage: 'zh',
|
|
chapterId: 'chapter-1',
|
|
label: '第 1 话',
|
|
images: [img(1)]
|
|
});
|
|
const parsed = mangaEpub.parseMangaEpub(created.bytes);
|
|
assert.strictEqual(parsed.sourceId, 'copymanga');
|
|
assert.strictEqual(mangaEpub.isMangaEpub(created.bytes, 'same-id', 'copymanga'), true);
|
|
assert.strictEqual(mangaEpub.isMangaEpub(created.bytes, 'same-id', 'mangadex'), false);
|
|
assert.throws(() => mangaEpub.appendMangaChapter(parsed, {
|
|
title: '另一来源',
|
|
sourceId: 'mangadex',
|
|
mangaId: 'same-id',
|
|
chapterId: 'chapter-2',
|
|
label: '第 2 话',
|
|
images: [img(2)]
|
|
}), /不是同一部漫画/);
|
|
}); |