@aigne/doc-smith 0.8.12-beta.8 → 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 (264) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/agents/publish/index.yaml +4 -0
  3. package/agents/publish/publish-docs.mjs +77 -5
  4. package/agents/publish/translate-meta.mjs +103 -0
  5. package/agents/update/generate-document.yaml +30 -28
  6. package/agents/update/update-document-detail.yaml +3 -1
  7. package/agents/utils/update-branding.mjs +69 -0
  8. package/package.json +16 -2
  9. package/prompts/common/document/role-and-personality.md +3 -1
  10. package/prompts/detail/d2-diagram/guide.md +7 -1
  11. package/prompts/detail/d2-diagram/user-prompt.md +3 -0
  12. package/prompts/detail/generate/system-prompt.md +6 -7
  13. package/prompts/detail/generate/user-prompt.md +12 -3
  14. package/prompts/detail/update/user-prompt.md +0 -2
  15. package/prompts/structure/update/user-prompt.md +0 -4
  16. package/utils/file-utils.mjs +69 -24
  17. package/utils/markdown-checker.mjs +0 -20
  18. package/utils/request.mjs +7 -0
  19. package/utils/upload-files.mjs +231 -0
  20. package/utils/utils.mjs +11 -1
  21. package/.aigne/doc-smith/config.yaml +0 -77
  22. package/.aigne/doc-smith/history.yaml +0 -37
  23. package/.aigne/doc-smith/media-description.yaml +0 -91
  24. package/.aigne/doc-smith/output/structure-plan.json +0 -162
  25. package/.aigne/doc-smith/preferences.yml +0 -97
  26. package/.aigne/doc-smith/upload-cache.yaml +0 -1830
  27. package/.github/PULL_REQUEST_TEMPLATE.md +0 -28
  28. package/.github/workflows/ci.yml +0 -54
  29. package/.github/workflows/create-release-pr.yaml +0 -21
  30. package/.github/workflows/publish-docs.yml +0 -65
  31. package/.github/workflows/release.yml +0 -49
  32. package/.github/workflows/reviewer.yml +0 -54
  33. package/.release-please-manifest.json +0 -3
  34. package/RELEASE.md +0 -9
  35. package/assets/screenshots/doc-complete-setup.png +0 -0
  36. package/assets/screenshots/doc-generate-docs.png +0 -0
  37. package/assets/screenshots/doc-generate.png +0 -0
  38. package/assets/screenshots/doc-generated-successfully.png +0 -0
  39. package/assets/screenshots/doc-publish.png +0 -0
  40. package/assets/screenshots/doc-regenerate.png +0 -0
  41. package/assets/screenshots/doc-translate-langs.png +0 -0
  42. package/assets/screenshots/doc-translate.png +0 -0
  43. package/assets/screenshots/doc-update.png +0 -0
  44. package/biome.json +0 -73
  45. package/codecov.yml +0 -15
  46. package/docs/_sidebar.md +0 -15
  47. package/docs/configuration-initial-setup.ja.md +0 -179
  48. package/docs/configuration-initial-setup.md +0 -198
  49. package/docs/configuration-initial-setup.zh-TW.md +0 -179
  50. package/docs/configuration-initial-setup.zh.md +0 -179
  51. package/docs/configuration-managing-preferences.ja.md +0 -100
  52. package/docs/configuration-managing-preferences.md +0 -100
  53. package/docs/configuration-managing-preferences.zh-TW.md +0 -100
  54. package/docs/configuration-managing-preferences.zh.md +0 -100
  55. package/docs/configuration.ja.md +0 -69
  56. package/docs/configuration.md +0 -69
  57. package/docs/configuration.zh-TW.md +0 -69
  58. package/docs/configuration.zh.md +0 -69
  59. package/docs/getting-started.ja.md +0 -107
  60. package/docs/getting-started.md +0 -107
  61. package/docs/getting-started.zh-TW.md +0 -107
  62. package/docs/getting-started.zh.md +0 -107
  63. package/docs/guides-cleaning-up.ja.md +0 -51
  64. package/docs/guides-cleaning-up.md +0 -52
  65. package/docs/guides-cleaning-up.zh-TW.md +0 -51
  66. package/docs/guides-cleaning-up.zh.md +0 -51
  67. package/docs/guides-evaluating-documents.ja.md +0 -66
  68. package/docs/guides-evaluating-documents.md +0 -107
  69. package/docs/guides-evaluating-documents.zh-TW.md +0 -66
  70. package/docs/guides-evaluating-documents.zh.md +0 -66
  71. package/docs/guides-generating-documentation.ja.md +0 -151
  72. package/docs/guides-generating-documentation.md +0 -89
  73. package/docs/guides-generating-documentation.zh-TW.md +0 -151
  74. package/docs/guides-generating-documentation.zh.md +0 -151
  75. package/docs/guides-interactive-chat.ja.md +0 -85
  76. package/docs/guides-interactive-chat.md +0 -93
  77. package/docs/guides-interactive-chat.zh-TW.md +0 -85
  78. package/docs/guides-interactive-chat.zh.md +0 -85
  79. package/docs/guides-managing-history.ja.md +0 -48
  80. package/docs/guides-managing-history.md +0 -53
  81. package/docs/guides-managing-history.zh-TW.md +0 -48
  82. package/docs/guides-managing-history.zh.md +0 -48
  83. package/docs/guides-publishing-your-docs.ja.md +0 -78
  84. package/docs/guides-publishing-your-docs.md +0 -83
  85. package/docs/guides-publishing-your-docs.zh-TW.md +0 -78
  86. package/docs/guides-publishing-your-docs.zh.md +0 -78
  87. package/docs/guides-translating-documentation.ja.md +0 -95
  88. package/docs/guides-translating-documentation.md +0 -100
  89. package/docs/guides-translating-documentation.zh-TW.md +0 -95
  90. package/docs/guides-translating-documentation.zh.md +0 -95
  91. package/docs/guides-updating-documentation.ja.md +0 -77
  92. package/docs/guides-updating-documentation.md +0 -79
  93. package/docs/guides-updating-documentation.zh-TW.md +0 -77
  94. package/docs/guides-updating-documentation.zh.md +0 -77
  95. package/docs/guides.ja.md +0 -32
  96. package/docs/guides.md +0 -32
  97. package/docs/guides.zh-TW.md +0 -32
  98. package/docs/guides.zh.md +0 -32
  99. package/docs/overview.ja.md +0 -61
  100. package/docs/overview.md +0 -61
  101. package/docs/overview.zh-TW.md +0 -61
  102. package/docs/overview.zh.md +0 -61
  103. package/docs/release-notes.ja.md +0 -255
  104. package/docs/release-notes.md +0 -288
  105. package/docs/release-notes.zh-TW.md +0 -255
  106. package/docs/release-notes.zh.md +0 -255
  107. package/prompts/common/afs/afs-tools-usage.md +0 -5
  108. package/prompts/common/afs/use-afs-instruction.md +0 -1
  109. package/release-please-config.json +0 -14
  110. package/tests/agents/chat/chat.test.mjs +0 -46
  111. package/tests/agents/clear/choose-contents.test.mjs +0 -284
  112. package/tests/agents/clear/clear-auth-tokens.test.mjs +0 -268
  113. package/tests/agents/clear/clear-document-config.test.mjs +0 -167
  114. package/tests/agents/clear/clear-document-structure.test.mjs +0 -380
  115. package/tests/agents/clear/clear-generated-docs.test.mjs +0 -222
  116. package/tests/agents/evaluate/code-snippet.test.mjs +0 -163
  117. package/tests/agents/evaluate/fixtures/api-services.md +0 -87
  118. package/tests/agents/evaluate/fixtures/js-sdk.md +0 -94
  119. package/tests/agents/evaluate/generate-report.test.mjs +0 -312
  120. package/tests/agents/generate/check-document-structure.test.mjs +0 -45
  121. package/tests/agents/generate/check-need-generate-structure.test.mjs +0 -279
  122. package/tests/agents/generate/document-structure-tools/add-document.test.mjs +0 -449
  123. package/tests/agents/generate/document-structure-tools/delete-document.test.mjs +0 -410
  124. package/tests/agents/generate/document-structure-tools/generate-sub-structure.test.mjs +0 -277
  125. package/tests/agents/generate/document-structure-tools/move-document.test.mjs +0 -476
  126. package/tests/agents/generate/document-structure-tools/update-document.test.mjs +0 -548
  127. package/tests/agents/generate/generate-structure.test.mjs +0 -45
  128. package/tests/agents/generate/user-review-document-structure.test.mjs +0 -319
  129. package/tests/agents/history/view.test.mjs +0 -97
  130. package/tests/agents/init/init.test.mjs +0 -1657
  131. package/tests/agents/prefs/prefs.test.mjs +0 -431
  132. package/tests/agents/publish/publish-docs.test.mjs +0 -787
  133. package/tests/agents/translate/choose-language.test.mjs +0 -311
  134. package/tests/agents/translate/translate-document.test.mjs +0 -51
  135. package/tests/agents/update/check-document.test.mjs +0 -463
  136. package/tests/agents/update/check-update-is-single.test.mjs +0 -300
  137. package/tests/agents/update/document-tools/update-document-content.test.mjs +0 -329
  138. package/tests/agents/update/generate-document.test.mjs +0 -51
  139. package/tests/agents/update/save-and-translate-document.test.mjs +0 -369
  140. package/tests/agents/update/user-review-document.test.mjs +0 -582
  141. package/tests/agents/utils/action-success.test.mjs +0 -54
  142. package/tests/agents/utils/check-detail-result.test.mjs +0 -743
  143. package/tests/agents/utils/check-feedback-refiner.test.mjs +0 -478
  144. package/tests/agents/utils/choose-docs.test.mjs +0 -406
  145. package/tests/agents/utils/exit.test.mjs +0 -70
  146. package/tests/agents/utils/feedback-refiner.test.mjs +0 -51
  147. package/tests/agents/utils/find-item-by-path.test.mjs +0 -517
  148. package/tests/agents/utils/find-user-preferences-by-path.test.mjs +0 -382
  149. package/tests/agents/utils/format-document-structure.test.mjs +0 -364
  150. package/tests/agents/utils/fs.test.mjs +0 -267
  151. package/tests/agents/utils/load-sources.test.mjs +0 -1470
  152. package/tests/agents/utils/save-docs.test.mjs +0 -109
  153. package/tests/agents/utils/save-output.test.mjs +0 -315
  154. package/tests/agents/utils/save-single-doc.test.mjs +0 -364
  155. package/tests/agents/utils/transform-detail-datasources.test.mjs +0 -320
  156. package/tests/utils/auth-utils.test.mjs +0 -596
  157. package/tests/utils/blocklet.test.mjs +0 -336
  158. package/tests/utils/conflict-detector.test.mjs +0 -355
  159. package/tests/utils/constants.test.mjs +0 -295
  160. package/tests/utils/d2-utils.test.mjs +0 -437
  161. package/tests/utils/deploy.test.mjs +0 -399
  162. package/tests/utils/docs-finder-utils.test.mjs +0 -650
  163. package/tests/utils/file-utils.test.mjs +0 -521
  164. package/tests/utils/history-utils.test.mjs +0 -206
  165. package/tests/utils/kroki-utils.test.mjs +0 -646
  166. package/tests/utils/linter/fixtures/css/keyword-error.css +0 -1
  167. package/tests/utils/linter/fixtures/css/missing-semicolon.css +0 -1
  168. package/tests/utils/linter/fixtures/css/syntax-error.css +0 -1
  169. package/tests/utils/linter/fixtures/css/undeclare-variable.css +0 -1
  170. package/tests/utils/linter/fixtures/css/unused-variable.css +0 -2
  171. package/tests/utils/linter/fixtures/css/valid-code.css +0 -1
  172. package/tests/utils/linter/fixtures/dockerfile/keyword-error.dockerfile +0 -1
  173. package/tests/utils/linter/fixtures/dockerfile/missing-semicolon.dockerfile +0 -2
  174. package/tests/utils/linter/fixtures/dockerfile/syntax-error.dockerfile +0 -2
  175. package/tests/utils/linter/fixtures/dockerfile/undeclare-variable.dockerfile +0 -1
  176. package/tests/utils/linter/fixtures/dockerfile/unused-variable.dockerfile +0 -1
  177. package/tests/utils/linter/fixtures/dockerfile/valid-code.dockerfile +0 -2
  178. package/tests/utils/linter/fixtures/go/keyword-error.go +0 -5
  179. package/tests/utils/linter/fixtures/go/missing-semicolon.go +0 -5
  180. package/tests/utils/linter/fixtures/go/syntax-error.go +0 -6
  181. package/tests/utils/linter/fixtures/go/undeclare-variable.go +0 -5
  182. package/tests/utils/linter/fixtures/go/unused-variable.go +0 -5
  183. package/tests/utils/linter/fixtures/go/valid-code.go +0 -7
  184. package/tests/utils/linter/fixtures/js/keyword-error.js +0 -3
  185. package/tests/utils/linter/fixtures/js/missing-semicolon.js +0 -6
  186. package/tests/utils/linter/fixtures/js/syntax-error.js +0 -4
  187. package/tests/utils/linter/fixtures/js/undeclare-variable.js +0 -3
  188. package/tests/utils/linter/fixtures/js/unused-variable.js +0 -7
  189. package/tests/utils/linter/fixtures/js/valid-code.js +0 -15
  190. package/tests/utils/linter/fixtures/json/keyword-error.json +0 -1
  191. package/tests/utils/linter/fixtures/json/missing-semicolon.json +0 -1
  192. package/tests/utils/linter/fixtures/json/syntax-error.json +0 -1
  193. package/tests/utils/linter/fixtures/json/undeclare-variable.json +0 -1
  194. package/tests/utils/linter/fixtures/json/unused-variable.json +0 -1
  195. package/tests/utils/linter/fixtures/json/valid-code.json +0 -1
  196. package/tests/utils/linter/fixtures/jsx/keyword-error.jsx +0 -5
  197. package/tests/utils/linter/fixtures/jsx/missing-semicolon.jsx +0 -5
  198. package/tests/utils/linter/fixtures/jsx/syntax-error.jsx +0 -5
  199. package/tests/utils/linter/fixtures/jsx/undeclare-variable.jsx +0 -5
  200. package/tests/utils/linter/fixtures/jsx/unused-variable.jsx +0 -4
  201. package/tests/utils/linter/fixtures/jsx/valid-code.jsx +0 -5
  202. package/tests/utils/linter/fixtures/python/keyword-error.py +0 -3
  203. package/tests/utils/linter/fixtures/python/missing-semicolon.py +0 -2
  204. package/tests/utils/linter/fixtures/python/syntax-error.py +0 -3
  205. package/tests/utils/linter/fixtures/python/undeclare-variable.py +0 -3
  206. package/tests/utils/linter/fixtures/python/unused-variable.py +0 -6
  207. package/tests/utils/linter/fixtures/python/valid-code.py +0 -12
  208. package/tests/utils/linter/fixtures/ruby/keyword-error.rb +0 -2
  209. package/tests/utils/linter/fixtures/ruby/missing-semicolon.rb +0 -1
  210. package/tests/utils/linter/fixtures/ruby/syntax-error.rb +0 -2
  211. package/tests/utils/linter/fixtures/ruby/undeclare-variable.rb +0 -1
  212. package/tests/utils/linter/fixtures/ruby/unused-variable.rb +0 -2
  213. package/tests/utils/linter/fixtures/ruby/valid-code.rb +0 -1
  214. package/tests/utils/linter/fixtures/sass/keyword-error.sass +0 -2
  215. package/tests/utils/linter/fixtures/sass/missing-semicolon.sass +0 -3
  216. package/tests/utils/linter/fixtures/sass/syntax-error.sass +0 -3
  217. package/tests/utils/linter/fixtures/sass/undeclare-variable.sass +0 -2
  218. package/tests/utils/linter/fixtures/sass/unused-variable.sass +0 -4
  219. package/tests/utils/linter/fixtures/sass/valid-code.sass +0 -2
  220. package/tests/utils/linter/fixtures/scss/keyword-error.scss +0 -1
  221. package/tests/utils/linter/fixtures/scss/missing-semicolon.scss +0 -1
  222. package/tests/utils/linter/fixtures/scss/syntax-error.scss +0 -1
  223. package/tests/utils/linter/fixtures/scss/undeclare-variable.scss +0 -1
  224. package/tests/utils/linter/fixtures/scss/unused-variable.scss +0 -2
  225. package/tests/utils/linter/fixtures/scss/valid-code.scss +0 -1
  226. package/tests/utils/linter/fixtures/shell/keyword-error.sh +0 -5
  227. package/tests/utils/linter/fixtures/shell/missing-semicolon.sh +0 -3
  228. package/tests/utils/linter/fixtures/shell/syntax-error.sh +0 -4
  229. package/tests/utils/linter/fixtures/shell/undeclare-variable.sh +0 -3
  230. package/tests/utils/linter/fixtures/shell/unused-variable.sh +0 -4
  231. package/tests/utils/linter/fixtures/shell/valid-code.sh +0 -3
  232. package/tests/utils/linter/fixtures/ts/keyword-error.ts +0 -1
  233. package/tests/utils/linter/fixtures/ts/missing-semicolon.ts +0 -1
  234. package/tests/utils/linter/fixtures/ts/syntax-error.ts +0 -1
  235. package/tests/utils/linter/fixtures/ts/undeclare-variable.ts +0 -1
  236. package/tests/utils/linter/fixtures/ts/unused-variable.ts +0 -3
  237. package/tests/utils/linter/fixtures/ts/valid-code.ts +0 -3
  238. package/tests/utils/linter/fixtures/tsx/keyword-error.tsx +0 -5
  239. package/tests/utils/linter/fixtures/tsx/missing-semicolon.tsx +0 -5
  240. package/tests/utils/linter/fixtures/tsx/syntax-error.tsx +0 -5
  241. package/tests/utils/linter/fixtures/tsx/undeclare-variable.tsx +0 -6
  242. package/tests/utils/linter/fixtures/tsx/unused-variable.tsx +0 -6
  243. package/tests/utils/linter/fixtures/tsx/valid-code.tsx +0 -5
  244. package/tests/utils/linter/fixtures/vue/keyword-error.vue +0 -6
  245. package/tests/utils/linter/fixtures/vue/missing-semicolon.vue +0 -6
  246. package/tests/utils/linter/fixtures/vue/syntax-error.vue +0 -6
  247. package/tests/utils/linter/fixtures/vue/undeclare-variable.vue +0 -6
  248. package/tests/utils/linter/fixtures/vue/unused-variable.vue +0 -7
  249. package/tests/utils/linter/fixtures/vue/valid-code.vue +0 -6
  250. package/tests/utils/linter/fixtures/yaml/keyword-error.yml +0 -1
  251. package/tests/utils/linter/fixtures/yaml/missing-semicolon.yml +0 -2
  252. package/tests/utils/linter/fixtures/yaml/syntax-error.yml +0 -1
  253. package/tests/utils/linter/fixtures/yaml/undeclare-variable.yml +0 -1
  254. package/tests/utils/linter/fixtures/yaml/unused-variable.yml +0 -2
  255. package/tests/utils/linter/fixtures/yaml/valid-code.yml +0 -3
  256. package/tests/utils/linter/index.test.mjs +0 -440
  257. package/tests/utils/linter/scan-results.mjs +0 -42
  258. package/tests/utils/load-config.test.mjs +0 -141
  259. package/tests/utils/markdown/index.test.mjs +0 -478
  260. package/tests/utils/mermaid-validator.test.mjs +0 -541
  261. package/tests/utils/mock-chat-model.mjs +0 -12
  262. package/tests/utils/preferences-utils.test.mjs +0 -465
  263. package/tests/utils/save-value-to-config.test.mjs +0 -483
  264. package/tests/utils/utils.test.mjs +0 -941
