import assert from 'node:assert/strict' import { readFile } from 'node:fs/promises' import ts from 'typescript' const sourceUrl = new URL( '../src/views/Editor/EnglishSpeaking/composables/articleAutoFormatRules.ts', import.meta.url, ) const compiled = ts.transpileModule(await readFile(sourceUrl, 'utf8'), { compilerOptions: { module: ts.ModuleKind.ESNext, target: ts.ScriptTarget.ES2022 }, }).outputText const { countWords, normalizeParagraphs, hasOverlongParagraph, shouldPromptOnPaste, MAX_PARAGRAPH_WORDS, PASTE_PROMPT_MIN_WORDS, } = await import(`data:text/javascript,${encodeURIComponent(compiled)}`) /** 生成 n 个单词的文本 */ const words = n => Array.from({ length: n }, (_, i) => `w${i}`).join(' ') // ---------------- countWords ---------------- assert.equal(countWords(''), 0) assert.equal(countWords(' \n\n '), 0) assert.equal(countWords('one two three'), 3) assert.equal(countWords(' leading and trailing '), 3) assert.equal(countWords(words(300)), 300) // ---------------- normalizeParagraphs ---------------- // 与后端 split_paragraphs 同规则:单 \n 与空行皆为段界 assert.equal(normalizeParagraphs('A one.\n\nB two.'), 'A one.\n\nB two.') assert.equal(normalizeParagraphs('A one.\nB two.'), 'A one.\n\nB two.') assert.equal(normalizeParagraphs('A one.\n\n\n\n \nB two.'), 'A one.\n\nB two.') assert.equal(normalizeParagraphs(' A one here. '), 'A one here.') assert.equal(normalizeParagraphs(''), '') assert.equal(normalizeParagraphs(' \n\n '), '') // 幂等:归一化结果再归一化不变(会回写 textarea,必须稳定) assert.equal(normalizeParagraphs(normalizeParagraphs('A.\nB.\n\n\nC.')), 'A.\n\nB.\n\nC.') // ---------------- hasOverlongParagraph(299 / 300 / 301 边界)---------------- assert.equal(MAX_PARAGRAPH_WORDS, 300) assert.equal(hasOverlongParagraph(''), false) assert.equal(hasOverlongParagraph(words(299)), false) assert.equal(hasOverlongParagraph(words(300)), false) assert.equal(hasOverlongParagraph(words(301)), true) // 多段:只要有一段超标就 true assert.equal(hasOverlongParagraph(`${words(10)}\n\n${words(301)}`), true) assert.equal(hasOverlongParagraph(`${words(200)}\n\n${words(200)}`), false) // 单 \n 也是段界,故 200+200 不算一段 400 assert.equal(hasOverlongParagraph(`${words(200)}\n${words(200)}`), false) // ---------------- shouldPromptOnPaste ---------------- assert.equal(PASTE_PROMPT_MIN_WORDS, 50) // spec §3.2 step 1 只有两条:原内容 trim 后为空,且新内容 > 50 词。 assert.equal(shouldPromptOnPaste('', words(60)), true) // 空框粘贴长文 → 弹 assert.equal(shouldPromptOnPaste(' ', words(51)), true) // 纯空白算空框;51 词过阈值 assert.equal(shouldPromptOnPaste('\n\n \n', words(60)), true) // 只剩换行的框同样算空 assert.equal(shouldPromptOnPaste('', words(50)), false) // 恰好 50 词不弹(严格 >) assert.equal(shouldPromptOnPaste('', words(40)), false) // 短文不弹 assert.equal(shouldPromptOnPaste(words(300), words(400)), false) // 原文非空 → 不打断编辑 assert.equal(shouldPromptOnPaste('x', words(400)), false) // 哪怕只有一个字符也不算空框 // 粘贴内容自带空行照样提示:带 \n\n ≠ 分得适合朗读(三段各 400 词就是反例) assert.equal(shouldPromptOnPaste('', `${words(30)}\n\n${words(30)}`), true) assert.equal(shouldPromptOnPaste('', `${words(400)}\n\n${words(400)}`), true) // enspeak 的 sentenceCount>=2 条件已弃用:无标点长文照样提示 assert.equal(shouldPromptOnPaste('', words(60).replace(/\./g, '')), true) console.log('article auto-format rules OK')