Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions src/components/RouterScreen.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { test, expect, describe, afterEach } from "bun:test";
import { spawnSync } from "node:child_process";
import { PACKAGE_VERSION } from "../constants";
import {
cleanupScreens,
Expand Down Expand Up @@ -63,6 +64,52 @@ function menuGroups(frame: string): { title: string | undefined; names: string[]
// rendered frames — behavior a user would see, not internal state.

describe("menu rendering", () => {
test("CLI-only command names use the same white and focused colors as other commands", () => {
// A separate process keeps ANSI enabled without changing the plain-text screen tests.
const result = spawnSync(
process.execPath,
[
"--eval",
`
import { renderScreen, waitForText } from "./src/testing/renderScreen.tsx";
const frames = [];
for (const [path, selected] of [
["/agentcore", "feedback"],
["/agentcore/harness", "logs"],
]) {
const screen = renderScreen(path);
await waitForText(screen.lastFrame, selected);
const idle = screen.lastFrame();
await screen.write(selected);
frames.push({ idle, focused: screen.lastFrame(), selected });
screen.unmount();
}
process.stdout.write(JSON.stringify(frames));
`,
],
{
cwd: new URL("../../", import.meta.url),
env: { ...process.env, FORCE_COLOR: "3", NO_COLOR: undefined, WT_SESSION: "bun-test" },
timeout: 10_000,
encoding: "utf8",
},
);
expect(result.status).toBe(0);
const frames = JSON.parse(result.stdout) as {
idle: string;
focused: string;
selected: string;
}[];
for (const [index, names] of [
["runtime", "feedback", "config", "update"],
["get", "update", "logs", "traces"],
].entries()) {
const frame = frames[index]!;
for (const name of names) expect(frame.idle).toContain(`\u001b[37m${name}`);
expect(frame.focused).toContain(`\u001b[36m${glyphs.pointer} \u001b[1m${frame.selected}`);
}
});

test("lists the current command's subcommands with their descriptions", async () => {
const r = renderScreen("/agentcore");
await waitForText(r.lastFrame, "No project detected");
Expand Down
14 changes: 2 additions & 12 deletions src/components/RouterScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,6 @@ interface Option {
description: string;
hint?: string;
notice?: string;
// cliOnly marks a subcommand without a screen; it is listed under a divider
// and opens its help instead.
cliOnly: boolean;
// section is the divider title this option is listed under, if any.
section?: string;
}
Expand Down Expand Up @@ -155,14 +152,13 @@ function CommandMenu({
description: c.description(),
hint: optionHints?.[c.name()],
notice: optionNotices?.[c.name()],
cliOnly,
section: belongsToCliSection ? CLI_ONLY_SECTION : sectionOf(index),
};
});
const actualNames = new Set(actual.map((option) => option.name));
const tuiOnly = tuiOnlyCommands
.filter((option) => !actualNames.has(option.name))
.map((option): Option => ({ ...option, hint: optionHints?.[option.name], cliOnly: false }));
.map((option): Option => ({ ...option, hint: optionHints?.[option.name] }));
return [
...tuiOnly,
...actual.filter((option) => option.section !== CLI_ONLY_SECTION),
Expand Down Expand Up @@ -362,13 +358,7 @@ function CommandMenuBody({
</Text>
<Text
bold={isHighlighted}
color={
isHighlighted
? theme.colors.focus
: option.cliOnly
? theme.colors.muted
: theme.colors.text
}
color={isHighlighted ? theme.colors.focus : theme.colors.text}
>
{option.name.padEnd(nameWidth)}
</Text>
Expand Down
Loading