// useListContinuation 纯逻辑层单测 —— 仓库无 vitest,走 node 内置测试跑(零第三方依赖): // node --test tests/useListContinuation.test.mjs // Node ≥22.6 原生剥离类型,可直接 import .ts(mjs 侧显式带 .ts 后缀)。 // 覆盖:识别(有序/无序/缩进/非列表)/ 围栏守卫 / 续写补号 / 空项退出 / 末位退格退出 / 边界。 import test from 'node:test' import assert from 'node:assert/strict' import { analyzeListContext, nextMarker, continueList, exitListOnBackspace, } from '../src/composables/ai/useListContinuation.ts' test('analyzeListContext: 有序标记识别(含缩进/字段)', () => { const ctx = analyzeListContext(' 12. hello', 10) assert.equal(ctx.kind, 'ordered') assert.equal(ctx.indent, ' ') assert.equal(ctx.marker, '12. ') assert.equal(ctx.number, 12) assert.equal(ctx.content, 'hello') assert.equal(ctx.lineStart, 0) assert.equal(ctx.markerEnd, 6) }) test('analyzeListContext: 有序 `)` 标记', () => { const ctx = analyzeListContext('3) x', 4) assert.equal(ctx.kind, 'ordered') assert.equal(ctx.marker, '3) ') assert.equal(ctx.number, 3) }) test('analyzeListContext: 无序三种符号', () => { for (const b of ['-', '*', '+']) { const ctx = analyzeListContext(`${b} item`, 3) assert.equal(ctx.kind, 'unordered') assert.equal(ctx.bullet, b) assert.equal(ctx.content, 'item') } }) test('analyzeListContext: 非列表行返回 null', () => { assert.equal(analyzeListContext('1.no-space', 2), null) // 标记后无空白 assert.equal(analyzeListContext('*bold*', 3), null) // 强调非列表 assert.equal(analyzeListContext('-', 1), null) // 裸符号 assert.equal(analyzeListContext('plain text', 5), null) const deep = ' 1. deep' // 4 空格缩进 > 3 assert.equal(analyzeListContext(deep, deep.length), null) }) test('analyzeListContext: 按光标所在行判定,不误判他行', () => { const text = '1. list\nplain' assert.equal(analyzeListContext(text, 9), null) // 光标在第二非列表行 assert.equal(analyzeListContext(text, 3)?.kind, 'ordered') }) test('围栏守卫: ``` 内不触发(未闭合)', () => { const text = '```\n- x' assert.equal(analyzeListContext(text, text.length), null) assert.equal(continueList(text, text.length), null) }) test('围栏守卫: 闭合围栏外的列表照常触发', () => { const text = '```\ncode\n```\n- x' const ctx = analyzeListContext(text, text.length) assert.equal(ctx?.kind, 'unordered') }) test('nextMarker: 有序 +1(保留分隔符)/ 无序同型', () => { assert.equal(nextMarker(analyzeListContext('1. a', 4)), '2. ') assert.equal(nextMarker(analyzeListContext('9) a', 4)), '10) ') assert.equal(nextMarker(analyzeListContext('- a', 3)), '- ') }) test('continueList: 非空有序项 → 换行补下一序号', () => { assert.deepEqual(continueList('1. foo', 6), { text: '1. foo\n2. ', caret: 10 }) }) test('continueList: 保留缩进', () => { assert.deepEqual(continueList(' 1. a', 6), { text: ' 1. a\n 2. ', caret: 12 }) }) test('continueList: 非空无序项 → 换行补同型符号', () => { assert.deepEqual(continueList('- foo', 5), { text: '- foo\n- ', caret: 8 }) }) test('continueList: 空项 → 退出列表(删标记留空行)', () => { assert.deepEqual(continueList('1. ', 3), { text: '', caret: 0 }) assert.deepEqual(continueList('a\n2. ', 5), { text: 'a\n', caret: 2 }) }) test('continueList: 非列表语境返回 null(交默认换行)', () => { assert.equal(continueList('plain', 5), null) }) test('exitListOnBackspace: 末位退格退出空项', () => { assert.deepEqual(exitListOnBackspace('1. ', 3), { text: '', caret: 0 }) assert.deepEqual(exitListOnBackspace('a\n- ', 4), { text: 'a\n', caret: 2 }) }) test('exitListOnBackspace: 非空项/非行末不触发', () => { assert.equal(exitListOnBackspace('1. x', 4), null) // 有正文 assert.equal(exitListOnBackspace('1. ', 1), null) // 光标不在行末 assert.equal(exitListOnBackspace('plain', 5), null) }) test('退出后文本不再被识别为列表(标记已移除)', () => { const r = continueList('1. ', 3) assert.equal(analyzeListContext(r.text, r.caret), null) })