122 lines
5.1 KiB
JavaScript
122 lines
5.1 KiB
JavaScript
// 回归测试:H5 — [⭐] / [▶] 行尾原子块识别
|
||
//
|
||
// 背景:旧版 MARKER_TAIL_RE 强制前导 `\s`,导致:
|
||
// - `unimportant[⭐]`(任务文本紧贴 [⭐],无空格)→ 解析为普通文本,important=false
|
||
// - 下次保存 serialize 后,原文 `unimportant[⭐]` → 变成 `unimportant`(标记丢失)
|
||
//
|
||
// 修复(v3.7+):tail 正则去掉 `\s` 前导,`[⭐]` / `[▶]` 在行尾被识别为
|
||
// 内联原子块(不是「必须前面有空白字符」)。本脚本验证这条修复路径。
|
||
//
|
||
// 跑法:node scripts/check-marker-tail.mjs
|
||
|
||
import {
|
||
parseMarkdown,
|
||
extractInlineMarkers
|
||
} from '../src/markdown-parser.js';
|
||
import { check, ok, summary, printSummary } from './_lib/check.mjs';
|
||
|
||
// ── [1] extractInlineMarkers:行尾 [⭐] 不再要求前导空白 ──
|
||
console.log('\n[1] extractInlineMarkers 行尾 [⭐] / [▶] 不要求前导空白');
|
||
|
||
{
|
||
// 修复前:\s 前导强制要求 → 失败;修复后:直接 [⭐]$ 匹配
|
||
const r1 = extractInlineMarkers('unimportant[⭐]');
|
||
check('unimportant[⭐](无前导空白)→ important=true',
|
||
r1.important === true,
|
||
`important=${r1.important}, text=${r1.text}`);
|
||
check('unimportant[⭐] 文本剥到只剩 unimportant',
|
||
r1.text === 'unimportant',
|
||
`text=${JSON.stringify(r1.text)}`);
|
||
|
||
// 带前导空白的老用法 —— 仍然支持(无回归)
|
||
const r2 = extractInlineMarkers('important task [⭐]');
|
||
check('带空白的 important task [⭐] 仍识别',
|
||
r2.important === true && r2.text === 'important task',
|
||
`important=${r2.important}, text=${JSON.stringify(r2.text)}`);
|
||
|
||
// 行尾 [▶] 同款修复
|
||
const r3 = extractInlineMarkers('focus[▶]');
|
||
check('focus[▶](无前导空白)→ current=true',
|
||
r3.current === true && r3.text === 'focus',
|
||
`current=${r3.current}, text=${JSON.stringify(r3.text)}`);
|
||
|
||
// 行尾同时有 ⭐ 和 ▶ —— 不带空格紧凑写法
|
||
const r4 = extractInlineMarkers('critical work[⭐][▶]');
|
||
check('critical work[⭐][▶] 两个标记都识别',
|
||
r4.important === true && r4.current === true,
|
||
`important=${r4.important}, current=${r4.current}, text=${JSON.stringify(r4.text)}`);
|
||
// loop 顺序剥:先 [⭐] 后 [▶],text 应剥到 "critical work"
|
||
check('critical work[⭐][▶] 文本剥到只剩 critical work',
|
||
r4.text === 'critical work',
|
||
`text=${JSON.stringify(r4.text)}`);
|
||
|
||
// 行内非尾部 [⭐] 不应被剥离(中间嵌入文本不应误识别)
|
||
const r5 = extractInlineMarkers('关键任务 [⭐] 描述');
|
||
// 这里 [⭐] 在中间 —— 不在行尾,按设计应该不剥离(不是 H5 修复范围)
|
||
// 行为约定:MARKER_TAIL_RE 的 `$` 锚点要求标记在行尾;中间位置不识别。
|
||
check('行内 [⭐] 不被尾部规则剥离(不在 H5 修复范围)',
|
||
r5.text.includes('[⭐]') && !r5.important,
|
||
`text=${JSON.stringify(r5.text)}, important=${r5.important}`);
|
||
|
||
// 不含标记的纯文本
|
||
const r6 = extractInlineMarkers('just text');
|
||
check('无标记 → important/current 都 false,text 不变',
|
||
!r6.important && !r6.current && r6.text === 'just text');
|
||
}
|
||
|
||
// ── [2] parseMarkdown 端到端:行尾 [⭐] 被解析为 important ──
|
||
console.log('\n[2] parseMarkdown 端到端:行尾 [⭐] 解析为 important');
|
||
|
||
{
|
||
const md = `# 全部任务
|
||
|
||
## 工作
|
||
|
||
- [ ] unimportant[⭐]
|
||
- [ ] 关注今日[▶]
|
||
- [ ] 双标[⭐][▶]
|
||
- [ ] 普通任务
|
||
|
||
## 未分类
|
||
|
||
`;
|
||
const cats = parseMarkdown(md);
|
||
const work = cats.find(c => c.name === '工作');
|
||
|
||
const find = (text) => work.tasks.find(t => t.text === text);
|
||
|
||
const t1 = find('unimportant');
|
||
check('「工作」下 unimportant[⭐] → text=unimportant, important=true',
|
||
!!t1 && t1.important === true && t1.completed === false,
|
||
`task=${JSON.stringify(t1)}`);
|
||
|
||
const t2 = find('关注今日');
|
||
check('「工作」下 关注今日[▶] → text=关注今日, current=true',
|
||
!!t2 && t2.current === true && t2.important === false,
|
||
`task=${JSON.stringify(t2)}`);
|
||
|
||
const t3 = find('双标');
|
||
check('「工作」下 双标[⭐][▶] → 两种标记同时 true',
|
||
!!t3 && t3.important === true && t3.current === true,
|
||
`task=${JSON.stringify(t3)}`);
|
||
|
||
const t4 = find('普通任务');
|
||
check('「工作」下 普通任务 → 标记均为 false',
|
||
!!t4 && !t4.important && !t4.current);
|
||
}
|
||
|
||
// ── [3] 写回往返:important 不会丢 ──
|
||
console.log('\n[3] 写回往返:行尾 [⭐] 经 serialize → parse 后标记保留');
|
||
|
||
{
|
||
// 这里只验证解析侧的不可逆(writeMarkdown 单测已在 check-md-ui-sync 覆盖):
|
||
// 解析出来后,task.important === true ⇒ 文本里仍可看到 [⭐];
|
||
// 后续写回会在 writer 端用 [⭐] 行首标记(统一格式)输出,不再依赖原始紧凑写法。
|
||
const md = `- [ ] hello[⭐]\n`;
|
||
const cats = parseMarkdown(`# 全部任务\n\n## 工作\n\n${md}\n\n## 未分类\n\n`);
|
||
const task = cats.find(c => c.name === '工作')?.tasks[0];
|
||
check('hello[⭐] 解析后 important=true(持久性证据)',
|
||
task && task.important === true && task.text === 'hello');
|
||
}
|
||
|
||
printSummary('check-marker-tail'); |