@aigne/doc-smith 0.8.12-beta.7 → 0.8.12-beta.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (284) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/agents/clear/choose-contents.mjs +14 -1
  3. package/agents/clear/clear-media-description.mjs +129 -0
  4. package/agents/clear/index.yaml +3 -1
  5. package/agents/evaluate/code-snippet.mjs +28 -24
  6. package/agents/evaluate/document-structure.yaml +0 -4
  7. package/agents/evaluate/document.yaml +1 -5
  8. package/agents/generate/index.yaml +1 -0
  9. package/agents/init/index.mjs +10 -0
  10. package/agents/media/batch-generate-media-description.yaml +44 -0
  11. package/agents/media/generate-media-description.yaml +47 -0
  12. package/agents/media/load-media-description.mjs +238 -0
  13. package/agents/publish/index.yaml +4 -0
  14. package/agents/publish/publish-docs.mjs +77 -5
  15. package/agents/publish/translate-meta.mjs +103 -0
  16. package/agents/update/generate-document.yaml +30 -28
  17. package/agents/update/index.yaml +1 -0
  18. package/agents/update/update-document-detail.yaml +3 -1
  19. package/agents/utils/load-sources.mjs +103 -53
  20. package/agents/utils/update-branding.mjs +69 -0
  21. package/aigne.yaml +6 -0
  22. package/assets/report-template/report.html +34 -34
  23. package/package.json +17 -2
  24. package/prompts/common/document/role-and-personality.md +3 -1
  25. package/prompts/detail/d2-diagram/guide.md +7 -1
  26. package/prompts/detail/d2-diagram/user-prompt.md +3 -0
  27. package/prompts/detail/generate/system-prompt.md +6 -7
  28. package/prompts/detail/generate/user-prompt.md +12 -3
  29. package/prompts/detail/update/user-prompt.md +0 -2
  30. package/prompts/evaluate/document-structure.md +6 -7
  31. package/prompts/evaluate/document.md +16 -25
  32. package/prompts/media/media-description/system-prompt.md +35 -0
  33. package/prompts/media/media-description/user-prompt.md +8 -0
  34. package/prompts/structure/update/user-prompt.md +0 -4
  35. package/utils/constants/index.mjs +0 -107
  36. package/utils/file-utils.mjs +86 -0
  37. package/utils/markdown-checker.mjs +0 -20
  38. package/utils/request.mjs +7 -0
  39. package/utils/upload-files.mjs +231 -0
  40. package/utils/utils.mjs +11 -1
  41. package/.aigne/doc-smith/config.yaml +0 -77
  42. package/.aigne/doc-smith/history.yaml +0 -37
  43. package/.aigne/doc-smith/output/structure-plan.json +0 -162
  44. package/.aigne/doc-smith/preferences.yml +0 -97
  45. package/.aigne/doc-smith/upload-cache.yaml +0 -1893
  46. package/.github/PULL_REQUEST_TEMPLATE.md +0 -28
  47. package/.github/workflows/ci.yml +0 -54
  48. package/.github/workflows/create-release-pr.yaml +0 -21
  49. package/.github/workflows/publish-docs.yml +0 -65
  50. package/.github/workflows/release.yml +0 -49
  51. package/.github/workflows/reviewer.yml +0 -54
  52. package/.release-please-manifest.json +0 -3
  53. package/RELEASE.md +0 -9
  54. package/assets/screenshots/doc-complete-setup.png +0 -0
  55. package/assets/screenshots/doc-generate-docs.png +0 -0
  56. package/assets/screenshots/doc-generate.png +0 -0
  57. package/assets/screenshots/doc-generated-successfully.png +0 -0
  58. package/assets/screenshots/doc-publish.png +0 -0
  59. package/assets/screenshots/doc-regenerate.png +0 -0
  60. package/assets/screenshots/doc-translate-langs.png +0 -0
  61. package/assets/screenshots/doc-translate.png +0 -0
  62. package/assets/screenshots/doc-update.png +0 -0
  63. package/biome.json +0 -73
  64. package/codecov.yml +0 -15
  65. package/docs/_sidebar.md +0 -15
  66. package/docs/configuration-initial-setup.ja.md +0 -179
  67. package/docs/configuration-initial-setup.md +0 -179
  68. package/docs/configuration-initial-setup.zh-TW.md +0 -179
  69. package/docs/configuration-initial-setup.zh.md +0 -179
  70. package/docs/configuration-managing-preferences.ja.md +0 -100
  71. package/docs/configuration-managing-preferences.md +0 -100
  72. package/docs/configuration-managing-preferences.zh-TW.md +0 -100
  73. package/docs/configuration-managing-preferences.zh.md +0 -100
  74. package/docs/configuration.ja.md +0 -96
  75. package/docs/configuration.md +0 -96
  76. package/docs/configuration.zh-TW.md +0 -96
  77. package/docs/configuration.zh.md +0 -96
  78. package/docs/getting-started.ja.md +0 -88
  79. package/docs/getting-started.md +0 -88
  80. package/docs/getting-started.zh-TW.md +0 -88
  81. package/docs/getting-started.zh.md +0 -88
  82. package/docs/guides-cleaning-up.ja.md +0 -51
  83. package/docs/guides-cleaning-up.md +0 -51
  84. package/docs/guides-cleaning-up.zh-TW.md +0 -51
  85. package/docs/guides-cleaning-up.zh.md +0 -51
  86. package/docs/guides-evaluating-documents.ja.md +0 -66
  87. package/docs/guides-evaluating-documents.md +0 -66
  88. package/docs/guides-evaluating-documents.zh-TW.md +0 -66
  89. package/docs/guides-evaluating-documents.zh.md +0 -66
  90. package/docs/guides-generating-documentation.ja.md +0 -151
  91. package/docs/guides-generating-documentation.md +0 -151
  92. package/docs/guides-generating-documentation.zh-TW.md +0 -151
  93. package/docs/guides-generating-documentation.zh.md +0 -151
  94. package/docs/guides-interactive-chat.ja.md +0 -85
  95. package/docs/guides-interactive-chat.md +0 -85
  96. package/docs/guides-interactive-chat.zh-TW.md +0 -85
  97. package/docs/guides-interactive-chat.zh.md +0 -85
  98. package/docs/guides-managing-history.ja.md +0 -48
  99. package/docs/guides-managing-history.md +0 -48
  100. package/docs/guides-managing-history.zh-TW.md +0 -48
  101. package/docs/guides-managing-history.zh.md +0 -48
  102. package/docs/guides-publishing-your-docs.ja.md +0 -78
  103. package/docs/guides-publishing-your-docs.md +0 -78
  104. package/docs/guides-publishing-your-docs.zh-TW.md +0 -78
  105. package/docs/guides-publishing-your-docs.zh.md +0 -78
  106. package/docs/guides-translating-documentation.ja.md +0 -95
  107. package/docs/guides-translating-documentation.md +0 -95
  108. package/docs/guides-translating-documentation.zh-TW.md +0 -95
  109. package/docs/guides-translating-documentation.zh.md +0 -95
  110. package/docs/guides-updating-documentation.ja.md +0 -77
  111. package/docs/guides-updating-documentation.md +0 -77
  112. package/docs/guides-updating-documentation.zh-TW.md +0 -77
  113. package/docs/guides-updating-documentation.zh.md +0 -77
  114. package/docs/guides.ja.md +0 -32
  115. package/docs/guides.md +0 -32
  116. package/docs/guides.zh-TW.md +0 -32
  117. package/docs/guides.zh.md +0 -32
  118. package/docs/overview.ja.md +0 -61
  119. package/docs/overview.md +0 -61
  120. package/docs/overview.zh-TW.md +0 -61
  121. package/docs/overview.zh.md +0 -61
  122. package/docs/release-notes.ja.md +0 -255
  123. package/docs/release-notes.md +0 -255
  124. package/docs/release-notes.zh-TW.md +0 -255
  125. package/docs/release-notes.zh.md +0 -255
  126. package/media.md +0 -19
  127. package/prompts/common/afs/afs-tools-usage.md +0 -5
  128. package/prompts/common/afs/use-afs-instruction.md +0 -1
  129. package/release-please-config.json +0 -14
  130. package/tests/agents/chat/chat.test.mjs +0 -46
  131. package/tests/agents/clear/choose-contents.test.mjs +0 -284
  132. package/tests/agents/clear/clear-auth-tokens.test.mjs +0 -268
  133. package/tests/agents/clear/clear-document-config.test.mjs +0 -167
  134. package/tests/agents/clear/clear-document-structure.test.mjs +0 -380
  135. package/tests/agents/clear/clear-generated-docs.test.mjs +0 -222
  136. package/tests/agents/evaluate/code-snippet.test.mjs +0 -163
  137. package/tests/agents/evaluate/fixtures/api-services.md +0 -87
  138. package/tests/agents/evaluate/fixtures/js-sdk.md +0 -94
  139. package/tests/agents/evaluate/generate-report.test.mjs +0 -312
  140. package/tests/agents/generate/check-document-structure.test.mjs +0 -45
  141. package/tests/agents/generate/check-need-generate-structure.test.mjs +0 -279
  142. package/tests/agents/generate/document-structure-tools/add-document.test.mjs +0 -449
  143. package/tests/agents/generate/document-structure-tools/delete-document.test.mjs +0 -410
  144. package/tests/agents/generate/document-structure-tools/generate-sub-structure.test.mjs +0 -277
  145. package/tests/agents/generate/document-structure-tools/move-document.test.mjs +0 -476
  146. package/tests/agents/generate/document-structure-tools/update-document.test.mjs +0 -548
  147. package/tests/agents/generate/generate-structure.test.mjs +0 -45
  148. package/tests/agents/generate/user-review-document-structure.test.mjs +0 -319
  149. package/tests/agents/history/view.test.mjs +0 -97
  150. package/tests/agents/init/init.test.mjs +0 -1657
  151. package/tests/agents/prefs/prefs.test.mjs +0 -431
  152. package/tests/agents/publish/publish-docs.test.mjs +0 -787
  153. package/tests/agents/translate/choose-language.test.mjs +0 -311
  154. package/tests/agents/translate/translate-document.test.mjs +0 -51
  155. package/tests/agents/update/check-document.test.mjs +0 -463
  156. package/tests/agents/update/check-update-is-single.test.mjs +0 -300
  157. package/tests/agents/update/document-tools/update-document-content.test.mjs +0 -329
  158. package/tests/agents/update/generate-document.test.mjs +0 -51
  159. package/tests/agents/update/save-and-translate-document.test.mjs +0 -369
  160. package/tests/agents/update/user-review-document.test.mjs +0 -582
  161. package/tests/agents/utils/action-success.test.mjs +0 -54
  162. package/tests/agents/utils/check-detail-result.test.mjs +0 -743
  163. package/tests/agents/utils/check-feedback-refiner.test.mjs +0 -478
  164. package/tests/agents/utils/choose-docs.test.mjs +0 -406
  165. package/tests/agents/utils/exit.test.mjs +0 -70
  166. package/tests/agents/utils/feedback-refiner.test.mjs +0 -51
  167. package/tests/agents/utils/find-item-by-path.test.mjs +0 -517
  168. package/tests/agents/utils/find-user-preferences-by-path.test.mjs +0 -382
  169. package/tests/agents/utils/format-document-structure.test.mjs +0 -364
  170. package/tests/agents/utils/fs.test.mjs +0 -267
  171. package/tests/agents/utils/load-sources.test.mjs +0 -1470
  172. package/tests/agents/utils/save-docs.test.mjs +0 -109
  173. package/tests/agents/utils/save-output.test.mjs +0 -315
  174. package/tests/agents/utils/save-single-doc.test.mjs +0 -364
  175. package/tests/agents/utils/transform-detail-datasources.test.mjs +0 -320
  176. package/tests/utils/auth-utils.test.mjs +0 -596
  177. package/tests/utils/blocklet.test.mjs +0 -336
  178. package/tests/utils/conflict-detector.test.mjs +0 -355
  179. package/tests/utils/constants.test.mjs +0 -295
  180. package/tests/utils/d2-utils.test.mjs +0 -437
  181. package/tests/utils/deploy.test.mjs +0 -399
  182. package/tests/utils/docs-finder-utils.test.mjs +0 -650
  183. package/tests/utils/file-utils.test.mjs +0 -521
  184. package/tests/utils/history-utils.test.mjs +0 -206
  185. package/tests/utils/kroki-utils.test.mjs +0 -646
  186. package/tests/utils/linter/fixtures/css/keyword-error.css +0 -1
  187. package/tests/utils/linter/fixtures/css/missing-semicolon.css +0 -1
  188. package/tests/utils/linter/fixtures/css/syntax-error.css +0 -1
  189. package/tests/utils/linter/fixtures/css/undeclare-variable.css +0 -1
  190. package/tests/utils/linter/fixtures/css/unused-variable.css +0 -2
  191. package/tests/utils/linter/fixtures/css/valid-code.css +0 -1
  192. package/tests/utils/linter/fixtures/dockerfile/keyword-error.dockerfile +0 -1
  193. package/tests/utils/linter/fixtures/dockerfile/missing-semicolon.dockerfile +0 -2
  194. package/tests/utils/linter/fixtures/dockerfile/syntax-error.dockerfile +0 -2
  195. package/tests/utils/linter/fixtures/dockerfile/undeclare-variable.dockerfile +0 -1
  196. package/tests/utils/linter/fixtures/dockerfile/unused-variable.dockerfile +0 -1
  197. package/tests/utils/linter/fixtures/dockerfile/valid-code.dockerfile +0 -2
  198. package/tests/utils/linter/fixtures/go/keyword-error.go +0 -5
  199. package/tests/utils/linter/fixtures/go/missing-semicolon.go +0 -5
  200. package/tests/utils/linter/fixtures/go/syntax-error.go +0 -6
  201. package/tests/utils/linter/fixtures/go/undeclare-variable.go +0 -5
  202. package/tests/utils/linter/fixtures/go/unused-variable.go +0 -5
  203. package/tests/utils/linter/fixtures/go/valid-code.go +0 -7
  204. package/tests/utils/linter/fixtures/js/keyword-error.js +0 -3
  205. package/tests/utils/linter/fixtures/js/missing-semicolon.js +0 -6
  206. package/tests/utils/linter/fixtures/js/syntax-error.js +0 -4
  207. package/tests/utils/linter/fixtures/js/undeclare-variable.js +0 -3
  208. package/tests/utils/linter/fixtures/js/unused-variable.js +0 -7
  209. package/tests/utils/linter/fixtures/js/valid-code.js +0 -15
  210. package/tests/utils/linter/fixtures/json/keyword-error.json +0 -1
  211. package/tests/utils/linter/fixtures/json/missing-semicolon.json +0 -1
  212. package/tests/utils/linter/fixtures/json/syntax-error.json +0 -1
  213. package/tests/utils/linter/fixtures/json/undeclare-variable.json +0 -1
  214. package/tests/utils/linter/fixtures/json/unused-variable.json +0 -1
  215. package/tests/utils/linter/fixtures/json/valid-code.json +0 -1
  216. package/tests/utils/linter/fixtures/jsx/keyword-error.jsx +0 -5
  217. package/tests/utils/linter/fixtures/jsx/missing-semicolon.jsx +0 -5
  218. package/tests/utils/linter/fixtures/jsx/syntax-error.jsx +0 -5
  219. package/tests/utils/linter/fixtures/jsx/undeclare-variable.jsx +0 -5
  220. package/tests/utils/linter/fixtures/jsx/unused-variable.jsx +0 -4
  221. package/tests/utils/linter/fixtures/jsx/valid-code.jsx +0 -5
  222. package/tests/utils/linter/fixtures/python/keyword-error.py +0 -3
  223. package/tests/utils/linter/fixtures/python/missing-semicolon.py +0 -2
  224. package/tests/utils/linter/fixtures/python/syntax-error.py +0 -3
  225. package/tests/utils/linter/fixtures/python/undeclare-variable.py +0 -3
  226. package/tests/utils/linter/fixtures/python/unused-variable.py +0 -6
  227. package/tests/utils/linter/fixtures/python/valid-code.py +0 -12
  228. package/tests/utils/linter/fixtures/ruby/keyword-error.rb +0 -2
  229. package/tests/utils/linter/fixtures/ruby/missing-semicolon.rb +0 -1
  230. package/tests/utils/linter/fixtures/ruby/syntax-error.rb +0 -2
  231. package/tests/utils/linter/fixtures/ruby/undeclare-variable.rb +0 -1
  232. package/tests/utils/linter/fixtures/ruby/unused-variable.rb +0 -2
  233. package/tests/utils/linter/fixtures/ruby/valid-code.rb +0 -1
  234. package/tests/utils/linter/fixtures/sass/keyword-error.sass +0 -2
  235. package/tests/utils/linter/fixtures/sass/missing-semicolon.sass +0 -3
  236. package/tests/utils/linter/fixtures/sass/syntax-error.sass +0 -3
  237. package/tests/utils/linter/fixtures/sass/undeclare-variable.sass +0 -2
  238. package/tests/utils/linter/fixtures/sass/unused-variable.sass +0 -4
  239. package/tests/utils/linter/fixtures/sass/valid-code.sass +0 -2
  240. package/tests/utils/linter/fixtures/scss/keyword-error.scss +0 -1
  241. package/tests/utils/linter/fixtures/scss/missing-semicolon.scss +0 -1
  242. package/tests/utils/linter/fixtures/scss/syntax-error.scss +0 -1
  243. package/tests/utils/linter/fixtures/scss/undeclare-variable.scss +0 -1
  244. package/tests/utils/linter/fixtures/scss/unused-variable.scss +0 -2
  245. package/tests/utils/linter/fixtures/scss/valid-code.scss +0 -1
  246. package/tests/utils/linter/fixtures/shell/keyword-error.sh +0 -5
  247. package/tests/utils/linter/fixtures/shell/missing-semicolon.sh +0 -3
  248. package/tests/utils/linter/fixtures/shell/syntax-error.sh +0 -4
  249. package/tests/utils/linter/fixtures/shell/undeclare-variable.sh +0 -3
  250. package/tests/utils/linter/fixtures/shell/unused-variable.sh +0 -4
  251. package/tests/utils/linter/fixtures/shell/valid-code.sh +0 -3
  252. package/tests/utils/linter/fixtures/ts/keyword-error.ts +0 -1
  253. package/tests/utils/linter/fixtures/ts/missing-semicolon.ts +0 -1
  254. package/tests/utils/linter/fixtures/ts/syntax-error.ts +0 -1
  255. package/tests/utils/linter/fixtures/ts/undeclare-variable.ts +0 -1
  256. package/tests/utils/linter/fixtures/ts/unused-variable.ts +0 -3
  257. package/tests/utils/linter/fixtures/ts/valid-code.ts +0 -3
  258. package/tests/utils/linter/fixtures/tsx/keyword-error.tsx +0 -5
  259. package/tests/utils/linter/fixtures/tsx/missing-semicolon.tsx +0 -5
  260. package/tests/utils/linter/fixtures/tsx/syntax-error.tsx +0 -5
  261. package/tests/utils/linter/fixtures/tsx/undeclare-variable.tsx +0 -6
  262. package/tests/utils/linter/fixtures/tsx/unused-variable.tsx +0 -6
  263. package/tests/utils/linter/fixtures/tsx/valid-code.tsx +0 -5
  264. package/tests/utils/linter/fixtures/vue/keyword-error.vue +0 -6
  265. package/tests/utils/linter/fixtures/vue/missing-semicolon.vue +0 -6
  266. package/tests/utils/linter/fixtures/vue/syntax-error.vue +0 -6
  267. package/tests/utils/linter/fixtures/vue/undeclare-variable.vue +0 -6
  268. package/tests/utils/linter/fixtures/vue/unused-variable.vue +0 -7
  269. package/tests/utils/linter/fixtures/vue/valid-code.vue +0 -6
  270. package/tests/utils/linter/fixtures/yaml/keyword-error.yml +0 -1
  271. package/tests/utils/linter/fixtures/yaml/missing-semicolon.yml +0 -2
  272. package/tests/utils/linter/fixtures/yaml/syntax-error.yml +0 -1
  273. package/tests/utils/linter/fixtures/yaml/undeclare-variable.yml +0 -1
  274. package/tests/utils/linter/fixtures/yaml/unused-variable.yml +0 -2
  275. package/tests/utils/linter/fixtures/yaml/valid-code.yml +0 -3
  276. package/tests/utils/linter/index.test.mjs +0 -440
  277. package/tests/utils/linter/scan-results.mjs +0 -42
  278. package/tests/utils/load-config.test.mjs +0 -141
  279. package/tests/utils/markdown/index.test.mjs +0 -478
  280. package/tests/utils/mermaid-validator.test.mjs +0 -541
  281. package/tests/utils/mock-chat-model.mjs +0 -12
  282. package/tests/utils/preferences-utils.test.mjs +0 -465
  283. package/tests/utils/save-value-to-config.test.mjs +0 -483
  284. package/tests/utils/utils.test.mjs +0 -941
