ccmanager 3.5.0 → 3.5.2
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/dist/cli.js +3 -2
- package/dist/components/App.d.ts +1 -0
- package/dist/components/App.js +17 -39
- package/dist/components/App.test.js +12 -44
- package/dist/components/Configuration.js +10 -15
- package/dist/components/ConfigureCommand.js +18 -106
- package/dist/components/ConfigureCustomCommand.js +2 -17
- package/dist/components/ConfigureOther.js +5 -23
- package/dist/components/ConfigureOther.test.js +7 -31
- package/dist/components/ConfigureShortcuts.js +4 -31
- package/dist/components/ConfigureStatusHooks.js +5 -44
- package/dist/components/ConfigureStatusHooks.test.js +7 -31
- package/dist/components/ConfigureTimeout.js +2 -14
- package/dist/components/ConfigureWorktree.js +4 -47
- package/dist/components/ConfigureWorktreeHooks.js +5 -37
- package/dist/components/ConfigureWorktreeHooks.test.js +8 -33
- package/dist/components/Confirmation.js +9 -21
- package/dist/components/CustomCommandSummary.js +2 -5
- package/dist/components/DeleteConfirmation.js +10 -47
- package/dist/components/DeleteWorktree.js +14 -42
- package/dist/components/DeleteWorktree.test.js +6 -6
- package/dist/components/LoadingSpinner.js +3 -6
- package/dist/components/LoadingSpinner.test.js +22 -22
- package/dist/components/Menu.d.ts +1 -0
- package/dist/components/Menu.js +10 -42
- package/dist/components/Menu.recent-projects.test.js +8 -8
- package/dist/components/Menu.test.js +10 -10
- package/dist/components/MergeWorktree.js +16 -88
- package/dist/components/MergeWorktree.test.js +5 -5
- package/dist/components/NewWorktree.js +25 -105
- package/dist/components/NewWorktree.test.js +8 -8
- package/dist/components/PresetSelector.js +3 -9
- package/dist/components/ProjectList.js +9 -38
- package/dist/components/ProjectList.recent-projects.test.js +7 -7
- package/dist/components/ProjectList.test.js +37 -37
- package/dist/components/RemoteBranchSelector.js +2 -21
- package/dist/components/RemoteBranchSelector.test.js +8 -8
- package/dist/components/TextInputWrapper.d.ts +5 -0
- package/dist/components/TextInputWrapper.js +138 -11
- package/dist/constants/statusIcons.d.ts +2 -1
- package/dist/constants/statusIcons.js +13 -4
- package/dist/constants/statusIcons.test.js +41 -11
- package/dist/contexts/ConfigEditorContext.d.ts +1 -1
- package/dist/contexts/ConfigEditorContext.js +3 -2
- package/dist/services/autoApprovalVerifier.js +1 -8
- package/dist/services/bunTerminal.js +41 -136
- package/dist/services/config/configEditor.js +2 -12
- package/dist/services/config/globalConfigManager.js +4 -24
- package/dist/services/config/projectConfigManager.js +3 -18
- package/dist/services/globalSessionOrchestrator.js +3 -12
- package/dist/services/globalSessionOrchestrator.test.js +1 -8
- package/dist/services/projectManager.js +13 -68
- package/dist/services/sessionManager.d.ts +1 -1
- package/dist/services/sessionManager.effect.test.js +9 -37
- package/dist/services/sessionManager.js +12 -28
- package/dist/services/sessionManager.test.js +48 -40
- package/dist/services/shortcutManager.js +7 -13
- package/dist/services/stateDetector/base.d.ts +1 -1
- package/dist/services/stateDetector/claude.d.ts +1 -1
- package/dist/services/stateDetector/claude.js +11 -4
- package/dist/services/stateDetector/claude.test.js +47 -24
- package/dist/services/stateDetector/cline.d.ts +1 -1
- package/dist/services/stateDetector/cline.js +1 -1
- package/dist/services/stateDetector/codex.d.ts +1 -1
- package/dist/services/stateDetector/codex.js +1 -1
- package/dist/services/stateDetector/cursor.d.ts +1 -1
- package/dist/services/stateDetector/cursor.js +1 -1
- package/dist/services/stateDetector/gemini.d.ts +1 -1
- package/dist/services/stateDetector/gemini.js +1 -1
- package/dist/services/stateDetector/github-copilot.d.ts +1 -1
- package/dist/services/stateDetector/github-copilot.js +1 -1
- package/dist/services/stateDetector/opencode.d.ts +1 -1
- package/dist/services/stateDetector/opencode.js +1 -1
- package/dist/services/stateDetector/types.d.ts +1 -1
- package/dist/services/worktreeConfigManager.js +3 -8
- package/dist/services/worktreeService.js +2 -12
- package/dist/types/index.js +4 -12
- package/dist/utils/logger.js +12 -33
- package/dist/utils/mutex.d.ts +1 -1
- package/dist/utils/mutex.js +4 -19
- package/dist/utils/worktreeUtils.js +4 -4
- package/package.json +12 -12
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect } from 'react';
|
|
2
3
|
import { Box, Text, useInput } from 'ink';
|
|
3
4
|
import SelectInput from 'ink-select-input';
|
|
4
5
|
import { Effect } from 'effect';
|
|
@@ -80,25 +81,13 @@ const DeleteWorktree = ({ projectPath, onComplete, onCancel, }) => {
|
|
|
80
81
|
}
|
|
81
82
|
});
|
|
82
83
|
if (isLoading) {
|
|
83
|
-
return (
|
|
84
|
-
React.createElement(Text, { color: "cyan" }, "Loading worktrees...")));
|
|
84
|
+
return (_jsx(Box, { flexDirection: "column", children: _jsx(Text, { color: "cyan", children: "Loading worktrees..." }) }));
|
|
85
85
|
}
|
|
86
86
|
if (error) {
|
|
87
|
-
return (
|
|
88
|
-
React.createElement(Text, { color: "red" }, "Error loading worktrees:"),
|
|
89
|
-
React.createElement(Text, { color: "red" }, error),
|
|
90
|
-
React.createElement(Text, { dimColor: true },
|
|
91
|
-
"Press ",
|
|
92
|
-
shortcutManager.getShortcutDisplay('cancel'),
|
|
93
|
-
" to return to menu")));
|
|
87
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "red", children: "Error loading worktrees:" }), _jsx(Text, { color: "red", children: error }), _jsxs(Text, { dimColor: true, children: ["Press ", shortcutManager.getShortcutDisplay('cancel'), " to return to menu"] })] }));
|
|
94
88
|
}
|
|
95
89
|
if (worktrees.length === 0) {
|
|
96
|
-
return (
|
|
97
|
-
React.createElement(Text, { color: "yellow" }, "No worktrees available to delete."),
|
|
98
|
-
React.createElement(Text, { dimColor: true },
|
|
99
|
-
"Press ",
|
|
100
|
-
shortcutManager.getShortcutDisplay('cancel'),
|
|
101
|
-
" to return to menu")));
|
|
90
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "yellow", children: "No worktrees available to delete." }), _jsxs(Text, { dimColor: true, children: ["Press ", shortcutManager.getShortcutDisplay('cancel'), " to return to menu"] })] }));
|
|
102
91
|
}
|
|
103
92
|
if (confirmMode) {
|
|
104
93
|
const selectedWorktrees = Array.from(selectedIndices).map(index => worktrees[index]);
|
|
@@ -109,32 +98,15 @@ const DeleteWorktree = ({ projectPath, onComplete, onCancel, }) => {
|
|
|
109
98
|
const handleCancel = () => {
|
|
110
99
|
setConfirmMode(false);
|
|
111
100
|
};
|
|
112
|
-
return (
|
|
101
|
+
return (_jsx(DeleteConfirmation, { worktrees: selectedWorktrees, onConfirm: handleConfirm, onCancel: handleCancel }));
|
|
113
102
|
}
|
|
114
|
-
return (
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}, limit: 10, indicatorComponent: ({ isSelected }) => (React.createElement(Text, { color: isSelected ? 'green' : undefined }, isSelected ? '>' : ' ')), itemComponent: ({ isSelected, label }) => {
|
|
123
|
-
// Check if this item is actually selected (checkbox checked)
|
|
124
|
-
const hasCheckmark = label.includes('[✓]');
|
|
125
|
-
return (React.createElement(Text, { color: isSelected ? 'green' : undefined, inverse: isSelected, dimColor: !isSelected && !hasCheckmark }, label));
|
|
126
|
-
} }),
|
|
127
|
-
React.createElement(Box, { marginTop: 1, flexDirection: "column" },
|
|
128
|
-
React.createElement(Text, { dimColor: true },
|
|
129
|
-
"Controls: \u2191\u2193/j/k Navigate, Space Select, Enter Confirm,",
|
|
130
|
-
' ',
|
|
131
|
-
shortcutManager.getShortcutDisplay('cancel'),
|
|
132
|
-
" Cancel"),
|
|
133
|
-
selectedIndices.size > 0 && (React.createElement(Text, { color: "yellow" },
|
|
134
|
-
selectedIndices.size,
|
|
135
|
-
" worktree",
|
|
136
|
-
selectedIndices.size > 1 ? 's' : '',
|
|
137
|
-
' ',
|
|
138
|
-
"selected")))));
|
|
103
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "red", children: "Delete Worktrees" }) }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { dimColor: true, children: "Select worktrees to delete (Space to select, Enter to confirm):" }) }), _jsx(SelectInput, { items: menuItems, onSelect: handleSelect, onHighlight: (item) => {
|
|
104
|
+
const index = parseInt(item.value, 10);
|
|
105
|
+
setFocusedIndex(index);
|
|
106
|
+
}, limit: 10, indicatorComponent: ({ isSelected }) => (_jsx(Text, { color: isSelected ? 'green' : undefined, children: isSelected ? '>' : ' ' })), itemComponent: ({ isSelected, label }) => {
|
|
107
|
+
// Check if this item is actually selected (checkbox checked)
|
|
108
|
+
const hasCheckmark = label.includes('[✓]');
|
|
109
|
+
return (_jsx(Text, { color: isSelected ? 'green' : undefined, inverse: isSelected, dimColor: !isSelected && !hasCheckmark, children: label }));
|
|
110
|
+
} }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Text, { dimColor: true, children: ["Controls: \u2191\u2193/j/k Navigate, Space Select, Enter Confirm,", ' ', shortcutManager.getShortcutDisplay('cancel'), " Cancel"] }), selectedIndices.size > 0 && (_jsxs(Text, { color: "yellow", children: [selectedIndices.size, " worktree", selectedIndices.size > 1 ? 's' : '', ' ', "selected"] }))] })] }));
|
|
139
111
|
};
|
|
140
112
|
export default DeleteWorktree;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { render } from 'ink-testing-library';
|
|
3
3
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
4
4
|
import { Effect } from 'effect';
|
|
@@ -60,7 +60,7 @@ describe('DeleteWorktree - Effect Integration', () => {
|
|
|
60
60
|
const onComplete = vi.fn();
|
|
61
61
|
const onCancel = vi.fn();
|
|
62
62
|
// WHEN: Component renders with projectPath
|
|
63
|
-
render(
|
|
63
|
+
render(_jsx(DeleteWorktree, { projectPath: projectPath, onComplete: onComplete, onCancel: onCancel }));
|
|
64
64
|
// Wait for Effect to execute
|
|
65
65
|
await new Promise(resolve => setTimeout(resolve, 50));
|
|
66
66
|
// THEN: WorktreeService was called with projectPath
|
|
@@ -85,7 +85,7 @@ describe('DeleteWorktree - Effect Integration', () => {
|
|
|
85
85
|
const onComplete = vi.fn();
|
|
86
86
|
const onCancel = vi.fn();
|
|
87
87
|
// WHEN: Component renders without projectPath
|
|
88
|
-
render(
|
|
88
|
+
render(_jsx(DeleteWorktree, { onComplete: onComplete, onCancel: onCancel }));
|
|
89
89
|
// Wait for Effect to execute
|
|
90
90
|
await new Promise(resolve => setTimeout(resolve, 50));
|
|
91
91
|
// THEN: WorktreeService was called with undefined (defaults to cwd)
|
|
@@ -117,7 +117,7 @@ describe('DeleteWorktree - Effect Integration', () => {
|
|
|
117
117
|
const onComplete = vi.fn();
|
|
118
118
|
const onCancel = vi.fn();
|
|
119
119
|
// WHEN: Component is rendered
|
|
120
|
-
const { lastFrame } = render(
|
|
120
|
+
const { lastFrame } = render(_jsx(DeleteWorktree, { onComplete: onComplete, onCancel: onCancel }));
|
|
121
121
|
// Wait for Effect to execute
|
|
122
122
|
await new Promise(resolve => setTimeout(resolve, 50));
|
|
123
123
|
// THEN: Effect method should be called
|
|
@@ -144,7 +144,7 @@ describe('DeleteWorktree - Effect Integration', () => {
|
|
|
144
144
|
const onComplete = vi.fn();
|
|
145
145
|
const onCancel = vi.fn();
|
|
146
146
|
// WHEN: Component is rendered
|
|
147
|
-
const { lastFrame } = render(
|
|
147
|
+
const { lastFrame } = render(_jsx(DeleteWorktree, { onComplete: onComplete, onCancel: onCancel }));
|
|
148
148
|
// Wait for Effect to execute
|
|
149
149
|
await new Promise(resolve => setTimeout(resolve, 50));
|
|
150
150
|
// THEN: Error should be displayed
|
|
@@ -180,7 +180,7 @@ describe('DeleteWorktree - Effect Integration', () => {
|
|
|
180
180
|
const onComplete = vi.fn();
|
|
181
181
|
const onCancel = vi.fn();
|
|
182
182
|
// WHEN: Component is rendered
|
|
183
|
-
const { lastFrame } = render(
|
|
183
|
+
const { lastFrame } = render(_jsx(DeleteWorktree, { onComplete: onComplete, onCancel: onCancel }));
|
|
184
184
|
// Wait for Effect to execute
|
|
185
185
|
await new Promise(resolve => setTimeout(resolve, 50));
|
|
186
186
|
// THEN: Only non-main worktree should be shown
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect } from 'react';
|
|
2
3
|
import { Box, Text } from 'ink';
|
|
3
4
|
import { supportsUnicode } from '../utils/terminalCapabilities.js';
|
|
4
5
|
const LoadingSpinner = ({ message, spinnerType, color = 'cyan', }) => {
|
|
@@ -28,10 +29,6 @@ const LoadingSpinner = ({ message, spinnerType, color = 'cyan', }) => {
|
|
|
28
29
|
};
|
|
29
30
|
}, [frames.length]);
|
|
30
31
|
const currentFrame = frames[frameIndex];
|
|
31
|
-
return (
|
|
32
|
-
React.createElement(Text, { color: color },
|
|
33
|
-
currentFrame,
|
|
34
|
-
" "),
|
|
35
|
-
React.createElement(Text, null, message)));
|
|
32
|
+
return (_jsxs(Box, { flexDirection: "row", children: [_jsxs(Text, { color: color, children: [currentFrame, " "] }), _jsx(Text, { children: message })] }));
|
|
36
33
|
};
|
|
37
34
|
export default LoadingSpinner;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
2
|
import { describe, it, expect, vi, afterEach } from 'vitest';
|
|
2
|
-
import React from 'react';
|
|
3
3
|
import { render } from 'ink-testing-library';
|
|
4
4
|
import LoadingSpinner from './LoadingSpinner.js';
|
|
5
5
|
describe('LoadingSpinner', () => {
|
|
@@ -19,21 +19,21 @@ describe('LoadingSpinner', () => {
|
|
|
19
19
|
});
|
|
20
20
|
describe('Core LoadingSpinner component with Unicode animation', () => {
|
|
21
21
|
it('should render with default props and display message with cyan spinner', () => {
|
|
22
|
-
const { lastFrame } = render(
|
|
22
|
+
const { lastFrame } = render(_jsx(LoadingSpinner, { message: "Loading..." }));
|
|
23
23
|
const output = lastFrame();
|
|
24
24
|
expect(output).toContain('Loading...');
|
|
25
25
|
// Should contain one of the Unicode spinner frames
|
|
26
26
|
expect(output).toMatch(/[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]/);
|
|
27
27
|
});
|
|
28
28
|
it('should render with custom color prop (yellow for devcontainer operations)', () => {
|
|
29
|
-
const { lastFrame } = render(
|
|
29
|
+
const { lastFrame } = render(_jsx(LoadingSpinner, { message: "Starting devcontainer...", color: "yellow" }));
|
|
30
30
|
const output = lastFrame();
|
|
31
31
|
expect(output).toContain('Starting devcontainer...');
|
|
32
32
|
// Verify spinner is present
|
|
33
33
|
expect(output).toMatch(/[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]/);
|
|
34
34
|
});
|
|
35
35
|
it('should use ASCII fallback frames when spinnerType is "line"', () => {
|
|
36
|
-
const { lastFrame } = render(
|
|
36
|
+
const { lastFrame } = render(_jsx(LoadingSpinner, { message: "Processing...", spinnerType: "line" }));
|
|
37
37
|
const output = lastFrame();
|
|
38
38
|
expect(output).toContain('Processing...');
|
|
39
39
|
// Should contain one of the ASCII spinner frames
|
|
@@ -41,23 +41,23 @@ describe('LoadingSpinner', () => {
|
|
|
41
41
|
});
|
|
42
42
|
it('should set up animation interval with 120ms timing', () => {
|
|
43
43
|
const setIntervalSpy = vi.spyOn(global, 'setInterval');
|
|
44
|
-
render(
|
|
44
|
+
render(_jsx(LoadingSpinner, { message: "Loading..." }));
|
|
45
45
|
// Verify setInterval was called with 120ms timing
|
|
46
46
|
expect(setIntervalSpy).toHaveBeenCalledWith(expect.any(Function), 120);
|
|
47
47
|
});
|
|
48
48
|
it('should cleanup interval on component unmount to prevent memory leaks', () => {
|
|
49
|
-
const { unmount } = render(
|
|
49
|
+
const { unmount } = render(_jsx(LoadingSpinner, { message: "Loading..." }));
|
|
50
50
|
// Verify unmount completes without errors (cleanup function runs properly)
|
|
51
51
|
expect(() => unmount()).not.toThrow();
|
|
52
52
|
});
|
|
53
53
|
it('should preserve message text', () => {
|
|
54
54
|
const message = 'Creating session...';
|
|
55
|
-
const { lastFrame } = render(
|
|
55
|
+
const { lastFrame } = render(_jsx(LoadingSpinner, { message: message }));
|
|
56
56
|
// Check message is rendered
|
|
57
57
|
expect(lastFrame()).toContain(message);
|
|
58
58
|
});
|
|
59
59
|
it('should render in flexDirection="row" layout with spinner and message', () => {
|
|
60
|
-
const { lastFrame } = render(
|
|
60
|
+
const { lastFrame } = render(_jsx(LoadingSpinner, { message: "Test message" }));
|
|
61
61
|
const output = lastFrame();
|
|
62
62
|
// Verify both spinner and message are present
|
|
63
63
|
// ANSI color codes may be present between spinner and message
|
|
@@ -67,45 +67,45 @@ describe('LoadingSpinner', () => {
|
|
|
67
67
|
});
|
|
68
68
|
describe('Component variations and edge cases', () => {
|
|
69
69
|
it('should accept "green" color option', () => {
|
|
70
|
-
const { lastFrame } = render(
|
|
70
|
+
const { lastFrame } = render(_jsx(LoadingSpinner, { message: "Success loading...", color: "green" }));
|
|
71
71
|
const output = lastFrame();
|
|
72
72
|
expect(output).toContain('Success loading...');
|
|
73
73
|
expect(output).toMatch(/[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]/);
|
|
74
74
|
});
|
|
75
75
|
it('should render Unicode frames correctly', () => {
|
|
76
|
-
const { lastFrame } = render(
|
|
76
|
+
const { lastFrame } = render(_jsx(LoadingSpinner, { message: "Test" }));
|
|
77
77
|
const output = lastFrame();
|
|
78
78
|
// Should contain one of the Unicode frames
|
|
79
79
|
expect(output).toMatch(/[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]/);
|
|
80
80
|
});
|
|
81
81
|
it('should render ASCII frames when using "line" spinner type', () => {
|
|
82
|
-
const { lastFrame } = render(
|
|
82
|
+
const { lastFrame } = render(_jsx(LoadingSpinner, { message: "Test", spinnerType: "line" }));
|
|
83
83
|
const output = lastFrame();
|
|
84
84
|
// Should contain one of the ASCII frames
|
|
85
85
|
expect(output).toMatch(/[-\\|/]/);
|
|
86
86
|
});
|
|
87
87
|
it('should handle empty message string', () => {
|
|
88
|
-
const { lastFrame } = render(
|
|
88
|
+
const { lastFrame } = render(_jsx(LoadingSpinner, { message: "" }));
|
|
89
89
|
const output = lastFrame();
|
|
90
90
|
// Should still render spinner even with empty message
|
|
91
91
|
expect(output).toMatch(/[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]/);
|
|
92
92
|
});
|
|
93
93
|
it('should handle long message text without breaking layout', () => {
|
|
94
94
|
const longMessage = 'This is a very long loading message that might wrap on narrow terminals';
|
|
95
|
-
const { lastFrame } = render(
|
|
95
|
+
const { lastFrame } = render(_jsx(LoadingSpinner, { message: longMessage }));
|
|
96
96
|
const output = lastFrame();
|
|
97
97
|
expect(output).toContain(longMessage);
|
|
98
98
|
expect(output).toMatch(/[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]/);
|
|
99
99
|
});
|
|
100
100
|
it('should use cyan as default color', () => {
|
|
101
|
-
const { lastFrame } = render(
|
|
101
|
+
const { lastFrame } = render(_jsx(LoadingSpinner, { message: "Test" }));
|
|
102
102
|
const output = lastFrame();
|
|
103
103
|
// Just verify it renders successfully with default color
|
|
104
104
|
expect(output).toContain('Test');
|
|
105
105
|
expect(output).toMatch(/[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]/);
|
|
106
106
|
});
|
|
107
107
|
it('should use dots as default spinner type', () => {
|
|
108
|
-
const { lastFrame } = render(
|
|
108
|
+
const { lastFrame } = render(_jsx(LoadingSpinner, { message: "Test" }));
|
|
109
109
|
const output = lastFrame();
|
|
110
110
|
// Default should be Unicode dots, not ASCII line
|
|
111
111
|
expect(output).toMatch(/[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]/);
|
|
@@ -115,7 +115,7 @@ describe('LoadingSpinner', () => {
|
|
|
115
115
|
it('should automatically detect Unicode support and use Unicode frames', () => {
|
|
116
116
|
// Set up environment for Unicode support
|
|
117
117
|
process.env['TERM'] = 'xterm-256color';
|
|
118
|
-
const { lastFrame } = render(
|
|
118
|
+
const { lastFrame } = render(_jsx(LoadingSpinner, { message: "Loading..." }));
|
|
119
119
|
const output = lastFrame();
|
|
120
120
|
// Should use Unicode frames when terminal supports it
|
|
121
121
|
expect(output).toMatch(/[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]/);
|
|
@@ -126,7 +126,7 @@ describe('LoadingSpinner', () => {
|
|
|
126
126
|
process.env['TERM'] = 'dumb';
|
|
127
127
|
delete process.env['LANG'];
|
|
128
128
|
delete process.env['LC_ALL'];
|
|
129
|
-
const { lastFrame } = render(
|
|
129
|
+
const { lastFrame } = render(_jsx(LoadingSpinner, { message: "Loading..." }));
|
|
130
130
|
const output = lastFrame();
|
|
131
131
|
// Should use ASCII frames when terminal doesn't support Unicode
|
|
132
132
|
expect(output).toMatch(/[-\\|/]/);
|
|
@@ -135,7 +135,7 @@ describe('LoadingSpinner', () => {
|
|
|
135
135
|
it('should respect explicit spinnerType prop over automatic detection', () => {
|
|
136
136
|
// Even with Unicode support, explicit "line" should use ASCII
|
|
137
137
|
process.env['TERM'] = 'xterm-256color';
|
|
138
|
-
const { lastFrame } = render(
|
|
138
|
+
const { lastFrame } = render(_jsx(LoadingSpinner, { message: "Loading...", spinnerType: "line" }));
|
|
139
139
|
const output = lastFrame();
|
|
140
140
|
// Explicit spinnerType should override detection
|
|
141
141
|
expect(output).toMatch(/[-\\|/]/);
|
|
@@ -148,7 +148,7 @@ describe('LoadingSpinner', () => {
|
|
|
148
148
|
configurable: true,
|
|
149
149
|
});
|
|
150
150
|
process.env['WT_SESSION'] = 'some-session-id';
|
|
151
|
-
const { lastFrame } = render(
|
|
151
|
+
const { lastFrame } = render(_jsx(LoadingSpinner, { message: "Loading..." }));
|
|
152
152
|
const output = lastFrame();
|
|
153
153
|
// Should use Unicode on Windows Terminal
|
|
154
154
|
expect(output).toMatch(/[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]/);
|
|
@@ -163,7 +163,7 @@ describe('LoadingSpinner', () => {
|
|
|
163
163
|
delete process.env['WT_SESSION'];
|
|
164
164
|
delete process.env['TERM'];
|
|
165
165
|
delete process.env['LANG'];
|
|
166
|
-
const { lastFrame } = render(
|
|
166
|
+
const { lastFrame } = render(_jsx(LoadingSpinner, { message: "Loading..." }));
|
|
167
167
|
const output = lastFrame();
|
|
168
168
|
// Should use ASCII on Windows without WT
|
|
169
169
|
expect(output).toMatch(/[-\\|/]/);
|
|
@@ -171,7 +171,7 @@ describe('LoadingSpinner', () => {
|
|
|
171
171
|
it('should detect Unicode support from LANG environment variable', () => {
|
|
172
172
|
delete process.env['TERM'];
|
|
173
173
|
process.env['LANG'] = 'en_US.UTF-8';
|
|
174
|
-
const { lastFrame } = render(
|
|
174
|
+
const { lastFrame } = render(_jsx(LoadingSpinner, { message: "Loading..." }));
|
|
175
175
|
const output = lastFrame();
|
|
176
176
|
// Should use Unicode when LANG indicates UTF-8
|
|
177
177
|
expect(output).toMatch(/[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]/);
|
|
@@ -179,7 +179,7 @@ describe('LoadingSpinner', () => {
|
|
|
179
179
|
it('should detect Unicode support with appropriate frame rate', () => {
|
|
180
180
|
process.env['TERM'] = 'xterm-256color';
|
|
181
181
|
const setIntervalSpy = vi.spyOn(global, 'setInterval');
|
|
182
|
-
render(
|
|
182
|
+
render(_jsx(LoadingSpinner, { message: "Loading..." }));
|
|
183
183
|
// Verify frame rate is 120ms regardless of detection
|
|
184
184
|
expect(setIntervalSpy).toHaveBeenCalledWith(expect.any(Function), 120);
|
|
185
185
|
});
|
package/dist/components/Menu.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect } from 'react';
|
|
2
3
|
import { Box, Text, useInput } from 'ink';
|
|
3
4
|
import SelectInput from 'ink-select-input';
|
|
4
5
|
import { Effect } from 'effect';
|
|
@@ -26,7 +27,7 @@ const createSeparatorWithText = (text, totalWidth = 35) => {
|
|
|
26
27
|
const formatGitError = (error) => {
|
|
27
28
|
return `Git command failed: ${error.command} (exit ${error.exitCode})\n${error.stderr}`;
|
|
28
29
|
};
|
|
29
|
-
const Menu = ({ sessionManager, worktreeService, onSelectWorktree, onSelectRecentProject, error, onDismissError, projectName, multiProject = false, }) => {
|
|
30
|
+
const Menu = ({ sessionManager, worktreeService, onSelectWorktree, onSelectRecentProject, error, onDismissError, projectName, multiProject = false, version, }) => {
|
|
30
31
|
const [baseWorktrees, setBaseWorktrees] = useState([]);
|
|
31
32
|
const [defaultBranch, setDefaultBranch] = useState(null);
|
|
32
33
|
const [loadError, setLoadError] = useState(null);
|
|
@@ -479,45 +480,12 @@ const Menu = ({ sessionManager, worktreeService, onSelectWorktree, onSelectRecen
|
|
|
479
480
|
onSelectWorktree(item.worktree);
|
|
480
481
|
}
|
|
481
482
|
};
|
|
482
|
-
return (
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
React.createElement(Text, null, "Search: "),
|
|
490
|
-
React.createElement(TextInputWrapper, { value: searchQuery, onChange: setSearchQuery, focus: true, placeholder: "Type to filter worktrees..." }))),
|
|
491
|
-
isSearchMode && items.length === 0 ? (React.createElement(Box, null,
|
|
492
|
-
React.createElement(Text, { color: "yellow" }, "No worktrees match your search"))) : isSearchMode ? (
|
|
493
|
-
// In search mode, show the items as a list without SelectInput
|
|
494
|
-
React.createElement(Box, { flexDirection: "column" }, items.slice(0, limit).map((item, index) => (React.createElement(Text, { key: item.value, color: index === selectedIndex ? 'green' : undefined },
|
|
495
|
-
index === selectedIndex ? '❯ ' : ' ',
|
|
496
|
-
item.label))))) : (React.createElement(SelectInput, { items: items, onSelect: item => handleSelect(item), isFocused: !error, initialIndex: selectedIndex, limit: limit })),
|
|
497
|
-
(error || loadError) && (React.createElement(Box, { marginTop: 1, paddingX: 1, borderStyle: "round", borderColor: "red" },
|
|
498
|
-
React.createElement(Box, { flexDirection: "column" },
|
|
499
|
-
React.createElement(Text, { color: "red", bold: true },
|
|
500
|
-
"Error: ",
|
|
501
|
-
error || loadError),
|
|
502
|
-
React.createElement(Text, { color: "gray", dimColor: true }, "Press any key to dismiss")))),
|
|
503
|
-
React.createElement(Box, { marginTop: 1, flexDirection: "column" },
|
|
504
|
-
React.createElement(Text, { dimColor: true },
|
|
505
|
-
"Status: ",
|
|
506
|
-
STATUS_ICONS.BUSY,
|
|
507
|
-
" ",
|
|
508
|
-
STATUS_LABELS.BUSY,
|
|
509
|
-
' ',
|
|
510
|
-
STATUS_ICONS.WAITING,
|
|
511
|
-
" ",
|
|
512
|
-
STATUS_LABELS.WAITING,
|
|
513
|
-
" ",
|
|
514
|
-
STATUS_ICONS.IDLE,
|
|
515
|
-
' ',
|
|
516
|
-
STATUS_LABELS.IDLE),
|
|
517
|
-
React.createElement(Text, { dimColor: true }, isSearchMode
|
|
518
|
-
? 'Search Mode: Type to filter, Enter to exit search, ESC to exit search'
|
|
519
|
-
: searchQuery
|
|
520
|
-
? `Filtered: "${searchQuery}" | ↑↓ Navigate Enter Select | /-Search ESC-Clear 0-9 Quick Select N-New M-Merge D-Delete ${multiProject ? 'C-Config' : 'P-ProjConfig C-GlobalConfig'} ${projectName ? 'B-Back' : 'Q-Quit'}`
|
|
521
|
-
: `Controls: ↑↓ Navigate Enter Select | Hotkeys: 0-9 Quick Select /-Search N-New M-Merge D-Delete ${multiProject ? 'C-Config' : 'P-ProjConfig C-GlobalConfig'} ${projectName ? 'B-Back' : 'Q-Quit'}`))));
|
|
483
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { marginBottom: 1, flexDirection: "column", children: [_jsxs(Text, { bold: true, color: "green", children: ["CCManager - Claude Code Worktree Manager v", version] }), projectName && (_jsx(Text, { bold: true, color: "green", children: projectName }))] }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { dimColor: true, children: "Select a worktree to start or resume a Claude Code session:" }) }), isSearchMode && (_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { children: "Search: " }), _jsx(TextInputWrapper, { value: searchQuery, onChange: setSearchQuery, focus: true, placeholder: "Type to filter worktrees..." })] })), isSearchMode && items.length === 0 ? (_jsx(Box, { children: _jsx(Text, { color: "yellow", children: "No worktrees match your search" }) })) : isSearchMode ? (
|
|
484
|
+
// In search mode, show the items as a list without SelectInput
|
|
485
|
+
_jsx(Box, { flexDirection: "column", children: items.slice(0, limit).map((item, index) => (_jsxs(Text, { color: index === selectedIndex ? 'green' : undefined, children: [index === selectedIndex ? '❯ ' : ' ', item.label] }, item.value))) })) : (_jsx(SelectInput, { items: items, onSelect: item => handleSelect(item), isFocused: !error, initialIndex: selectedIndex, limit: limit })), (error || loadError) && (_jsx(Box, { marginTop: 1, paddingX: 1, borderStyle: "round", borderColor: "red", children: _jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: "red", bold: true, children: ["Error: ", error || loadError] }), _jsx(Text, { color: "gray", dimColor: true, children: "Press any key to dismiss" })] }) })), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Text, { dimColor: true, children: ["Status: ", STATUS_ICONS.BUSY, " ", STATUS_LABELS.BUSY, ' ', STATUS_ICONS.WAITING, " ", STATUS_LABELS.WAITING, " ", STATUS_ICONS.IDLE, ' ', STATUS_LABELS.IDLE] }), _jsx(Text, { dimColor: true, children: isSearchMode
|
|
486
|
+
? 'Search Mode: Type to filter, Enter to exit search, ESC to exit search'
|
|
487
|
+
: searchQuery
|
|
488
|
+
? `Filtered: "${searchQuery}" | ↑↓ Navigate Enter Select | /-Search ESC-Clear 0-9 Quick Select N-New M-Merge D-Delete ${multiProject ? 'C-Config' : 'P-ProjConfig C-GlobalConfig'} ${projectName ? 'B-Back' : 'Q-Quit'}`
|
|
489
|
+
: `Controls: ↑↓ Navigate Enter Select | Hotkeys: 0-9 Quick Select /-Search N-New M-Merge D-Delete ${multiProject ? 'C-Config' : 'P-ProjConfig C-GlobalConfig'} ${projectName ? 'B-Back' : 'Q-Quit'}` })] })] }));
|
|
522
490
|
};
|
|
523
491
|
export default Menu;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
3
3
|
import { render } from 'ink-testing-library';
|
|
4
4
|
import { Effect } from 'effect';
|
|
@@ -106,7 +106,7 @@ describe('Menu - Recent Projects', () => {
|
|
|
106
106
|
vi.mocked(projectManager.getRecentProjects).mockReturnValue([
|
|
107
107
|
{ path: '/project1', name: 'Project 1', lastAccessed: 1000 },
|
|
108
108
|
]);
|
|
109
|
-
const { lastFrame } = render(
|
|
109
|
+
const { lastFrame } = render(_jsx(Menu, { sessionManager: mockSessionManager, worktreeService: mockWorktreeService, onSelectWorktree: vi.fn(), multiProject: false, version: "test" }));
|
|
110
110
|
const output = lastFrame();
|
|
111
111
|
expect(output).not.toContain('─ Recent ─');
|
|
112
112
|
expect(output).not.toContain('Project 1');
|
|
@@ -126,7 +126,7 @@ describe('Menu - Recent Projects', () => {
|
|
|
126
126
|
backgroundTasks: 0,
|
|
127
127
|
});
|
|
128
128
|
vi.spyOn(SessionManager, 'formatSessionCounts').mockReturnValue('');
|
|
129
|
-
const { lastFrame } = render(
|
|
129
|
+
const { lastFrame } = render(_jsx(Menu, { sessionManager: mockSessionManager, worktreeService: mockWorktreeService, onSelectWorktree: vi.fn(), onSelectRecentProject: vi.fn(), multiProject: true, version: "test" }));
|
|
130
130
|
// Wait for Effect to execute
|
|
131
131
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
132
132
|
const output = lastFrame();
|
|
@@ -136,7 +136,7 @@ describe('Menu - Recent Projects', () => {
|
|
|
136
136
|
});
|
|
137
137
|
it('should not show recent projects section when no recent projects', () => {
|
|
138
138
|
vi.mocked(projectManager.getRecentProjects).mockReturnValue([]);
|
|
139
|
-
const { lastFrame } = render(
|
|
139
|
+
const { lastFrame } = render(_jsx(Menu, { sessionManager: mockSessionManager, worktreeService: mockWorktreeService, onSelectWorktree: vi.fn(), onSelectRecentProject: vi.fn(), multiProject: true, version: "test" }));
|
|
140
140
|
const output = lastFrame();
|
|
141
141
|
expect(output).not.toContain('─ Recent ─');
|
|
142
142
|
});
|
|
@@ -157,7 +157,7 @@ describe('Menu - Recent Projects', () => {
|
|
|
157
157
|
backgroundTasks: 0,
|
|
158
158
|
});
|
|
159
159
|
vi.spyOn(SessionManager, 'formatSessionCounts').mockReturnValue('');
|
|
160
|
-
const { lastFrame } = render(
|
|
160
|
+
const { lastFrame } = render(_jsx(Menu, { sessionManager: mockSessionManager, worktreeService: mockWorktreeService, onSelectWorktree: vi.fn(), onSelectRecentProject: vi.fn(), multiProject: true, version: "test" }));
|
|
161
161
|
// Wait for Effect to execute
|
|
162
162
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
163
163
|
const output = lastFrame();
|
|
@@ -177,9 +177,9 @@ describe('Menu - Recent Projects', () => {
|
|
|
177
177
|
...mockWorktreeService,
|
|
178
178
|
getGitRootPath: vi.fn().mockReturnValue('/current/project'),
|
|
179
179
|
};
|
|
180
|
-
const { lastFrame, rerender } = render(
|
|
180
|
+
const { lastFrame, rerender } = render(_jsx(Menu, { sessionManager: mockSessionManager, worktreeService: worktreeServiceWithGitRoot, onSelectWorktree: vi.fn(), onSelectRecentProject: vi.fn(), multiProject: true, version: "test" }));
|
|
181
181
|
// Force a rerender to ensure all effects have run
|
|
182
|
-
rerender(
|
|
182
|
+
rerender(_jsx(Menu, { sessionManager: mockSessionManager, worktreeService: worktreeServiceWithGitRoot, onSelectWorktree: vi.fn(), onSelectRecentProject: vi.fn(), multiProject: true, version: "test" }));
|
|
183
183
|
// Wait for the state to update and component to re-render
|
|
184
184
|
await new Promise(resolve => setTimeout(resolve, 50));
|
|
185
185
|
const output = lastFrame();
|
|
@@ -198,7 +198,7 @@ describe('Menu - Recent Projects', () => {
|
|
|
198
198
|
]);
|
|
199
199
|
// Mock getGitRootPath to return the current project path
|
|
200
200
|
vi.mocked(mockWorktreeService.getGitRootPath).mockReturnValue('/current/project');
|
|
201
|
-
const { lastFrame } = render(
|
|
201
|
+
const { lastFrame } = render(_jsx(Menu, { sessionManager: mockSessionManager, worktreeService: mockWorktreeService, onSelectWorktree: vi.fn(), onSelectRecentProject: vi.fn(), multiProject: true, version: "test" }));
|
|
202
202
|
const output = lastFrame();
|
|
203
203
|
expect(output).not.toContain('─ Recent ─');
|
|
204
204
|
expect(output).not.toContain('Current Project');
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { render } from 'ink-testing-library';
|
|
3
3
|
import Menu from './Menu.js';
|
|
4
4
|
import { SessionManager } from '../services/sessionManager.js';
|
|
@@ -88,7 +88,7 @@ describe('Menu component Effect-based error handling', () => {
|
|
|
88
88
|
stdout: '',
|
|
89
89
|
});
|
|
90
90
|
vi.spyOn(worktreeService, 'getWorktreesEffect').mockReturnValue(Effect.fail(gitError));
|
|
91
|
-
const { lastFrame } = render(
|
|
91
|
+
const { lastFrame } = render(_jsx(Menu, { sessionManager: sessionManager, worktreeService: worktreeService, onSelectWorktree: onSelectWorktree, onDismissError: onDismissError, version: "test" }));
|
|
92
92
|
// Wait for Effect to execute
|
|
93
93
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
94
94
|
const output = lastFrame();
|
|
@@ -118,7 +118,7 @@ describe('Menu component Effect-based error handling', () => {
|
|
|
118
118
|
vi.spyOn(worktreeService, 'getWorktreesEffect').mockReturnValue(Effect.succeed(mockWorktrees));
|
|
119
119
|
// Mock getDefaultBranchEffect to return successful Effect
|
|
120
120
|
vi.spyOn(worktreeService, 'getDefaultBranchEffect').mockReturnValue(Effect.succeed('main'));
|
|
121
|
-
const { lastFrame } = render(
|
|
121
|
+
const { lastFrame } = render(_jsx(Menu, { sessionManager: sessionManager, worktreeService: worktreeService, onSelectWorktree: onSelectWorktree, version: "test" }));
|
|
122
122
|
// Wait for Effect to execute
|
|
123
123
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
124
124
|
const output = lastFrame();
|
|
@@ -149,7 +149,7 @@ describe('Menu component Effect-based error handling', () => {
|
|
|
149
149
|
stdout: '',
|
|
150
150
|
});
|
|
151
151
|
vi.spyOn(worktreeService, 'getDefaultBranchEffect').mockReturnValue(Effect.fail(gitError));
|
|
152
|
-
const { lastFrame } = render(
|
|
152
|
+
const { lastFrame } = render(_jsx(Menu, { sessionManager: sessionManager, worktreeService: worktreeService, onSelectWorktree: onSelectWorktree, onDismissError: onDismissError, version: "test" }));
|
|
153
153
|
// Wait for Effect to execute
|
|
154
154
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
155
155
|
const output = lastFrame();
|
|
@@ -176,7 +176,7 @@ describe('Menu component Effect-based error handling', () => {
|
|
|
176
176
|
const getDefaultBranchSpy = vi
|
|
177
177
|
.spyOn(worktreeService, 'getDefaultBranchEffect')
|
|
178
178
|
.mockReturnValue(Effect.succeed('main'));
|
|
179
|
-
render(
|
|
179
|
+
render(_jsx(Menu, { sessionManager: sessionManager, worktreeService: worktreeService, onSelectWorktree: onSelectWorktree, version: "test" }));
|
|
180
180
|
// Wait for Effect to execute
|
|
181
181
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
182
182
|
// Verify both Effect-based methods were called (Effect composition)
|
|
@@ -205,7 +205,7 @@ describe('Menu component rendering', () => {
|
|
|
205
205
|
it('should not render duplicate title when re-rendered with new key', async () => {
|
|
206
206
|
const onSelectWorktree = vi.fn();
|
|
207
207
|
// First render
|
|
208
|
-
const { unmount, lastFrame } = render(
|
|
208
|
+
const { unmount, lastFrame } = render(_jsx(Menu, { sessionManager: sessionManager, worktreeService: worktreeService, onSelectWorktree: onSelectWorktree, version: "test" }, 1));
|
|
209
209
|
// Wait for async operations
|
|
210
210
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
211
211
|
const firstRenderOutput = lastFrame();
|
|
@@ -215,7 +215,7 @@ describe('Menu component rendering', () => {
|
|
|
215
215
|
expect(titleCount).toBe(1);
|
|
216
216
|
// Unmount and re-render with new key
|
|
217
217
|
unmount();
|
|
218
|
-
const { lastFrame: lastFrame2 } = render(
|
|
218
|
+
const { lastFrame: lastFrame2 } = render(_jsx(Menu, { sessionManager: sessionManager, worktreeService: worktreeService, onSelectWorktree: onSelectWorktree, version: "test" }, 2));
|
|
219
219
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
220
220
|
const secondRenderOutput = lastFrame2();
|
|
221
221
|
const titleCount2 = (secondRenderOutput?.match(/CCManager - Claude Code Worktree Manager/g) ||
|
|
@@ -224,7 +224,7 @@ describe('Menu component rendering', () => {
|
|
|
224
224
|
});
|
|
225
225
|
it('should render title and description only once', async () => {
|
|
226
226
|
const onSelectWorktree = vi.fn();
|
|
227
|
-
const { lastFrame } = render(
|
|
227
|
+
const { lastFrame } = render(_jsx(Menu, { sessionManager: sessionManager, worktreeService: worktreeService, onSelectWorktree: onSelectWorktree, version: "test" }));
|
|
228
228
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
229
229
|
const output = lastFrame();
|
|
230
230
|
// Check title appears only once
|
|
@@ -279,7 +279,7 @@ describe('Menu component rendering', () => {
|
|
|
279
279
|
backgroundTasks: 0,
|
|
280
280
|
});
|
|
281
281
|
vi.spyOn(SessionManager, 'formatSessionCounts').mockReturnValue('');
|
|
282
|
-
const { lastFrame } = render(
|
|
282
|
+
const { lastFrame } = render(_jsx(Menu, { sessionManager: sessionManager, worktreeService: worktreeService, onSelectWorktree: onSelectWorktree, onSelectRecentProject: onSelectRecentProject, multiProject: true, version: "test" }));
|
|
283
283
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
284
284
|
const output = lastFrame();
|
|
285
285
|
// Check that worktrees have numbers 0-2
|
|
@@ -321,7 +321,7 @@ describe('Menu component rendering', () => {
|
|
|
321
321
|
backgroundTasks: 0,
|
|
322
322
|
});
|
|
323
323
|
vi.spyOn(SessionManager, 'formatSessionCounts').mockReturnValue('');
|
|
324
|
-
const { lastFrame } = render(
|
|
324
|
+
const { lastFrame } = render(_jsx(Menu, { sessionManager: sessionManager, worktreeService: worktreeService, onSelectWorktree: onSelectWorktree, onSelectRecentProject: onSelectRecentProject, multiProject: true, version: "test" }));
|
|
325
325
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
326
326
|
const output = lastFrame();
|
|
327
327
|
// Check that recent projects don't have numbers (just ❯ prefix)
|