chapter-path-sync.spec.ts 2.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. import { describe, expect, it } from "vitest"
  2. import {
  3. chapterContentMatchesPath,
  4. getDraftChapterPath,
  5. isDraftChapterPath,
  6. resolveChapterFlushMarkdown,
  7. shouldSkipEmptyChapterFlush,
  8. shouldSyncChapterOnLeave,
  9. } from "@/lib/novel/chapter-path-sync"
  10. const CH45 = `---
  11. type: chapter
  12. chapter_number: 45
  13. chapter_status: draft
  14. title: "第45章-空情之眼"
  15. ---
  16. # 第45章 空情之眼
  17. 第四十五章正文。`
  18. const CH46 = `---
  19. type: chapter
  20. chapter_number: 46
  21. chapter_status: draft
  22. title: "第46章-逆火第一轮"
  23. ---
  24. # 第46章 逆火第一轮
  25. 第四十六章正文。`
  26. describe("isDraftChapterPath", () => {
  27. it("detects AI draft chapter filenames", () => {
  28. expect(isDraftChapterPath("/proj/wiki/chapters/chapter-046.md")).toBe(true)
  29. expect(isDraftChapterPath("/proj/wiki/chapters/第46章-逆火第一轮.md")).toBe(false)
  30. })
  31. })
  32. describe("chapterContentMatchesPath", () => {
  33. it("rejects syncing chapter 46 content onto chapter 45 path", () => {
  34. expect(chapterContentMatchesPath("/proj/wiki/chapters/第45章-空情之眼.md", CH46)).toBe(false)
  35. expect(chapterContentMatchesPath("/proj/wiki/chapters/chapter-046.md", CH46)).toBe(true)
  36. })
  37. })
  38. describe("resolveChapterFlushMarkdown", () => {
  39. it("falls back to per-path snapshot instead of unrelated store content", () => {
  40. const snapshots = new Map([
  41. ["/proj/wiki/chapters/第45章-空情之眼.md", CH45],
  42. ["/proj/wiki/chapters/chapter-046.md", CH46],
  43. ])
  44. expect(resolveChapterFlushMarkdown("/proj/wiki/chapters/第45章-空情之眼.md", "", snapshots)).toBe(CH45)
  45. })
  46. })
  47. describe("shouldSyncChapterOnLeave", () => {
  48. it("skips empty flush when the path still has loaded content", () => {
  49. expect(shouldSyncChapterOnLeave("/proj/wiki/chapters/第45章-空情之眼.md", "", CH45)).toBe(false)
  50. })
  51. it("skips cross-chapter contamination", () => {
  52. expect(shouldSyncChapterOnLeave("/proj/wiki/chapters/第45章-空情之眼.md", CH46, CH45)).toBe(false)
  53. })
  54. it("allows unchanged content skip", () => {
  55. expect(shouldSyncChapterOnLeave("/proj/wiki/chapters/chapter-046.md", CH46, CH46)).toBe(false)
  56. })
  57. })
  58. describe("shouldSkipEmptyChapterFlush", () => {
  59. it("skips flushing empty markdown when disk snapshot still has content", () => {
  60. expect(shouldSkipEmptyChapterFlush("", CH45)).toBe(true)
  61. })
  62. it("allows flushing intentional empty edits when nothing was loaded", () => {
  63. expect(shouldSkipEmptyChapterFlush("", "")).toBe(false)
  64. })
  65. })
  66. describe("getDraftChapterPath", () => {
  67. it("builds the draft alias path for cleanup", () => {
  68. expect(getDraftChapterPath("/proj/wiki/chapters", 46)).toBe("/proj/wiki/chapters/chapter-046.md")
  69. })
  70. })