1
0

index.test.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. import { test } from 'node:test'
  2. import assert from 'node:assert/strict'
  3. import path from 'node:path'
  4. import { promises as fs } from 'node:fs'
  5. import { exportChapters } from '../../src/export/index.js'
  6. import { ChapterReader } from '../../src/storage/adapters/ChapterReader.js'
  7. import { tempBookCtx, repoCtx } from '../commands/_helper.js'
  8. // sample-book:第 1 章「开局」、第 2 章「初遇」,书名「测试书」
  9. async function readExport(ctx, name) {
  10. return fs.readFile(path.join(ctx.repoPath, '工作区', '导出', name), 'utf8')
  11. }
  12. test('单章导出:去 front matter 纯正文、无标题行、文件名零填充', async () => {
  13. const { ctx, cleanup } = await tempBookCtx()
  14. try {
  15. const r = await exportChapters(ctx, { mode: 'single', chapterNum: 1 })
  16. assert.equal(r.ok, true, r.error)
  17. assert.deepEqual(r.files, ['第0001章-开局.txt'])
  18. const body = (await new ChapterReader(ctx.repoPath).readBody(1)).body
  19. const exported = await readExport(ctx, '第0001章-开局.txt')
  20. assert.equal(exported, body.trim() + '\n')
  21. assert.doesNotMatch(exported, /^第1章/) // 正文体不带标题行
  22. assert.doesNotMatch(exported, /^---/) // 无 front matter
  23. } finally {
  24. await cleanup()
  25. }
  26. })
  27. test('范围导出:合并单文件、每章「第N章 标题」行、章间空两行', async () => {
  28. const { ctx, cleanup } = await tempBookCtx()
  29. try {
  30. const r = await exportChapters(ctx, { mode: 'range', start: 1, end: 2 })
  31. assert.equal(r.ok, true, r.error)
  32. assert.deepEqual(r.files, ['第0001-0002章.txt'])
  33. const reader = new ChapterReader(ctx.repoPath)
  34. const b1 = (await reader.readBody(1)).body.trim()
  35. const b2 = (await reader.readBody(2)).body.trim()
  36. const exported = await readExport(ctx, '第0001-0002章.txt')
  37. assert.equal(exported, `第1章 开局\n\n${b1}\n\n\n第2章 初遇\n\n${b2}\n`)
  38. } finally {
  39. await cleanup()
  40. }
  41. })
  42. test('全书导出:文件名含书名、内容同合并格式;重复导出覆盖', async () => {
  43. const { ctx, cleanup } = await tempBookCtx()
  44. try {
  45. const r1 = await exportChapters(ctx, { mode: 'all' })
  46. assert.equal(r1.ok, true, r1.error)
  47. assert.deepEqual(r1.files, ['全书-测试书.txt'])
  48. const first = await readExport(ctx, '全书-测试书.txt')
  49. assert.match(first, /^第1章 开局\n/)
  50. assert.match(first, /\n第2章 初遇\n/)
  51. const r2 = await exportChapters(ctx, { mode: 'all' })
  52. assert.equal(r2.ok, true, r2.error)
  53. assert.equal(await readExport(ctx, '全书-测试书.txt'), first) // 覆盖后仍完整
  54. } finally {
  55. await cleanup()
  56. }
  57. })
  58. test('缺章:范围含空洞人话列缺章,零写入', async () => {
  59. const { ctx, cleanup } = await tempBookCtx()
  60. try {
  61. const r = await exportChapters(ctx, { mode: 'range', start: 1, end: 4 })
  62. assert.equal(r.ok, false)
  63. assert.match(r.error, /第 3、4 章/)
  64. await assert.rejects(readExport(ctx, '第0001-0004章.txt'))
  65. } finally {
  66. await cleanup()
  67. }
  68. })
  69. test('单章不存在与范围颠倒:人话报错', async () => {
  70. const { ctx, cleanup } = await tempBookCtx()
  71. try {
  72. const miss = await exportChapters(ctx, { mode: 'single', chapterNum: 9 })
  73. assert.equal(miss.ok, false)
  74. assert.match(miss.error, /第 9 章/)
  75. const flipped = await exportChapters(ctx, { mode: 'range', start: 2, end: 1 })
  76. assert.equal(flipped.ok, false)
  77. assert.match(flipped.error, /起章不能大于止章/)
  78. } finally {
  79. await cleanup()
  80. }
  81. })
  82. test('空定稿区:人话报错', async () => {
  83. const { ctx, cleanup } = await repoCtx(null, {
  84. 'book.yaml': 'spec_version: "7.0"\n书名: 空书\n类型: 玄幻\n每章目标字数: 3000\n卷规模: 40\n',
  85. })
  86. try {
  87. const r = await exportChapters(ctx, { mode: 'all' })
  88. assert.equal(r.ok, false)
  89. assert.match(r.error, /还没有定稿章节/)
  90. } finally {
  91. await cleanup()
  92. }
  93. })