gitforest 0.1.0 → 1.0.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/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,756 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect, beforeEach } from "bun:test";
|
|
2
|
-
import { filterProjects, sortProjects } from "../../../src/utils/project-utils";
|
|
3
|
-
import type { Project, GitStatus } from "../../../src/types/index";
|
|
4
|
-
import {
|
|
5
|
-
createMockProject,
|
|
6
|
-
createCleanProject,
|
|
7
|
-
createDirtyProject,
|
|
8
|
-
createAheadProject,
|
|
9
|
-
createNonGitProject,
|
|
10
|
-
resetProjectIdCounter,
|
|
11
|
-
createBehindStatus,
|
|
12
|
-
createNoRemoteStatus,
|
|
13
|
-
createDirtyStatus,
|
|
14
|
-
defaultMockStatus
|
|
15
|
-
} from "../mocks";
|
|
16
|
-
|
|
17
|
-
// Reset project ID counter before each test
|
|
18
|
-
beforeEach(() => {
|
|
19
|
-
resetProjectIdCounter();
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
describe("filterProjects", () => {
|
|
23
|
-
const mockProjects: Project[] = [
|
|
24
|
-
{
|
|
25
|
-
id: "1",
|
|
26
|
-
name: "React App",
|
|
27
|
-
path: "/home/user/projects/react-app",
|
|
28
|
-
type: "git",
|
|
29
|
-
projectMarker: "package.json",
|
|
30
|
-
status: null,
|
|
31
|
-
submodule: null,
|
|
32
|
-
lastScanned: new Date(),
|
|
33
|
-
lastModified: null,
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
id: "2",
|
|
37
|
-
name: "Vue Project",
|
|
38
|
-
path: "/home/user/projects/vue-project",
|
|
39
|
-
type: "git",
|
|
40
|
-
projectMarker: "package.json",
|
|
41
|
-
status: null,
|
|
42
|
-
submodule: null,
|
|
43
|
-
lastScanned: new Date(),
|
|
44
|
-
lastModified: null,
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
id: "3",
|
|
48
|
-
name: "Rust Project",
|
|
49
|
-
path: "/home/user/projects/rust-project",
|
|
50
|
-
type: "git",
|
|
51
|
-
projectMarker: "Cargo.toml",
|
|
52
|
-
status: null,
|
|
53
|
-
submodule: null,
|
|
54
|
-
lastScanned: new Date(),
|
|
55
|
-
lastModified: null,
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
id: "4",
|
|
59
|
-
name: "Python Scripts",
|
|
60
|
-
path: "/home/user/scripts/python",
|
|
61
|
-
type: "non-git",
|
|
62
|
-
projectMarker: "pyproject.toml",
|
|
63
|
-
status: null,
|
|
64
|
-
submodule: null,
|
|
65
|
-
lastScanned: new Date(),
|
|
66
|
-
lastModified: null,
|
|
67
|
-
},
|
|
68
|
-
];
|
|
69
|
-
|
|
70
|
-
test("filters by name", () => {
|
|
71
|
-
const result = filterProjects(mockProjects, "react");
|
|
72
|
-
expect(result).toHaveLength(1);
|
|
73
|
-
expect(result[0]?.name).toBe("React App");
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
test("filters by name case insensitive", () => {
|
|
77
|
-
const result = filterProjects(mockProjects, "REACT");
|
|
78
|
-
expect(result).toHaveLength(1);
|
|
79
|
-
expect(result[0]?.name).toBe("React App");
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
test("filters by path", () => {
|
|
83
|
-
const result = filterProjects(mockProjects, "scripts");
|
|
84
|
-
expect(result).toHaveLength(1);
|
|
85
|
-
expect(result[0]?.path).toBe("/home/user/scripts/python");
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
test("filters by project marker", () => {
|
|
89
|
-
const result = filterProjects(mockProjects, "cargo");
|
|
90
|
-
expect(result).toHaveLength(1);
|
|
91
|
-
expect(result[0]?.projectMarker).toBe("Cargo.toml");
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
test("filters by type", () => {
|
|
95
|
-
const result = filterProjects(mockProjects, "non-git");
|
|
96
|
-
expect(result).toHaveLength(1);
|
|
97
|
-
expect(result[0]?.type).toBe("non-git");
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
test("returns all for empty filter", () => {
|
|
101
|
-
const result = filterProjects(mockProjects, "");
|
|
102
|
-
expect(result).toHaveLength(4);
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
test("returns all for whitespace-only filter", () => {
|
|
106
|
-
const result = filterProjects(mockProjects, " ");
|
|
107
|
-
expect(result).toHaveLength(4);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
test("handles special characters", () => {
|
|
111
|
-
const specialProject: Project = {
|
|
112
|
-
id: "5",
|
|
113
|
-
name: "Project@2.0",
|
|
114
|
-
path: "/home/user/projects/project-2.0",
|
|
115
|
-
type: "git",
|
|
116
|
-
projectMarker: null,
|
|
117
|
-
status: null,
|
|
118
|
-
submodule: null,
|
|
119
|
-
lastScanned: new Date(),
|
|
120
|
-
lastModified: null,
|
|
121
|
-
};
|
|
122
|
-
const specialProjects = [...mockProjects, specialProject];
|
|
123
|
-
|
|
124
|
-
const result = filterProjects(specialProjects, "@2.0");
|
|
125
|
-
expect(result).toHaveLength(1);
|
|
126
|
-
expect(result[0]?.name).toBe("Project@2.0");
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
test("handles no matches", () => {
|
|
130
|
-
const result = filterProjects(mockProjects, "nonexistent");
|
|
131
|
-
expect(result).toHaveLength(0);
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
test("handles partial matches", () => {
|
|
135
|
-
const result = filterProjects(mockProjects, "proj");
|
|
136
|
-
expect(result).toHaveLength(4); // React App, Vue Project, Rust Project, Python Scripts (path contains "proj")
|
|
137
|
-
});
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
describe("sortProjects", () => {
|
|
141
|
-
const mockGitStatus: GitStatus = {
|
|
142
|
-
hasUnstagedChanges: false,
|
|
143
|
-
hasStagedChanges: false,
|
|
144
|
-
hasUntrackedFiles: false,
|
|
145
|
-
modifiedCount: 0,
|
|
146
|
-
stagedCount: 0,
|
|
147
|
-
untrackedCount: 0,
|
|
148
|
-
currentBranch: "main",
|
|
149
|
-
trackingBranch: "origin/main",
|
|
150
|
-
unpushedCommits: 0,
|
|
151
|
-
unpulledCommits: 0,
|
|
152
|
-
hasRemote: true,
|
|
153
|
-
remoteUrl: "https://github.com/user/repo.git",
|
|
154
|
-
lastLocalCommit: new Date("2024-01-15"),
|
|
155
|
-
lastRemoteActivity: new Date("2024-01-10"),
|
|
156
|
-
hasCommits: true,
|
|
157
|
-
isDirty: false,
|
|
158
|
-
isAhead: false,
|
|
159
|
-
isBehind: false,
|
|
160
|
-
isOutOfSync: false,
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
const mockProjects: Project[] = [
|
|
164
|
-
{
|
|
165
|
-
id: "1",
|
|
166
|
-
name: "Alpha Project",
|
|
167
|
-
path: "/home/user/projects/alpha",
|
|
168
|
-
type: "git",
|
|
169
|
-
projectMarker: "package.json",
|
|
170
|
-
status: { ...mockGitStatus, lastLocalCommit: new Date("2024-01-15") },
|
|
171
|
-
submodule: null,
|
|
172
|
-
lastScanned: new Date(),
|
|
173
|
-
lastModified: null,
|
|
174
|
-
},
|
|
175
|
-
{
|
|
176
|
-
id: "2",
|
|
177
|
-
name: "Beta Project",
|
|
178
|
-
path: "/home/user/projects/beta",
|
|
179
|
-
type: "git",
|
|
180
|
-
projectMarker: "package.json",
|
|
181
|
-
status: { ...mockGitStatus, isDirty: true, lastLocalCommit: new Date("2024-01-10") },
|
|
182
|
-
submodule: null,
|
|
183
|
-
lastScanned: new Date(),
|
|
184
|
-
lastModified: null,
|
|
185
|
-
},
|
|
186
|
-
{
|
|
187
|
-
id: "3",
|
|
188
|
-
name: "Gamma Project",
|
|
189
|
-
path: "/home/user/projects/gamma",
|
|
190
|
-
type: "git",
|
|
191
|
-
projectMarker: "package.json",
|
|
192
|
-
status: { ...mockGitStatus, isBehind: true, lastLocalCommit: new Date("2024-01-20") },
|
|
193
|
-
submodule: null,
|
|
194
|
-
lastScanned: new Date(),
|
|
195
|
-
lastModified: null,
|
|
196
|
-
},
|
|
197
|
-
{
|
|
198
|
-
id: "4",
|
|
199
|
-
name: "Delta Project",
|
|
200
|
-
path: "/home/user/projects/delta",
|
|
201
|
-
type: "non-git",
|
|
202
|
-
projectMarker: "package.json",
|
|
203
|
-
status: null,
|
|
204
|
-
submodule: null,
|
|
205
|
-
lastScanned: new Date(),
|
|
206
|
-
lastModified: new Date("2024-01-05"),
|
|
207
|
-
},
|
|
208
|
-
];
|
|
209
|
-
|
|
210
|
-
describe("sort by name", () => {
|
|
211
|
-
test("sorts ascending", () => {
|
|
212
|
-
const result = sortProjects(mockProjects, "name", "asc");
|
|
213
|
-
expect(result[0]?.name).toBe("Alpha Project");
|
|
214
|
-
expect(result[1]?.name).toBe("Beta Project");
|
|
215
|
-
expect(result[2]?.name).toBe("Delta Project");
|
|
216
|
-
expect(result[3]?.name).toBe("Gamma Project");
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
test("sorts descending", () => {
|
|
220
|
-
const result = sortProjects(mockProjects, "name", "desc");
|
|
221
|
-
expect(result[0]?.name).toBe("Gamma Project");
|
|
222
|
-
expect(result[1]?.name).toBe("Delta Project");
|
|
223
|
-
expect(result[2]?.name).toBe("Beta Project");
|
|
224
|
-
expect(result[3]?.name).toBe("Alpha Project");
|
|
225
|
-
});
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
describe("sort by status", () => {
|
|
229
|
-
test("sorts by priority descending (most attention needed first)", () => {
|
|
230
|
-
const result = sortProjects(mockProjects, "status", "desc");
|
|
231
|
-
// Beta (dirty) should come first, then Gamma (behind), then Alpha (clean), then Delta (non-git)
|
|
232
|
-
expect(result[0]?.name).toBe("Beta Project"); // dirty
|
|
233
|
-
expect(result[1]?.name).toBe("Gamma Project"); // behind
|
|
234
|
-
expect(result[2]?.name).toBe("Alpha Project"); // clean
|
|
235
|
-
expect(result[3]?.name).toBe("Delta Project"); // non-git
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
test("sorts by priority ascending (least attention needed first)", () => {
|
|
239
|
-
const result = sortProjects(mockProjects, "status", "asc");
|
|
240
|
-
// Delta (non-git) should come first, then Alpha (clean), then Gamma (behind), then Beta (dirty)
|
|
241
|
-
expect(result[0]?.name).toBe("Delta Project"); // non-git
|
|
242
|
-
expect(result[1]?.name).toBe("Alpha Project"); // clean
|
|
243
|
-
expect(result[2]?.name).toBe("Gamma Project"); // behind
|
|
244
|
-
expect(result[3]?.name).toBe("Beta Project"); // dirty
|
|
245
|
-
});
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
describe("sort by lastActivity", () => {
|
|
249
|
-
test("sorts descending (most recent first)", () => {
|
|
250
|
-
const result = sortProjects(mockProjects, "lastActivity", "desc");
|
|
251
|
-
expect(result[0]?.name).toBe("Gamma Project"); // Jan 20
|
|
252
|
-
expect(result[1]?.name).toBe("Alpha Project"); // Jan 15
|
|
253
|
-
expect(result[2]?.name).toBe("Beta Project"); // Jan 10
|
|
254
|
-
expect(result[3]?.name).toBe("Delta Project"); // no status
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
test("sorts ascending (oldest first)", () => {
|
|
258
|
-
const result = sortProjects(mockProjects, "lastActivity", "asc");
|
|
259
|
-
expect(result[0]?.name).toBe("Delta Project"); // no status
|
|
260
|
-
expect(result[1]?.name).toBe("Beta Project"); // Jan 10
|
|
261
|
-
expect(result[2]?.name).toBe("Alpha Project"); // Jan 15
|
|
262
|
-
expect(result[3]?.name).toBe("Gamma Project"); // Jan 20
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
test("handles null dates", () => {
|
|
266
|
-
const projectsWithNulls: Project[] = [
|
|
267
|
-
...mockProjects.slice(0, 3),
|
|
268
|
-
{
|
|
269
|
-
...mockProjects[3]!,
|
|
270
|
-
status: null,
|
|
271
|
-
},
|
|
272
|
-
];
|
|
273
|
-
|
|
274
|
-
const result = sortProjects(projectsWithNulls, "lastActivity", "desc");
|
|
275
|
-
// Projects with null dates should be at the end for desc
|
|
276
|
-
expect(result[3]?.name).toBe("Delta Project");
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
test("handles string timestamps", () => {
|
|
280
|
-
const projectsWithStrings: Project[] = mockProjects.map(p => ({
|
|
281
|
-
...p,
|
|
282
|
-
status: p.status ? {
|
|
283
|
-
...p.status,
|
|
284
|
-
lastLocalCommit: p.status.lastLocalCommit ? new Date(p.status.lastLocalCommit) : null,
|
|
285
|
-
} : null,
|
|
286
|
-
}));
|
|
287
|
-
|
|
288
|
-
const result = sortProjects(projectsWithStrings, "lastActivity", "desc");
|
|
289
|
-
expect(result[0]?.name).toBe("Gamma Project");
|
|
290
|
-
expect(result[3]?.name).toBe("Delta Project");
|
|
291
|
-
});
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
test("handles empty array", () => {
|
|
295
|
-
const result = sortProjects([], "name", "asc");
|
|
296
|
-
expect(result).toEqual([]);
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
test("handles single item", () => {
|
|
300
|
-
const result = sortProjects([mockProjects[0]!], "name", "asc");
|
|
301
|
-
expect(result).toHaveLength(1);
|
|
302
|
-
expect(result[0]?.name).toBe("Alpha Project");
|
|
303
|
-
});
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
describe("Enhanced sortProjects tests", () => {
|
|
307
|
-
describe("sort by name", () => {
|
|
308
|
-
test("sorts by name ascending with mixed case", () => {
|
|
309
|
-
const projects = [
|
|
310
|
-
createMockProject({ name: "zebra" }),
|
|
311
|
-
createMockProject({ name: "Alpha" }),
|
|
312
|
-
createMockProject({ name: "beta" }),
|
|
313
|
-
createMockProject({ name: "Charlie" }),
|
|
314
|
-
];
|
|
315
|
-
|
|
316
|
-
const result = sortProjects(projects, "name", "asc");
|
|
317
|
-
expect(result[0]?.name).toBe("Alpha");
|
|
318
|
-
expect(result[1]?.name).toBe("beta");
|
|
319
|
-
expect(result[2]?.name).toBe("Charlie");
|
|
320
|
-
expect(result[3]?.name).toBe("zebra");
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
test("sorts by name descending with mixed case", () => {
|
|
324
|
-
const projects = [
|
|
325
|
-
createMockProject({ name: "zebra" }),
|
|
326
|
-
createMockProject({ name: "Alpha" }),
|
|
327
|
-
createMockProject({ name: "beta" }),
|
|
328
|
-
createMockProject({ name: "Charlie" }),
|
|
329
|
-
];
|
|
330
|
-
|
|
331
|
-
const result = sortProjects(projects, "name", "desc");
|
|
332
|
-
expect(result[0]?.name).toBe("zebra");
|
|
333
|
-
expect(result[1]?.name).toBe("Charlie");
|
|
334
|
-
expect(result[2]?.name).toBe("beta");
|
|
335
|
-
expect(result[3]?.name).toBe("Alpha");
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
test("handles projects with same name prefix", () => {
|
|
339
|
-
const projects = [
|
|
340
|
-
createMockProject({ name: "project-alpha" }),
|
|
341
|
-
createMockProject({ name: "project-beta" }),
|
|
342
|
-
createMockProject({ name: "project-gamma" }),
|
|
343
|
-
];
|
|
344
|
-
|
|
345
|
-
const result = sortProjects(projects, "name", "asc");
|
|
346
|
-
expect(result[0]?.name).toBe("project-alpha");
|
|
347
|
-
expect(result[1]?.name).toBe("project-beta");
|
|
348
|
-
expect(result[2]?.name).toBe("project-gamma");
|
|
349
|
-
});
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
describe("sort by status priority", () => {
|
|
353
|
-
test("dirty projects have highest priority in desc", () => {
|
|
354
|
-
const projects = [
|
|
355
|
-
createCleanProject("clean"),
|
|
356
|
-
createDirtyProject("dirty"),
|
|
357
|
-
createAheadProject("ahead", 2),
|
|
358
|
-
];
|
|
359
|
-
|
|
360
|
-
const result = sortProjects(projects, "status", "desc");
|
|
361
|
-
expect(result[0]?.name).toBe("dirty");
|
|
362
|
-
expect(result[1]?.name).toBe("ahead");
|
|
363
|
-
expect(result[2]?.name).toBe("clean");
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
test("behind projects have high priority in desc", () => {
|
|
367
|
-
const behindProject = createMockProject({
|
|
368
|
-
name: "behind",
|
|
369
|
-
type: "git",
|
|
370
|
-
status: {
|
|
371
|
-
...createBehindStatus(3),
|
|
372
|
-
isBehind: true,
|
|
373
|
-
unpulledCommits: 3,
|
|
374
|
-
},
|
|
375
|
-
});
|
|
376
|
-
|
|
377
|
-
const projects = [
|
|
378
|
-
createCleanProject("clean"),
|
|
379
|
-
behindProject,
|
|
380
|
-
createAheadProject("ahead", 1),
|
|
381
|
-
];
|
|
382
|
-
|
|
383
|
-
const result = sortProjects(projects, "status", "desc");
|
|
384
|
-
expect(result[0]?.name).toBe("behind");
|
|
385
|
-
expect(result[1]?.name).toBe("ahead");
|
|
386
|
-
expect(result[2]?.name).toBe("clean");
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
test("non-git projects have lowest priority", () => {
|
|
390
|
-
const projects = [
|
|
391
|
-
createCleanProject("clean"),
|
|
392
|
-
createDirtyProject("dirty"),
|
|
393
|
-
createNonGitProject("non-git"),
|
|
394
|
-
];
|
|
395
|
-
|
|
396
|
-
const result = sortProjects(projects, "status", "desc");
|
|
397
|
-
expect(result[0]?.name).toBe("dirty");
|
|
398
|
-
expect(result[1]?.name).toBe("clean");
|
|
399
|
-
expect(result[2]?.name).toBe("non-git");
|
|
400
|
-
});
|
|
401
|
-
|
|
402
|
-
test("projects without status have low priority", () => {
|
|
403
|
-
const noStatusProject = createMockProject({
|
|
404
|
-
name: "no-status",
|
|
405
|
-
type: "git",
|
|
406
|
-
status: null,
|
|
407
|
-
});
|
|
408
|
-
|
|
409
|
-
const projects = [
|
|
410
|
-
createCleanProject("clean"),
|
|
411
|
-
noStatusProject,
|
|
412
|
-
createDirtyProject("dirty"),
|
|
413
|
-
];
|
|
414
|
-
|
|
415
|
-
const result = sortProjects(projects, "status", "desc");
|
|
416
|
-
expect(result[0]?.name).toBe("dirty");
|
|
417
|
-
expect(result[1]?.name).toBe("clean");
|
|
418
|
-
expect(result[2]?.name).toBe("no-status");
|
|
419
|
-
});
|
|
420
|
-
|
|
421
|
-
test("priority combines correctly (dirty + ahead)", () => {
|
|
422
|
-
const dirtyAheadProject = createMockProject({
|
|
423
|
-
name: "dirty-ahead",
|
|
424
|
-
type: "git",
|
|
425
|
-
status: {
|
|
426
|
-
...createDirtyStatus(),
|
|
427
|
-
unpushedCommits: 5,
|
|
428
|
-
isAhead: true,
|
|
429
|
-
isOutOfSync: true,
|
|
430
|
-
},
|
|
431
|
-
});
|
|
432
|
-
|
|
433
|
-
const justDirtyProject = createDirtyProject("just-dirty");
|
|
434
|
-
const justAheadProject = createAheadProject("just-ahead", 5);
|
|
435
|
-
|
|
436
|
-
const projects = [
|
|
437
|
-
justAheadProject,
|
|
438
|
-
justDirtyProject,
|
|
439
|
-
dirtyAheadProject,
|
|
440
|
-
];
|
|
441
|
-
|
|
442
|
-
const result = sortProjects(projects, "status", "desc");
|
|
443
|
-
// dirty+ahead should come before just-dirty due to combined priority
|
|
444
|
-
expect(result[0]?.name).toBe("dirty-ahead");
|
|
445
|
-
expect(result[1]?.name).toBe("just-dirty");
|
|
446
|
-
expect(result[2]?.name).toBe("just-ahead");
|
|
447
|
-
});
|
|
448
|
-
|
|
449
|
-
test("no remote projects have medium priority", () => {
|
|
450
|
-
const noRemoteProject = createMockProject({
|
|
451
|
-
name: "no-remote",
|
|
452
|
-
type: "git",
|
|
453
|
-
status: createNoRemoteStatus(),
|
|
454
|
-
});
|
|
455
|
-
|
|
456
|
-
const projects = [
|
|
457
|
-
createCleanProject("clean"),
|
|
458
|
-
noRemoteProject,
|
|
459
|
-
createDirtyProject("dirty"),
|
|
460
|
-
];
|
|
461
|
-
|
|
462
|
-
const result = sortProjects(projects, "status", "desc");
|
|
463
|
-
expect(result[0]?.name).toBe("dirty");
|
|
464
|
-
expect(result[1]?.name).toBe("no-remote");
|
|
465
|
-
expect(result[2]?.name).toBe("clean");
|
|
466
|
-
});
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
describe("sort by lastActivity", () => {
|
|
470
|
-
test("handles Date objects for lastLocalCommit", () => {
|
|
471
|
-
const projects = [
|
|
472
|
-
createMockProject({
|
|
473
|
-
name: "old",
|
|
474
|
-
type: "git",
|
|
475
|
-
status: {
|
|
476
|
-
...createCleanProject("").status!,
|
|
477
|
-
lastLocalCommit: new Date("2024-01-01"),
|
|
478
|
-
},
|
|
479
|
-
}),
|
|
480
|
-
createMockProject({
|
|
481
|
-
name: "new",
|
|
482
|
-
type: "git",
|
|
483
|
-
status: {
|
|
484
|
-
...createCleanProject("").status!,
|
|
485
|
-
lastLocalCommit: new Date("2024-12-31"),
|
|
486
|
-
},
|
|
487
|
-
}),
|
|
488
|
-
];
|
|
489
|
-
|
|
490
|
-
const result = sortProjects(projects, "lastActivity", "desc");
|
|
491
|
-
expect(result[0]?.name).toBe("new");
|
|
492
|
-
expect(result[1]?.name).toBe("old");
|
|
493
|
-
});
|
|
494
|
-
|
|
495
|
-
test("handles string timestamps for lastLocalCommit", () => {
|
|
496
|
-
const projects = [
|
|
497
|
-
createMockProject({
|
|
498
|
-
name: "old",
|
|
499
|
-
type: "git",
|
|
500
|
-
status: {
|
|
501
|
-
...defaultMockStatus,
|
|
502
|
-
lastLocalCommit: new Date("2024-01-01T00:00:00Z"),
|
|
503
|
-
},
|
|
504
|
-
}),
|
|
505
|
-
createMockProject({
|
|
506
|
-
name: "new",
|
|
507
|
-
type: "git",
|
|
508
|
-
status: {
|
|
509
|
-
...defaultMockStatus,
|
|
510
|
-
lastLocalCommit: new Date("2024-12-31T23:59:59Z"),
|
|
511
|
-
},
|
|
512
|
-
}),
|
|
513
|
-
];
|
|
514
|
-
|
|
515
|
-
const result = sortProjects(projects, "lastActivity", "desc");
|
|
516
|
-
expect(result[0]?.name).toBe("new");
|
|
517
|
-
expect(result[1]?.name).toBe("old");
|
|
518
|
-
});
|
|
519
|
-
|
|
520
|
-
test("handles null lastLocalCommit", () => {
|
|
521
|
-
const projects = [
|
|
522
|
-
createMockProject({
|
|
523
|
-
name: "no-commit-date",
|
|
524
|
-
type: "git",
|
|
525
|
-
status: {
|
|
526
|
-
...createCleanProject("").status!,
|
|
527
|
-
lastLocalCommit: null,
|
|
528
|
-
},
|
|
529
|
-
}),
|
|
530
|
-
createMockProject({
|
|
531
|
-
name: "with-commit-date",
|
|
532
|
-
type: "git",
|
|
533
|
-
status: {
|
|
534
|
-
...createCleanProject("").status!,
|
|
535
|
-
lastLocalCommit: new Date("2024-06-15"),
|
|
536
|
-
},
|
|
537
|
-
}),
|
|
538
|
-
];
|
|
539
|
-
|
|
540
|
-
const result = sortProjects(projects, "lastActivity", "desc");
|
|
541
|
-
// Projects with null dates should be at the end for desc
|
|
542
|
-
expect(result[0]?.name).toBe("with-commit-date");
|
|
543
|
-
expect(result[1]?.name).toBe("no-commit-date");
|
|
544
|
-
});
|
|
545
|
-
|
|
546
|
-
test("sorts by lastActivity ascending (oldest first)", () => {
|
|
547
|
-
const projects = [
|
|
548
|
-
createMockProject({
|
|
549
|
-
name: "new",
|
|
550
|
-
type: "git",
|
|
551
|
-
status: {
|
|
552
|
-
...createCleanProject("").status!,
|
|
553
|
-
lastLocalCommit: new Date("2024-12-31"),
|
|
554
|
-
},
|
|
555
|
-
}),
|
|
556
|
-
createMockProject({
|
|
557
|
-
name: "old",
|
|
558
|
-
type: "git",
|
|
559
|
-
status: {
|
|
560
|
-
...createCleanProject("").status!,
|
|
561
|
-
lastLocalCommit: new Date("2024-01-01"),
|
|
562
|
-
},
|
|
563
|
-
}),
|
|
564
|
-
createMockProject({
|
|
565
|
-
name: "middle",
|
|
566
|
-
type: "git",
|
|
567
|
-
status: {
|
|
568
|
-
...createCleanProject("").status!,
|
|
569
|
-
lastLocalCommit: new Date("2024-06-15"),
|
|
570
|
-
},
|
|
571
|
-
}),
|
|
572
|
-
];
|
|
573
|
-
|
|
574
|
-
const result = sortProjects(projects, "lastActivity", "asc");
|
|
575
|
-
expect(result[0]?.name).toBe("old");
|
|
576
|
-
expect(result[1]?.name).toBe("middle");
|
|
577
|
-
expect(result[2]?.name).toBe("new");
|
|
578
|
-
});
|
|
579
|
-
|
|
580
|
-
test("non-git projects are sorted to the end", () => {
|
|
581
|
-
const projects = [
|
|
582
|
-
createNonGitProject("non-git-1"),
|
|
583
|
-
createMockProject({
|
|
584
|
-
name: "git-project",
|
|
585
|
-
type: "git",
|
|
586
|
-
status: {
|
|
587
|
-
...createCleanProject("").status!,
|
|
588
|
-
lastLocalCommit: new Date("2024-01-01"),
|
|
589
|
-
},
|
|
590
|
-
}),
|
|
591
|
-
createNonGitProject("non-git-2"),
|
|
592
|
-
];
|
|
593
|
-
|
|
594
|
-
const result = sortProjects(projects, "lastActivity", "desc");
|
|
595
|
-
expect(result[0]?.name).toBe("git-project");
|
|
596
|
-
expect(result[1]?.type).toBe("non-git");
|
|
597
|
-
expect(result[2]?.type).toBe("non-git");
|
|
598
|
-
});
|
|
599
|
-
});
|
|
600
|
-
});
|
|
601
|
-
|
|
602
|
-
describe("Enhanced filterProjects tests", () => {
|
|
603
|
-
test("filter by name is case insensitive", () => {
|
|
604
|
-
const projects = [
|
|
605
|
-
createMockProject({ name: "ReactApp" }),
|
|
606
|
-
createMockProject({ name: "vueapp" }),
|
|
607
|
-
createMockProject({ name: "ANGULAR_APP" }),
|
|
608
|
-
];
|
|
609
|
-
|
|
610
|
-
const result = filterProjects(projects, "app");
|
|
611
|
-
expect(result).toHaveLength(3);
|
|
612
|
-
expect(result.map(p => p.name)).toEqual(["ReactApp", "vueapp", "ANGULAR_APP"]);
|
|
613
|
-
});
|
|
614
|
-
|
|
615
|
-
test("filter by path", () => {
|
|
616
|
-
const projects = [
|
|
617
|
-
createMockProject({ path: "/home/user/projects/react" }),
|
|
618
|
-
createMockProject({ path: "/home/user/work/vue" }),
|
|
619
|
-
createMockProject({ path: "/home/user/projects/angular" }),
|
|
620
|
-
];
|
|
621
|
-
|
|
622
|
-
const result = filterProjects(projects, "projects");
|
|
623
|
-
expect(result).toHaveLength(2);
|
|
624
|
-
expect(result.map(p => p.path)).toContain("/home/user/projects/react");
|
|
625
|
-
expect(result.map(p => p.path)).toContain("/home/user/projects/angular");
|
|
626
|
-
});
|
|
627
|
-
|
|
628
|
-
test("filter by projectMarker", () => {
|
|
629
|
-
const projects = [
|
|
630
|
-
createMockProject({ projectMarker: "package.json" }),
|
|
631
|
-
createMockProject({ projectMarker: "Cargo.toml" }),
|
|
632
|
-
createMockProject({ projectMarker: "pyproject.toml" }),
|
|
633
|
-
];
|
|
634
|
-
|
|
635
|
-
const result = filterProjects(projects, "toml");
|
|
636
|
-
expect(result).toHaveLength(2);
|
|
637
|
-
expect(result.map(p => p.projectMarker)).toContain("Cargo.toml");
|
|
638
|
-
expect(result.map(p => p.projectMarker)).toContain("pyproject.toml");
|
|
639
|
-
});
|
|
640
|
-
|
|
641
|
-
test("filter by type", () => {
|
|
642
|
-
const projects = [
|
|
643
|
-
createMockProject({ type: "git" }),
|
|
644
|
-
createMockProject({ type: "non-git" }),
|
|
645
|
-
createMockProject({ type: "git-submodule" }),
|
|
646
|
-
];
|
|
647
|
-
|
|
648
|
-
const result = filterProjects(projects, "git");
|
|
649
|
-
expect(result).toHaveLength(3); // Matches "git" in both "git" and "git-submodule" types
|
|
650
|
-
expect(result.map(p => p.type)).toContain("git");
|
|
651
|
-
expect(result.map(p => p.type)).toContain("git-submodule");
|
|
652
|
-
});
|
|
653
|
-
|
|
654
|
-
test("empty filter returns all projects", () => {
|
|
655
|
-
const projects = [
|
|
656
|
-
createMockProject({ name: "project1" }),
|
|
657
|
-
createMockProject({ name: "project2" }),
|
|
658
|
-
createMockProject({ name: "project3" }),
|
|
659
|
-
];
|
|
660
|
-
|
|
661
|
-
const result = filterProjects(projects, "");
|
|
662
|
-
expect(result).toHaveLength(3);
|
|
663
|
-
});
|
|
664
|
-
|
|
665
|
-
test("whitespace-only filter returns all projects", () => {
|
|
666
|
-
const projects = [
|
|
667
|
-
createMockProject({ name: "project1" }),
|
|
668
|
-
createMockProject({ name: "project2" }),
|
|
669
|
-
];
|
|
670
|
-
|
|
671
|
-
const result = filterProjects(projects, " \n\t ");
|
|
672
|
-
expect(result).toHaveLength(2);
|
|
673
|
-
});
|
|
674
|
-
|
|
675
|
-
test("no matches returns empty array", () => {
|
|
676
|
-
const projects = [
|
|
677
|
-
createMockProject({ name: "react" }),
|
|
678
|
-
createMockProject({ name: "vue" }),
|
|
679
|
-
];
|
|
680
|
-
|
|
681
|
-
const result = filterProjects(projects, "angular");
|
|
682
|
-
expect(result).toHaveLength(0);
|
|
683
|
-
});
|
|
684
|
-
|
|
685
|
-
test("partial matches work", () => {
|
|
686
|
-
const projects = [
|
|
687
|
-
createMockProject({ name: "my-react-app" }),
|
|
688
|
-
createMockProject({ name: "react-component" }),
|
|
689
|
-
createMockProject({ name: "vue-app" }),
|
|
690
|
-
];
|
|
691
|
-
|
|
692
|
-
const result = filterProjects(projects, "react");
|
|
693
|
-
expect(result).toHaveLength(2);
|
|
694
|
-
expect(result.map(p => p.name)).toContain("my-react-app");
|
|
695
|
-
expect(result.map(p => p.name)).toContain("react-component");
|
|
696
|
-
});
|
|
697
|
-
|
|
698
|
-
test("filters across multiple fields", () => {
|
|
699
|
-
const projects = [
|
|
700
|
-
createMockProject({ name: "react", path: "/apps/frontend", projectMarker: "package.json" }),
|
|
701
|
-
createMockProject({ name: "backend", path: "/apps/backend", projectMarker: "requirements.txt" }),
|
|
702
|
-
createMockProject({ name: "mobile", path: "/apps/react-native", projectMarker: "package.json" }),
|
|
703
|
-
];
|
|
704
|
-
|
|
705
|
-
// Should match both react projects (name and path)
|
|
706
|
-
const result = filterProjects(projects, "react");
|
|
707
|
-
expect(result).toHaveLength(2);
|
|
708
|
-
});
|
|
709
|
-
|
|
710
|
-
test("handles projects with null projectMarker", () => {
|
|
711
|
-
const projects = [
|
|
712
|
-
createMockProject({ projectMarker: null }),
|
|
713
|
-
createMockProject({ projectMarker: "package.json" }),
|
|
714
|
-
];
|
|
715
|
-
|
|
716
|
-
const result = filterProjects(projects, "package");
|
|
717
|
-
expect(result).toHaveLength(1);
|
|
718
|
-
expect(result[0]?.projectMarker).toBe("package.json");
|
|
719
|
-
});
|
|
720
|
-
|
|
721
|
-
test("handles special characters in filter", () => {
|
|
722
|
-
const projects = [
|
|
723
|
-
createMockProject({ name: "project@2.0" }),
|
|
724
|
-
createMockProject({ name: "project#1" }),
|
|
725
|
-
createMockProject({ name: "project$test" }),
|
|
726
|
-
];
|
|
727
|
-
|
|
728
|
-
const result1 = filterProjects(projects, "@2.0");
|
|
729
|
-
expect(result1).toHaveLength(1);
|
|
730
|
-
expect(result1[0]?.name).toBe("project@2.0");
|
|
731
|
-
|
|
732
|
-
const result2 = filterProjects(projects, "#1");
|
|
733
|
-
expect(result2).toHaveLength(1);
|
|
734
|
-
expect(result2[0]?.name).toBe("project#1");
|
|
735
|
-
|
|
736
|
-
const result3 = filterProjects(projects, "$test");
|
|
737
|
-
expect(result3).toHaveLength(1);
|
|
738
|
-
expect(result3[0]?.name).toBe("project$test");
|
|
739
|
-
});
|
|
740
|
-
|
|
741
|
-
test("handles unicode characters", () => {
|
|
742
|
-
const projects = [
|
|
743
|
-
createMockProject({ name: "项目" }),
|
|
744
|
-
createMockProject({ name: "プロジェクト" }),
|
|
745
|
-
createMockProject({ name: "project" }),
|
|
746
|
-
];
|
|
747
|
-
|
|
748
|
-
const result1 = filterProjects(projects, "项目");
|
|
749
|
-
expect(result1).toHaveLength(1);
|
|
750
|
-
expect(result1[0]?.name).toBe("项目");
|
|
751
|
-
|
|
752
|
-
const result2 = filterProjects(projects, "プロ");
|
|
753
|
-
expect(result2).toHaveLength(1);
|
|
754
|
-
expect(result2[0]?.name).toBe("プロジェクト");
|
|
755
|
-
});
|
|
756
|
-
});
|