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
6 changes: 6 additions & 0 deletions cmd/sgai/webapp/happydom.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
import { mock } from "bun:test";
import { GlobalRegistrator } from "@happy-dom/global-registrator";

mock.module("@/assets/sgai-logo.svg", () => ({
default: "/assets/sgai-logo.svg",
}));

GlobalRegistrator.register();
16 changes: 12 additions & 4 deletions cmd/sgai/webapp/src/components/WorkspaceRepositoryAction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ const repositoryActionIcons: Record<ApiRepositoryActionIcon, LucideIcon> = {
delete: Trash2,
};

const treeTriggerGlyphs: Record<ApiRepositoryActionIcon, string> = {
choose: "⋯",
detach: "⊘",
delete: "✕",
};

function getConfirmOperation(action: ApiRepositoryAction): ApiRepositoryOperation | null {
if (!action.defaultOperation) {
return null;
Expand Down Expand Up @@ -83,8 +89,8 @@ const TreeTrigger = forwardRef<HTMLButtonElement, TriggerButtonProps>(function T
className,
...props
}, ref) {
const Icon = repositoryActionIcons[icon];
const isDestructive = tone === "destructive";
const glyph = treeTriggerGlyphs[icon];

return (
<Button
Expand All @@ -93,14 +99,16 @@ const TreeTrigger = forwardRef<HTMLButtonElement, TriggerButtonProps>(function T
variant="ghost"
size="icon"
className={cn(
"h-6 w-6 shrink-0 rounded p-0.5 opacity-0 transition-opacity focus:opacity-100 group-hover/row:opacity-100",
isDestructive ? "hover:bg-destructive/20" : "hover:bg-accent",
"h-6 w-5 shrink-0 rounded px-0 font-mono text-[0.75rem] font-semibold leading-none opacity-80 transition-colors hover:opacity-100 focus-visible:opacity-100",
isDestructive
? "text-destructive/75 hover:bg-destructive/15 hover:text-destructive"
: "text-muted-foreground hover:bg-accent hover:text-foreground",
className,
)}
aria-label={label}
{...props}
>
<Icon className={`h-3 w-3 text-muted-foreground ${isDestructive ? "hover:text-destructive" : ""}`} />
<span aria-hidden="true">{glyph}</span>
</Button>
);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,75 @@ describe("WorkspaceRepositoryAction", () => {
await expectFocusRestoreAfterCancel("tree", "Choose action for fork demo-fork");
});

it("renders tree detach triggers as text glyphs instead of svg icons", () => {
render(
<WorkspaceRepositoryAction
workspace={createWorkspace({
repositoryAction: createRepositoryAction({
repositoryMode: "standalone",
entryPoint: "confirm",
allowedOperations: ["detach"],
defaultOperation: "detach",
}),
})}
context="tree"
/>,
);

const trigger = screen.getByRole("button", { name: "Detach demo-fork" });

expect(trigger.textContent?.trim()).toBe("⊘");
expect(trigger.querySelector("svg")).toBeNull();
});

it("renders chooser tree triggers as text glyphs instead of svg icons", () => {
render(
<WorkspaceRepositoryAction
workspace={createWorkspace()}
context="tree"
/>,
);

const trigger = screen.getByRole("button", { name: "Choose action for fork demo-fork" });

expect(trigger.textContent?.trim()).toBe("⋯");
expect(trigger.querySelector("svg")).toBeNull();
});

it("renders delete tree triggers as text glyphs instead of svg icons", () => {
render(
<WorkspaceRepositoryAction
workspace={createWorkspace({
repositoryAction: createRepositoryAction({
repositoryMode: "standalone",
entryPoint: "confirm",
allowedOperations: ["delete"],
defaultOperation: "delete",
}),
})}
context="tree"
/>,
);

const trigger = screen.getByRole("button", { name: "Delete demo-fork" });

expect(trigger.textContent?.trim()).toBe("✕");
expect(trigger.querySelector("svg")).toBeNull();
});

it("keeps fork-row chooser triggers as svg icons", () => {
render(
<WorkspaceRepositoryAction
workspace={createWorkspace()}
context="fork-row"
/>,
);

const trigger = screen.getByRole("button", { name: "Choose action for fork demo-fork" });

expect(trigger.querySelector("svg")).toBeTruthy();
});

it("restores focus to the fork-row trigger after cancel", async () => {
await expectFocusRestoreAfterCancel("fork-row", "Choose action for fork demo-fork");
});
Expand Down
Loading