im-pickle-rick 0.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/README.md +242 -0
- package/bin.js +3 -0
- package/dist/pickle +0 -0
- package/dist/worker-executor.js +207 -0
- package/package.json +53 -0
- package/src/games/GameSidebarManager.test.ts +64 -0
- package/src/games/GameSidebarManager.ts +78 -0
- package/src/games/gameboy/GameboyView.test.ts +25 -0
- package/src/games/gameboy/GameboyView.ts +100 -0
- package/src/games/gameboy/gameboy-polyfills.ts +313 -0
- package/src/games/index.test.ts +9 -0
- package/src/games/index.ts +4 -0
- package/src/games/snake/SnakeGame.test.ts +35 -0
- package/src/games/snake/SnakeGame.ts +145 -0
- package/src/games/snake/SnakeView.test.ts +25 -0
- package/src/games/snake/SnakeView.ts +290 -0
- package/src/index.test.ts +24 -0
- package/src/index.ts +141 -0
- package/src/services/commands/worker.test.ts +14 -0
- package/src/services/commands/worker.ts +262 -0
- package/src/services/config/index.ts +2 -0
- package/src/services/config/settings.test.ts +42 -0
- package/src/services/config/settings.ts +220 -0
- package/src/services/config/state.test.ts +88 -0
- package/src/services/config/state.ts +130 -0
- package/src/services/config/types.ts +39 -0
- package/src/services/execution/index.ts +1 -0
- package/src/services/execution/pickle-source.test.ts +88 -0
- package/src/services/execution/pickle-source.ts +264 -0
- package/src/services/execution/prompt.test.ts +93 -0
- package/src/services/execution/prompt.ts +322 -0
- package/src/services/execution/sequential.test.ts +91 -0
- package/src/services/execution/sequential.ts +422 -0
- package/src/services/execution/worker-client.ts +94 -0
- package/src/services/execution/worker-executor.ts +41 -0
- package/src/services/execution/worker.test.ts +73 -0
- package/src/services/git/branch.test.ts +147 -0
- package/src/services/git/branch.ts +128 -0
- package/src/services/git/diff.test.ts +113 -0
- package/src/services/git/diff.ts +323 -0
- package/src/services/git/index.ts +4 -0
- package/src/services/git/pr.test.ts +104 -0
- package/src/services/git/pr.ts +192 -0
- package/src/services/git/worktree.test.ts +99 -0
- package/src/services/git/worktree.ts +141 -0
- package/src/services/providers/base.test.ts +86 -0
- package/src/services/providers/base.ts +438 -0
- package/src/services/providers/codex.test.ts +39 -0
- package/src/services/providers/codex.ts +208 -0
- package/src/services/providers/gemini.test.ts +40 -0
- package/src/services/providers/gemini.ts +169 -0
- package/src/services/providers/index.test.ts +28 -0
- package/src/services/providers/index.ts +41 -0
- package/src/services/providers/opencode.test.ts +64 -0
- package/src/services/providers/opencode.ts +228 -0
- package/src/services/providers/types.ts +44 -0
- package/src/skills/code-implementer.md +105 -0
- package/src/skills/code-researcher.md +78 -0
- package/src/skills/implementation-planner.md +105 -0
- package/src/skills/plan-reviewer.md +100 -0
- package/src/skills/prd-drafter.md +123 -0
- package/src/skills/research-reviewer.md +79 -0
- package/src/skills/ruthless-refactorer.md +52 -0
- package/src/skills/ticket-manager.md +135 -0
- package/src/types/index.ts +2 -0
- package/src/types/rpc.ts +14 -0
- package/src/types/tasks.ts +50 -0
- package/src/types.d.ts +9 -0
- package/src/ui/common.ts +28 -0
- package/src/ui/components/FilePickerView.test.ts +79 -0
- package/src/ui/components/FilePickerView.ts +161 -0
- package/src/ui/components/MultiLineInput.test.ts +27 -0
- package/src/ui/components/MultiLineInput.ts +233 -0
- package/src/ui/components/SessionChip.test.ts +69 -0
- package/src/ui/components/SessionChip.ts +481 -0
- package/src/ui/components/ToyboxSidebar.test.ts +36 -0
- package/src/ui/components/ToyboxSidebar.ts +329 -0
- package/src/ui/components/refactor_plan.md +35 -0
- package/src/ui/controllers/DashboardController.integration.test.ts +43 -0
- package/src/ui/controllers/DashboardController.ts +650 -0
- package/src/ui/dashboard.test.ts +43 -0
- package/src/ui/dashboard.ts +309 -0
- package/src/ui/dialogs/DashboardDialog.test.ts +146 -0
- package/src/ui/dialogs/DashboardDialog.ts +399 -0
- package/src/ui/dialogs/Dialog.test.ts +50 -0
- package/src/ui/dialogs/Dialog.ts +241 -0
- package/src/ui/dialogs/DialogSidebar.test.ts +60 -0
- package/src/ui/dialogs/DialogSidebar.ts +71 -0
- package/src/ui/dialogs/DiffViewDialog.test.ts +57 -0
- package/src/ui/dialogs/DiffViewDialog.ts +510 -0
- package/src/ui/dialogs/PRPreviewDialog.test.ts +50 -0
- package/src/ui/dialogs/PRPreviewDialog.ts +346 -0
- package/src/ui/dialogs/test-utils.ts +232 -0
- package/src/ui/file-picker-utils.test.ts +71 -0
- package/src/ui/file-picker-utils.ts +200 -0
- package/src/ui/input-chrome.test.ts +62 -0
- package/src/ui/input-chrome.ts +172 -0
- package/src/ui/logger.test.ts +68 -0
- package/src/ui/logger.ts +45 -0
- package/src/ui/mock-factory.ts +6 -0
- package/src/ui/spinner.test.ts +65 -0
- package/src/ui/spinner.ts +41 -0
- package/src/ui/test-setup.ts +300 -0
- package/src/ui/theme.test.ts +23 -0
- package/src/ui/theme.ts +16 -0
- package/src/ui/views/LandingView.integration.test.ts +21 -0
- package/src/ui/views/LandingView.test.ts +24 -0
- package/src/ui/views/LandingView.ts +221 -0
- package/src/ui/views/LogView.test.ts +24 -0
- package/src/ui/views/LogView.ts +277 -0
- package/src/ui/views/ToyboxView.test.ts +46 -0
- package/src/ui/views/ToyboxView.ts +323 -0
- package/src/utils/clipboard.test.ts +86 -0
- package/src/utils/clipboard.ts +100 -0
- package/src/utils/index.test.ts +68 -0
- package/src/utils/index.ts +95 -0
- package/src/utils/persona.test.ts +12 -0
- package/src/utils/persona.ts +8 -0
- package/src/utils/project-root.test.ts +38 -0
- package/src/utils/project-root.ts +22 -0
- package/src/utils/resources.test.ts +64 -0
- package/src/utils/resources.ts +92 -0
- package/src/utils/search.test.ts +48 -0
- package/src/utils/search.ts +103 -0
- package/src/utils/session-tracker.test.ts +46 -0
- package/src/utils/session-tracker.ts +67 -0
- package/src/utils/spinner.test.ts +54 -0
- package/src/utils/spinner.ts +87 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { mock, expect, test, describe, beforeEach } from "bun:test";
|
|
2
|
+
|
|
3
|
+
const mockGit = {
|
|
4
|
+
status: mock(async () => ({ files: [], current: "main" })),
|
|
5
|
+
stash: mock(async () => {}),
|
|
6
|
+
checkout: mock(async () => {}),
|
|
7
|
+
pull: mock(async () => ({ catch: () => ({}) })),
|
|
8
|
+
checkoutLocalBranch: mock(async () => {}),
|
|
9
|
+
branchLocal: mock(async () => ({ all: ["main"], current: "main" })),
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
mock.module("simple-git", () => ({
|
|
13
|
+
default: mock(() => mockGit),
|
|
14
|
+
simpleGit: mock(() => mockGit),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
import * as branchService from "./branch.js";
|
|
18
|
+
|
|
19
|
+
describe("Branch Service", () => {
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
// Reset mockGit manual mocks
|
|
22
|
+
Object.values(mockGit).forEach(m => m.mockClear());
|
|
23
|
+
|
|
24
|
+
// Setup default behaviors
|
|
25
|
+
mockGit.status.mockResolvedValue({ files: [], current: "main" } as any);
|
|
26
|
+
mockGit.branchLocal.mockResolvedValue({ all: ["main", "master"], current: "main" } as any);
|
|
27
|
+
mockGit.pull.mockResolvedValue({ catch: () => ({}) } as any);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe("slugify", () => {
|
|
31
|
+
test("should convert text to kebab-case", () => {
|
|
32
|
+
expect(branchService.slugify("Feature: Add tests")).toBe("feature-add-tests");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("should remove special characters", () => {
|
|
36
|
+
expect(branchService.slugify("Fix! @bug #123")).toBe("fix-bug-123");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("should limit length to 50 characters", () => {
|
|
40
|
+
const long = "a".repeat(100);
|
|
41
|
+
expect(branchService.slugify(long)).toHaveLength(50);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("should trim dashes from ends", () => {
|
|
45
|
+
expect(branchService.slugify("-start-and-end-")).toBe("start-and-end");
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe("createTaskBranch", () => {
|
|
50
|
+
test("should create a branch with slugified task name", async () => {
|
|
51
|
+
const branchName = await branchService.createTaskBranch("New Task", "main");
|
|
52
|
+
expect(branchName).toBe("pickle/new-task");
|
|
53
|
+
expect(mockGit.checkoutLocalBranch).toHaveBeenCalledWith("pickle/new-task");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("should stash if there are uncommitted changes", async () => {
|
|
57
|
+
mockGit.status.mockResolvedValue({ files: ["file1.ts"], current: "main" } as any);
|
|
58
|
+
|
|
59
|
+
await branchService.createTaskBranch("New Task", "main");
|
|
60
|
+
|
|
61
|
+
expect(mockGit.stash).toHaveBeenCalledWith(["push", "-m", "pickle-autostash"]);
|
|
62
|
+
expect(mockGit.stash).toHaveBeenCalledWith(["pop"]);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("should checkout and pull base branch", async () => {
|
|
66
|
+
await branchService.createTaskBranch("New Task", "main");
|
|
67
|
+
|
|
68
|
+
expect(mockGit.checkout).toHaveBeenCalledWith("main");
|
|
69
|
+
expect(mockGit.pull).toHaveBeenCalledWith("origin", "main");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("should fallback to checkout if checkoutLocalBranch fails", async () => {
|
|
73
|
+
mockGit.checkoutLocalBranch.mockRejectedValue(new Error("Already exists"));
|
|
74
|
+
|
|
75
|
+
await branchService.createTaskBranch("New Task", "main");
|
|
76
|
+
|
|
77
|
+
expect(mockGit.checkout).toHaveBeenCalledWith("pickle/new-task");
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe("getCurrentBranch", () => {
|
|
82
|
+
test("should return current branch from status", async () => {
|
|
83
|
+
mockGit.status.mockResolvedValue({ current: "feature-branch" } as any);
|
|
84
|
+
const branch = await branchService.getCurrentBranch(undefined, mockGit as any);
|
|
85
|
+
expect(branch).toBe("feature-branch");
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe("getDefaultBaseBranch", () => {
|
|
90
|
+
test("should prefer main if available", async () => {
|
|
91
|
+
mockGit.branchLocal.mockResolvedValue({ all: ["main", "master"], current: "main" } as any);
|
|
92
|
+
const base = await branchService.getDefaultBaseBranch();
|
|
93
|
+
expect(base).toBe("main");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("should use master if main is missing", async () => {
|
|
97
|
+
mockGit.branchLocal.mockResolvedValue({ all: ["master", "other"], current: "master" } as any);
|
|
98
|
+
const base = await branchService.getDefaultBaseBranch();
|
|
99
|
+
expect(base).toBe("master");
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("should fallback to current branch if neither main nor master exists", async () => {
|
|
103
|
+
mockGit.branchLocal.mockResolvedValue({ all: ["dev"], current: "dev" } as any);
|
|
104
|
+
const base = await branchService.getDefaultBaseBranch();
|
|
105
|
+
expect(base).toBe("dev");
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe("hasUncommittedChanges", () => {
|
|
110
|
+
test("should return true if status has files", async () => {
|
|
111
|
+
mockGit.status.mockResolvedValue({ files: ["one"] } as any);
|
|
112
|
+
expect(await branchService.hasUncommittedChanges()).toBe(true);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("should return false if status has no files", async () => {
|
|
116
|
+
mockGit.status.mockResolvedValue({ files: [] } as any);
|
|
117
|
+
expect(await branchService.hasUncommittedChanges()).toBe(false);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe("getGitStatusInfo", () => {
|
|
122
|
+
test("should return mapped status info", async () => {
|
|
123
|
+
mockGit.status.mockResolvedValue({
|
|
124
|
+
current: "main",
|
|
125
|
+
ahead: 2,
|
|
126
|
+
behind: 1,
|
|
127
|
+
files: ["file.ts"]
|
|
128
|
+
} as any);
|
|
129
|
+
|
|
130
|
+
const info = await branchService.getGitStatusInfo();
|
|
131
|
+
expect(info).toEqual({
|
|
132
|
+
branch: "main",
|
|
133
|
+
ahead: 2,
|
|
134
|
+
behind: 1,
|
|
135
|
+
modified: 1,
|
|
136
|
+
isClean: false
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test("should return default info on error", async () => {
|
|
141
|
+
mockGit.status.mockRejectedValue(new Error("Git error"));
|
|
142
|
+
const info = await branchService.getGitStatusInfo();
|
|
143
|
+
expect(info.branch).toBe("unknown");
|
|
144
|
+
expect(info.isClean).toBe(true);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
});
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import simpleGit, { type SimpleGit } from "simple-git";
|
|
2
|
+
import type { GitStatusInfo } from "../../types/tasks.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Slugify text for branch names
|
|
6
|
+
*/
|
|
7
|
+
export function slugify(text: string): string {
|
|
8
|
+
return text
|
|
9
|
+
.toLowerCase()
|
|
10
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
11
|
+
.replace(/^-|-$/g, "")
|
|
12
|
+
.slice(0, 50);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Create a task branch
|
|
17
|
+
*/
|
|
18
|
+
export async function createTaskBranch(
|
|
19
|
+
task: string,
|
|
20
|
+
baseBranch: string,
|
|
21
|
+
workDir = process.cwd(),
|
|
22
|
+
): Promise<string> {
|
|
23
|
+
const git: SimpleGit = simpleGit(workDir);
|
|
24
|
+
const branchName = `pickle/${slugify(task)}`;
|
|
25
|
+
|
|
26
|
+
// Stash any changes
|
|
27
|
+
let stashed = false;
|
|
28
|
+
const status = await git.status();
|
|
29
|
+
if (status.files.length > 0) {
|
|
30
|
+
await git.stash(["push", "-m", "pickle-autostash"]);
|
|
31
|
+
stashed = true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
// Checkout base branch and pull
|
|
36
|
+
await git.checkout(baseBranch);
|
|
37
|
+
await git.pull("origin", baseBranch).catch(() => {
|
|
38
|
+
// Ignore pull errors
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Create new branch (or checkout if exists)
|
|
42
|
+
try {
|
|
43
|
+
await git.checkoutLocalBranch(branchName);
|
|
44
|
+
} catch {
|
|
45
|
+
await git.checkout(branchName);
|
|
46
|
+
}
|
|
47
|
+
} finally {
|
|
48
|
+
// Pop stash if we stashed
|
|
49
|
+
if (stashed) {
|
|
50
|
+
await git.stash(["pop"]).catch(() => {
|
|
51
|
+
// Ignore stash pop errors
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return branchName;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Return to the base branch
|
|
61
|
+
*/
|
|
62
|
+
export async function returnToBaseBranch(
|
|
63
|
+
baseBranch: string,
|
|
64
|
+
workDir = process.cwd(),
|
|
65
|
+
): Promise<void> {
|
|
66
|
+
const git: SimpleGit = simpleGit(workDir);
|
|
67
|
+
await git.checkout(baseBranch).catch(() => {
|
|
68
|
+
// Ignore checkout errors
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Get the current branch name
|
|
74
|
+
*/
|
|
75
|
+
export async function getCurrentBranch(workDir = process.cwd(), gitInstance?: SimpleGit): Promise<string> {
|
|
76
|
+
const git: SimpleGit = gitInstance || simpleGit(workDir);
|
|
77
|
+
const status = await git.status();
|
|
78
|
+
return status.current || "";
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Get the default base branch (main or master)
|
|
82
|
+
*/
|
|
83
|
+
export async function getDefaultBaseBranch(workDir = process.cwd()): Promise<string> {
|
|
84
|
+
const git: SimpleGit = simpleGit(workDir);
|
|
85
|
+
|
|
86
|
+
// Try main first, then master
|
|
87
|
+
const branches = await git.branchLocal();
|
|
88
|
+
if (branches.all.includes("main")) return "main";
|
|
89
|
+
if (branches.all.includes("master")) return "master";
|
|
90
|
+
|
|
91
|
+
// Fall back to current branch
|
|
92
|
+
return branches.current;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Check if there are uncommitted changes
|
|
97
|
+
*/
|
|
98
|
+
export async function hasUncommittedChanges(workDir = process.cwd()): Promise<boolean> {
|
|
99
|
+
const git: SimpleGit = simpleGit(workDir);
|
|
100
|
+
const status = await git.status();
|
|
101
|
+
return status.files.length > 0;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Get comprehensive git status info for display
|
|
106
|
+
*/
|
|
107
|
+
export async function getGitStatusInfo(workDir = process.cwd()): Promise<GitStatusInfo> {
|
|
108
|
+
const git: SimpleGit = simpleGit(workDir);
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
const status = await git.status();
|
|
112
|
+
return {
|
|
113
|
+
branch: status.current || "unknown",
|
|
114
|
+
ahead: status.ahead || 0,
|
|
115
|
+
behind: status.behind || 0,
|
|
116
|
+
modified: status.files.length,
|
|
117
|
+
isClean: status.files.length === 0,
|
|
118
|
+
};
|
|
119
|
+
} catch {
|
|
120
|
+
return {
|
|
121
|
+
branch: "unknown",
|
|
122
|
+
ahead: 0,
|
|
123
|
+
behind: 0,
|
|
124
|
+
modified: 0,
|
|
125
|
+
isClean: true,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { mock, expect, test, describe, beforeEach } from "bun:test";
|
|
2
|
+
|
|
3
|
+
const mockGit = {
|
|
4
|
+
diffSummary: mock(async () => ({ files: [] })),
|
|
5
|
+
raw: mock(async () => ""),
|
|
6
|
+
status: mock(async () => ({ not_added: [], created: [], deleted: [] })),
|
|
7
|
+
diff: mock(async () => ""),
|
|
8
|
+
show: mock(async () => ""),
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
mock.module("simple-git", () => ({
|
|
12
|
+
default: mock(() => mockGit),
|
|
13
|
+
simpleGit: mock(() => mockGit),
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
mock.module("node:fs/promises", () => ({
|
|
17
|
+
readFile: mock(async () => "line1\nline2"),
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
import * as diffService from "./diff.js";
|
|
21
|
+
|
|
22
|
+
describe("Diff Service", () => {
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
Object.values(mockGit).forEach(m => m.mockClear());
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe("getChangedFiles", () => {
|
|
28
|
+
test("should combine committed and uncommitted changes", async () => {
|
|
29
|
+
mockGit.diffSummary.mockResolvedValueOnce({
|
|
30
|
+
files: [{ file: "file1.ts", insertions: 10, deletions: 5 }]
|
|
31
|
+
} as any);
|
|
32
|
+
mockGit.raw.mockResolvedValueOnce("A\tfile1.ts");
|
|
33
|
+
|
|
34
|
+
const files = await diffService.getChangedFiles("/wt", "main", mockGit as any);
|
|
35
|
+
expect(files).toHaveLength(1);
|
|
36
|
+
expect(files[0].path).toBe("file1.ts");
|
|
37
|
+
expect(files[0].status).toBe("added");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("should handle renamed files in committed diff", async () => {
|
|
41
|
+
mockGit.diffSummary.mockResolvedValueOnce({
|
|
42
|
+
files: [{ file: "old.ts => new.ts" }]
|
|
43
|
+
} as any);
|
|
44
|
+
|
|
45
|
+
const files = await diffService.getChangedFiles("/wt", "main", mockGit as any);
|
|
46
|
+
expect(files[0].status).toBe("renamed");
|
|
47
|
+
expect(files[0].oldPath).toBe("old.ts");
|
|
48
|
+
expect(files[0].path).toBe("new.ts");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("should handle complex renamed files (with braces)", async () => {
|
|
52
|
+
mockGit.diffSummary.mockResolvedValueOnce({
|
|
53
|
+
files: [{ file: "src/{old.ts => new.ts}" }]
|
|
54
|
+
} as any);
|
|
55
|
+
|
|
56
|
+
const files = await diffService.getChangedFiles("/wt", "main", mockGit as any);
|
|
57
|
+
expect(files[0].status).toBe("renamed");
|
|
58
|
+
expect(files[0].oldPath).toBe("src/old.ts");
|
|
59
|
+
expect(files[0].path).toBe("src/new.ts");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("should include untracked and deleted files from status", async () => {
|
|
63
|
+
mockGit.status.mockResolvedValueOnce({
|
|
64
|
+
not_added: ["untracked.ts"],
|
|
65
|
+
created: [],
|
|
66
|
+
deleted: ["gone.ts"]
|
|
67
|
+
} as any);
|
|
68
|
+
|
|
69
|
+
const files = await diffService.getChangedFiles("/wt", "main", mockGit as any);
|
|
70
|
+
expect(files.find(f => f.path === "untracked.ts")?.status).toBe("added");
|
|
71
|
+
expect(files.find(f => f.path === "gone.ts")?.status).toBe("deleted");
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe("getFileDiff", () => {
|
|
76
|
+
test("should return diff for existing file", async () => {
|
|
77
|
+
mockGit.diff.mockResolvedValueOnce("some diff");
|
|
78
|
+
const diff = await diffService.getFileDiff("/wt", "main", "file.ts", "modified", mockGit as any);
|
|
79
|
+
expect(diff).toBe("some diff");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("should create synthetic diff for new files", async () => {
|
|
83
|
+
mockGit.diff.mockResolvedValueOnce("");
|
|
84
|
+
mockGit.show.mockRejectedValueOnce(new Error("No such file in base"));
|
|
85
|
+
|
|
86
|
+
const diff = await diffService.getFileDiff("/wt", "main", "new.ts", "added", mockGit as any);
|
|
87
|
+
expect(diff).toContain("new file mode 100644");
|
|
88
|
+
expect(diff).toContain("+line1");
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe("getFileType", () => {
|
|
93
|
+
test("should return correct language for extensions", () => {
|
|
94
|
+
expect(diffService.getFileType("test.ts")).toBe("typescript");
|
|
95
|
+
expect(diffService.getFileType("app.jsx")).toBe("javascript");
|
|
96
|
+
expect(diffService.getFileType("README.md")).toBe("markdown");
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("should return text for unknown extensions", () => {
|
|
100
|
+
expect(diffService.getFileType("config.unknown")).toBe("text");
|
|
101
|
+
expect(diffService.getFileType("no-ext")).toBe("text");
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe("getStatusIndicator", () => {
|
|
106
|
+
test("should return correct character", () => {
|
|
107
|
+
expect(diffService.getStatusIndicator("added")).toBe("A");
|
|
108
|
+
expect(diffService.getStatusIndicator("modified")).toBe("M");
|
|
109
|
+
expect(diffService.getStatusIndicator("deleted")).toBe("D");
|
|
110
|
+
expect(diffService.getStatusIndicator("renamed")).toBe("R");
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
});
|