@@ -1,650 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, spyOn, test } from "bun:test";
2
- import * as fs from "node:fs/promises";
3
- import * as path from "node:path";
4
- import {
5
- addFeedbackToItems,
6
- fileNameToFlatPath,
7
- findItemByFlatName,
8
- findItemByPath,
9
- generateFileName,
10
- getActionText,
11
- getMainLanguageFiles,
12
- processSelectedFiles,
13
- readFileContent,
14
- } from "../../utils/docs-finder-utils.mjs";
15
-
16
- describe("docs-finder-utils", () => {
17
- let readdirSpy;
18
- let readFileSpy;
19
- let accessSpy;
20
- let joinSpy;
21
- let consoleWarnSpy;
22
-
23
- beforeEach(() => {
24
- // Mock file system operations
25
- readdirSpy = spyOn(fs, "readdir").mockResolvedValue([]);
26
- readFileSpy = spyOn(fs, "readFile").mockResolvedValue("test content");
27
- accessSpy = spyOn(fs, "access").mockResolvedValue(undefined);
28
- joinSpy = spyOn(path, "join").mockImplementation((...paths) => paths.join("/"));
29
-
30
- // Mock console methods
31
- consoleWarnSpy = spyOn(console, "warn").mockImplementation(() => {});
32
- });
33
-
34
- afterEach(() => {
35
- readdirSpy?.mockRestore();
36
- readFileSpy?.mockRestore();
37
- accessSpy?.mockRestore();
38
- joinSpy?.mockRestore();
39
- consoleWarnSpy?.mockRestore();
40
- });
41
-
42
- // UTILITY FUNCTIONS TESTS
43
- describe("getActionText", () => {
44
- test("should return 'update' action for non-translate", () => {
45
- const result = getActionText(false, "Please {action} the docs");
46
- expect(result).toBe("Please update the docs");
47
- });
48
-
49
- test("should return 'translate' action for translate", () => {
50
- const result = getActionText(true, "Please {action} the docs");
51
- expect(result).toBe("Please translate the docs");
52
- });
53
-
54
- test("should handle multiple action placeholders", () => {
55
- const result = getActionText(false, "{action} docs, then {action} more");
56
- expect(result).toBe("update docs, then {action} more");
57
- });
58
-
59
- test("should handle text without action placeholder", () => {
60
- const result = getActionText(true, "No placeholders here");
61
- expect(result).toBe("No placeholders here");
62
- });
63
-
64
- test("should handle empty text", () => {
65
- const result = getActionText(false, "");
66
- expect(result).toBe("");
67
- });
68
- });
69
-
70
- describe("fileNameToFlatPath", () => {
71
- test("should remove .md extension", () => {
72
- const result = fileNameToFlatPath("overview.md");
73
- expect(result).toBe("overview");
74
- });
75
-
76
- test("should remove language suffix and .md extension", () => {
77
- const result = fileNameToFlatPath("overview.zh.md");
78
- expect(result).toBe("overview");
79
- });
80
-
81
- test("should remove complex language suffix", () => {
82
- const result = fileNameToFlatPath("overview.zh-CN.md");
83
- expect(result).toBe("overview");
84
- });
85
-
86
- test("should handle files without language suffix", () => {
87
- const result = fileNameToFlatPath("getting-started.md");
88
- expect(result).toBe("getting-started");
89
- });
90
-
91
- test("should handle multiple dots in filename", () => {
92
- const result = fileNameToFlatPath("api.v1.guide.fr.md");
93
- expect(result).toBe("api.v1.guide");
94
- });
95
-
96
- test("should handle files without .md extension", () => {
97
- const result = fileNameToFlatPath("overview");
98
- expect(result).toBe("overview");
99
- });
100
- });
101
-
102
- describe("findItemByFlatName", () => {
103
- const documentStructure = [
104
- { path: "/getting-started", title: "Getting Started" },
105
- { path: "/api/overview", title: "API Overview" },
106
- { path: "/guides/advanced", title: "Advanced Guide" },
107
- ];
108
-
109
- test("should find item by exact flat name match", () => {
110
- const result = findItemByFlatName(documentStructure, "getting-started");
111
- expect(result).toEqual({ path: "/getting-started", title: "Getting Started" });
112
- });
113
-
114
- test("should find item with nested path", () => {
115
- const result = findItemByFlatName(documentStructure, "api-overview");
116
- expect(result).toEqual({ path: "/api/overview", title: "API Overview" });
117
- });
118
-
119
- test("should find item with deep nested path", () => {
120
- const result = findItemByFlatName(documentStructure, "guides-advanced");
121
- expect(result).toEqual({ path: "/guides/advanced", title: "Advanced Guide" });
122
- });
123
-
124
- test("should return undefined for non-existent item", () => {
125
- const result = findItemByFlatName(documentStructure, "non-existent");
126
- expect(result).toBeUndefined();
127
- });
128
-
129
- test("should handle empty documentation structure", () => {
130
- const result = findItemByFlatName([], "any-name");
131
- expect(result).toBeUndefined();
132
- });
133
- });
134
-
135
- describe("addFeedbackToItems", () => {
136
- const items = [
137
- { path: "/guide1", title: "Guide 1" },
138
- { path: "/guide2", title: "Guide 2" },
139
- ];
140
-
141
- test("should add feedback to all items", () => {
142
- const result = addFeedbackToItems(items, "Please improve");
143
- expect(result).toEqual([
144
- { path: "/guide1", title: "Guide 1", feedback: "Please improve" },
145
- { path: "/guide2", title: "Guide 2", feedback: "Please improve" },
146
- ]);
147
- });
148
-
149
- test("should trim feedback text", () => {
150
- const result = addFeedbackToItems(items, " Please improve ");
151
- expect(result).toEqual([
152
- { path: "/guide1", title: "Guide 1", feedback: "Please improve" },
153
- { path: "/guide2", title: "Guide 2", feedback: "Please improve" },
154
- ]);
155
- });
156
-
157
- test("should return original items for empty feedback", () => {
158
- const result = addFeedbackToItems(items, "");
159
- expect(result).toEqual(items);
160
- });
161
-
162
- test("should return original items for whitespace-only feedback", () => {
163
- const result = addFeedbackToItems(items, " ");
164
- expect(result).toEqual(items);
165
- });
166
-
167
- test("should return original items for null feedback", () => {
168
- const result = addFeedbackToItems(items, null);
169
- expect(result).toEqual(items);
170
- });
171
-
172
- test("should return original items for undefined feedback", () => {
173
- const result = addFeedbackToItems(items, undefined);
174
- expect(result).toEqual(items);
175
- });
176
-
177
- test("should handle empty items array", () => {
178
- const result = addFeedbackToItems([], "feedback");
179
- expect(result).toEqual([]);
180
- });
181
- });
182
-
183
- // FILE OPERATIONS TESTS
184
- describe("readFileContent", () => {
185
- test("should read file content successfully", async () => {
186
- readFileSpy.mockResolvedValue("file content");
187
-
188
- const result = await readFileContent("/docs", "test.md");
189
-
190
- expect(result).toBe("file content");
191
- expect(joinSpy).toHaveBeenCalledWith("/docs", "test.md");
192
- expect(readFileSpy).toHaveBeenCalledWith("/docs/test.md", "utf-8");
193
- });
194
-
195
- test("should return null and warn on read error", async () => {
196
- readFileSpy.mockRejectedValue(new Error("File not found"));
197
-
198
- const result = await readFileContent("/docs", "missing.md");
199
-
200
- expect(result).toBeNull();
201
- expect(consoleWarnSpy).toHaveBeenCalledWith(
202
- "⚠️ Could not read content from missing.md:",
203
- "File not found",
204
- );
205
- });
206
-
207
- test("should handle different file paths", async () => {
208
- readFileSpy.mockResolvedValue("content");
209
-
210
- await readFileContent("/path/to/docs", "nested/file.md");
211
-
212
- expect(joinSpy).toHaveBeenCalledWith("/path/to/docs", "nested/file.md");
213
- });
214
- });
215
-
216
- describe("getMainLanguageFiles", () => {
217
- test("should filter English files correctly", async () => {
218
- readdirSpy.mockResolvedValue([
219
- "overview.md",
220
- "overview.zh.md",
221
- "guide.md",
222
- "guide.fr.md",
223
- "_sidebar.md",
224
- "README.txt",
225
- ]);
226
-
227
- const result = await getMainLanguageFiles("/docs", "en");
228
-
229
- expect(result).toEqual(["guide.md", "overview.md"]);
230
- });
231
-
232
- test("should filter non-English files correctly", async () => {
233
- readdirSpy.mockResolvedValue([
234
- "overview.md",
235
- "overview.zh.md",
236
- "guide.md",
237
- "guide.zh.md",
238
- "intro.fr.md",
239
- "_sidebar.md",
240
- ]);
241
-
242
- const result = await getMainLanguageFiles("/docs", "zh");
243
-
244
- expect(result).toEqual(["guide.zh.md", "overview.zh.md"]);
245
- });
246
-
247
- test("should sort files by documentation structure order", async () => {
248
- readdirSpy.mockResolvedValue(["guide.md", "overview.md", "advanced.md"]);
249
-
250
- const documentStructure = [{ path: "/overview" }, { path: "/guide" }, { path: "/advanced" }];
251
-
252
- const result = await getMainLanguageFiles("/docs", "en", documentStructure);
253
-
254
- expect(result).toEqual(["overview.md", "guide.md", "advanced.md"]);
255
- });
256
-
257
- test("should handle files not in documentation structure", async () => {
258
- readdirSpy.mockResolvedValue(["guide.md", "extra.md", "overview.md"]);
259
-
260
- const documentStructure = [{ path: "/overview" }, { path: "/guide" }];
261
-
262
- const result = await getMainLanguageFiles("/docs", "en", documentStructure);
263
-
264
- expect(result).toEqual(["overview.md", "guide.md", "extra.md"]);
265
- });
266
-
267
- test("should handle complex language suffixes", async () => {
268
- readdirSpy.mockResolvedValue(["overview.zh-CN.md", "guide.zh-TW.md", "intro.zh.md"]);
269
-
270
- const result = await getMainLanguageFiles("/docs", "zh");
271
-
272
- expect(result).toEqual(["intro.zh.md"]);
273
- });
274
-
275
- test("should exclude _sidebar.md files", async () => {
276
- readdirSpy.mockResolvedValue(["_sidebar.md", "_sidebar.zh.md", "overview.md"]);
277
-
278
- const result = await getMainLanguageFiles("/docs", "en");
279
-
280
- expect(result).toEqual(["overview.md"]);
281
- });
282
- });
283
-
284
- // COMPLEX ITEM FINDING TESTS
285
- describe("findItemByPath", () => {
286
- const documentStructure = [
287
- { path: "/getting-started", title: "Getting Started", description: "Intro" },
288
- { path: "/api/overview", title: "API Overview", category: "API" },
289
- { path: "/guides/advanced", title: "Advanced Guide", tags: ["advanced"] },
290
- ];
291
-
292
- test("should find item by direct path match", async () => {
293
- const result = await findItemByPath(documentStructure, "/getting-started", null, null);
294
-
295
- expect(result).toEqual({
296
- path: "/getting-started",
297
- title: "Getting Started",
298
- description: "Intro",
299
- });
300
- });
301
-
302
- test("should find item by .md filename", async () => {
303
- readFileSpy.mockResolvedValue("file content");
304
-
305
- const result = await findItemByPath(
306
- documentStructure,
307
- "api-overview.md",
308
- null,
309
- "/docs",
310
- "en",
311
- );
312
-
313
- expect(result).toEqual({
314
- path: "/api/overview",
315
- title: "API Overview",
316
- category: "API",
317
- content: "file content",
318
- });
319
- expect(readFileSpy).toHaveBeenCalledWith("/docs/api-overview.md", "utf-8");
320
- });
321
-
322
- test("should find item by boardId-flattened format", async () => {
323
- const result = await findItemByPath(
324
- documentStructure,
325
- "board123-guides-advanced",
326
- "board123",
327
- null,
328
- );
329
-
330
- expect(result).toEqual({
331
- path: "/guides/advanced",
332
- title: "Advanced Guide",
333
- tags: ["advanced"],
334
- });
335
- });
336
-
337
- test("should handle non-English locale for filename generation", async () => {
338
- readFileSpy.mockResolvedValue("Chinese content");
339
-
340
- const result = await findItemByPath(
341
- documentStructure,
342
- "/getting-started",
343
- null,
344
- "/docs",
345
- "zh",
346
- );
347
-
348
- expect(result).toEqual({
349
- path: "/getting-started",
350
- title: "Getting Started",
351
- description: "Intro",
352
- content: "Chinese content",
353
- });
354
- expect(readFileSpy).toHaveBeenCalledWith("/docs/getting-started.zh.md", "utf-8");
355
- });
356
-
357
- test("should return null for non-existent path", async () => {
358
- const result = await findItemByPath(documentStructure, "/non-existent", null, null);
359
- expect(result).toBeNull();
360
- });
361
-
362
- test("should return null for invalid boardId format", async () => {
363
- const result = await findItemByPath(documentStructure, "invalid-format", "board123", null);
364
- expect(result).toBeNull();
365
- });
366
-
367
- test("should handle file read errors gracefully", async () => {
368
- readFileSpy.mockRejectedValue(new Error("File not found"));
369
-
370
- const result = await findItemByPath(documentStructure, "/getting-started", null, "/docs");
371
-
372
- expect(result).toEqual({
373
- path: "/getting-started",
374
- title: "Getting Started",
375
- description: "Intro",
376
- });
377
- expect(consoleWarnSpy).toHaveBeenCalled();
378
- });
379
-
380
- test("should not read content when docsDir is not provided", async () => {
381
- const result = await findItemByPath(documentStructure, "/getting-started", null, null);
382
-
383
- expect(result).toEqual({
384
- path: "/getting-started",
385
- title: "Getting Started",
386
- description: "Intro",
387
- });
388
- expect(readFileSpy).not.toHaveBeenCalled();
389
- });
390
-
391
- test("should handle .md filename with language suffix", async () => {
392
- readFileSpy.mockResolvedValue("localized content");
393
-
394
- const result = await findItemByPath(
395
- documentStructure,
396
- "getting-started.zh.md",
397
- null,
398
- "/docs",
399
- );
400
-
401
- expect(result).toEqual({
402
- path: "/getting-started",
403
- title: "Getting Started",
404
- description: "Intro",
405
- content: "localized content",
406
- });
407
- });
408
-
409
- test("should handle boardId with special characters", async () => {
410
- const specialStructure = [{ path: "/test-guide", title: "Test Guide" }];
411
-
412
- const result = await findItemByPath(
413
- specialStructure,
414
- "special-board_123-test-guide",
415
- "special-board_123",
416
- null,
417
- );
418
-
419
- expect(result).toEqual({
420
- path: "/test-guide",
421
- title: "Test Guide",
422
- });
423
- });
424
- });
425
-
426
- describe("processSelectedFiles", () => {
427
- const documentStructure = [
428
- { path: "/overview", title: "Overview" },
429
- { path: "/api/guide", title: "API Guide" },
430
- { path: "/advanced", title: "Advanced" },
431
- ];
432
-
433
- test("should process selected files successfully", async () => {
434
- readFileSpy
435
- .mockResolvedValueOnce("overview content")
436
- .mockResolvedValueOnce("api guide content");
437
-
438
- const selectedFiles = ["overview.md", "api-guide.md"];
439
- const result = await processSelectedFiles(selectedFiles, documentStructure, "/docs");
440
-
441
- expect(result).toEqual([
442
- {
443
- path: "/overview",
444
- title: "Overview",
445
- content: "overview content",
446
- },
447
- {
448
- path: "/api/guide",
449
- title: "API Guide",
450
- content: "api guide content",
451
- },
452
- ]);
453
- });
454
-
455
- test("should handle files with language suffixes", async () => {
456
- readFileSpy.mockResolvedValue("localized content");
457
-
458
- const selectedFiles = ["overview.zh.md"];
459
- const result = await processSelectedFiles(selectedFiles, documentStructure, "/docs");
460
-
461
- expect(result).toEqual([
462
- {
463
- path: "/overview",
464
- title: "Overview",
465
- content: "localized content",
466
- },
467
- ]);
468
- });
469
-
470
- test("should handle files without content", async () => {
471
- readFileSpy.mockResolvedValue(null);
472
-
473
- const selectedFiles = ["overview.md"];
474
- const result = await processSelectedFiles(selectedFiles, documentStructure, "/docs");
475
-
476
- expect(result).toEqual([
477
- {
478
- path: "/overview",
479
- title: "Overview",
480
- },
481
- ]);
482
- });
483
-
484
- test("should warn for files not in documentation structure", async () => {
485
- readFileSpy.mockResolvedValue("content");
486
-
487
- const selectedFiles = ["unknown.md"];
488
- const result = await processSelectedFiles(selectedFiles, documentStructure, "/docs");
489
-
490
- expect(result).toEqual([]);
491
- expect(consoleWarnSpy).toHaveBeenCalledWith(
492
- "⚠️ No documentation structure item found for file: unknown.md",
493
- );
494
- });
495
-
496
- test("should handle mixed valid and invalid files", async () => {
497
- readFileSpy
498
- .mockResolvedValueOnce("overview content")
499
- .mockResolvedValueOnce("unknown content");
500
-
501
- const selectedFiles = ["overview.md", "unknown.md"];
502
- const result = await processSelectedFiles(selectedFiles, documentStructure, "/docs");
503
-
504
- expect(result).toEqual([
505
- {
506
- path: "/overview",
507
- title: "Overview",
508
- content: "overview content",
509
- },
510
- ]);
511
- expect(consoleWarnSpy).toHaveBeenCalledWith(
512
- "⚠️ No documentation structure item found for file: unknown.md",
513
- );
514
- });
515
-
516
- test("should handle empty selected files array", async () => {
517
- const result = await processSelectedFiles([], documentStructure, "/docs");
518
- expect(result).toEqual([]);
519
- });
520
-
521
- test("should handle file read errors", async () => {
522
- readFileSpy.mockRejectedValue(new Error("Read error"));
523
-
524
- const selectedFiles = ["overview.md"];
525
- const result = await processSelectedFiles(selectedFiles, documentStructure, "/docs");
526
-
527
- expect(result).toEqual([
528
- {
529
- path: "/overview",
530
- title: "Overview",
531
- },
532
- ]);
533
- });
534
-
535
- test("should preserve item properties", async () => {
536
- const complexStructure = [
537
- {
538
- path: "/overview",
539
- title: "Overview",
540
- description: "Main overview",
541
- category: "intro",
542
- tags: ["getting-started"],
543
- order: 1,
544
- },
545
- ];
546
-
547
- readFileSpy.mockResolvedValue("content");
548
-
549
- const selectedFiles = ["overview.md"];
550
- const result = await processSelectedFiles(selectedFiles, complexStructure, "/docs");
551
-
552
- expect(result).toEqual([
553
- {
554
- path: "/overview",
555
- title: "Overview",
556
- description: "Main overview",
557
- category: "intro",
558
- tags: ["getting-started"],
559
- order: 1,
560
- content: "content",
561
- },
562
- ]);
563
- });
564
- });
565
-
566
- // EDGE CASES AND ERROR HANDLING
567
- describe("edge cases", () => {
568
- test("getActionText should handle case-sensitive action placeholder", () => {
569
- const result = getActionText(true, "Please {ACTION} or {action} the docs");
570
- expect(result).toBe("Please {ACTION} or translate the docs");
571
- });
572
-
573
- test("fileNameToFlatPath should handle edge case filenames", () => {
574
- expect(fileNameToFlatPath("")).toBe("");
575
- expect(fileNameToFlatPath("file.with.many.dots.md")).toBe("file.with.many"); // removes last .dots as language suffix
576
- expect(fileNameToFlatPath("file-without-extension")).toBe("file-without-extension");
577
- expect(fileNameToFlatPath(".hidden.md")).toBe(""); // .hidden is treated as language suffix and removed
578
- });
579
-
580
- test("findItemByFlatName should handle paths with leading/trailing slashes", () => {
581
- const documentStructure = [
582
- { path: "no-leading-slash", title: "No Leading" },
583
- { path: "/normal-path", title: "Normal" },
584
- { path: "/trailing-slash/", title: "Trailing" },
585
- ];
586
-
587
- const result1 = findItemByFlatName(documentStructure, "no-leading-slash");
588
- const result2 = findItemByFlatName(documentStructure, "normal-path");
589
- const result3 = findItemByFlatName(documentStructure, "trailing-slash");
590
-
591
- expect(result1).toEqual({ path: "no-leading-slash", title: "No Leading" });
592
- expect(result2).toEqual({ path: "/normal-path", title: "Normal" });
593
- expect(result3).toBeUndefined(); // trailing slash breaks the flattening
594
- });
595
-
596
- test("addFeedbackToItems should handle items with existing feedback", () => {
597
- const itemsWithFeedback = [{ path: "/guide", title: "Guide", feedback: "Old feedback" }];
598
-
599
- const result = addFeedbackToItems(itemsWithFeedback, "New feedback");
600
-
601
- expect(result).toEqual([{ path: "/guide", title: "Guide", feedback: "New feedback" }]);
602
- });
603
-
604
- test("getMainLanguageFiles should handle empty directory", async () => {
605
- readdirSpy.mockResolvedValue([]);
606
-
607
- const result = await getMainLanguageFiles("/empty", "en");
608
-
609
- expect(result).toEqual([]);
610
- });
611
-
612
- test("getMainLanguageFiles should handle readdir errors", async () => {
613
- accessSpy.mockResolvedValue(undefined); // Directory exists
614
- readdirSpy.mockRejectedValue(new Error("Permission denied"));
615
-
616
- await expect(getMainLanguageFiles("/denied", "en")).rejects.toThrow("Permission denied");
617
- });
618
-
619
- test("getMainLanguageFiles should throw non-ENOENT access errors", async () => {
620
- const permissionError = new Error("Permission denied");
621
- permissionError.code = "EACCES";
622
- accessSpy.mockRejectedValue(permissionError);
623
-
624
- await expect(getMainLanguageFiles("/denied", "en")).rejects.toThrow("Permission denied");
625
- });
626
-
627
- test("processSelectedFiles should handle empty documentation structure", async () => {
628
- readFileSpy.mockResolvedValue("content");
629
-
630
- const result = await processSelectedFiles(["test.md"], [], "/docs");
631
-
632
- expect(result).toEqual([]);
633
- expect(consoleWarnSpy).toHaveBeenCalledWith(
634
- "⚠️ No documentation structure item found for file: test.md",
635
- );
636
- });
637
-
638
- test("fileNameToFlatPath should handle files with multiple language suffixes", () => {
639
- expect(fileNameToFlatPath("file.zh-CN.md")).toBe("file");
640
- expect(fileNameToFlatPath("file.en-US.md")).toBe("file");
641
- expect(fileNameToFlatPath("file.fr-FR.md")).toBe("file");
642
- });
643
-
644
- test("generateFileName should handle special characters in flatName", () => {
645
- expect(generateFileName("api-v1-guide", "en")).toBe("api-v1-guide.md");
646
- expect(generateFileName("api-v1-guide", "zh")).toBe("api-v1-guide.zh.md");
647
- expect(generateFileName("test_special-chars", "fr")).toBe("test_special-chars.fr.md");
648
- });
649
- });
650
- });