148 lines
4.2 KiB
TypeScript
148 lines
4.2 KiB
TypeScript
import { describe, expect, it } from 'bun:test'
|
||
import {
|
||
generateMosaic,
|
||
selectKeywords,
|
||
type KeywordCandidate,
|
||
} from './mosaic'
|
||
|
||
const SOURCE =
|
||
'我一直以为记忆会忠实地保存一切,后来才发现,每一次回想都在重新编造过去。'
|
||
|
||
function retain(
|
||
candidates: KeywordCandidate[],
|
||
words: string[],
|
||
): KeywordCandidate[] {
|
||
return candidates.map((candidate) => ({
|
||
...candidate,
|
||
selected: words.includes(candidate.text),
|
||
}))
|
||
}
|
||
|
||
describe('selectKeywords', () => {
|
||
it('优先提供中文实词候选并排除常见停用词', () => {
|
||
const candidates = selectKeywords(SOURCE, 14, 1)
|
||
const words = candidates.map((candidate) => candidate.text)
|
||
|
||
expect(words).toContain('记忆')
|
||
expect(words).toContain('保存')
|
||
expect(words).toContain('回想')
|
||
expect(words).toContain('编造')
|
||
expect(words).not.toContain('一直')
|
||
expect(words).not.toContain('后来')
|
||
})
|
||
|
||
it('在有候选词时至少自动保留一个', () => {
|
||
const candidates = selectKeywords('风穿过废弃车站', 5, 1)
|
||
|
||
expect(candidates.some((candidate) => candidate.selected)).toBe(true)
|
||
})
|
||
|
||
it('显影 0% 不保留词语,100% 保留全部候选词', () => {
|
||
const hidden = selectKeywords(SOURCE, 0, 1)
|
||
const revealed = selectKeywords(SOURCE, 100, 1)
|
||
|
||
expect(hidden.some((candidate) => candidate.selected)).toBe(false)
|
||
expect(revealed.every((candidate) => candidate.selected)).toBe(true)
|
||
})
|
||
})
|
||
|
||
describe('generateMosaic', () => {
|
||
it('保留指定词语并彻底替换原标点', () => {
|
||
const candidates = retain(selectKeywords(SOURCE, 14, 1), [
|
||
'记忆',
|
||
'保存',
|
||
'回想',
|
||
'编造',
|
||
'过去',
|
||
])
|
||
const result = generateMosaic(SOURCE, candidates, {
|
||
volume: 100,
|
||
disorder: 58,
|
||
seed: 42,
|
||
})
|
||
|
||
expect(result.output).toContain('记忆')
|
||
expect(result.output).toContain('保存')
|
||
expect(result.output).toContain('回想')
|
||
expect(result.output).toContain('编造')
|
||
expect(result.output).toContain('过去')
|
||
expect(result.output).not.toMatch(/[,。!?;:“”‘’()【】、]/u)
|
||
})
|
||
|
||
it('将总可见长度维持在设定体量附近', () => {
|
||
const candidates = selectKeywords(SOURCE, 14, 1)
|
||
const result = generateMosaic(SOURCE, candidates, {
|
||
volume: 100,
|
||
disorder: 58,
|
||
seed: 42,
|
||
})
|
||
const ratio = result.outputLength / result.inputLength
|
||
|
||
expect(ratio).toBeGreaterThanOrEqual(0.97)
|
||
expect(ratio).toBeLessThanOrEqual(1.03)
|
||
})
|
||
|
||
it('支持将体量压缩到 10% 或膨胀到 1000%', () => {
|
||
const compressed = generateMosaic(SOURCE, [], {
|
||
volume: 10,
|
||
disorder: 0,
|
||
seed: 42,
|
||
})
|
||
const expanded = generateMosaic(SOURCE, [], {
|
||
volume: 1000,
|
||
disorder: 0,
|
||
seed: 42,
|
||
})
|
||
|
||
expect(compressed.outputLength).toBe(Math.round(compressed.inputLength * 0.1))
|
||
expect(expanded.outputLength).toBe(expanded.inputLength * 10)
|
||
})
|
||
|
||
it('不生成可能跳出正常行框的 Unicode 组合符', () => {
|
||
const outputs = Array.from({ length: 20 }, (_, seed) =>
|
||
generateMosaic(SOURCE, [], {
|
||
volume: 1000,
|
||
disorder: 100,
|
||
seed,
|
||
}).output,
|
||
).join('')
|
||
|
||
expect(outputs).not.toMatch(/\p{Mark}/u)
|
||
})
|
||
|
||
it('相同种子可复现,不同种子会重新侵蚀', () => {
|
||
const candidates = selectKeywords(SOURCE, 14, 1)
|
||
const first = generateMosaic(SOURCE, candidates, {
|
||
volume: 100,
|
||
disorder: 58,
|
||
seed: 42,
|
||
})
|
||
const repeated = generateMosaic(SOURCE, candidates, {
|
||
volume: 100,
|
||
disorder: 58,
|
||
seed: 42,
|
||
})
|
||
const rerolled = generateMosaic(SOURCE, candidates, {
|
||
volume: 100,
|
||
disorder: 58,
|
||
seed: 43,
|
||
})
|
||
|
||
expect(repeated.output).toBe(first.output)
|
||
expect(rerolled.output).not.toBe(first.output)
|
||
})
|
||
|
||
it('保留换行但替换每一行中的隐藏内容', () => {
|
||
const source = '记忆,在这里。\n过去,在那里。'
|
||
const candidates = retain(selectKeywords(source, 20, 1), ['记忆', '过去'])
|
||
const result = generateMosaic(source, candidates, {
|
||
volume: 100,
|
||
disorder: 30,
|
||
seed: 7,
|
||
})
|
||
|
||
expect(result.output.split('\n')).toHaveLength(2)
|
||
expect(result.output).not.toMatch(/[,。]/u)
|
||
})
|
||
})
|