RemoteFile (used on all desktop platforms) extended File([]) with empty data and overrode slice(), text(), arrayBuffer() but not stream(). The large file path (>8MB) introduced in #3320 uses file.stream() to read content incrementally, which returned empty data from the base File class, causing "No chapters detected" for large TXT files. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -260,6 +260,60 @@ describe('TxtToEpubConverter', () => {
|
||||
expect(result.chapterCount).toBe(2);
|
||||
});
|
||||
|
||||
it('convert large file should work when stream() is built from slice() like RemoteFile', async () => {
|
||||
const converter = new TxtToEpubConverter() as unknown as TxtConverterFlowPrivateAPI;
|
||||
const content = '第一章 开始\n这是第一章的内容。\n\n第二章 继续\n这是第二章的内容。';
|
||||
const backingBlob = new Blob([content]);
|
||||
|
||||
// Simulate a fixed RemoteFile: stream() reads data via slice(), not from base File([])
|
||||
const fixedFile = new File([], 'large.txt');
|
||||
const fileSize = 9 * 1024 * 1024;
|
||||
Object.defineProperty(fixedFile, 'size', { value: fileSize });
|
||||
Object.defineProperty(fixedFile, 'slice', {
|
||||
value: (start?: number, end?: number) => backingBlob.slice(start, end),
|
||||
});
|
||||
Object.defineProperty(fixedFile, 'stream', {
|
||||
value: () => {
|
||||
const CHUNK_SIZE = 1024 * 1024;
|
||||
let offset = 0;
|
||||
return new ReadableStream<Uint8Array>({
|
||||
pull: async (controller) => {
|
||||
if (offset >= fileSize) {
|
||||
controller.close();
|
||||
return;
|
||||
}
|
||||
const end = Math.min(offset + CHUNK_SIZE, fileSize);
|
||||
const buf = await backingBlob.slice(offset, end).arrayBuffer();
|
||||
controller.enqueue(new Uint8Array(buf));
|
||||
offset = end;
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
converter.createEpub = async () => new Blob();
|
||||
|
||||
const result = await converter.convert({ file: fixedFile });
|
||||
expect(result.chapterCount).toBeGreaterThanOrEqual(1);
|
||||
});
|
||||
|
||||
it('convert large file should fail when stream() returns empty data (unfixed RemoteFile)', async () => {
|
||||
const converter = new TxtToEpubConverter() as unknown as TxtConverterFlowPrivateAPI;
|
||||
|
||||
// Simulate the bug: RemoteFile with unoverridden stream() returns empty data
|
||||
const brokenFile = new File([], 'large.txt');
|
||||
Object.defineProperty(brokenFile, 'size', { value: 9 * 1024 * 1024 });
|
||||
Object.defineProperty(brokenFile, 'slice', {
|
||||
value: (start?: number, end?: number) =>
|
||||
new Blob(['第一章 开始\n内容\n\n第二章 继续\n内容']).slice(start, end),
|
||||
});
|
||||
// stream() is NOT overridden — inherits base File's empty stream
|
||||
|
||||
converter.createEpub = async () => new Blob();
|
||||
|
||||
await expect(converter.convert({ file: brokenFile })).rejects.toThrow('No chapters detected');
|
||||
});
|
||||
|
||||
it('iterateSegmentsFromFile should cancel stream on early return', async () => {
|
||||
const converter = new TxtToEpubConverter() as unknown as TxtConverterFlowPrivateAPI & {
|
||||
iterateSegmentsFromFile(
|
||||
|
||||
@@ -453,6 +453,26 @@ export class RemoteFile extends File implements ClosableFile {
|
||||
return new DeferredBlob(dataPromise, contentType);
|
||||
}
|
||||
|
||||
override stream(): ReadableStream<Uint8Array<ArrayBuffer>> {
|
||||
const CHUNK_SIZE = 1024 * 1024;
|
||||
let offset = 0;
|
||||
|
||||
return new ReadableStream<Uint8Array<ArrayBuffer>>({
|
||||
pull: async (controller) => {
|
||||
if (offset >= this.size) {
|
||||
controller.close();
|
||||
return;
|
||||
}
|
||||
|
||||
const end = Math.min(offset + CHUNK_SIZE, this.size);
|
||||
const buffer = await this.fetchRange(offset, end - 1);
|
||||
|
||||
controller.enqueue(new Uint8Array(buffer));
|
||||
offset = end;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
override async text() {
|
||||
const blob = this.slice(0, this.size);
|
||||
return blob.text();
|
||||
|
||||
Reference in New Issue
Block a user