gitforest 0.1.0 → 1.1.0
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/LICENSE +21 -0
- package/package.json +24 -4
- package/src/components/onboarding/DirectoriesStep.tsx +19 -19
- package/src/github/auth.ts +3 -3
- package/src/utils/debug.ts +4 -4
- package/.bunignore +0 -7
- package/.github/workflows/ci.yml +0 -73
- package/CLAUDE.md +0 -111
- package/CONTRIBUTING.md +0 -145
- package/bun.lock +0 -267
- package/bunfig.toml +0 -15
- package/cli +0 -0
- package/docs/ai/IMPROVEMENT_PLAN.md +0 -341
- package/docs/ai/VERIFICATION_REPORT.md +0 -87
- package/docs/ai/architecture.md +0 -169
- package/docs/ai/checks/check-2025-12-02-tests.md +0 -40
- package/docs/ai/checks/check-2025-12-02.md +0 -55
- package/docs/ai/checks/test-verification-report.md +0 -85
- package/docs/ai/implementation-guide.md +0 -776
- package/docs/ai/research/gitty-codebase-analysis.md +0 -221
- package/docs/ai/tickets/GENERAL-sitrep.md +0 -30
- package/docs/ai/tickets/TASK-database-tests-sitrep.md +0 -25
- package/docs/ai/tickets/TASK-deprecated-functions-sitrep.md +0 -28
- package/docs/ai/tickets/TASK-detail-modal-sitrep.md +0 -28
- package/docs/ai/tickets/TASK-filter-overlay-sitrep.md +0 -24
- package/docs/ai/tickets/TASK-github-service-sitrep.md +0 -32
- package/docs/ai/tickets/TASK-github-token-sitrep.md +0 -51
- package/docs/ai/tickets/TASK-hascommits-sitrep.md +0 -35
- package/docs/ai/tickets/TASK-keybindings-sitrep.md +0 -26
- package/docs/ai/tickets/TASK-layout-sitrep.md +0 -25
- package/docs/ai/tickets/TASK-markdown-sitrep.md +0 -28
- package/docs/ai/tickets/TASK-project-item-sitrep.md +0 -79
- package/docs/ai/tickets/TASK-sitrep.md +0 -28
- package/docs/ai/tickets/TASK-state-sitrep.md +0 -26
- package/docs/ai/tickets/TASK-types-sitrep.md +0 -25
- package/docs/ai/tickets/TASK-unified-item-fix-sitrep.md +0 -26
- package/docs/ai/tickets/TKT-001-sitrep.md +0 -24
- package/docs/ai/tickets/TKT-002-sitrep.md +0 -25
- package/docs/ai/tickets/TKT-003-git-service-refactoring-complete.md +0 -46
- package/docs/ai/tickets/TKT-003-git-service-refactoring-plan.md +0 -135
- package/docs/ai/tickets/TKT-003-sitrep.md +0 -26
- package/docs/ai/tickets/TKT-004-sitrep.md +0 -27
- package/docs/ai/tickets/TKT-005-sitrep.md +0 -25
- package/docs/ai/tickets/TKT-006-sitrep.md +0 -26
- package/docs/ai/tickets/TKT-007-sitrep.md +0 -30
- package/docs/ai/tickets/TKT-008-sitrep.md +0 -32
- package/docs/ai/tickets/TKT-009-sitrep.md +0 -27
- package/docs/ai/tickets/TKT-010-sitrep.md +0 -27
- package/docs/ai/tickets/TKT-011-sitrep.md +0 -26
- package/docs/ai/tickets/TKT-012-sitrep.md +0 -25
- package/docs/ai/tickets/sitreps/TASK-actions-sitrep.md +0 -28
- package/docs/ai/tickets/sitreps/TASK-actions-test-sitrep.md +0 -25
- package/docs/ai/tickets/sitreps/TASK-app-integration-sitrep.md +0 -25
- package/docs/ai/tickets/sitreps/TASK-background-fetch-sitrep.md +0 -24
- package/docs/ai/tickets/sitreps/TASK-background-fetch-test-sitrep.md +0 -29
- package/docs/ai/tickets/sitreps/TASK-batch-tests-sitrep.md +0 -29
- package/docs/ai/tickets/sitreps/TASK-bun-test-sitrep.md +0 -26
- package/docs/ai/tickets/sitreps/TASK-cache-tests-sitrep.md +0 -30
- package/docs/ai/tickets/sitreps/TASK-cli-tests-sitrep.md +0 -28
- package/docs/ai/tickets/sitreps/TASK-clone-error-handling-sitrep.md +0 -26
- package/docs/ai/tickets/sitreps/TASK-commands-tests-sitrep.md +0 -25
- package/docs/ai/tickets/sitreps/TASK-component-tests-1-sitrep.md +0 -30
- package/docs/ai/tickets/sitreps/TASK-configloader-tests-sitrep.md +0 -25
- package/docs/ai/tickets/sitreps/TASK-confirm-dialog-test-sitrep.md +0 -29
- package/docs/ai/tickets/sitreps/TASK-coverage-sitrep.md +0 -95
- package/docs/ai/tickets/sitreps/TASK-database-tests-summary.md +0 -61
- package/docs/ai/tickets/sitreps/TASK-error-boundary-sitrep.md +0 -30
- package/docs/ai/tickets/sitreps/TASK-error-tests-sitrep.md +0 -27
- package/docs/ai/tickets/sitreps/TASK-errors-tests-sitrep.md +0 -25
- package/docs/ai/tickets/sitreps/TASK-extract-reducer-sitrep.md +0 -27
- package/docs/ai/tickets/sitreps/TASK-filter-overlay-test-sitrep.md +0 -25
- package/docs/ai/tickets/sitreps/TASK-final-verification-sitrep.md +0 -28
- package/docs/ai/tickets/sitreps/TASK-fix-all-tests-sitrep.md +0 -25
- package/docs/ai/tickets/sitreps/TASK-fix-hooks-sitrep.md +0 -26
- package/docs/ai/tickets/sitreps/TASK-fix-remaining-tests-sitrep.md +0 -25
- package/docs/ai/tickets/sitreps/TASK-fix-test-failures-sitrep.md +0 -26
- package/docs/ai/tickets/sitreps/TASK-fix-tests-sitrep.md +0 -24
- package/docs/ai/tickets/sitreps/TASK-formatters-tests-sitrep.md +0 -25
- package/docs/ai/tickets/sitreps/TASK-git-timeouts-sitrep.md +0 -29
- package/docs/ai/tickets/sitreps/TASK-github-cache-test-sitrep.md +0 -25
- package/docs/ai/tickets/sitreps/TASK-githubcli-tests-sitrep.md +0 -24
- package/docs/ai/tickets/sitreps/TASK-gitstatus-tests-sitrep.md +0 -24
- package/docs/ai/tickets/sitreps/TASK-hooks-isolation-sitrep.md +0 -27
- package/docs/ai/tickets/sitreps/TASK-keybindings-tests-sitrep.md +0 -25
- package/docs/ai/tickets/sitreps/TASK-layout-tests-sitrep.md +0 -25
- package/docs/ai/tickets/sitreps/TASK-mock-factories-sitrep.md +0 -27
- package/docs/ai/tickets/sitreps/TASK-modal-tests-sitrep.md +0 -32
- package/docs/ai/tickets/sitreps/TASK-processbatch-fix-sitrep.md +0 -27
- package/docs/ai/tickets/sitreps/TASK-projectlist-tests-sitrep.md +0 -30
- package/docs/ai/tickets/sitreps/TASK-projectutils-tests-sitrep.md +0 -25
- package/docs/ai/tickets/sitreps/TASK-scanner-tests-sitrep.md +0 -29
- package/docs/ai/tickets/sitreps/TASK-select-all-sitrep.md +0 -25
- package/docs/ai/tickets/sitreps/TASK-shell-error-handling-sitrep.md +0 -27
- package/docs/ai/tickets/sitreps/TASK-store-tests-sitrep.md +0 -25
- package/docs/ai/tickets/sitreps/TASK-test-fixes-sitrep.md +0 -26
- package/docs/ai/tickets/sitreps/TASK-test-summary-sitrep.md +0 -25
- package/docs/ai/tickets/sitreps/TASK-test-verification-sitrep.md +0 -27
- package/docs/ai/tickets/sitreps/TASK-testsuite-sitrep.md +0 -75
- package/docs/ai/tickets/sitreps/TASK-unified-reducer-tests-sitrep.md +0 -29
- package/docs/ai/tickets/sitreps/TASK-unified-repos-test-sitrep.md +0 -29
- package/docs/ai/tickets/sitreps/TASK-unified-tests-sitrep.md +0 -25
- package/docs/ai/tickets/sitreps/TASK-useprojects-tests-sitrep.md +0 -25
- package/docs/ai/tickets/sitreps/TASK-utility-tests-sitrep.md +0 -32
- package/docs/ai/tickets/sitreps/TKT-003-git-service-refactoring-sitrep.md +0 -64
- package/docs/ai/tkt-001-fix-database-error.md +0 -217
- package/docs/ai/ui-enhancement-plan.md +0 -562
- package/test/integration/app.isolated.tsx +0 -240
- package/test/integration/cli-commands.test.ts +0 -287
- package/test/integration/cli-validation.test.ts +0 -264
- package/test/integration/git-operations.test.ts +0 -218
- package/test/integration/scanner.test.ts +0 -228
- package/test/preload.ts +0 -18
- package/test/unit/cli/commands.test.ts +0 -13
- package/test/unit/cli/formatters.test.ts +0 -1116
- package/test/unit/cli/github-commands.test.ts +0 -12
- package/test/unit/components/CloneDialog.test.tsx +0 -240
- package/test/unit/components/ColumnHeader.test.tsx +0 -128
- package/test/unit/components/CommandPalette.test.tsx +0 -355
- package/test/unit/components/ConfirmDialog.test.tsx +0 -111
- package/test/unit/components/ErrorBoundary.test.tsx +0 -139
- package/test/unit/components/FilterBar.test.tsx +0 -43
- package/test/unit/components/FilterOptionsOverlay.test.tsx +0 -197
- package/test/unit/components/HelpOverlay.test.tsx +0 -90
- package/test/unit/components/Layout.test.tsx +0 -328
- package/test/unit/components/MarkdownRenderer.test.tsx +0 -45
- package/test/unit/components/ProgressBar.test.tsx +0 -138
- package/test/unit/components/ProjectItem.test.tsx +0 -182
- package/test/unit/components/ProjectList.test.tsx +0 -311
- package/test/unit/components/RepoDetailModal.test.tsx +0 -445
- package/test/unit/components/StatusBar.test.tsx +0 -112
- package/test/unit/components/UnifiedProjectItem.test.tsx +0 -618
- package/test/unit/components/ViewModeIndicator.test.tsx +0 -137
- package/test/unit/components/test-utils.tsx +0 -63
- package/test/unit/config/loader.test.ts +0 -692
- package/test/unit/db/database.test.ts +0 -978
- package/test/unit/db/index.test.ts +0 -314
- package/test/unit/fixtures/setup.ts +0 -186
- package/test/unit/git/commands-untested.test.ts +0 -205
- package/test/unit/git/commands.test.ts +0 -269
- package/test/unit/git/operations.test.ts +0 -322
- package/test/unit/git/status.test.ts +0 -219
- package/test/unit/github/auth.test.ts +0 -317
- package/test/unit/github/cache.test.ts +0 -1028
- package/test/unit/github/cli.test.ts +0 -135
- package/test/unit/github/unified.test.ts +0 -1201
- package/test/unit/graceful-shutdown.test.ts +0 -83
- package/test/unit/hooks/useBackgroundFetch.test.tsx +0 -239
- package/test/unit/hooks/useConfirmDialogActions.test.tsx +0 -81
- package/test/unit/hooks/useKeyBindings.isolated.ts +0 -715
- package/test/unit/hooks/useProjects.test.tsx +0 -186
- package/test/unit/hooks/useUnifiedRepos-simple.test.tsx +0 -115
- package/test/unit/hooks/useUnifiedRepos.test.tsx +0 -177
- package/test/unit/mocks/config.ts +0 -109
- package/test/unit/mocks/git-service.ts +0 -274
- package/test/unit/mocks/github-service.ts +0 -250
- package/test/unit/mocks/index.ts +0 -72
- package/test/unit/mocks/project.ts +0 -148
- package/test/unit/mocks/state-mocks.ts +0 -187
- package/test/unit/mocks/unified.ts +0 -169
- package/test/unit/operations/batch.test.ts +0 -216
- package/test/unit/operations/commands.test.ts +0 -550
- package/test/unit/scanner/errors.test.ts +0 -297
- package/test/unit/scanner/index.test.ts +0 -1011
- package/test/unit/scanner/markers.test.ts +0 -150
- package/test/unit/scanner/submodules.test.ts +0 -99
- package/test/unit/services/git-errors.test.ts +0 -190
- package/test/unit/services/git.test.ts +0 -442
- package/test/unit/services/github-errors.test.ts +0 -293
- package/test/unit/services/github.test.ts +0 -200
- package/test/unit/state/actions.test.ts +0 -217
- package/test/unit/state/reducer.test.ts +0 -745
- package/test/unit/state/store.test.tsx +0 -711
- package/test/unit/types/commands.test.ts +0 -220
- package/test/unit/types/schema.test.ts +0 -179
- package/test/unit/utils/array.test.ts +0 -73
- package/test/unit/utils/debug.test.ts +0 -23
- package/test/unit/utils/errors.test.ts +0 -295
- package/test/unit/utils/markdown.test.ts +0 -163
- package/test/unit/utils/project-utils.test.ts +0 -756
- package/test/unit/utils/rate-limiter.test.ts +0 -256
- package/test/unit/utils/retry.test.ts +0 -165
- package/test/unit/utils/strip-ansi.ts +0 -13
- package/test/unit/utils/timeout.test.ts +0 -93
- package/tsconfig.json +0 -29
|
@@ -1,692 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect, beforeEach, afterEach } from "bun:test";
|
|
2
|
-
import { join } from "path";
|
|
3
|
-
import { mkdtemp, rm, writeFile } from "fs/promises";
|
|
4
|
-
import { tmpdir } from "os";
|
|
5
|
-
import { stringify } from "yaml";
|
|
6
|
-
import { loadConfig, findConfigPath, applyEnvOverrides, getSupportedEnvVars } from "../../../src/config/loader.ts";
|
|
7
|
-
import type { GitforestConfig } from "../../../src/types/index.ts";
|
|
8
|
-
import {
|
|
9
|
-
createTempDir,
|
|
10
|
-
cleanupTempDir,
|
|
11
|
-
createMockConfig,
|
|
12
|
-
createMockYamlConfig,
|
|
13
|
-
} from "../fixtures/setup.ts";
|
|
14
|
-
|
|
15
|
-
describe("findConfigPath", () => {
|
|
16
|
-
let tempDir: string;
|
|
17
|
-
|
|
18
|
-
beforeEach(() => {
|
|
19
|
-
tempDir = createTempDir("config-test");
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
afterEach(() => {
|
|
23
|
-
cleanupTempDir(tempDir);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
test("finds gitforest.config.yaml in current directory first", () => {
|
|
27
|
-
createMockYamlConfig(tempDir, "directories:\n - path: ~/test");
|
|
28
|
-
const result = findConfigPath(tempDir);
|
|
29
|
-
// Should find the config in specified directory (highest priority)
|
|
30
|
-
expect(result).toBe(join(tempDir, "gitforest.config.yaml"));
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
test("finds gitforest.config.json in current directory", () => {
|
|
34
|
-
createMockConfig(tempDir, { directories: [{ path: "~/test" }] });
|
|
35
|
-
const result = findConfigPath(tempDir);
|
|
36
|
-
expect(result).toBe(join(tempDir, "gitforest.config.json"));
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
test("prefers yaml over json in current directory", () => {
|
|
40
|
-
createMockYamlConfig(tempDir, "directories:\n - path: ~/yaml");
|
|
41
|
-
createMockConfig(tempDir, { directories: [{ path: "~/json" }] });
|
|
42
|
-
const result = findConfigPath(tempDir);
|
|
43
|
-
// YAML should be found first per priority order
|
|
44
|
-
expect(result).toBe(join(tempDir, "gitforest.config.yaml"));
|
|
45
|
-
});
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
describe("loadConfig", () => {
|
|
49
|
-
let tempDir: string;
|
|
50
|
-
|
|
51
|
-
beforeEach(() => {
|
|
52
|
-
tempDir = createTempDir("config-load-test");
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
afterEach(() => {
|
|
56
|
-
cleanupTempDir(tempDir);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
test("loads valid JSON config", async () => {
|
|
60
|
-
const configPath = createMockConfig(tempDir, {
|
|
61
|
-
directories: [{ path: "~/projects", maxDepth: 2 }],
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
const config = await loadConfig(configPath);
|
|
65
|
-
|
|
66
|
-
expect(config.directories).toHaveLength(1);
|
|
67
|
-
expect(config.directories[0]?.maxDepth).toBe(2);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
test("loads valid YAML config", async () => {
|
|
71
|
-
const configPath = createMockYamlConfig(
|
|
72
|
-
tempDir,
|
|
73
|
-
`
|
|
74
|
-
directories:
|
|
75
|
-
- path: ~/projects
|
|
76
|
-
maxDepth: 3
|
|
77
|
-
label: Projects
|
|
78
|
-
- path: ~/work
|
|
79
|
-
maxDepth: 2
|
|
80
|
-
|
|
81
|
-
scan:
|
|
82
|
-
ignore:
|
|
83
|
-
- node_modules
|
|
84
|
-
- vendor
|
|
85
|
-
includeHidden: false
|
|
86
|
-
|
|
87
|
-
github:
|
|
88
|
-
defaultVisibility: private
|
|
89
|
-
`
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
const config = await loadConfig(configPath);
|
|
93
|
-
|
|
94
|
-
expect(config.directories).toHaveLength(2);
|
|
95
|
-
expect(config.directories[0]?.maxDepth).toBe(3);
|
|
96
|
-
expect(config.directories[0]?.label).toBe("Projects");
|
|
97
|
-
expect(config.scan.ignore).toContain("vendor");
|
|
98
|
-
expect(config.github.defaultVisibility).toBe("private");
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
test("expands tilde in paths", async () => {
|
|
102
|
-
const configPath = createMockConfig(tempDir, {
|
|
103
|
-
directories: [{ path: "~/projects" }],
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
const config = await loadConfig(configPath);
|
|
107
|
-
|
|
108
|
-
expect(config.directories[0]?.path).not.toContain("~");
|
|
109
|
-
expect(config.directories[0]?.path).toContain(process.env.HOME || "");
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
test("throws error for invalid config", async () => {
|
|
113
|
-
const configPath = createMockConfig(tempDir, {
|
|
114
|
-
directories: [], // Invalid: empty array
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
await expect(loadConfig(configPath)).rejects.toThrow();
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
test("throws error for missing required field", async () => {
|
|
121
|
-
const configPath = createMockConfig(tempDir, {
|
|
122
|
-
// Missing directories field
|
|
123
|
-
scan: { ignore: [] },
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
await expect(loadConfig(configPath)).rejects.toThrow();
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
test("throws error when config file does not exist", async () => {
|
|
130
|
-
const nonExistentPath = join(tempDir, "non-existent.yaml");
|
|
131
|
-
await expect(loadConfig(nonExistentPath)).rejects.toThrow();
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
test("applies default values for optional fields", async () => {
|
|
135
|
-
const configPath = createMockConfig(tempDir, {
|
|
136
|
-
directories: [{ path: "~/projects" }],
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
const config = await loadConfig(configPath);
|
|
140
|
-
|
|
141
|
-
expect(config.scan.concurrency).toBe(5);
|
|
142
|
-
expect(config.display.sortBy).toBe("status");
|
|
143
|
-
expect(config.cache.ttlSeconds).toBe(300);
|
|
144
|
-
});
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
describe("applyEnvOverrides", () => {
|
|
148
|
-
// Store original env values
|
|
149
|
-
const originalEnv: Record<string, string | undefined> = {};
|
|
150
|
-
const envVarsToClean = [
|
|
151
|
-
"GITTY_GITHUB_VISIBILITY",
|
|
152
|
-
"GITTY_CONCURRENCY",
|
|
153
|
-
"GITTY_INCLUDE_HIDDEN",
|
|
154
|
-
"GITTY_SORT_BY",
|
|
155
|
-
"GITTY_SORT_DIR",
|
|
156
|
-
"GITTY_SHOW_SUBMODULES",
|
|
157
|
-
"GITTY_CACHE_TTL",
|
|
158
|
-
];
|
|
159
|
-
|
|
160
|
-
const createBaseConfig = (): GitforestConfig => ({
|
|
161
|
-
directories: [{ path: "/home/user/projects", maxDepth: 2 }],
|
|
162
|
-
scan: {
|
|
163
|
-
concurrency: 5,
|
|
164
|
-
ignore: ["node_modules"],
|
|
165
|
-
includeHidden: false,
|
|
166
|
-
},
|
|
167
|
-
display: {
|
|
168
|
-
showSubmodules: true,
|
|
169
|
-
showNonGitProjects: true,
|
|
170
|
-
sortBy: "status",
|
|
171
|
-
sortDirection: "desc",
|
|
172
|
-
},
|
|
173
|
-
github: {
|
|
174
|
-
defaultVisibility: "private",
|
|
175
|
-
},
|
|
176
|
-
cache: {
|
|
177
|
-
ttlSeconds: 300,
|
|
178
|
-
githubTtlSeconds: 600,
|
|
179
|
-
enableBackgroundRefresh: true,
|
|
180
|
-
backgroundRefreshIntervalSeconds: 300,
|
|
181
|
-
},
|
|
182
|
-
commands: [],
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
beforeEach(() => {
|
|
186
|
-
// Save original values
|
|
187
|
-
for (const key of envVarsToClean) {
|
|
188
|
-
originalEnv[key] = process.env[key];
|
|
189
|
-
delete process.env[key];
|
|
190
|
-
}
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
afterEach(() => {
|
|
194
|
-
// Restore original values
|
|
195
|
-
for (const key of envVarsToClean) {
|
|
196
|
-
if (originalEnv[key] !== undefined) {
|
|
197
|
-
process.env[key] = originalEnv[key];
|
|
198
|
-
} else {
|
|
199
|
-
delete process.env[key];
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
test("returns config unchanged when no env vars set", () => {
|
|
205
|
-
const config = createBaseConfig();
|
|
206
|
-
const result = applyEnvOverrides(config);
|
|
207
|
-
|
|
208
|
-
expect(result).toEqual(config);
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
test("overrides github.defaultVisibility from GITTY_GITHUB_VISIBILITY", () => {
|
|
212
|
-
process.env.GITTY_GITHUB_VISIBILITY = "public";
|
|
213
|
-
const config = createBaseConfig();
|
|
214
|
-
|
|
215
|
-
const result = applyEnvOverrides(config);
|
|
216
|
-
|
|
217
|
-
expect(result.github?.defaultVisibility).toBe("public");
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
test("overrides scan.concurrency from GITTY_CONCURRENCY", () => {
|
|
221
|
-
process.env.GITTY_CONCURRENCY = "10";
|
|
222
|
-
const config = createBaseConfig();
|
|
223
|
-
|
|
224
|
-
const result = applyEnvOverrides(config);
|
|
225
|
-
|
|
226
|
-
expect(result.scan?.concurrency).toBe(10);
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
test("overrides scan.includeHidden from GITTY_INCLUDE_HIDDEN", () => {
|
|
230
|
-
process.env.GITTY_INCLUDE_HIDDEN = "true";
|
|
231
|
-
const config = createBaseConfig();
|
|
232
|
-
|
|
233
|
-
const result = applyEnvOverrides(config);
|
|
234
|
-
|
|
235
|
-
expect(result.scan?.includeHidden).toBe(true);
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
test("overrides display.sortBy from GITTY_SORT_BY", () => {
|
|
239
|
-
process.env.GITTY_SORT_BY = "name";
|
|
240
|
-
const config = createBaseConfig();
|
|
241
|
-
|
|
242
|
-
const result = applyEnvOverrides(config);
|
|
243
|
-
|
|
244
|
-
expect(result.display?.sortBy).toBe("name");
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
test("overrides display.sortDirection from GITTY_SORT_DIR", () => {
|
|
248
|
-
process.env.GITTY_SORT_DIR = "asc";
|
|
249
|
-
const config = createBaseConfig();
|
|
250
|
-
|
|
251
|
-
const result = applyEnvOverrides(config);
|
|
252
|
-
|
|
253
|
-
expect(result.display?.sortDirection).toBe("asc");
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
test("overrides display.showSubmodules from GITTY_SHOW_SUBMODULES", () => {
|
|
257
|
-
process.env.GITTY_SHOW_SUBMODULES = "false";
|
|
258
|
-
const config = createBaseConfig();
|
|
259
|
-
|
|
260
|
-
const result = applyEnvOverrides(config);
|
|
261
|
-
|
|
262
|
-
expect(result.display?.showSubmodules).toBe(false);
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
test("overrides cache.ttlSeconds from GITTY_CACHE_TTL", () => {
|
|
266
|
-
process.env.GITTY_CACHE_TTL = "600";
|
|
267
|
-
const config = createBaseConfig();
|
|
268
|
-
|
|
269
|
-
const result = applyEnvOverrides(config);
|
|
270
|
-
|
|
271
|
-
expect(result.cache?.ttlSeconds).toBe(600);
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
test("applies multiple env overrides at once", () => {
|
|
275
|
-
process.env.GITTY_GITHUB_VISIBILITY = "public";
|
|
276
|
-
process.env.GITTY_CONCURRENCY = "20";
|
|
277
|
-
process.env.GITTY_SORT_BY = "lastActivity";
|
|
278
|
-
process.env.GITTY_CACHE_TTL = "1200";
|
|
279
|
-
const config = createBaseConfig();
|
|
280
|
-
|
|
281
|
-
const result = applyEnvOverrides(config);
|
|
282
|
-
|
|
283
|
-
expect(result.github?.defaultVisibility).toBe("public");
|
|
284
|
-
expect(result.scan?.concurrency).toBe(20);
|
|
285
|
-
expect(result.display?.sortBy).toBe("lastActivity");
|
|
286
|
-
expect(result.cache?.ttlSeconds).toBe(1200);
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
test("does not modify original config", () => {
|
|
290
|
-
process.env.GITTY_CONCURRENCY = "15";
|
|
291
|
-
const config = createBaseConfig();
|
|
292
|
-
const originalConcurrency = config.scan?.concurrency;
|
|
293
|
-
|
|
294
|
-
applyEnvOverrides(config);
|
|
295
|
-
|
|
296
|
-
expect(config.scan?.concurrency).toBe(originalConcurrency);
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
test("ignores empty string env values", () => {
|
|
300
|
-
process.env.GITTY_GITHUB_VISIBILITY = "";
|
|
301
|
-
const config = createBaseConfig();
|
|
302
|
-
|
|
303
|
-
const result = applyEnvOverrides(config);
|
|
304
|
-
|
|
305
|
-
// Empty env values should be ignored, keeping the original value
|
|
306
|
-
expect(result.github?.defaultVisibility).toBe("private");
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
test("creates missing parent objects", () => {
|
|
310
|
-
process.env.GITTY_CACHE_TTL = "900";
|
|
311
|
-
// Start with a minimal but complete config
|
|
312
|
-
const config: GitforestConfig = {
|
|
313
|
-
directories: [{ path: "/test", maxDepth: 1 }],
|
|
314
|
-
scan: { ignore: [], includeHidden: false, concurrency: 4 },
|
|
315
|
-
github: { defaultVisibility: "private" },
|
|
316
|
-
display: { showSubmodules: true, showNonGitProjects: true, sortBy: "status", sortDirection: "desc" },
|
|
317
|
-
cache: {
|
|
318
|
-
ttlSeconds: 300,
|
|
319
|
-
githubTtlSeconds: 600,
|
|
320
|
-
enableBackgroundRefresh: true,
|
|
321
|
-
backgroundRefreshIntervalSeconds: 300,
|
|
322
|
-
},
|
|
323
|
-
commands: [],
|
|
324
|
-
};
|
|
325
|
-
|
|
326
|
-
const result = applyEnvOverrides(config);
|
|
327
|
-
|
|
328
|
-
expect(result.cache?.ttlSeconds).toBe(900);
|
|
329
|
-
});
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
describe("getSupportedEnvVars", () => {
|
|
333
|
-
test("returns list of supported env vars", () => {
|
|
334
|
-
const envVars = getSupportedEnvVars();
|
|
335
|
-
|
|
336
|
-
expect(envVars.length).toBeGreaterThan(0);
|
|
337
|
-
expect(envVars.some((v) => v.env === "GITTY_GITHUB_VISIBILITY")).toBe(true);
|
|
338
|
-
expect(envVars.some((v) => v.env === "GITTY_CONCURRENCY")).toBe(true);
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
test("each env var has required properties", () => {
|
|
342
|
-
const envVars = getSupportedEnvVars();
|
|
343
|
-
|
|
344
|
-
for (const envVar of envVars) {
|
|
345
|
-
expect(envVar.env).toBeDefined();
|
|
346
|
-
expect(envVar.path).toBeDefined();
|
|
347
|
-
expect(envVar.description).toBeDefined();
|
|
348
|
-
expect(envVar.env.length).toBeGreaterThan(0);
|
|
349
|
-
expect(envVar.path.length).toBeGreaterThan(0);
|
|
350
|
-
}
|
|
351
|
-
});
|
|
352
|
-
|
|
353
|
-
test("path format is dot-separated", () => {
|
|
354
|
-
const envVars = getSupportedEnvVars();
|
|
355
|
-
const visibility = envVars.find((v) => v.env === "GITTY_GITHUB_VISIBILITY");
|
|
356
|
-
|
|
357
|
-
expect(visibility?.path).toBe("github.defaultVisibility");
|
|
358
|
-
});
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
describe("loadConfig - comprehensive tests", () => {
|
|
362
|
-
let tempDir: string;
|
|
363
|
-
|
|
364
|
-
beforeEach(async () => {
|
|
365
|
-
tempDir = await mkdtemp(join(tmpdir(), "gitforest-config-test-"));
|
|
366
|
-
});
|
|
367
|
-
|
|
368
|
-
afterEach(async () => {
|
|
369
|
-
await rm(tempDir, { recursive: true, force: true });
|
|
370
|
-
});
|
|
371
|
-
|
|
372
|
-
describe("loadConfig from default location", () => {
|
|
373
|
-
test("loads config from default location when no path provided", async () => {
|
|
374
|
-
const configContent = {
|
|
375
|
-
directories: [{ path: "~/projects", maxDepth: 2 }]
|
|
376
|
-
};
|
|
377
|
-
await writeFile(join(tempDir, "gitforest.config.yaml"), stringify(configContent));
|
|
378
|
-
|
|
379
|
-
const config = await loadConfig(undefined, tempDir);
|
|
380
|
-
expect(config.directories).toHaveLength(1);
|
|
381
|
-
expect(config.directories[0]?.path).toContain(process.env.HOME || "");
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
test("loads config from custom path", async () => {
|
|
385
|
-
const configPath = join(tempDir, "custom-config.yaml");
|
|
386
|
-
const configContent = {
|
|
387
|
-
directories: [{ path: "/test/path", maxDepth: 3 }]
|
|
388
|
-
};
|
|
389
|
-
await writeFile(configPath, stringify(configContent));
|
|
390
|
-
|
|
391
|
-
const config = await loadConfig(configPath);
|
|
392
|
-
expect(config.directories[0]?.path).toBe("/test/path");
|
|
393
|
-
expect(config.directories[0]?.maxDepth).toBe(3);
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
test("throws error when specified config file does not exist", async () => {
|
|
397
|
-
// Test with a specific path that doesn't exist
|
|
398
|
-
const nonExistentPath = join(tempDir, "does-not-exist.yaml");
|
|
399
|
-
await expect(loadConfig(nonExistentPath)).rejects.toThrow();
|
|
400
|
-
});
|
|
401
|
-
|
|
402
|
-
test("handles YAML parse errors gracefully", async () => {
|
|
403
|
-
const configPath = join(tempDir, "invalid.yaml");
|
|
404
|
-
await writeFile(configPath, "invalid: yaml: content: [");
|
|
405
|
-
|
|
406
|
-
await expect(loadConfig(configPath)).rejects.toThrow();
|
|
407
|
-
});
|
|
408
|
-
|
|
409
|
-
test("handles invalid config structure", async () => {
|
|
410
|
-
const configPath = join(tempDir, "invalid-structure.yaml");
|
|
411
|
-
const invalidConfig = {
|
|
412
|
-
directories: [{ path: 123 }], // path should be string
|
|
413
|
-
};
|
|
414
|
-
await writeFile(configPath, stringify(invalidConfig));
|
|
415
|
-
|
|
416
|
-
await expect(loadConfig(configPath)).rejects.toThrow(/Invalid config file/);
|
|
417
|
-
});
|
|
418
|
-
|
|
419
|
-
test("expands ~ in directory paths", async () => {
|
|
420
|
-
const configPath = join(tempDir, "tilde-config.yaml");
|
|
421
|
-
const configContent = {
|
|
422
|
-
directories: [
|
|
423
|
-
{ path: "~/projects" },
|
|
424
|
-
{ path: "~/work/test" },
|
|
425
|
-
{ path: "/absolute/path" }
|
|
426
|
-
]
|
|
427
|
-
};
|
|
428
|
-
await writeFile(configPath, stringify(configContent));
|
|
429
|
-
|
|
430
|
-
const config = await loadConfig(configPath);
|
|
431
|
-
expect(config.directories[0]?.path).toBe(join(process.env.HOME || "", "projects"));
|
|
432
|
-
expect(config.directories[1]?.path).toBe(join(process.env.HOME || "", "work/test"));
|
|
433
|
-
expect(config.directories[2]?.path).toBe("/absolute/path");
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
test("expands environment variables in paths", async () => {
|
|
437
|
-
process.env.TEST_PROJECTS_DIR = "/test/projects";
|
|
438
|
-
const configPath = join(tempDir, "env-var-config.yaml");
|
|
439
|
-
const configContent = {
|
|
440
|
-
directories: [
|
|
441
|
-
{ path: "${TEST_PROJECTS_DIR}/frontend" },
|
|
442
|
-
{ path: "~/backend" }
|
|
443
|
-
]
|
|
444
|
-
};
|
|
445
|
-
await writeFile(configPath, stringify(configContent));
|
|
446
|
-
|
|
447
|
-
const config = await loadConfig(configPath);
|
|
448
|
-
// Note: The current implementation doesn't expand env vars, this is expected behavior
|
|
449
|
-
expect(config.directories[0]?.path).toBe("${TEST_PROJECTS_DIR}/frontend");
|
|
450
|
-
delete process.env.TEST_PROJECTS_DIR;
|
|
451
|
-
});
|
|
452
|
-
});
|
|
453
|
-
|
|
454
|
-
describe("Validation tests", () => {
|
|
455
|
-
test("validates required directories field exists", async () => {
|
|
456
|
-
const configPath = join(tempDir, "no-directories.yaml");
|
|
457
|
-
const configContent = {
|
|
458
|
-
scan: { ignore: ["node_modules"] }
|
|
459
|
-
};
|
|
460
|
-
await writeFile(configPath, stringify(configContent));
|
|
461
|
-
|
|
462
|
-
await expect(loadConfig(configPath)).rejects.toThrow(/Invalid config file/);
|
|
463
|
-
});
|
|
464
|
-
|
|
465
|
-
test("validates directory path is non-empty", async () => {
|
|
466
|
-
const configPath = join(tempDir, "empty-path.yaml");
|
|
467
|
-
const configContent = {
|
|
468
|
-
directories: [{ path: "" }]
|
|
469
|
-
};
|
|
470
|
-
await writeFile(configPath, stringify(configContent));
|
|
471
|
-
|
|
472
|
-
await expect(loadConfig(configPath)).rejects.toThrow(/Invalid config file/);
|
|
473
|
-
});
|
|
474
|
-
|
|
475
|
-
test("validates maxDepth is within range (0-10)", async () => {
|
|
476
|
-
const configPath = join(tempDir, "invalid-depth.yaml");
|
|
477
|
-
const configContent = {
|
|
478
|
-
directories: [{ path: "~/test", maxDepth: 15 }]
|
|
479
|
-
};
|
|
480
|
-
await writeFile(configPath, stringify(configContent));
|
|
481
|
-
|
|
482
|
-
await expect(loadConfig(configPath)).rejects.toThrow(/Invalid config file/);
|
|
483
|
-
});
|
|
484
|
-
|
|
485
|
-
test("validates scan.concurrency is within range (1-20)", async () => {
|
|
486
|
-
const configPath = join(tempDir, "invalid-concurrency.yaml");
|
|
487
|
-
const configContent = {
|
|
488
|
-
directories: [{ path: "~/test" }],
|
|
489
|
-
scan: { concurrency: 25 }
|
|
490
|
-
};
|
|
491
|
-
await writeFile(configPath, stringify(configContent));
|
|
492
|
-
|
|
493
|
-
await expect(loadConfig(configPath)).rejects.toThrow(/Invalid config file/);
|
|
494
|
-
});
|
|
495
|
-
|
|
496
|
-
test("validates command keys are not reserved", async () => {
|
|
497
|
-
const configPath = join(tempDir, "reserved-key.yaml");
|
|
498
|
-
const configContent = {
|
|
499
|
-
directories: [{ path: "~/test" }],
|
|
500
|
-
commands: [
|
|
501
|
-
{ name: "test", key: "j", command: "echo test" } // 'j' is reserved
|
|
502
|
-
]
|
|
503
|
-
};
|
|
504
|
-
await writeFile(configPath, stringify(configContent));
|
|
505
|
-
|
|
506
|
-
await expect(loadConfig(configPath)).rejects.toThrow(/reserved/);
|
|
507
|
-
});
|
|
508
|
-
|
|
509
|
-
test("validates no duplicate command keys", async () => {
|
|
510
|
-
const configPath = join(tempDir, "duplicate-keys.yaml");
|
|
511
|
-
const configContent = {
|
|
512
|
-
directories: [{ path: "~/test" }],
|
|
513
|
-
commands: [
|
|
514
|
-
{ name: "test1", key: "t", command: "echo test1" },
|
|
515
|
-
{ name: "test2", key: "t", command: "echo test2" }
|
|
516
|
-
]
|
|
517
|
-
};
|
|
518
|
-
await writeFile(configPath, stringify(configContent));
|
|
519
|
-
|
|
520
|
-
await expect(loadConfig(configPath)).rejects.toThrow(/Duplicate command keys/);
|
|
521
|
-
});
|
|
522
|
-
|
|
523
|
-
test("validates command key is single character", async () => {
|
|
524
|
-
const configPath = join(tempDir, "multi-char-key.yaml");
|
|
525
|
-
const configContent = {
|
|
526
|
-
directories: [{ path: "~/test" }],
|
|
527
|
-
commands: [
|
|
528
|
-
{ name: "test", key: "abc", command: "echo test" }
|
|
529
|
-
]
|
|
530
|
-
};
|
|
531
|
-
await writeFile(configPath, stringify(configContent));
|
|
532
|
-
|
|
533
|
-
await expect(loadConfig(configPath)).rejects.toThrow(/Invalid config file/);
|
|
534
|
-
});
|
|
535
|
-
});
|
|
536
|
-
|
|
537
|
-
describe("Default values tests", () => {
|
|
538
|
-
test("uses default ignore patterns", async () => {
|
|
539
|
-
const configPath = join(tempDir, "default-ignore.yaml");
|
|
540
|
-
const configContent = {
|
|
541
|
-
directories: [{ path: "~/test" }]
|
|
542
|
-
};
|
|
543
|
-
await writeFile(configPath, stringify(configContent));
|
|
544
|
-
|
|
545
|
-
const config = await loadConfig(configPath);
|
|
546
|
-
expect(config.scan.ignore).toContain("node_modules");
|
|
547
|
-
expect(config.scan.ignore).toContain(".git");
|
|
548
|
-
expect(config.scan.ignore).toContain("vendor");
|
|
549
|
-
expect(config.scan.ignore).toContain("__pycache__");
|
|
550
|
-
expect(config.scan.ignore).toContain("target");
|
|
551
|
-
expect(config.scan.ignore).toContain("dist");
|
|
552
|
-
expect(config.scan.ignore).toContain("build");
|
|
553
|
-
});
|
|
554
|
-
|
|
555
|
-
test("uses default cache TTL", async () => {
|
|
556
|
-
const configPath = join(tempDir, "default-ttl.yaml");
|
|
557
|
-
const configContent = {
|
|
558
|
-
directories: [{ path: "~/test" }]
|
|
559
|
-
};
|
|
560
|
-
await writeFile(configPath, stringify(configContent));
|
|
561
|
-
|
|
562
|
-
const config = await loadConfig(configPath);
|
|
563
|
-
expect(config.cache.ttlSeconds).toBe(300);
|
|
564
|
-
expect(config.cache.githubTtlSeconds).toBe(600);
|
|
565
|
-
});
|
|
566
|
-
|
|
567
|
-
test("uses default concurrency", async () => {
|
|
568
|
-
const configPath = join(tempDir, "default-concurrency.yaml");
|
|
569
|
-
const configContent = {
|
|
570
|
-
directories: [{ path: "~/test" }]
|
|
571
|
-
};
|
|
572
|
-
await writeFile(configPath, stringify(configContent));
|
|
573
|
-
|
|
574
|
-
const config = await loadConfig(configPath);
|
|
575
|
-
expect(config.scan.concurrency).toBe(5);
|
|
576
|
-
});
|
|
577
|
-
|
|
578
|
-
test("uses default visibility", async () => {
|
|
579
|
-
const configPath = join(tempDir, "default-visibility.yaml");
|
|
580
|
-
const configContent = {
|
|
581
|
-
directories: [{ path: "~/test" }]
|
|
582
|
-
};
|
|
583
|
-
await writeFile(configPath, stringify(configContent));
|
|
584
|
-
|
|
585
|
-
const config = await loadConfig(configPath);
|
|
586
|
-
expect(config.github.defaultVisibility).toBe("private");
|
|
587
|
-
});
|
|
588
|
-
|
|
589
|
-
test("uses default display settings", async () => {
|
|
590
|
-
const configPath = join(tempDir, "default-display.yaml");
|
|
591
|
-
const configContent = {
|
|
592
|
-
directories: [{ path: "~/test" }]
|
|
593
|
-
};
|
|
594
|
-
await writeFile(configPath, stringify(configContent));
|
|
595
|
-
|
|
596
|
-
const config = await loadConfig(configPath);
|
|
597
|
-
expect(config.display.showSubmodules).toBe(true);
|
|
598
|
-
expect(config.display.showNonGitProjects).toBe(true);
|
|
599
|
-
expect(config.display.sortBy).toBe("status");
|
|
600
|
-
expect(config.display.sortDirection).toBe("desc");
|
|
601
|
-
});
|
|
602
|
-
});
|
|
603
|
-
|
|
604
|
-
describe("Edge cases", () => {
|
|
605
|
-
test("handles empty config file", async () => {
|
|
606
|
-
const configPath = join(tempDir, "empty.yaml");
|
|
607
|
-
await writeFile(configPath, "");
|
|
608
|
-
|
|
609
|
-
await expect(loadConfig(configPath)).rejects.toThrow();
|
|
610
|
-
});
|
|
611
|
-
|
|
612
|
-
test("handles config with only directories", async () => {
|
|
613
|
-
const configPath = join(tempDir, "minimal-config.yaml");
|
|
614
|
-
const configContent = {
|
|
615
|
-
directories: [{ path: "~/projects" }]
|
|
616
|
-
};
|
|
617
|
-
await writeFile(configPath, stringify(configContent));
|
|
618
|
-
|
|
619
|
-
const config = await loadConfig(configPath);
|
|
620
|
-
expect(config.directories).toHaveLength(1);
|
|
621
|
-
expect(config.directories[0]?.path).toContain("projects");
|
|
622
|
-
// All other fields should have defaults
|
|
623
|
-
expect(config.scan).toBeDefined();
|
|
624
|
-
expect(config.display).toBeDefined();
|
|
625
|
-
expect(config.cache).toBeDefined();
|
|
626
|
-
expect(config.github).toBeDefined();
|
|
627
|
-
});
|
|
628
|
-
|
|
629
|
-
test("handles config with all optional fields", async () => {
|
|
630
|
-
const configPath = join(tempDir, "full-config.yaml");
|
|
631
|
-
const configContent = {
|
|
632
|
-
directories: [
|
|
633
|
-
{ path: "~/projects", maxDepth: 3, label: "My Projects" },
|
|
634
|
-
{ path: "~/work", maxDepth: 2, label: "Work Stuff" }
|
|
635
|
-
],
|
|
636
|
-
scan: {
|
|
637
|
-
ignore: ["custom_ignore"],
|
|
638
|
-
includeHidden: true,
|
|
639
|
-
concurrency: 10
|
|
640
|
-
},
|
|
641
|
-
github: {
|
|
642
|
-
defaultVisibility: "public" as const
|
|
643
|
-
},
|
|
644
|
-
display: {
|
|
645
|
-
showSubmodules: false,
|
|
646
|
-
showNonGitProjects: false,
|
|
647
|
-
sortBy: "name" as const,
|
|
648
|
-
sortDirection: "asc" as const
|
|
649
|
-
},
|
|
650
|
-
cache: {
|
|
651
|
-
ttlSeconds: 600,
|
|
652
|
-
githubTtlSeconds: 1200,
|
|
653
|
-
enableBackgroundRefresh: false,
|
|
654
|
-
backgroundRefreshIntervalSeconds: 600
|
|
655
|
-
},
|
|
656
|
-
commands: [
|
|
657
|
-
{ name: "Custom Command", key: "z", command: "echo 'Custom!'", confirm: true, background: false }
|
|
658
|
-
]
|
|
659
|
-
};
|
|
660
|
-
await writeFile(configPath, stringify(configContent));
|
|
661
|
-
|
|
662
|
-
const config = await loadConfig(configPath);
|
|
663
|
-
expect(config.directories).toHaveLength(2);
|
|
664
|
-
expect(config.scan.concurrency).toBe(10);
|
|
665
|
-
expect(config.github.defaultVisibility).toBe("public");
|
|
666
|
-
expect(config.display.sortBy).toBe("name");
|
|
667
|
-
expect(config.display.sortDirection).toBe("asc");
|
|
668
|
-
expect(config.cache.ttlSeconds).toBe(600);
|
|
669
|
-
expect(config.commands).toHaveLength(1);
|
|
670
|
-
expect(config.commands[0]?.key).toBe("z");
|
|
671
|
-
});
|
|
672
|
-
|
|
673
|
-
test("handles JSON config files", async () => {
|
|
674
|
-
const configPath = join(tempDir, "config.json");
|
|
675
|
-
const configContent = {
|
|
676
|
-
directories: [{ path: "~/projects" }]
|
|
677
|
-
};
|
|
678
|
-
await writeFile(configPath, JSON.stringify(configContent));
|
|
679
|
-
|
|
680
|
-
const config = await loadConfig(configPath);
|
|
681
|
-
expect(config.directories).toHaveLength(1);
|
|
682
|
-
expect(config.directories[0]?.path).toContain("projects");
|
|
683
|
-
});
|
|
684
|
-
|
|
685
|
-
test("throws error for unknown file format", async () => {
|
|
686
|
-
const configPath = join(tempDir, "config.txt");
|
|
687
|
-
await writeFile(configPath, "directories: []");
|
|
688
|
-
|
|
689
|
-
await expect(loadConfig(configPath)).rejects.toThrow(/Unknown config file format/);
|
|
690
|
-
});
|
|
691
|
-
});
|
|
692
|
-
});
|