@abacus-ai/cli 1.106.25007 → 2.0.0-canary.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/.oxlintrc.json +8 -0
- package/dist/index.mjs +12603 -0
- package/package.json +7 -39
- package/resources/abacus.ico +0 -0
- package/resources/entitlements.plist +9 -0
- package/src/__e2e__/README.md +196 -0
- package/src/__e2e__/agent-interactions.e2e.test.tsx +61 -0
- package/src/__e2e__/cli-commands.e2e.test.tsx +77 -0
- package/src/__e2e__/conversation-throttle.e2e.test.ts +453 -0
- package/src/__e2e__/conversation.e2e.test.tsx +56 -0
- package/src/__e2e__/diff-preview.e2e.test.tsx +3399 -0
- package/src/__e2e__/file-creation.e2e.test.tsx +149 -0
- package/src/__e2e__/helpers/test-helpers.ts +450 -0
- package/src/__e2e__/keyboard-navigation.e2e.test.tsx +34 -0
- package/src/__e2e__/llm-models.e2e.test.ts +402 -0
- package/src/__e2e__/mcp/mcp-callback-flow.e2e.test.tsx +71 -0
- package/src/__e2e__/mcp/mcp-full-app-ui.e2e.test.tsx +167 -0
- package/src/__e2e__/mcp/mcp-ui-rendering.e2e.test.tsx +185 -0
- package/src/__e2e__/repl.e2e.test.tsx +78 -0
- package/src/__e2e__/shell-compatibility.e2e.test.tsx +76 -0
- package/src/__e2e__/theme-mcp.e2e.test.tsx +98 -0
- package/src/__e2e__/tool-permissions.e2e.test.tsx +66 -0
- package/src/args.ts +22 -0
- package/src/components/__tests__/react-compiler.test.tsx +78 -0
- package/src/components/__tests__/status-indicator.test.tsx +403 -0
- package/src/components/composer/__tests__/bash-runner.test.tsx +263 -0
- package/src/components/composer/agent-mode-indicator.tsx +63 -0
- package/src/components/composer/bash-runner.tsx +54 -0
- package/src/components/composer/commands/default-commands.tsx +615 -0
- package/src/components/composer/commands/handler.tsx +59 -0
- package/src/components/composer/commands/picker.tsx +273 -0
- package/src/components/composer/commands/registry.ts +233 -0
- package/src/components/composer/commands/types.ts +33 -0
- package/src/components/composer/context.tsx +88 -0
- package/src/components/composer/file-mention-picker.tsx +83 -0
- package/src/components/composer/help.tsx +44 -0
- package/src/components/composer/index.tsx +1006 -0
- package/src/components/composer/mentions.ts +57 -0
- package/src/components/composer/message-queue.tsx +70 -0
- package/src/components/composer/mode-panel.tsx +35 -0
- package/src/components/composer/modes/__tests__/bash-handler.test.tsx +755 -0
- package/src/components/composer/modes/__tests__/bash-renderer.test.tsx +1108 -0
- package/src/components/composer/modes/bash-handler.tsx +132 -0
- package/src/components/composer/modes/bash-renderer.tsx +175 -0
- package/src/components/composer/modes/default-handlers.tsx +33 -0
- package/src/components/composer/modes/index.ts +41 -0
- package/src/components/composer/modes/types.ts +21 -0
- package/src/components/composer/persistent-shell.ts +283 -0
- package/src/components/composer/process.ts +65 -0
- package/src/components/composer/types.ts +9 -0
- package/src/components/composer/use-mention-search.ts +68 -0
- package/src/components/error-boundry.tsx +60 -0
- package/src/components/exit-message.tsx +29 -0
- package/src/components/expanded-view.tsx +74 -0
- package/src/components/file-completion.tsx +127 -0
- package/src/components/header.tsx +47 -0
- package/src/components/logo.tsx +37 -0
- package/src/components/segments.tsx +356 -0
- package/src/components/status-indicator.tsx +306 -0
- package/src/components/tool-group-summary.tsx +263 -0
- package/src/components/tool-permissions/ask-user-question-permission-ui.tsx +312 -0
- package/src/components/tool-permissions/diff-preview.tsx +355 -0
- package/src/components/tool-permissions/index.ts +5 -0
- package/src/components/tool-permissions/permission-options.tsx +375 -0
- package/src/components/tool-permissions/permission-preview-header.tsx +57 -0
- package/src/components/tool-permissions/tool-permission-ui.tsx +398 -0
- package/src/components/tools/agent/ask-user-question.tsx +101 -0
- package/src/components/tools/agent/enter-plan-mode.tsx +49 -0
- package/src/components/tools/agent/exit-plan-mode.tsx +75 -0
- package/src/components/tools/agent/handoff-to-main.tsx +27 -0
- package/src/components/tools/agent/subagent.tsx +37 -0
- package/src/components/tools/agent/todo-write.tsx +104 -0
- package/src/components/tools/browser/close-tab.tsx +58 -0
- package/src/components/tools/browser/computer.tsx +70 -0
- package/src/components/tools/browser/get-interactive-elements.tsx +54 -0
- package/src/components/tools/browser/get-tab-content.tsx +51 -0
- package/src/components/tools/browser/navigate-to.tsx +59 -0
- package/src/components/tools/browser/new-tab.tsx +60 -0
- package/src/components/tools/browser/perform-action.tsx +63 -0
- package/src/components/tools/browser/refresh-tab.tsx +43 -0
- package/src/components/tools/browser/switch-tab.tsx +58 -0
- package/src/components/tools/filesystem/delete-file.tsx +104 -0
- package/src/components/tools/filesystem/edit.tsx +220 -0
- package/src/components/tools/filesystem/list-dir.tsx +78 -0
- package/src/components/tools/filesystem/read-file.tsx +180 -0
- package/src/components/tools/filesystem/upload-image.tsx +76 -0
- package/src/components/tools/ide/ide-diagnostics.tsx +62 -0
- package/src/components/tools/index.ts +91 -0
- package/src/components/tools/mcp/mcp-tool.tsx +158 -0
- package/src/components/tools/search/fetch-url.tsx +73 -0
- package/src/components/tools/search/file-search.tsx +78 -0
- package/src/components/tools/search/grep.tsx +90 -0
- package/src/components/tools/search/semantic-search.tsx +66 -0
- package/src/components/tools/search/web-search.tsx +71 -0
- package/src/components/tools/shared/index.tsx +48 -0
- package/src/components/tools/shared/zod-coercion.ts +35 -0
- package/src/components/tools/terminal/bash-tool-output.tsx +174 -0
- package/src/components/tools/terminal/get-terminal-output.tsx +85 -0
- package/src/components/tools/terminal/run-in-terminal.tsx +106 -0
- package/src/components/tools/types.ts +16 -0
- package/src/components/tools.tsx +66 -0
- package/src/components/ui/__tests__/divider.test.tsx +61 -0
- package/src/components/ui/__tests__/gradient.test.tsx +125 -0
- package/src/components/ui/__tests__/input.test.tsx +166 -0
- package/src/components/ui/__tests__/select.test.tsx +273 -0
- package/src/components/ui/__tests__/shimmer.test.tsx +99 -0
- package/src/components/ui/blinking-indicator.tsx +25 -0
- package/src/components/ui/divider.tsx +162 -0
- package/src/components/ui/gradient.tsx +56 -0
- package/src/components/ui/input.tsx +228 -0
- package/src/components/ui/select.tsx +151 -0
- package/src/components/ui/shimmer.tsx +84 -0
- package/src/context/agent-mode.tsx +95 -0
- package/src/context/extension-file.tsx +136 -0
- package/src/context/network-activity.tsx +45 -0
- package/src/context/notification.tsx +62 -0
- package/src/context/shell-size.tsx +49 -0
- package/src/context/shell-title.tsx +38 -0
- package/src/entrypoints/print-mode.ts +312 -0
- package/src/entrypoints/repl.tsx +401 -0
- package/src/hooks/use-agent.ts +15 -0
- package/src/hooks/use-api-client.ts +1 -0
- package/src/hooks/use-available-height.ts +8 -0
- package/src/hooks/use-cleanup.ts +29 -0
- package/src/hooks/use-interrupt-manager.ts +242 -0
- package/src/hooks/use-models.ts +22 -0
- package/src/index.ts +217 -0
- package/src/lib/__tests__/ansi.test.ts +255 -0
- package/src/lib/__tests__/cli.test.ts +122 -0
- package/src/lib/__tests__/commands.test.ts +325 -0
- package/src/lib/__tests__/constants.test.ts +15 -0
- package/src/lib/__tests__/focusables.test.ts +25 -0
- package/src/lib/__tests__/fs.test.ts +231 -0
- package/src/lib/__tests__/markdown.test.tsx +348 -0
- package/src/lib/__tests__/mcpCommandHandler.test.ts +173 -0
- package/src/lib/__tests__/mcpManagement.test.ts +38 -0
- package/src/lib/__tests__/path-paste.test.ts +144 -0
- package/src/lib/__tests__/path.test.ts +300 -0
- package/src/lib/__tests__/queries.test.ts +39 -0
- package/src/lib/__tests__/standaloneMcpService.test.ts +71 -0
- package/src/lib/__tests__/text-buffer.test.ts +328 -0
- package/src/lib/__tests__/text-utils.test.ts +32 -0
- package/src/lib/__tests__/timing.test.ts +78 -0
- package/src/lib/__tests__/utils.test.ts +238 -0
- package/src/lib/__tests__/vim-buffer-actions.test.ts +154 -0
- package/src/lib/ansi.ts +150 -0
- package/src/lib/cli-push-server.ts +112 -0
- package/src/lib/cli.ts +44 -0
- package/src/lib/clipboard.ts +226 -0
- package/src/lib/command-utils.ts +93 -0
- package/src/lib/commands.ts +270 -0
- package/src/lib/constants.ts +3 -0
- package/src/lib/extension-connection.ts +181 -0
- package/src/lib/focusables.ts +7 -0
- package/src/lib/fs.ts +533 -0
- package/src/lib/markdown/code-block.tsx +63 -0
- package/src/lib/markdown/index.ts +4 -0
- package/src/lib/markdown/link.tsx +19 -0
- package/src/lib/markdown/markdown.tsx +372 -0
- package/src/lib/markdown/types.ts +15 -0
- package/src/lib/mcpCommandHandler.ts +121 -0
- package/src/lib/mcpManagement.ts +44 -0
- package/src/lib/path-paste.ts +185 -0
- package/src/lib/path.ts +179 -0
- package/src/lib/queries.ts +15 -0
- package/src/lib/standaloneMcpService.ts +688 -0
- package/src/lib/status-utils.ts +237 -0
- package/src/lib/test-utils.tsx +72 -0
- package/src/lib/text-buffer.ts +2415 -0
- package/src/lib/text-utils.ts +272 -0
- package/src/lib/timing.ts +63 -0
- package/src/lib/types.ts +295 -0
- package/src/lib/utils.ts +182 -0
- package/src/lib/vim-buffer-actions.ts +732 -0
- package/src/providers/agent.tsx +1075 -0
- package/src/providers/api-client.tsx +43 -0
- package/src/services/logger.ts +85 -0
- package/src/terminal/detection.ts +187 -0
- package/src/terminal/exit.ts +279 -0
- package/src/terminal/notification.ts +83 -0
- package/src/terminal/progress.ts +201 -0
- package/src/terminal/setup.ts +797 -0
- package/src/terminal/suspend.ts +58 -0
- package/src/terminal/types.ts +51 -0
- package/src/theme/context.tsx +57 -0
- package/src/theme/index.ts +4 -0
- package/src/theme/themed.tsx +35 -0
- package/src/theme/themes.json +546 -0
- package/src/theme/types.ts +110 -0
- package/src/tools/types.ts +59 -0
- package/src/tools/utils/__tests__/zod-coercion.test.ts +33 -0
- package/src/tools/utils/tool-ui-components.tsx +631 -0
- package/src/tools/utils/zod-coercion.ts +35 -0
- package/tsconfig.json +11 -0
- package/tsconfig.node.json +29 -0
- package/tsconfig.test.json +27 -0
- package/tsdown.config.ts +17 -0
- package/vitest.config.ts +76 -0
- package/README.md +0 -28
- package/dist/index.js +0 -26
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { Text } from "@codellm/jar";
|
|
2
|
+
import stripAnsi from "strip-ansi";
|
|
3
|
+
import { describe, expect } from "vitest";
|
|
4
|
+
|
|
5
|
+
// TODO: re-enable when jar testing utilities are available
|
|
6
|
+
import { render, logInk, cleanup } from "../../../lib/test-utils.js";
|
|
7
|
+
import { Gradient } from "../gradient.js";
|
|
8
|
+
|
|
9
|
+
describe.concurrent("Gradient", () => {
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
cleanup();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("should render text with a named gradient", () => {
|
|
15
|
+
const instance = render(
|
|
16
|
+
<Gradient name="rainbow">
|
|
17
|
+
<Text>Hello World</Text>
|
|
18
|
+
</Gradient>,
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
logInk(instance);
|
|
22
|
+
|
|
23
|
+
const output = instance.frames.join("");
|
|
24
|
+
const plainText = "Hello World";
|
|
25
|
+
|
|
26
|
+
expect(output).toBeDefined();
|
|
27
|
+
expect(typeof output).toBe("string");
|
|
28
|
+
// The gradient should apply ANSI color codes to the text
|
|
29
|
+
expect(stripAnsi(output)).toContain(plainText);
|
|
30
|
+
// Verify that ANSI codes are present (output should be longer than plain text)
|
|
31
|
+
expect(output.length).toBeGreaterThan(plainText.length);
|
|
32
|
+
// Verify ANSI escape sequences are present (should contain escape character)
|
|
33
|
+
expect(output).toMatch(new RegExp(String.fromCharCode(27) + "\\["));
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("should render text with custom colors", () => {
|
|
37
|
+
const instance = render(
|
|
38
|
+
<Gradient colors={["#ff0000", "#0000ff"]}>
|
|
39
|
+
<Text>Test Text</Text>
|
|
40
|
+
</Gradient>,
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
logInk(instance);
|
|
44
|
+
|
|
45
|
+
const output = instance.frames.join("");
|
|
46
|
+
const plainText = "Test Text";
|
|
47
|
+
|
|
48
|
+
expect(output).toBeDefined();
|
|
49
|
+
expect(stripAnsi(output)).toContain(plainText);
|
|
50
|
+
// Verify that ANSI codes are present (output should be longer than plain text)
|
|
51
|
+
expect(output.length).toBeGreaterThan(plainText.length);
|
|
52
|
+
// Verify ANSI escape sequences are present
|
|
53
|
+
expect(output).toMatch(new RegExp(String.fromCharCode(27) + "\\["));
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const gradientNames = [
|
|
57
|
+
"atlas",
|
|
58
|
+
"cristal",
|
|
59
|
+
"teen",
|
|
60
|
+
"mind",
|
|
61
|
+
"morning",
|
|
62
|
+
"vice",
|
|
63
|
+
"passion",
|
|
64
|
+
"fruit",
|
|
65
|
+
"instagram",
|
|
66
|
+
"retro",
|
|
67
|
+
"summer",
|
|
68
|
+
"pastel",
|
|
69
|
+
"rainbow",
|
|
70
|
+
] as const;
|
|
71
|
+
for (const name of gradientNames) {
|
|
72
|
+
it(`should handle gradient name: ${name}`, () => {
|
|
73
|
+
const instance = render(
|
|
74
|
+
<Gradient name={name}>
|
|
75
|
+
<Text>Test Text</Text>
|
|
76
|
+
</Gradient>,
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
logInk(instance);
|
|
80
|
+
|
|
81
|
+
const output = instance.frames.join("");
|
|
82
|
+
const plainText = "Test Text";
|
|
83
|
+
|
|
84
|
+
expect(output).toBeDefined();
|
|
85
|
+
expect(stripAnsi(output)).toContain(plainText);
|
|
86
|
+
// Verify that ANSI codes are present
|
|
87
|
+
expect(output.length).toBeGreaterThan(plainText.length);
|
|
88
|
+
expect(output).toMatch(new RegExp(String.fromCharCode(27) + "\\["));
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
it("should handle multiline text", () => {
|
|
93
|
+
const instance = render(
|
|
94
|
+
<Gradient name="rainbow">
|
|
95
|
+
<Text>Line 1{"\n"}Line 2</Text>
|
|
96
|
+
</Gradient>,
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
logInk(instance);
|
|
100
|
+
|
|
101
|
+
const output = instance.frames.join("");
|
|
102
|
+
|
|
103
|
+
expect(output).toBeDefined();
|
|
104
|
+
expect(stripAnsi(output)).toContain("Line 1");
|
|
105
|
+
expect(stripAnsi(output)).toContain("Line 2");
|
|
106
|
+
// Verify that ANSI codes are present
|
|
107
|
+
expect(output).toMatch(new RegExp(String.fromCharCode(27) + "\\["));
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("should handle empty text", () => {
|
|
111
|
+
const instance = render(
|
|
112
|
+
<Gradient name="rainbow">
|
|
113
|
+
<Text></Text>
|
|
114
|
+
</Gradient>,
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
logInk(instance);
|
|
118
|
+
|
|
119
|
+
const output = instance.frames.join("");
|
|
120
|
+
|
|
121
|
+
expect(output).toBeDefined();
|
|
122
|
+
// Empty text should still be valid (may or may not have ANSI codes)
|
|
123
|
+
expect(typeof output).toBe("string");
|
|
124
|
+
});
|
|
125
|
+
});
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { createRef } from "react";
|
|
2
|
+
import { describe, expect, it, vi } from "vitest";
|
|
3
|
+
|
|
4
|
+
import { stripAnsi } from "../../../lib/ansi.js";
|
|
5
|
+
import { render, logInk, cleanup } from "../../../lib/test-utils.js";
|
|
6
|
+
import { Input } from "../input.js";
|
|
7
|
+
|
|
8
|
+
describe.concurrent("Input", () => {
|
|
9
|
+
const TestWrapper = ({ children }: React.PropsWithChildren) => <>{children}</>;
|
|
10
|
+
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
cleanup();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("should render with placeholder", () => {
|
|
16
|
+
const instance = render(
|
|
17
|
+
<TestWrapper>
|
|
18
|
+
<Input placeholder="Enter text..." />
|
|
19
|
+
</TestWrapper>,
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
logInk(instance);
|
|
23
|
+
|
|
24
|
+
const output = stripAnsi(instance.frames.join(""));
|
|
25
|
+
|
|
26
|
+
expect(output).toBeDefined();
|
|
27
|
+
expect(typeof output).toBe("string");
|
|
28
|
+
expect(output).toContain("Enter text...");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("should render with initial value", () => {
|
|
32
|
+
const instance = render(
|
|
33
|
+
<TestWrapper>
|
|
34
|
+
<Input initialValue="Hello" />
|
|
35
|
+
</TestWrapper>,
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
logInk(instance);
|
|
39
|
+
|
|
40
|
+
const output = stripAnsi(instance.frames.join(""));
|
|
41
|
+
|
|
42
|
+
expect(output).toBeDefined();
|
|
43
|
+
expect(output).toContain("Hello");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("should call onChange when value changes", () => {
|
|
47
|
+
const onChange = vi.fn();
|
|
48
|
+
render(
|
|
49
|
+
<TestWrapper>
|
|
50
|
+
<Input onChange={onChange} />
|
|
51
|
+
</TestWrapper>,
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
// Note: Testing actual keypress events would require more complex setup
|
|
55
|
+
// This test verifies the component renders correctly with the onChange prop
|
|
56
|
+
expect(onChange).toBeDefined();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("should call onSubmit when enter is pressed", () => {
|
|
60
|
+
const onSubmit = vi.fn();
|
|
61
|
+
render(
|
|
62
|
+
<TestWrapper>
|
|
63
|
+
<Input initialValue="test" onSubmit={onSubmit} />
|
|
64
|
+
</TestWrapper>,
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
// Note: Testing actual keypress events would require more complex setup
|
|
68
|
+
// This test verifies the component renders correctly with the onSubmit prop
|
|
69
|
+
expect(onSubmit).toBeDefined();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("should handle ref", () => {
|
|
73
|
+
const ref = createRef<any>();
|
|
74
|
+
render(
|
|
75
|
+
<TestWrapper>
|
|
76
|
+
<Input ref={ref} />
|
|
77
|
+
</TestWrapper>,
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
// The ref should be set after render
|
|
81
|
+
expect(ref.current).toBeDefined();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("should render with custom placeholder props", () => {
|
|
85
|
+
const instance = render(
|
|
86
|
+
<TestWrapper>
|
|
87
|
+
<Input placeholder="Custom" placeholderProps={{ color: "blue" }} />
|
|
88
|
+
</TestWrapper>,
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
logInk(instance);
|
|
92
|
+
|
|
93
|
+
const output = stripAnsi(instance.frames.join(""));
|
|
94
|
+
|
|
95
|
+
expect(output).toBeDefined();
|
|
96
|
+
expect(output).toContain("Custom");
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("should handle disabled state", () => {
|
|
100
|
+
const instance = render(
|
|
101
|
+
<TestWrapper>
|
|
102
|
+
<Input disabled placeholder="Disabled input" />
|
|
103
|
+
</TestWrapper>,
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
logInk(instance);
|
|
107
|
+
|
|
108
|
+
const output = stripAnsi(instance.frames.join(""));
|
|
109
|
+
|
|
110
|
+
expect(output).toBeDefined();
|
|
111
|
+
expect(output).toContain("Disabled input");
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("should handle grammar highlighting", () => {
|
|
115
|
+
const grammar = [
|
|
116
|
+
{ regex: /\d+/g, color: "red" },
|
|
117
|
+
{ regex: /[a-z]+/g, color: "blue" },
|
|
118
|
+
];
|
|
119
|
+
|
|
120
|
+
const instance = render(
|
|
121
|
+
<TestWrapper>
|
|
122
|
+
<Input initialValue="test123" grammar={grammar} />
|
|
123
|
+
</TestWrapper>,
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
logInk(instance);
|
|
127
|
+
|
|
128
|
+
const output = stripAnsi(instance.frames.join(""));
|
|
129
|
+
|
|
130
|
+
expect(output).toBeDefined();
|
|
131
|
+
expect(output).toContain("test");
|
|
132
|
+
expect(output).toContain("123");
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("should handle onKeyDown callback", () => {
|
|
136
|
+
const onKeyDown = vi.fn();
|
|
137
|
+
render(
|
|
138
|
+
<TestWrapper>
|
|
139
|
+
<Input onKeyDown={onKeyDown} />
|
|
140
|
+
</TestWrapper>,
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
// Note: Testing actual keypress events would require more complex setup
|
|
144
|
+
// This test verifies the component renders correctly with the onKeyDown prop
|
|
145
|
+
expect(onKeyDown).toBeDefined();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("should handle width and height constraints", () => {
|
|
149
|
+
const instance = render(
|
|
150
|
+
<TestWrapper>
|
|
151
|
+
<Input
|
|
152
|
+
initialValue="This is a long text that should be constrained"
|
|
153
|
+
width={20}
|
|
154
|
+
height={5}
|
|
155
|
+
/>
|
|
156
|
+
</TestWrapper>,
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
logInk(instance);
|
|
160
|
+
|
|
161
|
+
const output = stripAnsi(instance.frames.join(""));
|
|
162
|
+
|
|
163
|
+
expect(output).toBeDefined();
|
|
164
|
+
expect(output).toContain("This is a long");
|
|
165
|
+
});
|
|
166
|
+
});
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { cleanup, render, logInk } from "../../../lib/test-utils.js";
|
|
4
|
+
import { Select } from "../select.js";
|
|
5
|
+
|
|
6
|
+
describe.concurrent("Select", () => {
|
|
7
|
+
const TestWrapper = ({ children }: React.PropsWithChildren) => <>{children}</>;
|
|
8
|
+
|
|
9
|
+
afterEach(() => {
|
|
10
|
+
cleanup();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("should render items", () => {
|
|
14
|
+
const items = ["Item 1", "Item 2", "Item 3"];
|
|
15
|
+
const instance = render(
|
|
16
|
+
<TestWrapper>
|
|
17
|
+
<Select items={items} />
|
|
18
|
+
</TestWrapper>,
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
logInk(instance);
|
|
22
|
+
|
|
23
|
+
const output = instance.frames.join("");
|
|
24
|
+
|
|
25
|
+
expect(output).toBeDefined();
|
|
26
|
+
expect(typeof output).toBe("string");
|
|
27
|
+
expect(output).toContain("Item 1");
|
|
28
|
+
expect(output).toContain("Item 2");
|
|
29
|
+
expect(output).toContain("Item 3");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("should highlight the first item by default", () => {
|
|
33
|
+
const items = ["First", "Second", "Third"];
|
|
34
|
+
const instance = render(
|
|
35
|
+
<TestWrapper>
|
|
36
|
+
<Select items={items} />
|
|
37
|
+
</TestWrapper>,
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
logInk(instance);
|
|
41
|
+
|
|
42
|
+
const output = instance.frames.join("");
|
|
43
|
+
|
|
44
|
+
expect(output).toBeDefined();
|
|
45
|
+
// The selected item should have a '>' prefix
|
|
46
|
+
expect(output).toContain("> First");
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("should use initialIndex to set initial selection", () => {
|
|
50
|
+
const items = ["First", "Second", "Third"];
|
|
51
|
+
const instance = render(
|
|
52
|
+
<TestWrapper>
|
|
53
|
+
<Select items={items} initialIndex={1} />
|
|
54
|
+
</TestWrapper>,
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
logInk(instance);
|
|
58
|
+
|
|
59
|
+
const output = instance.frames.join("");
|
|
60
|
+
|
|
61
|
+
expect(output).toBeDefined();
|
|
62
|
+
expect(output).toContain("> Second");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should show "No matches" when items array is empty', () => {
|
|
66
|
+
const instance = render(
|
|
67
|
+
<TestWrapper>
|
|
68
|
+
<Select items={[]} />
|
|
69
|
+
</TestWrapper>,
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
logInk(instance);
|
|
73
|
+
|
|
74
|
+
const output = instance.frames.join("");
|
|
75
|
+
|
|
76
|
+
expect(output).toBeDefined();
|
|
77
|
+
expect(output).toContain("No matches");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("should respect limit prop", () => {
|
|
81
|
+
const items = Array.from({ length: 20 }, (_, i) => `Item ${i + 1}`);
|
|
82
|
+
const instance = render(
|
|
83
|
+
<TestWrapper>
|
|
84
|
+
<Select items={items} limit={5} />
|
|
85
|
+
</TestWrapper>,
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
logInk(instance);
|
|
89
|
+
|
|
90
|
+
const output = instance.frames.join("");
|
|
91
|
+
|
|
92
|
+
expect(output).toBeDefined();
|
|
93
|
+
// Should only show up to limit items
|
|
94
|
+
expect(output).toContain("Item 1");
|
|
95
|
+
expect(output).toContain("Item 5");
|
|
96
|
+
// Should not show items beyond limit
|
|
97
|
+
expect(output).not.toContain("Item 6");
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("should show index when showIndex is true", () => {
|
|
101
|
+
const items = ["Item 1", "Item 2", "Item 3"];
|
|
102
|
+
const instance = render(
|
|
103
|
+
<TestWrapper>
|
|
104
|
+
<Select items={items} showIndex={true} />
|
|
105
|
+
</TestWrapper>,
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
logInk(instance);
|
|
109
|
+
|
|
110
|
+
const output = instance.frames.join("");
|
|
111
|
+
|
|
112
|
+
expect(output).toBeDefined();
|
|
113
|
+
// Should show index like (1/3)
|
|
114
|
+
expect(output).toContain("(1/3)");
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("should not show index when showIndex is false", () => {
|
|
118
|
+
const items = ["Item 1", "Item 2", "Item 3"];
|
|
119
|
+
const instance = render(
|
|
120
|
+
<TestWrapper>
|
|
121
|
+
<Select items={items} showIndex={false} />
|
|
122
|
+
</TestWrapper>,
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
logInk(instance);
|
|
126
|
+
|
|
127
|
+
const output = instance.frames.join("");
|
|
128
|
+
|
|
129
|
+
expect(output).toBeDefined();
|
|
130
|
+
// Should not show index
|
|
131
|
+
expect(output).not.toContain("(1/3)");
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("should show indicators when showIndicators is true and there are more items", () => {
|
|
135
|
+
const items = Array.from({ length: 15 }, (_, i) => `Item ${i + 1}`);
|
|
136
|
+
const instance = render(
|
|
137
|
+
<TestWrapper>
|
|
138
|
+
<Select items={items} limit={5} showIndicators={true} />
|
|
139
|
+
</TestWrapper>,
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
logInk(instance);
|
|
143
|
+
|
|
144
|
+
const output = instance.frames.join("");
|
|
145
|
+
|
|
146
|
+
expect(output).toBeDefined();
|
|
147
|
+
// Should show scroll indicators when there are more items
|
|
148
|
+
expect(output).toContain("▼");
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("should call onHighlight when selection changes", () => {
|
|
152
|
+
const onHighlight = vi.fn();
|
|
153
|
+
const items = ["Item 1", "Item 2", "Item 3"];
|
|
154
|
+
render(
|
|
155
|
+
<TestWrapper>
|
|
156
|
+
<Select items={items} onHighlight={onHighlight} />
|
|
157
|
+
</TestWrapper>,
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
// onHighlight should be called with the initial selection
|
|
161
|
+
// Note: Testing actual keypress events would require more complex setup
|
|
162
|
+
expect(onHighlight).toBeDefined();
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("should call onSubmit when enter is pressed", () => {
|
|
166
|
+
const onSubmit = vi.fn();
|
|
167
|
+
const items = ["Item 1", "Item 2", "Item 3"];
|
|
168
|
+
render(
|
|
169
|
+
<TestWrapper>
|
|
170
|
+
<Select items={items} onSubmit={onSubmit} />
|
|
171
|
+
</TestWrapper>,
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
// Note: Testing actual keypress events would require more complex setup
|
|
175
|
+
// This test verifies the component renders correctly with the onSubmit prop
|
|
176
|
+
expect(onSubmit).toBeDefined();
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it("should call onCancel when escape is pressed", () => {
|
|
180
|
+
const onCancel = vi.fn();
|
|
181
|
+
const items = ["Item 1", "Item 2", "Item 3"];
|
|
182
|
+
render(
|
|
183
|
+
<TestWrapper>
|
|
184
|
+
<Select items={items} onCancel={onCancel} />
|
|
185
|
+
</TestWrapper>,
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
// Note: Testing actual keypress events would require more complex setup
|
|
189
|
+
// This test verifies the component renders correctly with the onCancel prop
|
|
190
|
+
expect(onCancel).toBeDefined();
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it("should call onTab when tab is pressed", () => {
|
|
194
|
+
const onTab = vi.fn();
|
|
195
|
+
const items = ["Item 1", "Item 2", "Item 3"];
|
|
196
|
+
render(
|
|
197
|
+
<TestWrapper>
|
|
198
|
+
<Select items={items} onTab={onTab} />
|
|
199
|
+
</TestWrapper>,
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
// Note: Testing actual keypress events would require more complex setup
|
|
203
|
+
// This test verifies the component renders correctly with the onTab prop
|
|
204
|
+
expect(onTab).toBeDefined();
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it("should respect isActive prop", () => {
|
|
208
|
+
const items = ["Item 1", "Item 2", "Item 3"];
|
|
209
|
+
const instance = render(
|
|
210
|
+
<TestWrapper>
|
|
211
|
+
<Select items={items} isActive={false} />
|
|
212
|
+
</TestWrapper>,
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
logInk(instance);
|
|
216
|
+
|
|
217
|
+
const output = instance.frames.join("");
|
|
218
|
+
|
|
219
|
+
expect(output).toBeDefined();
|
|
220
|
+
expect(output).toContain("Item 1");
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it("should handle maxHeight constraint", () => {
|
|
224
|
+
const items = Array.from({ length: 20 }, (_, i) => `Item ${i + 1}`);
|
|
225
|
+
const instance = render(
|
|
226
|
+
<TestWrapper>
|
|
227
|
+
<Select items={items} limit={10} maxHeight={5} />
|
|
228
|
+
</TestWrapper>,
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
logInk(instance);
|
|
232
|
+
|
|
233
|
+
const output = instance.frames.join("");
|
|
234
|
+
|
|
235
|
+
expect(output).toBeDefined();
|
|
236
|
+
// Should respect maxHeight constraint
|
|
237
|
+
expect(output).toContain("Item 1");
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it("should clamp initialIndex to valid range", () => {
|
|
241
|
+
const items = ["Item 1", "Item 2", "Item 3"];
|
|
242
|
+
const instance = render(
|
|
243
|
+
<TestWrapper>
|
|
244
|
+
<Select items={items} initialIndex={10} />
|
|
245
|
+
</TestWrapper>,
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
logInk(instance);
|
|
249
|
+
|
|
250
|
+
const output = instance.frames.join("");
|
|
251
|
+
|
|
252
|
+
expect(output).toBeDefined();
|
|
253
|
+
// Should clamp to last item (index 2)
|
|
254
|
+
expect(output).toContain("> Item 3");
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it("should handle negative initialIndex", () => {
|
|
258
|
+
const items = ["Item 1", "Item 2", "Item 3"];
|
|
259
|
+
const instance = render(
|
|
260
|
+
<TestWrapper>
|
|
261
|
+
<Select items={items} initialIndex={-1} />
|
|
262
|
+
</TestWrapper>,
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
logInk(instance);
|
|
266
|
+
|
|
267
|
+
const output = instance.frames.join("");
|
|
268
|
+
|
|
269
|
+
expect(output).toBeDefined();
|
|
270
|
+
// Should clamp to first item (index 0)
|
|
271
|
+
expect(output).toContain("> Item 1");
|
|
272
|
+
});
|
|
273
|
+
});
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import stripAnsi from "strip-ansi";
|
|
2
|
+
import { describe, expect } from "vitest";
|
|
3
|
+
|
|
4
|
+
import { render, logInk, cleanup } from "../../../lib/test-utils.js";
|
|
5
|
+
import { Shimmer } from "../shimmer.js";
|
|
6
|
+
|
|
7
|
+
describe.concurrent("Shimmer", () => {
|
|
8
|
+
afterEach(() => {
|
|
9
|
+
cleanup();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("should render text with shimmer effect", () => {
|
|
13
|
+
const instance = render(<Shimmer text="Hello World Shimmer" />);
|
|
14
|
+
|
|
15
|
+
logInk(instance);
|
|
16
|
+
|
|
17
|
+
const output = instance.frames.join("");
|
|
18
|
+
const plainText = "Hello World Shimmer";
|
|
19
|
+
|
|
20
|
+
expect(output).toBeDefined();
|
|
21
|
+
expect(typeof output).toBe("string");
|
|
22
|
+
expect(stripAnsi(output)).toContain(plainText);
|
|
23
|
+
// Verify that ANSI codes are present (output should be longer than plain text)
|
|
24
|
+
expect(output.length).toBeGreaterThan(plainText.length);
|
|
25
|
+
// Verify ANSI escape sequences are present
|
|
26
|
+
expect(output).toMatch(new RegExp(String.fromCharCode(27) + "\\["));
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("should render with custom color", () => {
|
|
30
|
+
const instance = render(<Shimmer text="Test" color="#ff0000" />);
|
|
31
|
+
|
|
32
|
+
logInk(instance);
|
|
33
|
+
|
|
34
|
+
const output = instance.frames.join("");
|
|
35
|
+
const plainText = "Test";
|
|
36
|
+
|
|
37
|
+
expect(output).toBeDefined();
|
|
38
|
+
expect(stripAnsi(output)).toContain(plainText);
|
|
39
|
+
// Verify that ANSI codes are present
|
|
40
|
+
expect(output.length).toBeGreaterThan(plainText.length);
|
|
41
|
+
expect(output).toMatch(new RegExp(String.fromCharCode(27) + "\\["));
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("should handle empty text", () => {
|
|
45
|
+
const instance = render(<Shimmer text="" />);
|
|
46
|
+
|
|
47
|
+
logInk(instance);
|
|
48
|
+
|
|
49
|
+
const output = instance.frames.join("");
|
|
50
|
+
|
|
51
|
+
expect(output).toBeDefined();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("should handle single character", () => {
|
|
55
|
+
const instance = render(<Shimmer text="A" />);
|
|
56
|
+
|
|
57
|
+
logInk(instance);
|
|
58
|
+
|
|
59
|
+
const output = instance.frames.join("");
|
|
60
|
+
const plainText = "A";
|
|
61
|
+
|
|
62
|
+
expect(output).toBeDefined();
|
|
63
|
+
expect(stripAnsi(output)).toContain(plainText);
|
|
64
|
+
// Verify that ANSI codes are present
|
|
65
|
+
expect(output.length).toBeGreaterThan(plainText.length);
|
|
66
|
+
expect(output).toMatch(new RegExp(String.fromCharCode(27) + "\\["));
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("should handle long text", () => {
|
|
70
|
+
const longText =
|
|
71
|
+
"This is a very long text that should still render properly with the shimmer effect";
|
|
72
|
+
const instance = render(<Shimmer text={longText} />);
|
|
73
|
+
|
|
74
|
+
logInk(instance);
|
|
75
|
+
|
|
76
|
+
const output = instance.frames.join("");
|
|
77
|
+
|
|
78
|
+
expect(output).toBeDefined();
|
|
79
|
+
expect(stripAnsi(output)).toContain(longText);
|
|
80
|
+
// Verify that ANSI codes are present
|
|
81
|
+
expect(output.length).toBeGreaterThan(longText.length);
|
|
82
|
+
expect(output).toMatch(new RegExp(String.fromCharCode(27) + "\\["));
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("should use default color when not provided", () => {
|
|
86
|
+
const instance = render(<Shimmer text="Default Shimmer" />);
|
|
87
|
+
|
|
88
|
+
logInk(instance);
|
|
89
|
+
|
|
90
|
+
const output = instance.frames.join("");
|
|
91
|
+
const plainText = "Default Shimmer";
|
|
92
|
+
|
|
93
|
+
expect(output).toBeDefined();
|
|
94
|
+
expect(stripAnsi(output)).toContain(plainText);
|
|
95
|
+
// Verify that ANSI codes are present
|
|
96
|
+
expect(output.length).toBeGreaterThan(plainText.length);
|
|
97
|
+
expect(output).toMatch(new RegExp(String.fromCharCode(27) + "\\["));
|
|
98
|
+
});
|
|
99
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Text } from "@codellm/jar";
|
|
2
|
+
import { memo, useEffect, useState } from "react";
|
|
3
|
+
|
|
4
|
+
interface BlinkingIndicatorProps {
|
|
5
|
+
char: string;
|
|
6
|
+
color?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const BLINK_INTERVAL = 500; // ms
|
|
10
|
+
|
|
11
|
+
export const BlinkingIndicator = memo(({ char, color }: BlinkingIndicatorProps) => {
|
|
12
|
+
const [visible, setVisible] = useState(true);
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
const interval = setInterval(() => {
|
|
16
|
+
setVisible((prev) => !prev);
|
|
17
|
+
}, BLINK_INTERVAL);
|
|
18
|
+
|
|
19
|
+
return () => clearInterval(interval);
|
|
20
|
+
}, []);
|
|
21
|
+
|
|
22
|
+
return <Text color={color}>{visible ? char : " "}</Text>;
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
BlinkingIndicator.displayName = "BlinkingIndicator";
|