yaml-dialect.test.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. import { test } from 'node:test'
  2. import assert from 'node:assert/strict'
  3. import { serializeYAML } from '../../../src/storage/serializers/yaml-dialect.js'
  4. import { serializeFrontMatter } from '../../../src/storage/serializers/front-matter.js'
  5. import { parseFrontMatter } from '../../../src/storage/parsers/front-matter.js'
  6. test('列表输出块格式', () => {
  7. const data = { 伏笔: ['伏笔-001', '伏笔-002'] }
  8. const yaml = serializeYAML(data)
  9. assert.ok(yaml.includes('伏笔:\n - 伏笔-001\n - 伏笔-002'))
  10. assert.ok(!yaml.includes('[伏笔-001, 伏笔-002]'))
  11. })
  12. test('危险值加引号:数字串', () => {
  13. const data = { 章号: '123' }
  14. const yaml = serializeYAML(data)
  15. assert.ok(yaml.includes('章号: "123"'))
  16. })
  17. test('危险值加引号:布尔字面值', () => {
  18. const data = { 开关: 'true', 标志: 'false' }
  19. const yaml = serializeYAML(data)
  20. assert.ok(yaml.includes('开关: "true"'))
  21. assert.ok(yaml.includes('标志: "false"'))
  22. })
  23. test('危险值加引号:null 字面值', () => {
  24. const data = { 值: 'null' }
  25. const yaml = serializeYAML(data)
  26. assert.ok(yaml.includes('值: "null"'))
  27. })
  28. test('危险值加引号:含冒号', () => {
  29. const data = { 标题: '包含:冒号' }
  30. const yaml = serializeYAML(data)
  31. assert.ok(yaml.includes('标题: "包含:冒号"'))
  32. })
  33. test('危险值加引号:以 # 或 - 开头', () => {
  34. const data = { 注释: '#comment', 项: '-item' }
  35. const yaml = serializeYAML(data)
  36. assert.ok(yaml.includes('注释: "#comment"'))
  37. assert.ok(yaml.includes('项: "-item"'))
  38. })
  39. test('正常字符串不加引号', () => {
  40. const data = { 标题: '测试章节', 视角: '林晚' }
  41. const yaml = serializeYAML(data)
  42. assert.ok(yaml.includes('标题: 测试章节'))
  43. assert.ok(yaml.includes('视角: 林晚'))
  44. })
  45. test('数字和布尔值正常输出', () => {
  46. const data = { 章号: 1, 字数: 3000, 开关: true }
  47. const yaml = serializeYAML(data)
  48. assert.ok(yaml.includes('章号: 1'))
  49. assert.ok(yaml.includes('字数: 3000'))
  50. assert.ok(yaml.includes('开关: true'))
  51. })
  52. test('嵌套映射抛错', () => {
  53. const data = { 外层: { 内层: '值' } }
  54. assert.throws(() => {
  55. serializeYAML(data)
  56. }, /禁止嵌套映射/)
  57. })
  58. test('空数组', () => {
  59. const data = { 列表: [] }
  60. const yaml = serializeYAML(data)
  61. assert.equal(yaml, '列表:')
  62. })
  63. test('混合字段', () => {
  64. const data = {
  65. 章号: 1,
  66. 标题: '测试',
  67. 伏笔: ['伏笔-001', '伏笔-002'],
  68. 危险数字: '123',
  69. }
  70. const yaml = serializeYAML(data)
  71. assert.ok(yaml.includes('章号: 1'))
  72. assert.ok(yaml.includes('标题: 测试'))
  73. assert.ok(yaml.includes('伏笔:\n - 伏笔-001'))
  74. assert.ok(yaml.includes('危险数字: "123"'))
  75. })
  76. // —— R7/R8 往返回归表:写出的 front matter 必须原样读得回 ——
  77. // 每个值都是第三轮 review 复现过 DRIFT(类型漂移/被裁)或 PARSEFAIL(整文件读不回)的案例。
  78. const 往返用例 = [
  79. ['空串', ''],
  80. ['前后空格', ' 甲 '],
  81. ['中间制表符', 'a\tb'],
  82. ['十六进制形态', '0x1F'],
  83. ['八进制形态', '0o17'],
  84. ['二进制形态', '0b101'],
  85. ['科学计数', '1e3'],
  86. ['带符号整数', '+5'],
  87. ['带符号科学计数', '-2.5E-3'],
  88. ['波浪线', '~'],
  89. ['inf 字面值', '.inf'],
  90. ['nan 字面值', '.NaN'],
  91. ['方括号起首', '[快穿]反派'],
  92. ['星号起首', '*追读'],
  93. ['锚点起首', '&a'],
  94. ['双引号起首', '"引号"书'],
  95. ['单引号起首', "'单引'"],
  96. ['横杠空格起首', '- 开局'],
  97. ['反斜杠加冒号', 'C:\\Users\\x'],
  98. ['纯反斜杠路径', '路径\\子目录\\文件'],
  99. ['折叠符起首', '|折叠'],
  100. ['引用符起首', '>引用'],
  101. ['叹号起首', '!标签'],
  102. ['百分号起首', '%指令'],
  103. ['逗号起首', ',逗号'],
  104. ['问号加空格起首', '? 问'],
  105. ['行内注释', '前半 #后半'],
  106. ['花括号起首', '{流式}'],
  107. ['换行值', '第一行\n第二行'],
  108. ]
  109. for (const [名称, 值] of 往返用例) {
  110. test(`往返保真:${名称}`, () => {
  111. const content = serializeFrontMatter({ 标题: '往返', 值 }, '正文\n')
  112. const parsed = parseFrontMatter(content)
  113. assert.equal(parsed.ok, true, `解析失败:${parsed.error}`)
  114. assert.equal(parsed.data.值, 值, `往返漂移:${JSON.stringify(parsed.data.值)} !== ${JSON.stringify(值)}`)
  115. })
  116. }
  117. test('往返保真:数组项含危险值', () => {
  118. const content = serializeFrontMatter({ 标题: '往返', 本章要写到的事: ['[伏笔] 埋线', '*重点*', '1e3'] }, '')
  119. const parsed = parseFrontMatter(content)
  120. assert.equal(parsed.ok, true, `解析失败:${parsed.error}`)
  121. assert.deepEqual(parsed.data.本章要写到的事, ['[伏笔] 埋线', '*重点*', '1e3'])
  122. })