Files
readest/apps/readest-app/src/__tests__/document/document-loader-epub-case.test.ts
T
loveheaven 9f0aa2f55d fix(tests): materialize zip.js blob before wrapping in File for case-mismatch fixture (#4203)
The case-mismatch EPUB fixture builds an archive with @zip.js/zip.js' BlobWriter and then wraps the resulting Blob into a File:

  const blob = await writer.close();
  new File([blob], 'case-mismatch.epub', ...);

Under vitest's happy-dom/jsdom, the File/Blob polyfill does not correctly pull bytes out of a nested Blob part produced by zip.js. The outer File reports a non-zero size, but the bytes BlobReader sees in DocumentLoader (libs/document.ts: 'new BlobReader(this.file)') are not a valid ZIP — getEntries() yields nothing, open() falls through with book = null, and the test crashes at:

  TypeError: Cannot read properties of null (reading 'sections')

Materialize the zip bytes into a plain ArrayBuffer first, then construct the File from that. ArrayBuffer parts go through the polyfill cleanly because they don't require recursive Blob unwrapping, so zip.js reads a real archive and the test passes:

  const arrayBuffer = await blob.arrayBuffer();
  new File([arrayBuffer], 'case-mismatch.epub', ...);

This brings the fixture in line with the rest of the test suite (paginator-expand, page-progress-epub, toc-cfi-mapping, ...) which already use ArrayBuffer-based File construction. No production code is affected: real browsers handle nested-Blob File construction correctly.
2026-05-17 16:31:19 +02:00

66 lines
2.2 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { DocumentLoader } from '@/libs/document';
const createCaseMismatchEpub = async () => {
const { ZipWriter, BlobWriter, TextReader } = await import('@zip.js/zip.js');
const writer = new ZipWriter(new BlobWriter('application/epub+zip'));
await writer.add('mimetype', new TextReader('application/epub+zip'), { level: 0 });
await writer.add(
'META-INF/container.xml',
new TextReader(`<?xml version="1.0" encoding="UTF-8"?>
<container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container">
<rootfiles>
<rootfile full-path="OPS/content.opf" media-type="application/oebps-package+xml"/>
</rootfiles>
</container>`),
);
await writer.add(
'OPS/content.opf',
new TextReader(`<?xml version="1.0" encoding="UTF-8"?>
<package version="3.0" unique-identifier="bookid" xmlns="http://www.idpf.org/2007/opf">
<metadata xmlns:dc="http://purl.org/dc/elements/1.1/">
<dc:identifier id="bookid">case-mismatch</dc:identifier>
<dc:title>Case mismatch</dc:title>
<dc:language>en</dc:language>
</metadata>
<manifest>
<item id="chapter1" href="Text/Chapter1.xhtml" media-type="application/xhtml+xml"/>
</manifest>
<spine>
<itemref idref="chapter1"/>
</spine>
</package>`),
);
await writer.add(
'OPS/text/chapter1.xhtml',
new TextReader(`<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Case mismatch</title>
</head>
<body>
<p>Hello from the lowercase chapter entry.</p>
</body>
</html>`),
);
const blob = await writer.close();
const arrayBuffer = await blob.arrayBuffer();
return new File([arrayBuffer], 'case-mismatch.epub', { type: 'application/epub+zip' });
};
describe('DocumentLoader EPUB zip lookup', () => {
it('loads EPUB resources when manifest href casing differs from the zip entry', async () => {
const file = await createCaseMismatchEpub();
const loader = new DocumentLoader(file);
const { book, format } = await loader.open();
expect(format).toBe('EPUB');
const doc = await book.sections[0]!.createDocument();
expect(doc.body.textContent).toContain('Hello from the lowercase chapter entry.');
});
});