@@ -1,941 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, spyOn, test } from "bun:test";
2
- import * as childProcess from "node:child_process";
3
- import * as fs from "node:fs";
4
- import fsPromisesDefault, * as fsPromises from "node:fs/promises";
5
- import {
6
- detectSystemLanguage,
7
- getAvailablePaths,
8
- getContentHash,
9
- getCurrentGitHead,
10
- getGitHubRepoInfo,
11
- getGithubRepoUrl,
12
- getModifiedFilesBetweenCommits,
13
- getProjectInfo,
14
- hasFileChangesBetweenCommits,
15
- hasSourceFilesChanged,
16
- isGlobPattern,
17
- loadConfigFromFile,
18
- normalizePath,
19
- processConfigFields,
20
- processContent,
21
- resolveFileReferences,
22
- saveDocWithTranslations,
23
- saveGitHeadToConfig,
24
- saveValueToConfig,
25
- toRelativePath,
26
- validatePath,
27
- validatePaths,
28
- } from "../../utils/utils.mjs";
29
-
30
- describe("utils", () => {
31
- let originalEnv;
32
- let execSyncSpy;
33
- let existsSyncSpy;
34
- let mkdirSyncSpy;
35
- let readdirSyncSpy;
36
- let statSyncSpy;
37
- let accessSyncSpy;
38
- let readFileSpy;
39
- let writeFileSpy;
40
- let mkdirSpy;
41
- let consoleSpy;
42
- let processSpies;
43
- let fetchSpy;
44
-
45
- beforeEach(() => {
46
- originalEnv = { ...process.env };
47
-
48
- // Mock child_process
49
- execSyncSpy = spyOn(childProcess, "execSync").mockReturnValue("mocked-hash");
50
-
51
- // Mock fs sync operations
52
- existsSyncSpy = spyOn(fs, "existsSync").mockReturnValue(true);
53
- mkdirSyncSpy = spyOn(fs, "mkdirSync").mockImplementation(() => {});
54
- readdirSyncSpy = spyOn(fs, "readdirSync").mockReturnValue([]);
55
- statSyncSpy = spyOn(fs, "statSync").mockReturnValue({
56
- isDirectory: () => false,
57
- });
58
- accessSyncSpy = spyOn(fs, "accessSync").mockImplementation(() => {});
59
-
60
- // Mock fs async operations
61
- readFileSpy = spyOn(fsPromises, "readFile").mockResolvedValue("test content");
62
- writeFileSpy = spyOn(fsPromises, "writeFile").mockResolvedValue();
63
- mkdirSpy = spyOn(fsPromises, "mkdir").mockResolvedValue();
64
-
65
- // Also mock the default import that utils.mjs uses
66
- spyOn(fsPromisesDefault, "writeFile").mockResolvedValue();
67
- spyOn(fsPromisesDefault, "mkdir").mockResolvedValue();
68
-
69
- // Mock console
70
- consoleSpy = spyOn(console, "warn").mockImplementation(() => {});
71
-
72
- // Mock process methods
73
- processSpies = {
74
- cwd: spyOn(process, "cwd").mockReturnValue("/mock/cwd"),
75
- };
76
-
77
- // Mock global fetch
78
- fetchSpy = spyOn(global, "fetch").mockResolvedValue({
79
- ok: true,
80
- json: () => Promise.resolve({ name: "test-repo", description: "Test repo" }),
81
- statusText: "OK",
82
- });
83
- });
84
-
85
- afterEach(() => {
86
- // Restore environment
87
- process.env = originalEnv;
88
-
89
- // Restore all spies
90
- execSyncSpy?.mockRestore();
91
- existsSyncSpy?.mockRestore();
92
- mkdirSyncSpy?.mockRestore();
93
- readdirSyncSpy?.mockRestore();
94
- statSyncSpy?.mockRestore();
95
- accessSyncSpy?.mockRestore();
96
- readFileSpy?.mockRestore();
97
- writeFileSpy?.mockRestore();
98
- mkdirSpy?.mockRestore();
99
- consoleSpy?.mockRestore();
100
- fetchSpy?.mockRestore();
101
-
102
- // Restore process spies - important for isolation between test files
103
- Object.values(processSpies).forEach((spy) => {
104
- spy?.mockRestore();
105
- });
106
-
107
- // Reset process spies object to ensure clean state
108
- processSpies = {};
109
- });
110
-
111
- // PATH FUNCTIONS TESTS
112
- describe("normalizePath", () => {
113
- test("should return absolute path if already absolute", () => {
114
- const absolutePath = "/home/user/project";
115
- const result = normalizePath(absolutePath);
116
- expect(result).toBe(absolutePath);
117
- });
118
-
119
- test("should convert relative path to absolute", () => {
120
- processSpies.cwd.mockReturnValue("/home/user");
121
- const relativePath = "./project";
122
- const result = normalizePath(relativePath);
123
- expect(result).toBe("/home/user/project");
124
- });
125
-
126
- test("should handle complex relative paths", () => {
127
- processSpies.cwd.mockReturnValue("/home/user");
128
- const complexPath = "../other/project";
129
- const result = normalizePath(complexPath);
130
- expect(result).toBe("/home/other/project");
131
- });
132
- });
133
-
134
- describe("toRelativePath", () => {
135
- test("should convert absolute path to relative", () => {
136
- processSpies.cwd.mockReturnValue("/home/user");
137
- const absolutePath = "/home/user/project/file.md";
138
- const result = toRelativePath(absolutePath);
139
- expect(result).toBe("project/file.md");
140
- });
141
-
142
- test("should return relative path unchanged", () => {
143
- const relativePath = "./project/file.md";
144
- const result = toRelativePath(relativePath);
145
- expect(result).toBe(relativePath);
146
- });
147
- });
148
-
149
- describe("isGlobPattern", () => {
150
- test("should detect glob patterns", () => {
151
- expect(isGlobPattern("*.js")).toBe(true);
152
- expect(isGlobPattern("**/*.md")).toBe(true);
153
- expect(isGlobPattern("file?.txt")).toBe(true);
154
- expect(isGlobPattern("files[0-9].txt")).toBe(true);
155
- });
156
-
157
- test("should return false for non-glob patterns", () => {
158
- expect(isGlobPattern("file.txt")).toBe(false);
159
- expect(isGlobPattern("path/to/file")).toBe(false);
160
- expect(isGlobPattern("")).toBe(false);
161
- });
162
-
163
- test("should handle null/undefined input", () => {
164
- expect(isGlobPattern(null)).toBe(false);
165
- expect(isGlobPattern(undefined)).toBe(false);
166
- });
167
- });
168
-
169
- // CONTENT PROCESSING TESTS
170
- describe("processContent", () => {
171
- test("should convert relative links to flattened format", () => {
172
- const content = "Check out [API Guide](/api/guide) for details.";
173
- const result = processContent({ content });
174
- expect(result).toBe("Check out [API Guide](./api-guide.md) for details.");
175
- });
176
-
177
- test("should preserve external links", () => {
178
- const content = "Visit [Google](https://google.com) or [Email](mailto:test@example.com).";
179
- const result = processContent({ content });
180
- expect(result).toBe(content);
181
- });
182
-
183
- test("should preserve anchors", () => {
184
- const content = "See [Section](/guide#section) for details.";
185
- const result = processContent({ content });
186
- expect(result).toBe("See [Section](./guide.md#section) for details.");
187
- });
188
-
189
- test("should skip images", () => {
190
- const content = "![Image](/path/to/image) and [Link](/path/to/file)";
191
- const result = processContent({ content });
192
- expect(result).toBe("![Image](/path/to/image) and [Link](./path-to-file.md)");
193
- });
194
-
195
- test("should handle links with existing extensions", () => {
196
- const content = "Download [file](/downloads/file.pdf) here.";
197
- const result = processContent({ content });
198
- expect(result).toBe(content);
199
- });
200
-
201
- test("should handle dot-prefixed paths", () => {
202
- const content = "See [Guide](./guide/intro) for basics.";
203
- const result = processContent({ content });
204
- expect(result).toBe("See [Guide](./guide-intro.md) for basics.");
205
- });
206
- });
207
-
208
- // UTILITY FUNCTIONS TESTS
209
- describe("getContentHash", () => {
210
- test("should generate consistent hash for same content", () => {
211
- const content = "test content";
212
- const hash1 = getContentHash(content);
213
- const hash2 = getContentHash(content);
214
- expect(hash1).toBe(hash2);
215
- expect(typeof hash1).toBe("string");
216
- expect(hash1.length).toBe(64); // SHA256 hex length
217
- });
218
-
219
- test("should trim content by default", () => {
220
- const content = " test content ";
221
- const trimmedHash = getContentHash(content);
222
- const directHash = getContentHash("test content");
223
- expect(trimmedHash).toBe(directHash);
224
- });
225
-
226
- test("should not trim when trim=false", () => {
227
- const content = " test content ";
228
- const noTrimHash = getContentHash(content, { trim: false });
229
- const directHash = getContentHash("test content");
230
- expect(noTrimHash).not.toBe(directHash);
231
- });
232
-
233
- test("should handle different content types", () => {
234
- expect(getContentHash("")).toBe(getContentHash(""));
235
- expect(getContentHash("a")).not.toBe(getContentHash("b"));
236
- });
237
- });
238
-
239
- describe("detectSystemLanguage", () => {
240
- test("should detect language from LANG environment variable", () => {
241
- process.env.LANG = "zh_CN.UTF-8";
242
- const result = detectSystemLanguage();
243
- expect(result).toBe("zh");
244
- });
245
-
246
- test("should detect Traditional Chinese when zh is not in supported languages", () => {
247
- // Mock a scenario where "zh" is not found in SUPPORTED_LANGUAGES
248
- // but the system has zh_TW locale
249
- process.env.LANG = "zh_TW.UTF-8";
250
- const result = detectSystemLanguage();
251
- // The function will find "zh" in SUPPORTED_LANGUAGES and return it
252
- expect(result).toBe("zh");
253
- });
254
-
255
- test("should fall back to English for unsupported languages", () => {
256
- process.env.LANG = "unknown_LANG.UTF-8";
257
- const result = detectSystemLanguage();
258
- expect(result).toBe("en");
259
- });
260
-
261
- test("should handle missing environment variables", () => {
262
- delete process.env.LANG;
263
- delete process.env.LANGUAGE;
264
- delete process.env.LC_ALL;
265
- const result = detectSystemLanguage();
266
- expect(result).toBe("en");
267
- });
268
-
269
- test("should detect from LANGUAGE variable", () => {
270
- delete process.env.LANG;
271
- process.env.LANGUAGE = "fr_FR";
272
- const result = detectSystemLanguage();
273
- expect(result).toBe("fr");
274
- });
275
- });
276
-
277
- // GIT OPERATIONS TESTS
278
- describe("getCurrentGitHead", () => {
279
- test("should return git HEAD commit hash", () => {
280
- execSyncSpy.mockReturnValue("abc123def456\n");
281
- const result = getCurrentGitHead();
282
- expect(result).toBe("abc123def456");
283
- expect(execSyncSpy).toHaveBeenCalledWith("git rev-parse HEAD", {
284
- encoding: "utf8",
285
- stdio: ["pipe", "pipe", "ignore"],
286
- });
287
- });
288
-
289
- test("should return null and warn on git error", () => {
290
- execSyncSpy.mockImplementation(() => {
291
- throw new Error("Not a git repository");
292
- });
293
- const result = getCurrentGitHead();
294
- expect(result).toBeNull();
295
- expect(consoleSpy).toHaveBeenCalledWith("Failed to get git HEAD:", "Not a git repository");
296
- });
297
- });
298
-
299
- describe("getGithubRepoUrl", () => {
300
- test("should return GitHub URL", () => {
301
- execSyncSpy.mockReturnValue("git@github.com:user/repo.git\n");
302
- const result = getGithubRepoUrl();
303
- expect(result).toBe("git@github.com:user/repo.git");
304
- });
305
-
306
- test("should return empty string for non-GitHub repos", () => {
307
- execSyncSpy.mockReturnValue("git@gitlab.com:user/repo.git\n");
308
- const result = getGithubRepoUrl();
309
- expect(result).toBe("");
310
- });
311
-
312
- test("should return empty string on git error", () => {
313
- execSyncSpy.mockImplementation(() => {
314
- throw new Error("No origin remote");
315
- });
316
- const result = getGithubRepoUrl();
317
- expect(result).toBe("");
318
- });
319
- });
320
-
321
- describe("getModifiedFilesBetweenCommits", () => {
322
- test("should return modified files", () => {
323
- execSyncSpy.mockReturnValue("file1.js\nfile2.md\nfile3.txt\n");
324
- const result = getModifiedFilesBetweenCommits("abc123", "def456");
325
- expect(result).toEqual(["file1.js", "file2.md", "file3.txt"]);
326
- });
327
-
328
- test("should filter files by provided paths", () => {
329
- execSyncSpy.mockReturnValue("file1.js\nfile2.md\nfile3.txt\n");
330
- processSpies.cwd.mockReturnValue("/project");
331
- const result = getModifiedFilesBetweenCommits("abc123", "def456", ["/project/file1.js"]);
332
- expect(result).toEqual(["file1.js"]);
333
- });
334
-
335
- test("should handle git command errors", () => {
336
- execSyncSpy.mockImplementation(() => {
337
- throw new Error("Git command failed");
338
- });
339
- const result = getModifiedFilesBetweenCommits("abc123", "def456");
340
- expect(result).toEqual([]);
341
- expect(consoleSpy).toHaveBeenCalled();
342
- });
343
- });
344
-
345
- describe("hasSourceFilesChanged", () => {
346
- test("should return true when source files are modified", () => {
347
- processSpies.cwd.mockReturnValue("/project");
348
- const sourceIds = ["/project/src/file.js"];
349
- const modifiedFiles = ["src/file.js", "other/file.md"];
350
- const result = hasSourceFilesChanged(sourceIds, modifiedFiles);
351
- expect(result).toBe(true);
352
- });
353
-
354
- test("should return false when no source files are modified", () => {
355
- const sourceIds = ["src/file.js"];
356
- const modifiedFiles = ["other/file.md"];
357
- const result = hasSourceFilesChanged(sourceIds, modifiedFiles);
358
- expect(result).toBe(false);
359
- });
360
-
361
- test("should handle empty arrays", () => {
362
- expect(hasSourceFilesChanged([], ["file.js"])).toBe(false);
363
- expect(hasSourceFilesChanged(["file.js"], [])).toBe(false);
364
- expect(hasSourceFilesChanged([], [])).toBe(false);
365
- });
366
-
367
- test("should handle null/undefined inputs", () => {
368
- expect(hasSourceFilesChanged(null, ["file.js"])).toBe(false);
369
- expect(hasSourceFilesChanged(["file.js"], null)).toBe(false);
370
- });
371
- });
372
-
373
- // VALIDATION TESTS
374
- describe("validatePath", () => {
375
- test("should return valid for existing accessible path", () => {
376
- existsSyncSpy.mockReturnValue(true);
377
- accessSyncSpy.mockImplementation(() => {}); // No error means accessible
378
-
379
- const result = validatePath("/existing/path");
380
-
381
- expect(result.isValid).toBe(true);
382
- expect(result.error).toBeNull();
383
- });
384
-
385
- test("should return invalid for non-existent path", () => {
386
- existsSyncSpy.mockReturnValue(false);
387
-
388
- const result = validatePath("/non/existent");
389
-
390
- expect(result.isValid).toBe(false);
391
- expect(result.error).toBe("Path does not exist: /non/existent");
392
- });
393
-
394
- test("should return invalid for inaccessible path", () => {
395
- existsSyncSpy.mockReturnValue(true);
396
- accessSyncSpy.mockImplementation(() => {
397
- throw new Error("Permission denied");
398
- });
399
-
400
- const result = validatePath("/inaccessible");
401
-
402
- expect(result.isValid).toBe(false);
403
- expect(result.error).toBe("Path is not accessible: /inaccessible");
404
- });
405
-
406
- test("should handle path format errors", () => {
407
- // This is a bit tricky to test without modifying the actual function
408
- const result = validatePath("validpath");
409
- expect(result.isValid).toBe(true); // Will be valid if mocks succeed
410
- });
411
- });
412
-
413
- describe("validatePaths", () => {
414
- test("should separate valid and invalid paths", () => {
415
- existsSyncSpy
416
- .mockReturnValueOnce(true) // first path exists
417
- .mockReturnValueOnce(false); // second path doesn't exist
418
- accessSyncSpy.mockImplementation(() => {}); // accessible when it exists
419
-
420
- const paths = ["/valid/path", "/invalid/path"];
421
- const result = validatePaths(paths);
422
-
423
- expect(result.validPaths).toEqual(["/valid/path"]);
424
- expect(result.errors).toEqual([
425
- { path: "/invalid/path", error: "Path does not exist: /invalid/path" },
426
- ]);
427
- });
428
-
429
- test("should handle empty paths array", () => {
430
- const result = validatePaths([]);
431
- expect(result.validPaths).toEqual([]);
432
- expect(result.errors).toEqual([]);
433
- });
434
- });
435
-
436
- // CONFIG MANAGEMENT TESTS
437
- describe("loadConfigFromFile", () => {
438
- test("should return null for non-existent file", async () => {
439
- existsSyncSpy.mockReturnValue(false);
440
-
441
- const result = await loadConfigFromFile();
442
-
443
- expect(result).toBeNull();
444
- });
445
- });
446
-
447
- // GITHUB INTEGRATION TESTS
448
- describe("getGitHubRepoInfo", () => {
449
- test("should fetch repository information", async () => {
450
- const mockRepo = {
451
- name: "test-repo",
452
- description: "A test repository",
453
- owner: { avatar_url: "https://example.com/avatar.png" },
454
- };
455
- fetchSpy.mockResolvedValue({
456
- ok: true,
457
- json: () => Promise.resolve(mockRepo),
458
- });
459
-
460
- const result = await getGitHubRepoInfo("https://github.com/user/test-repo");
461
-
462
- expect(result).toEqual({
463
- name: "test-repo",
464
- description: "A test repository",
465
- icon: "https://example.com/avatar.png",
466
- });
467
- });
468
-
469
- test("should return null for invalid GitHub URL", async () => {
470
- const result = await getGitHubRepoInfo("https://gitlab.com/user/repo");
471
- expect(result).toBeNull();
472
- });
473
-
474
- test("should handle fetch errors", async () => {
475
- fetchSpy.mockRejectedValue(new Error("Network error"));
476
-
477
- const result = await getGitHubRepoInfo("https://github.com/user/repo");
478
-
479
- expect(result).toBeNull();
480
- expect(consoleSpy).toHaveBeenCalled();
481
- });
482
-
483
- test("should handle non-ok response", async () => {
484
- fetchSpy.mockResolvedValue({
485
- ok: false,
486
- statusText: "Not Found",
487
- });
488
-
489
- const result = await getGitHubRepoInfo("https://github.com/user/nonexistent");
490
-
491
- expect(result).toBeNull();
492
- expect(consoleSpy).toHaveBeenCalled();
493
- });
494
- });
495
-
496
- // FILE REFERENCES TESTS (simplified due to complex file system interactions)
497
- describe("resolveFileReferences", () => {
498
- test("should return original value for missing files", async () => {
499
- existsSyncSpy.mockReturnValue(false);
500
-
501
- const config = { notes: "@missing.txt" };
502
- const result = await resolveFileReferences(config);
503
-
504
- expect(result.notes).toBe("@missing.txt");
505
- });
506
-
507
- test("should handle non-file reference strings", async () => {
508
- const config = {
509
- normal: "not a file reference",
510
- email: "user@example.com",
511
- };
512
-
513
- const result = await resolveFileReferences(config);
514
-
515
- expect(result).toEqual(config);
516
- });
517
-
518
- test("should handle unsupported file extensions", async () => {
519
- existsSyncSpy.mockReturnValue(true);
520
-
521
- const config = { data: "@data.exe" };
522
- const result = await resolveFileReferences(config);
523
-
524
- expect(result.data).toBe("@data.exe");
525
- });
526
- });
527
-
528
- // PATH DISCOVERY TESTS
529
- describe("getAvailablePaths", () => {
530
- test("should return current directory contents for empty input", () => {
531
- readdirSyncSpy.mockReturnValue([
532
- { name: "file1.txt", isDirectory: () => false },
533
- { name: "folder1", isDirectory: () => true },
534
- ]);
535
-
536
- const result = getAvailablePaths("");
537
-
538
- expect(result).toHaveLength(2);
539
- expect(result[0].description).toBe("📁 Directory");
540
- expect(result[1].description).toBe("📄 File");
541
- });
542
-
543
- test("should handle absolute paths", () => {
544
- readdirSyncSpy.mockReturnValue([{ name: "config.yaml", isDirectory: () => false }]);
545
-
546
- const result = getAvailablePaths("/etc/config");
547
-
548
- expect(result).toEqual(
549
- expect.arrayContaining([
550
- expect.objectContaining({
551
- name: expect.stringContaining("config.yaml"),
552
- description: "📄 File",
553
- }),
554
- ]),
555
- );
556
- });
557
-
558
- test("should filter results by search term", () => {
559
- readdirSyncSpy.mockReturnValue([
560
- { name: "test.js", isDirectory: () => false },
561
- { name: "example.md", isDirectory: () => false },
562
- { name: "test-utils.js", isDirectory: () => false },
563
- ]);
564
-
565
- const result = getAvailablePaths("test");
566
-
567
- expect(result.length).toBeGreaterThan(0);
568
- expect(result.every((item) => item.name.toLowerCase().includes("test"))).toBe(true);
569
- });
570
- });
571
-
572
- // PROJECT INFO TESTS
573
- describe("getProjectInfo", () => {
574
- test("should get project info from git repository", async () => {
575
- execSyncSpy.mockReturnValue("https://github.com/user/repo.git\n");
576
- fetchSpy.mockResolvedValue({
577
- ok: true,
578
- json: () =>
579
- Promise.resolve({
580
- name: "repo",
581
- description: "Test repository",
582
- owner: { avatar_url: "https://avatar.png" },
583
- }),
584
- });
585
-
586
- const result = await getProjectInfo();
587
-
588
- expect(result.name).toBe("repo");
589
- expect(result.description).toBe("Test repository");
590
- expect(result.icon).toBe("https://avatar.png");
591
- expect(result.fromGitHub).toBe(true);
592
- });
593
-
594
- test("should fallback to directory name for non-git projects", async () => {
595
- execSyncSpy.mockImplementation(() => {
596
- throw new Error("Not a git repository");
597
- });
598
- processSpies.cwd.mockReturnValue("/home/user/my-project");
599
-
600
- const result = await getProjectInfo();
601
-
602
- expect(result.name).toBe("my-project");
603
- expect(result.fromGitHub).toBe(false);
604
- });
605
- });
606
-
607
- // FILE CHANGE DETECTION TESTS
608
- describe("hasFileChangesBetweenCommits", () => {
609
- test("should detect added files", () => {
610
- execSyncSpy.mockReturnValue("A\tnew-file.md\nM\texisting.js\n");
611
-
612
- const result = hasFileChangesBetweenCommits("abc123", "def456");
613
-
614
- expect(result).toBe(true);
615
- });
616
-
617
- test("should detect deleted files", () => {
618
- execSyncSpy.mockReturnValue("D\tdeleted-file.md\nM\texisting.js\n");
619
-
620
- const result = hasFileChangesBetweenCommits("abc123", "def456");
621
-
622
- expect(result).toBe(true);
623
- });
624
-
625
- test("should ignore modifications", () => {
626
- execSyncSpy.mockReturnValue("M\texisting.js\nM\tanother.ts\n");
627
-
628
- const result = hasFileChangesBetweenCommits("abc123", "def456");
629
-
630
- expect(result).toBe(false);
631
- });
632
-
633
- test("should handle git errors", () => {
634
- execSyncSpy.mockImplementation(() => {
635
- throw new Error("Git error");
636
- });
637
-
638
- const result = hasFileChangesBetweenCommits("abc123", "def456");
639
-
640
- expect(result).toBe(false);
641
- expect(consoleSpy).toHaveBeenCalled();
642
- });
643
- });
644
-
645
- describe("saveGitHeadToConfig", () => {
646
- test("should skip if gitHead is null", async () => {
647
- await saveGitHeadToConfig(null);
648
- expect(writeFileSpy).not.toHaveBeenCalled();
649
- });
650
-
651
- test("should skip if in test environment", async () => {
652
- process.env.NODE_ENV = "test";
653
- await saveGitHeadToConfig("abc123");
654
- expect(writeFileSpy).not.toHaveBeenCalled();
655
- });
656
-
657
- test("should handle file operation errors", async () => {
658
- delete process.env.NODE_ENV;
659
- existsSyncSpy.mockImplementation(() => {
660
- throw new Error("File system error");
661
- });
662
-
663
- await saveGitHeadToConfig("abc123");
664
-
665
- expect(consoleSpy).toHaveBeenCalledWith(
666
- "Failed to save git HEAD to config.yaml:",
667
- "File system error",
668
- );
669
- });
670
- });
671
-
672
- describe("saveValueToConfig", () => {
673
- test("should skip if value is undefined", async () => {
674
- await saveValueToConfig("key", undefined);
675
- expect(writeFileSpy).not.toHaveBeenCalled();
676
- });
677
-
678
- test("should handle file operation errors", async () => {
679
- existsSyncSpy.mockImplementation(() => {
680
- throw new Error("File system error");
681
- });
682
-
683
- await saveValueToConfig("key", "value");
684
-
685
- expect(consoleSpy).toHaveBeenCalledWith(
686
- "Failed to save key to config.yaml:",
687
- "File system error",
688
- );
689
- });
690
- });
691
-
692
- // CONFIGURATION PROCESSING TESTS
693
- describe("processConfigFields", () => {
694
- test("should apply default values for missing fields", () => {
695
- const config = {};
696
- const result = processConfigFields(config);
697
-
698
- expect(result.nodeName).toBe("Section");
699
- expect(result.locale).toBe("en");
700
- expect(result.sourcesPath).toEqual(["./"]);
701
- expect(result.docsDir).toBe("./.aigne/doc-smith/docs");
702
- expect(result.outputDir).toBe("./.aigne/doc-smith/output");
703
- expect(result.translateLanguages).toEqual([]);
704
- expect(result.rules).toBe("");
705
- expect(result.targetAudience).toBe("");
706
- });
707
-
708
- test("should process document purpose with valid key", () => {
709
- const config = {
710
- documentPurpose: ["getStarted"],
711
- };
712
- const result = processConfigFields(config);
713
-
714
- expect(result.rules).toContain("Document Purpose");
715
- });
716
-
717
- test("should process target audience types with valid key", () => {
718
- const config = {
719
- targetAudienceTypes: ["developers"],
720
- };
721
- const result = processConfigFields(config);
722
-
723
- expect(result.rules).toContain("Target Audience");
724
- expect(result.targetAudience).toContain("Developers");
725
- });
726
-
727
- test("should combine existing rules with processed content", () => {
728
- const config = {
729
- rules: "Existing rules",
730
- documentPurpose: ["getStarted"],
731
- };
732
- const result = processConfigFields(config);
733
-
734
- expect(result.rules).toContain("Existing rules");
735
- expect(result.rules).toContain("Document Purpose");
736
- });
737
- });
738
-
739
- // PATH DISCOVERY EDGE CASES
740
- describe("getAvailablePaths edge cases", () => {
741
- test("should handle directory reading errors", () => {
742
- readdirSyncSpy.mockImplementation(() => {
743
- throw new Error("Permission denied");
744
- });
745
-
746
- const result = getAvailablePaths("");
747
-
748
- expect(result).toEqual([]);
749
- expect(consoleSpy).toHaveBeenCalled();
750
- });
751
-
752
- test("should handle non-existent directory in relative path", () => {
753
- existsSyncSpy.mockReturnValue(false);
754
-
755
- const result = getAvailablePaths("./nonexistent/");
756
-
757
- expect(result).toEqual(
758
- expect.arrayContaining([
759
- expect.objectContaining({
760
- description: expect.stringContaining("does not exist"),
761
- }),
762
- ]),
763
- );
764
- });
765
-
766
- test("should handle path validation in relative path search", () => {
767
- existsSyncSpy.mockReturnValue(false);
768
-
769
- const result = getAvailablePaths("./invalid/path");
770
-
771
- expect(result).toEqual(
772
- expect.arrayContaining([
773
- expect.objectContaining({
774
- description: expect.stringContaining("Path does not exist"),
775
- }),
776
- ]),
777
- );
778
- });
779
-
780
- test("should add exact path match for valid files", () => {
781
- existsSyncSpy.mockReturnValue(true);
782
- accessSyncSpy.mockImplementation(() => {});
783
- statSyncSpy.mockReturnValue({ isDirectory: () => false });
784
- readdirSyncSpy.mockReturnValue([]);
785
-
786
- const result = getAvailablePaths("existing-file.txt");
787
-
788
- expect(result[0]).toEqual({
789
- name: "existing-file.txt",
790
- value: "existing-file.txt",
791
- description: "📄 File",
792
- });
793
- });
794
-
795
- test("should preserve ./ prefix in directory paths", () => {
796
- readdirSyncSpy.mockReturnValue([{ name: "test.js", isDirectory: () => false }]);
797
-
798
- const result = getAvailablePaths("");
799
-
800
- expect(result[0].name).toMatch(/^\.\//);
801
- });
802
- });
803
-
804
- // DOCUMENT MANAGEMENT TESTS
805
- describe("saveDocWithTranslations", () => {
806
- beforeEach(() => {
807
- // Reset mocks for each test
808
- mkdirSpy?.mockClear();
809
- writeFileSpy?.mockClear();
810
- });
811
-
812
- test("should save main document without translations", async () => {
813
- const params = {
814
- path: "/api/guide",
815
- content: "API Guide content",
816
- docsDir: "/docs",
817
- locale: "en",
818
- };
819
-
820
- const result = await saveDocWithTranslations(params);
821
-
822
- expect(result).toBeDefined();
823
- expect(Array.isArray(result)).toBe(true);
824
- expect(result).toHaveLength(1);
825
- expect(result[0]).toEqual({
826
- path: "/docs/api-guide.md",
827
- success: true,
828
- });
829
- });
830
-
831
- test("should save document with translations", async () => {
832
- const params = {
833
- path: "/api/guide",
834
- content: "API Guide content",
835
- docsDir: "/docs",
836
- locale: "en",
837
- translates: [{ language: "zh", translation: "API指南内容" }],
838
- };
839
-
840
- const result = await saveDocWithTranslations(params);
841
-
842
- expect(result).toHaveLength(2);
843
- expect(result.every((r) => r.success)).toBe(true);
844
- expect(result[0]).toEqual({
845
- path: "/docs/api-guide.md",
846
- success: true,
847
- });
848
- expect(result[1]).toEqual({
849
- path: "/docs/api-guide.zh.md",
850
- success: true,
851
- });
852
- });
853
-
854
- test("should skip main content when isTranslate is true", async () => {
855
- const params = {
856
- path: "/api/guide",
857
- content: "Content",
858
- docsDir: "/docs",
859
- locale: "en",
860
- isTranslate: true,
861
- translates: [{ language: "zh", translation: "翻译内容" }],
862
- };
863
-
864
- const result = await saveDocWithTranslations(params);
865
-
866
- expect(result).toHaveLength(1);
867
- expect(result[0]).toEqual({
868
- path: "/docs/api-guide.zh.md",
869
- success: true,
870
- });
871
- });
872
-
873
- test("should add labels front matter", async () => {
874
- const params = {
875
- path: "/guide",
876
- content: "Content",
877
- docsDir: "/docs",
878
- locale: "en",
879
- labels: ["test"],
880
- };
881
-
882
- const result = await saveDocWithTranslations(params);
883
-
884
- expect(result).toHaveLength(1);
885
- expect(result[0]).toEqual({
886
- path: "/docs/guide.md",
887
- success: true,
888
- });
889
- });
890
-
891
- test("should handle non-English locale", async () => {
892
- const params = {
893
- path: "/guide",
894
- content: "Contenu français",
895
- docsDir: "/docs",
896
- locale: "fr",
897
- };
898
-
899
- const result = await saveDocWithTranslations(params);
900
-
901
- expect(result).toHaveLength(1);
902
- expect(result[0]).toEqual({
903
- path: "/docs/guide.fr.md",
904
- success: true,
905
- });
906
- });
907
-
908
- test("should flatten complex paths", async () => {
909
- const params = {
910
- path: "/deep/nested/path",
911
- content: "Content",
912
- docsDir: "/docs",
913
- locale: "en",
914
- };
915
-
916
- const result = await saveDocWithTranslations(params);
917
-
918
- expect(result).toHaveLength(1);
919
- expect(result[0]).toEqual({
920
- path: "/docs/deep-nested-path.md",
921
- success: true,
922
- });
923
- });
924
-
925
- test("should handle errors gracefully", async () => {
926
- // Mock fs promises mkdir to throw error
927
- spyOn(fsPromisesDefault, "mkdir").mockRejectedValueOnce(new Error("Test error"));
928
-
929
- const params = {
930
- path: "/guide",
931
- content: "Content",
932
- docsDir: "/docs",
933
- locale: "en",
934
- };
935
-
936
- const result = await saveDocWithTranslations(params);
937
-
938
- expect(result).toEqual([{ path: "/guide", success: false, error: "Test error" }]);
939
- });
940
- });
941
- });