Export template source from 11bc3ae35b0034c113daca4c35d1b341aa31ef70:examples/local-first/packages/gutenberg-runtime
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { test, after } from "node:test";
|
||||
import { stripTypeScriptTypes } from "node:module";
|
||||
import { readFile, mkdtemp, writeFile, rm } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
const directory = await mkdtemp(join(tmpdir(), "gutenberg-test-"));
|
||||
for (const name of ["catalog", "download"]) {
|
||||
const source = await readFile(new URL("../src/" + name + ".ts", import.meta.url), "utf8");
|
||||
await writeFile(
|
||||
join(directory, name + ".mjs"),
|
||||
stripTypeScriptTypes(source).replace("./catalog.js", "./catalog.mjs"),
|
||||
);
|
||||
}
|
||||
after(() => rm(directory, { recursive: true, force: true }));
|
||||
const { parseIndex, searchIndex, openingExcerpt } = await import(pathToFileURL(join(directory, "catalog.mjs")));
|
||||
const { createBookLoader, bookURL, fetchBook } = await import(pathToFileURL(join(directory, "download.mjs")));
|
||||
test("CSV preserves quoted/multiline titles and selects text editions", () => {
|
||||
const books = parseIndex(
|
||||
'Text#,Type,Title,Language,Authors\r\n1342,Text,"Pride, and\r\nPrejudice",en,"Austen, Jane"\r\n11,Text,"Alice and ""friends""",en,Carroll\r\n12,Sound,Audio,en,Someone\r\n',
|
||||
);
|
||||
assert.deepEqual(books, [
|
||||
{ ebook: 1342, title: "Pride, and Prejudice", language: "en", author: "Austen, Jane" },
|
||||
{ ebook: 11, title: 'Alice and "friends"', language: "en", author: "Carroll" },
|
||||
]);
|
||||
assert.throws(() => parseIndex('Text#,Type,Title,Language,Authors\n1,Text,"broken'), /Unclosed/);
|
||||
});
|
||||
test("fuzzy titles and diacritics work, with a bounded result list", () => {
|
||||
const books = [
|
||||
{ ebook: 1342, title: "Pride and Prejudice", author: "Austen, Jane", language: "en" },
|
||||
{ ebook: 11, title: "Alice in Wonderland", author: "Carroll", language: "en" },
|
||||
...Array.from({ length: 40 }, (_, i) => ({
|
||||
ebook: 100 + i,
|
||||
title: "Émile " + i,
|
||||
author: "Rousseau",
|
||||
language: "fr",
|
||||
})),
|
||||
];
|
||||
const search = searchIndex(books);
|
||||
assert.equal(search("prde and prejudi").at(0).ebook, 1342);
|
||||
assert.equal(search("austen").at(0).ebook, 1342);
|
||||
assert.equal(search("emile").length, 20);
|
||||
assert.deepEqual(search("a"), []);
|
||||
});
|
||||
test("opening excerpt keeps paragraph boundaries and excludes Gutenberg framing", () => {
|
||||
const body =
|
||||
"license\n*** START OF THE PROJECT GUTENBERG EBOOK TEST ***\n\nFirst\nline\n\nSecond\n\n*** END OF THE PROJECT GUTENBERG EBOOK TEST ***\nlicense";
|
||||
assert.deepEqual(openingExcerpt(body), { text: "First\nline\n\nSecond", words: 3 });
|
||||
const first = "word ".repeat(600).trim(),
|
||||
second = "more ".repeat(450).trim();
|
||||
const text = "*** START OF THE PROJECT GUTENBERG EBOOK TEST ***\n\n" + first + "\n\n" + second;
|
||||
assert.equal(openingExcerpt(text), null, "Wait for the whole final paragraph");
|
||||
assert.deepEqual(openingExcerpt(text + "\n\nignored"), { text: first + "\n\n" + second, words: 1050 });
|
||||
assert.deepEqual(openingExcerpt("short book", true), { text: "short book", words: 2 });
|
||||
const long = "😀".repeat(30000);
|
||||
const bounded = openingExcerpt(long, true);
|
||||
assert(bounded.text.length <= 56000);
|
||||
assert(!/[\uD800-\uDBFF]$/.test(bounded.text));
|
||||
});
|
||||
test("streaming fetch stops at the excerpt and releases the remaining download", async () => {
|
||||
const original = globalThis.fetch;
|
||||
let cancelled = false,
|
||||
reads = 0;
|
||||
const chunks = [
|
||||
"licence\n*** START OF THE PROJECT GUTEN",
|
||||
"BERG EBOOK TEST ***\n\n",
|
||||
"word ".repeat(600) + "\n\n",
|
||||
"more ".repeat(450) + "\n\n",
|
||||
"unused rest of the book",
|
||||
];
|
||||
globalThis.fetch = async () =>
|
||||
new Response(
|
||||
new ReadableStream(
|
||||
{
|
||||
pull(controller) {
|
||||
reads++;
|
||||
const chunk = chunks.shift();
|
||||
if (chunk) controller.enqueue(new TextEncoder().encode(chunk));
|
||||
else controller.close();
|
||||
},
|
||||
cancel() {
|
||||
cancelled = true;
|
||||
},
|
||||
},
|
||||
{ highWaterMark: 0 },
|
||||
),
|
||||
);
|
||||
try {
|
||||
const text = await fetchBook(1342);
|
||||
assert.equal(reads, 4);
|
||||
assert.equal(cancelled, true);
|
||||
assert.equal(openingExcerpt(text, true).words, 1050);
|
||||
assert(!text.includes("unused"));
|
||||
} finally {
|
||||
globalThis.fetch = original;
|
||||
}
|
||||
});
|
||||
test("downloads coalesce, cache, serialize, and permit retries after failure", async () => {
|
||||
let calls = 0,
|
||||
active = 0,
|
||||
maximum = 0,
|
||||
fail = true;
|
||||
const load = createBookLoader(async (ebook) => {
|
||||
calls++;
|
||||
active++;
|
||||
maximum = Math.max(maximum, active);
|
||||
await new Promise((r) => setTimeout(r, 2));
|
||||
active--;
|
||||
if (ebook === 3 && fail) {
|
||||
fail = false;
|
||||
throw Error("offline");
|
||||
}
|
||||
return "Book " + ebook;
|
||||
}, 1);
|
||||
assert.deepEqual(await Promise.all([load(1), load(1), load(2)]), [
|
||||
{ text: "Book 1", words: 2 },
|
||||
{ text: "Book 1", words: 2 },
|
||||
{ text: "Book 2", words: 2 },
|
||||
]);
|
||||
assert.equal(calls, 2);
|
||||
assert.equal(maximum, 1);
|
||||
await load(1);
|
||||
assert.equal(calls, 2);
|
||||
await assert.rejects(load(3), /offline/);
|
||||
assert.deepEqual(await load(3), { text: "Book 3", words: 2 });
|
||||
assert.equal(bookURL(1342), "https://gutenberg.pglaf.org/cache/epub/1342/pg1342.txt");
|
||||
});
|
||||
Reference in New Issue
Block a user