@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.
- package/CHANGELOG.md +13 -0
- package/agents/publish/index.yaml +4 -0
- package/agents/publish/publish-docs.mjs +77 -5
- package/agents/publish/translate-meta.mjs +103 -0
- package/agents/update/generate-document.yaml +30 -28
- package/agents/update/update-document-detail.yaml +3 -1
- package/agents/utils/update-branding.mjs +69 -0
- package/package.json +16 -2
- package/prompts/common/document/role-and-personality.md +3 -1
- package/prompts/detail/d2-diagram/guide.md +7 -1
- package/prompts/detail/d2-diagram/user-prompt.md +3 -0
- package/prompts/detail/generate/system-prompt.md +6 -7
- package/prompts/detail/generate/user-prompt.md +12 -3
- package/prompts/detail/update/user-prompt.md +0 -2
- package/prompts/structure/update/user-prompt.md +0 -4
- package/utils/file-utils.mjs +69 -24
- package/utils/markdown-checker.mjs +0 -20
- package/utils/request.mjs +7 -0
- package/utils/upload-files.mjs +231 -0
- package/utils/utils.mjs +11 -1
- package/.aigne/doc-smith/config.yaml +0 -77
- package/.aigne/doc-smith/history.yaml +0 -37
- package/.aigne/doc-smith/media-description.yaml +0 -91
- package/.aigne/doc-smith/output/structure-plan.json +0 -162
- package/.aigne/doc-smith/preferences.yml +0 -97
- package/.aigne/doc-smith/upload-cache.yaml +0 -1830
- package/.github/PULL_REQUEST_TEMPLATE.md +0 -28
- package/.github/workflows/ci.yml +0 -54
- package/.github/workflows/create-release-pr.yaml +0 -21
- package/.github/workflows/publish-docs.yml +0 -65
- package/.github/workflows/release.yml +0 -49
- package/.github/workflows/reviewer.yml +0 -54
- package/.release-please-manifest.json +0 -3
- package/RELEASE.md +0 -9
- package/assets/screenshots/doc-complete-setup.png +0 -0
- package/assets/screenshots/doc-generate-docs.png +0 -0
- package/assets/screenshots/doc-generate.png +0 -0
- package/assets/screenshots/doc-generated-successfully.png +0 -0
- package/assets/screenshots/doc-publish.png +0 -0
- package/assets/screenshots/doc-regenerate.png +0 -0
- package/assets/screenshots/doc-translate-langs.png +0 -0
- package/assets/screenshots/doc-translate.png +0 -0
- package/assets/screenshots/doc-update.png +0 -0
- package/biome.json +0 -73
- package/codecov.yml +0 -15
- package/docs/_sidebar.md +0 -15
- package/docs/configuration-initial-setup.ja.md +0 -179
- package/docs/configuration-initial-setup.md +0 -198
- package/docs/configuration-initial-setup.zh-TW.md +0 -179
- package/docs/configuration-initial-setup.zh.md +0 -179
- package/docs/configuration-managing-preferences.ja.md +0 -100
- package/docs/configuration-managing-preferences.md +0 -100
- package/docs/configuration-managing-preferences.zh-TW.md +0 -100
- package/docs/configuration-managing-preferences.zh.md +0 -100
- package/docs/configuration.ja.md +0 -69
- package/docs/configuration.md +0 -69
- package/docs/configuration.zh-TW.md +0 -69
- package/docs/configuration.zh.md +0 -69
- package/docs/getting-started.ja.md +0 -107
- package/docs/getting-started.md +0 -107
- package/docs/getting-started.zh-TW.md +0 -107
- package/docs/getting-started.zh.md +0 -107
- package/docs/guides-cleaning-up.ja.md +0 -51
- package/docs/guides-cleaning-up.md +0 -52
- package/docs/guides-cleaning-up.zh-TW.md +0 -51
- package/docs/guides-cleaning-up.zh.md +0 -51
- package/docs/guides-evaluating-documents.ja.md +0 -66
- package/docs/guides-evaluating-documents.md +0 -107
- package/docs/guides-evaluating-documents.zh-TW.md +0 -66
- package/docs/guides-evaluating-documents.zh.md +0 -66
- package/docs/guides-generating-documentation.ja.md +0 -151
- package/docs/guides-generating-documentation.md +0 -89
- package/docs/guides-generating-documentation.zh-TW.md +0 -151
- package/docs/guides-generating-documentation.zh.md +0 -151
- package/docs/guides-interactive-chat.ja.md +0 -85
- package/docs/guides-interactive-chat.md +0 -93
- package/docs/guides-interactive-chat.zh-TW.md +0 -85
- package/docs/guides-interactive-chat.zh.md +0 -85
- package/docs/guides-managing-history.ja.md +0 -48
- package/docs/guides-managing-history.md +0 -53
- package/docs/guides-managing-history.zh-TW.md +0 -48
- package/docs/guides-managing-history.zh.md +0 -48
- package/docs/guides-publishing-your-docs.ja.md +0 -78
- package/docs/guides-publishing-your-docs.md +0 -83
- package/docs/guides-publishing-your-docs.zh-TW.md +0 -78
- package/docs/guides-publishing-your-docs.zh.md +0 -78
- package/docs/guides-translating-documentation.ja.md +0 -95
- package/docs/guides-translating-documentation.md +0 -100
- package/docs/guides-translating-documentation.zh-TW.md +0 -95
- package/docs/guides-translating-documentation.zh.md +0 -95
- package/docs/guides-updating-documentation.ja.md +0 -77
- package/docs/guides-updating-documentation.md +0 -79
- package/docs/guides-updating-documentation.zh-TW.md +0 -77
- package/docs/guides-updating-documentation.zh.md +0 -77
- package/docs/guides.ja.md +0 -32
- package/docs/guides.md +0 -32
- package/docs/guides.zh-TW.md +0 -32
- package/docs/guides.zh.md +0 -32
- package/docs/overview.ja.md +0 -61
- package/docs/overview.md +0 -61
- package/docs/overview.zh-TW.md +0 -61
- package/docs/overview.zh.md +0 -61
- package/docs/release-notes.ja.md +0 -255
- package/docs/release-notes.md +0 -288
- package/docs/release-notes.zh-TW.md +0 -255
- package/docs/release-notes.zh.md +0 -255
- package/prompts/common/afs/afs-tools-usage.md +0 -5
- package/prompts/common/afs/use-afs-instruction.md +0 -1
- package/release-please-config.json +0 -14
- package/tests/agents/chat/chat.test.mjs +0 -46
- package/tests/agents/clear/choose-contents.test.mjs +0 -284
- package/tests/agents/clear/clear-auth-tokens.test.mjs +0 -268
- package/tests/agents/clear/clear-document-config.test.mjs +0 -167
- package/tests/agents/clear/clear-document-structure.test.mjs +0 -380
- package/tests/agents/clear/clear-generated-docs.test.mjs +0 -222
- package/tests/agents/evaluate/code-snippet.test.mjs +0 -163
- package/tests/agents/evaluate/fixtures/api-services.md +0 -87
- package/tests/agents/evaluate/fixtures/js-sdk.md +0 -94
- package/tests/agents/evaluate/generate-report.test.mjs +0 -312
- package/tests/agents/generate/check-document-structure.test.mjs +0 -45
- package/tests/agents/generate/check-need-generate-structure.test.mjs +0 -279
- package/tests/agents/generate/document-structure-tools/add-document.test.mjs +0 -449
- package/tests/agents/generate/document-structure-tools/delete-document.test.mjs +0 -410
- package/tests/agents/generate/document-structure-tools/generate-sub-structure.test.mjs +0 -277
- package/tests/agents/generate/document-structure-tools/move-document.test.mjs +0 -476
- package/tests/agents/generate/document-structure-tools/update-document.test.mjs +0 -548
- package/tests/agents/generate/generate-structure.test.mjs +0 -45
- package/tests/agents/generate/user-review-document-structure.test.mjs +0 -319
- package/tests/agents/history/view.test.mjs +0 -97
- package/tests/agents/init/init.test.mjs +0 -1657
- package/tests/agents/prefs/prefs.test.mjs +0 -431
- package/tests/agents/publish/publish-docs.test.mjs +0 -787
- package/tests/agents/translate/choose-language.test.mjs +0 -311
- package/tests/agents/translate/translate-document.test.mjs +0 -51
- package/tests/agents/update/check-document.test.mjs +0 -463
- package/tests/agents/update/check-update-is-single.test.mjs +0 -300
- package/tests/agents/update/document-tools/update-document-content.test.mjs +0 -329
- package/tests/agents/update/generate-document.test.mjs +0 -51
- package/tests/agents/update/save-and-translate-document.test.mjs +0 -369
- package/tests/agents/update/user-review-document.test.mjs +0 -582
- package/tests/agents/utils/action-success.test.mjs +0 -54
- package/tests/agents/utils/check-detail-result.test.mjs +0 -743
- package/tests/agents/utils/check-feedback-refiner.test.mjs +0 -478
- package/tests/agents/utils/choose-docs.test.mjs +0 -406
- package/tests/agents/utils/exit.test.mjs +0 -70
- package/tests/agents/utils/feedback-refiner.test.mjs +0 -51
- package/tests/agents/utils/find-item-by-path.test.mjs +0 -517
- package/tests/agents/utils/find-user-preferences-by-path.test.mjs +0 -382
- package/tests/agents/utils/format-document-structure.test.mjs +0 -364
- package/tests/agents/utils/fs.test.mjs +0 -267
- package/tests/agents/utils/load-sources.test.mjs +0 -1470
- package/tests/agents/utils/save-docs.test.mjs +0 -109
- package/tests/agents/utils/save-output.test.mjs +0 -315
- package/tests/agents/utils/save-single-doc.test.mjs +0 -364
- package/tests/agents/utils/transform-detail-datasources.test.mjs +0 -320
- package/tests/utils/auth-utils.test.mjs +0 -596
- package/tests/utils/blocklet.test.mjs +0 -336
- package/tests/utils/conflict-detector.test.mjs +0 -355
- package/tests/utils/constants.test.mjs +0 -295
- package/tests/utils/d2-utils.test.mjs +0 -437
- package/tests/utils/deploy.test.mjs +0 -399
- package/tests/utils/docs-finder-utils.test.mjs +0 -650
- package/tests/utils/file-utils.test.mjs +0 -521
- package/tests/utils/history-utils.test.mjs +0 -206
- package/tests/utils/kroki-utils.test.mjs +0 -646
- package/tests/utils/linter/fixtures/css/keyword-error.css +0 -1
- package/tests/utils/linter/fixtures/css/missing-semicolon.css +0 -1
- package/tests/utils/linter/fixtures/css/syntax-error.css +0 -1
- package/tests/utils/linter/fixtures/css/undeclare-variable.css +0 -1
- package/tests/utils/linter/fixtures/css/unused-variable.css +0 -2
- package/tests/utils/linter/fixtures/css/valid-code.css +0 -1
- package/tests/utils/linter/fixtures/dockerfile/keyword-error.dockerfile +0 -1
- package/tests/utils/linter/fixtures/dockerfile/missing-semicolon.dockerfile +0 -2
- package/tests/utils/linter/fixtures/dockerfile/syntax-error.dockerfile +0 -2
- package/tests/utils/linter/fixtures/dockerfile/undeclare-variable.dockerfile +0 -1
- package/tests/utils/linter/fixtures/dockerfile/unused-variable.dockerfile +0 -1
- package/tests/utils/linter/fixtures/dockerfile/valid-code.dockerfile +0 -2
- package/tests/utils/linter/fixtures/go/keyword-error.go +0 -5
- package/tests/utils/linter/fixtures/go/missing-semicolon.go +0 -5
- package/tests/utils/linter/fixtures/go/syntax-error.go +0 -6
- package/tests/utils/linter/fixtures/go/undeclare-variable.go +0 -5
- package/tests/utils/linter/fixtures/go/unused-variable.go +0 -5
- package/tests/utils/linter/fixtures/go/valid-code.go +0 -7
- package/tests/utils/linter/fixtures/js/keyword-error.js +0 -3
- package/tests/utils/linter/fixtures/js/missing-semicolon.js +0 -6
- package/tests/utils/linter/fixtures/js/syntax-error.js +0 -4
- package/tests/utils/linter/fixtures/js/undeclare-variable.js +0 -3
- package/tests/utils/linter/fixtures/js/unused-variable.js +0 -7
- package/tests/utils/linter/fixtures/js/valid-code.js +0 -15
- package/tests/utils/linter/fixtures/json/keyword-error.json +0 -1
- package/tests/utils/linter/fixtures/json/missing-semicolon.json +0 -1
- package/tests/utils/linter/fixtures/json/syntax-error.json +0 -1
- package/tests/utils/linter/fixtures/json/undeclare-variable.json +0 -1
- package/tests/utils/linter/fixtures/json/unused-variable.json +0 -1
- package/tests/utils/linter/fixtures/json/valid-code.json +0 -1
- package/tests/utils/linter/fixtures/jsx/keyword-error.jsx +0 -5
- package/tests/utils/linter/fixtures/jsx/missing-semicolon.jsx +0 -5
- package/tests/utils/linter/fixtures/jsx/syntax-error.jsx +0 -5
- package/tests/utils/linter/fixtures/jsx/undeclare-variable.jsx +0 -5
- package/tests/utils/linter/fixtures/jsx/unused-variable.jsx +0 -4
- package/tests/utils/linter/fixtures/jsx/valid-code.jsx +0 -5
- package/tests/utils/linter/fixtures/python/keyword-error.py +0 -3
- package/tests/utils/linter/fixtures/python/missing-semicolon.py +0 -2
- package/tests/utils/linter/fixtures/python/syntax-error.py +0 -3
- package/tests/utils/linter/fixtures/python/undeclare-variable.py +0 -3
- package/tests/utils/linter/fixtures/python/unused-variable.py +0 -6
- package/tests/utils/linter/fixtures/python/valid-code.py +0 -12
- package/tests/utils/linter/fixtures/ruby/keyword-error.rb +0 -2
- package/tests/utils/linter/fixtures/ruby/missing-semicolon.rb +0 -1
- package/tests/utils/linter/fixtures/ruby/syntax-error.rb +0 -2
- package/tests/utils/linter/fixtures/ruby/undeclare-variable.rb +0 -1
- package/tests/utils/linter/fixtures/ruby/unused-variable.rb +0 -2
- package/tests/utils/linter/fixtures/ruby/valid-code.rb +0 -1
- package/tests/utils/linter/fixtures/sass/keyword-error.sass +0 -2
- package/tests/utils/linter/fixtures/sass/missing-semicolon.sass +0 -3
- package/tests/utils/linter/fixtures/sass/syntax-error.sass +0 -3
- package/tests/utils/linter/fixtures/sass/undeclare-variable.sass +0 -2
- package/tests/utils/linter/fixtures/sass/unused-variable.sass +0 -4
- package/tests/utils/linter/fixtures/sass/valid-code.sass +0 -2
- package/tests/utils/linter/fixtures/scss/keyword-error.scss +0 -1
- package/tests/utils/linter/fixtures/scss/missing-semicolon.scss +0 -1
- package/tests/utils/linter/fixtures/scss/syntax-error.scss +0 -1
- package/tests/utils/linter/fixtures/scss/undeclare-variable.scss +0 -1
- package/tests/utils/linter/fixtures/scss/unused-variable.scss +0 -2
- package/tests/utils/linter/fixtures/scss/valid-code.scss +0 -1
- package/tests/utils/linter/fixtures/shell/keyword-error.sh +0 -5
- package/tests/utils/linter/fixtures/shell/missing-semicolon.sh +0 -3
- package/tests/utils/linter/fixtures/shell/syntax-error.sh +0 -4
- package/tests/utils/linter/fixtures/shell/undeclare-variable.sh +0 -3
- package/tests/utils/linter/fixtures/shell/unused-variable.sh +0 -4
- package/tests/utils/linter/fixtures/shell/valid-code.sh +0 -3
- package/tests/utils/linter/fixtures/ts/keyword-error.ts +0 -1
- package/tests/utils/linter/fixtures/ts/missing-semicolon.ts +0 -1
- package/tests/utils/linter/fixtures/ts/syntax-error.ts +0 -1
- package/tests/utils/linter/fixtures/ts/undeclare-variable.ts +0 -1
- package/tests/utils/linter/fixtures/ts/unused-variable.ts +0 -3
- package/tests/utils/linter/fixtures/ts/valid-code.ts +0 -3
- package/tests/utils/linter/fixtures/tsx/keyword-error.tsx +0 -5
- package/tests/utils/linter/fixtures/tsx/missing-semicolon.tsx +0 -5
- package/tests/utils/linter/fixtures/tsx/syntax-error.tsx +0 -5
- package/tests/utils/linter/fixtures/tsx/undeclare-variable.tsx +0 -6
- package/tests/utils/linter/fixtures/tsx/unused-variable.tsx +0 -6
- package/tests/utils/linter/fixtures/tsx/valid-code.tsx +0 -5
- package/tests/utils/linter/fixtures/vue/keyword-error.vue +0 -6
- package/tests/utils/linter/fixtures/vue/missing-semicolon.vue +0 -6
- package/tests/utils/linter/fixtures/vue/syntax-error.vue +0 -6
- package/tests/utils/linter/fixtures/vue/undeclare-variable.vue +0 -6
- package/tests/utils/linter/fixtures/vue/unused-variable.vue +0 -7
- package/tests/utils/linter/fixtures/vue/valid-code.vue +0 -6
- package/tests/utils/linter/fixtures/yaml/keyword-error.yml +0 -1
- package/tests/utils/linter/fixtures/yaml/missing-semicolon.yml +0 -2
- package/tests/utils/linter/fixtures/yaml/syntax-error.yml +0 -1
- package/tests/utils/linter/fixtures/yaml/undeclare-variable.yml +0 -1
- package/tests/utils/linter/fixtures/yaml/unused-variable.yml +0 -2
- package/tests/utils/linter/fixtures/yaml/valid-code.yml +0 -3
- package/tests/utils/linter/index.test.mjs +0 -440
- package/tests/utils/linter/scan-results.mjs +0 -42
- package/tests/utils/load-config.test.mjs +0 -141
- package/tests/utils/markdown/index.test.mjs +0 -478
- package/tests/utils/mermaid-validator.test.mjs +0 -541
- package/tests/utils/mock-chat-model.mjs +0 -12
- package/tests/utils/preferences-utils.test.mjs +0 -465
- package/tests/utils/save-value-to-config.test.mjs +0 -483
- 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 = " and [Link](/path/to/file)";
|
|
191
|
-
const result = processContent({ content });
|
|
192
|
-
expect(result).toBe(" 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
|
-
});
|