1
0

goto-chapter.test.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. import { test } from 'node:test'
  2. import assert from 'node:assert/strict'
  3. import { promises as fs } from 'node:fs'
  4. import path from 'node:path'
  5. import { gotoChapter } from '../../../src/state-machine/flows/goto-chapter.js'
  6. import { makeGitBook, chapter } from '../_helper.js'
  7. function bookWithChapters() {
  8. return makeGitBook(
  9. { 'book.yaml': 'spec_version: "7.0"\n书名: 测\n' },
  10. {
  11. commits: [
  12. { message: 'ch(1): 起', files: { '定稿/正文/0001-起.md': chapter(1) } },
  13. { message: 'ch(2): 承', files: { '定稿/正文/0002-承.md': chapter(2) } },
  14. ],
  15. }
  16. )
  17. }
  18. test('回到第N章 confirm=false:列出将丢弃的提交', async () => {
  19. const { ctx, cleanup } = await bookWithChapters()
  20. try {
  21. const r = await gotoChapter(ctx, { chapterNum: 1, confirm: false })
  22. assert.equal(r.ok, true)
  23. assert.equal(r.needsConfirm, true)
  24. assert.ok(r.willLose.some((s) => s.includes('ch(2)')))
  25. } finally {
  26. await cleanup()
  27. }
  28. })
  29. test('回到第N章 confirm=true:reset 到该章 + 备份 ref,后续章消失', async () => {
  30. const { ctx, root, git, cleanup } = await bookWithChapters()
  31. try {
  32. const r = await gotoChapter(ctx, { chapterNum: 1, confirm: true })
  33. assert.equal(r.ok, true)
  34. assert.equal(r.reverted, true)
  35. await assert.rejects(() => fs.access(path.join(root, '定稿/正文/0002-承.md')))
  36. await fs.access(path.join(root, '定稿/正文/0001-起.md'))
  37. const { stdout } = await git(['rev-parse', '--verify', r.backupRef])
  38. assert.ok(stdout.trim().length >= 7)
  39. } finally {
  40. await cleanup()
  41. }
  42. })
  43. test('回到第N章:不存在的章 → ok=false', async () => {
  44. const { ctx, cleanup } = await bookWithChapters()
  45. try {
  46. const r = await gotoChapter(ctx, { chapterNum: 99, confirm: true })
  47. assert.equal(r.ok, false)
  48. } finally {
  49. await cleanup()
  50. }
  51. })
  52. test('P1-6:定稿有未登记手改 + confirm → 拒绝 reset(不丢手改)', async () => {
  53. const { ctx, root, git, cleanup } = await bookWithChapters()
  54. try {
  55. // 在已跟踪的第1章上手改(不提交)
  56. const path1 = path.join(root, '定稿/正文/0001-起.md')
  57. await fs.writeFile(path1, (await fs.readFile(path1, 'utf8')) + '\n手改了一句。', 'utf8')
  58. const r = await gotoChapter(ctx, { chapterNum: 1, confirm: true })
  59. assert.equal(r.ok, false, '脏树应拒绝 reset')
  60. assert.match(r.error, /手改/)
  61. // 手改仍在,未被抹掉(rescue ref 不含工作树,拒绝才安全)
  62. assert.ok((await fs.readFile(path1, 'utf8')).includes('手改了一句'), '手改应保留')
  63. // 第2章也仍在(未 reset)
  64. await fs.stat(path.join(root, '定稿/正文/0002-承.md'))
  65. void git
  66. } finally {
  67. await cleanup()
  68. }
  69. })
  70. test('R1:有进行中批次 → goto 直接拒绝,批次与定稿都原样', async () => {
  71. const { ctx, root, cleanup } = await bookWithChapters()
  72. try {
  73. const dir = path.join(root, '工作区', '待定稿', '0003-批内章')
  74. await fs.mkdir(dir, { recursive: true })
  75. await fs.writeFile(
  76. path.join(root, '工作区', '待定稿', '批次.json'),
  77. JSON.stringify({ 章列表: [{ 章号: 3, 标题: '批内章', 状态: '待审收', 目录: '0003-批内章' }] }),
  78. 'utf8'
  79. )
  80. const r = await gotoChapter(ctx, { chapterNum: 1, confirm: true })
  81. assert.equal(r.ok, false, '批次在场应拒绝回退')
  82. assert.match(r.error, /待定稿批次/)
  83. assert.match(r.error, /finalize-batch|batch-discard/)
  84. // 定稿未被 reset、批次原样
  85. await fs.access(path.join(root, '定稿/正文/0002-承.md'))
  86. await fs.access(dir)
  87. // needsConfirm 预览路径同样被拦(不给出误导性的"确认请带 confirm")
  88. const preview = await gotoChapter(ctx, { chapterNum: 1, confirm: false })
  89. assert.equal(preview.ok, false)
  90. assert.match(preview.error, /待定稿批次/)
  91. } finally {
  92. await cleanup()
  93. }
  94. })