Download tests/test_reader.cjs from vomebook/Hitokoto: direct link, hf CLI and curl.
- Browser
- Download file 4.49 kB
-
https://huggingface.co/spaces/vomebook/Hitokoto/resolve/main/tests/test_reader.cjs
- Command line
-
hf download hf://spaces/vomebook/Hitokoto/tests/test_reader.cjs
-
curl -L -o test_reader.cjs https://huggingface.co/spaces/vomebook/Hitokoto/resolve/main/tests/test_reader.cjs
4.49 kB
| ; | |
| const assert = require("node:assert/strict"); | |
| require("../static/reader.js"); | |
| const { split, clipPlan, locations, readBookmark, bookmarkKey } = globalThis.ReaderPlan; | |
| for (const text of ["这是一篇长文。".repeat(200), "甲".repeat(598) + "2026年3.1415版本CPUModel。".repeat(60), | |
| "𠮷长文与😀测试。".repeat(200), "甲".repeat(20000)]) { | |
| const plan = split(text); | |
| assert.equal(plan.map((part) => part.text).join(""), text); | |
| let offset = 0; | |
| for (const part of plan) { | |
| assert.equal(part.start, offset); | |
| assert.equal(text.slice(part.start, part.end), part.text); | |
| assert.ok(Array.from(part.text).length <= 600); | |
| assert.ok(Array.from(part.context_before).length <= 32); | |
| assert.ok(Array.from(part.context_after).length <= 32); | |
| assert.ok(text.slice(0, part.start).endsWith(part.context_before)); | |
| assert.ok(text.slice(part.end).startsWith(part.context_after)); | |
| offset = part.end; | |
| } | |
| assert.equal(offset, text.length); | |
| } | |
| assert.equal(split("甲".repeat(598) + "2026年。", 600, 600)[0].text, "甲".repeat(598)); | |
| assert.throws(() => split("a".repeat(601))); | |
| assert.equal(split("a".repeat(200))[0].text, "a".repeat(200)); | |
| assert.throws(() => split("甲".repeat(20001))); | |
| assert.throws(() => split(" ")); | |
| const good = { version: 1, text: "长文😀", voice: "zf_001", speed: 1, offset: 2, seconds: 1.3 }; | |
| const storage = { getItem: (key) => key === bookmarkKey ? JSON.stringify(good) : null }; | |
| assert.deepEqual(readBookmark(storage), good); | |
| for (const changes of [{ seconds: -1 }, { offset: 100 }, { speed: 9 }, { voice: "unknown" }, { playbackRate: 8 }, | |
| { pitchSemitones: 4 }, { pitchSemitones: 1.5 }, { pitchSemitones: "2" }, { pitchSemitones: true }, { text: "甲".repeat(20001) }]) { | |
| assert.equal(readBookmark({ getItem: () => JSON.stringify({ ...good, ...changes }) }), null); | |
| } | |
| assert.equal(readBookmark({ getItem: () => "malformed" }), null); | |
| assert.equal(readBookmark({ getItem: () => JSON.stringify({ ...good, pitchSemitones: -3 }) }).pitchSemitones, -3); | |
| const sources = ["第一章\n\n正文第一句。正文第二句。\n1、第一项\n2、第二项\n", "甲\n".repeat(400), | |
| "甲".repeat(58) + "2026年3.1415版本CPUModel。", "𠀀😀正文。".repeat(50), "正文。" + "\n".repeat(100), | |
| "\n".repeat(100) + "正文。", "甲".repeat(800), "校长文章写得很好。".repeat(40), | |
| "前言。\n1.马克思主义的基本原理是什么。\n2.为什么要学习。\n(3).第三条。", "目录:1.总论,2.分论。" + "甲".repeat(70)]; | |
| for (const token of ["2026-10-18", "版本10.02.3", "编号00123", "电话138-0013-8000", "2026年10月18日", "1,234.50元", "3/4"]) { | |
| sources.push("甲".repeat(58) + token + "。后面的长文。"); | |
| const text = "甲".repeat(178) + token + "。"; | |
| const plan = split(text); | |
| for (const atom of ReaderPlan.numberSpans(text)) assert.ok(plan.some((part) => part.start <= atom.start && part.end >= atom.end)); | |
| } | |
| const { spawnSync } = require("node:child_process"); | |
| const python = spawnSync("python3", ["-B", "-c", "import json,sys;from tts.text import playback_segments,utf16_offset;print(json.dumps([[{'start':utf16_offset(t,a),'end':utf16_offset(t,b)} for a,b,_ in playback_segments(t)] for t in json.load(sys.stdin)]))"], | |
| { cwd: require("node:path").resolve(__dirname, ".."), input: JSON.stringify(sources), encoding: "utf8" }); | |
| assert.equal(python.status, 0, python.stderr); | |
| assert.deepEqual(sources.map(clipPlan), JSON.parse(python.stdout)); | |
| for (const text of sources) { | |
| const list = locations(split(text)); | |
| assert.equal(list.map((part) => text.slice(part.start, part.end)).join(""), text); | |
| assert.ok(list.every((part, index) => part.start === (index ? list[index - 1].end : 0))); | |
| } | |
| console.log("Reader: lossless Unicode batches, source navigation/Python parity, context and opt-in bookmarks PASS"); | |
| let now = 0; | |
| const sleep = new ReaderPlan.SleepTimer(() => now); | |
| sleep.arm("5"); | |
| assert.equal(sleep.remaining(), 300); | |
| now = 299999; assert.equal(sleep.due(), false); | |
| now = 300000; assert.equal(sleep.due(), true); | |
| sleep.arm("clip", "0:1"); | |
| assert.equal(sleep.ended("0:0"), false); | |
| assert.equal(sleep.ended("0:1"), true); | |
| sleep.arm("clip"); sleep.bind("1:2"); sleep.bind("1:3"); | |
| assert.equal(sleep.ended("1:2"), true); | |
| sleep.clear(); assert.equal(sleep.due(), false); assert.equal(sleep.ended("1:2"), false); | |
| assert.throws(() => sleep.arm("0.01")); | |
| console.log("Sleep timer: wall-clock deadlines, current-clip binding and clearing PASS"); | |