Files
AgentHub/client/src/components/__tests__/Sidebar.test.tsx
T
nntrivi2001 22de5009ff feat: v2.7.8 — Artifacts hub, clone related repos as extra folders, quieter resume prompt
- Artifacts (/artifacts): claude.ai pages sessions publish (captured from the
  Artifact tool's hook), pinned workspace files, skills and plugins, each
  kept as a snapshot; shared ones appear in a Hub and a skill or plugin
  installs into the viewer's own account (server/lib/artifacts.js,
  /api/artifacts, docs/plans/artifacts/).
- Workspace extra folders: clone one or more related repos beside the
  workspace and add them in one step (POST /api/lanes/:id/sources/clone).
- A pane that ended by itself while tmux kept running is no longer offered
  for resume (tmux.serverStartedAt in reconcileOrphans).
- Changes panel: a folder that is not a git repo opens on Files, and its
  files open from there.
- Plus the parallel sessions' work in this tree: File Explorer button in the
  panel header, user delete, RSA SSH keys, chat/terminal and pet fixes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 16:06:54 +07:00

146 lines
5.7 KiB
TypeScript

/**
* @file Sidebar.test.tsx
* @description Unit tests for the Sidebar: the brand, the navigation links, the small collapse button in the brand's corner, no footer / language / theme controls (those moved to the status bar and Settings), and the connection details modal carrying the version and the release check.
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
import { describe, it, expect, vi } from "vitest";
import { act, render, screen, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { MemoryRouter } from "react-router-dom";
import { Sidebar } from "../Sidebar";
import { eventBus } from "../../lib/eventBus";
function renderSidebar(
wsConnected: boolean,
collapsed = false,
extra: { onToggle?: () => void; connectionOpen?: boolean } = {}
) {
return render(
<MemoryRouter>
<Sidebar
wsConnected={wsConnected}
collapsed={collapsed}
onToggle={extra.onToggle ?? (() => {})}
connectionOpen={extra.connectionOpen ?? false}
onConnectionOpenChange={() => {}}
/>
</MemoryRouter>
);
}
describe("Sidebar", () => {
it("should render the brand logo in place of the text subtitle", () => {
renderSidebar(true);
// One wordmark in both themes — no light/dark variant.
expect(screen.getByAltText("SmartGift Solution")).toHaveAttribute(
"src",
expect.stringContaining("logo-main.png")
);
});
it("should render the favicon mark when collapsed", () => {
renderSidebar(true, true);
const mark = screen.getByAltText("SmartGift Solution");
expect(mark).toHaveAttribute("src", "https://smartgift.vn/favicon.ico");
});
it("should render all navigation links", () => {
renderSidebar(true);
expect(screen.getByText("Dashboard")).toBeInTheDocument();
expect(screen.getByText("Sessions")).toBeInTheDocument();
// Activity Feed and Claude Config are no longer top-level entries: they
// moved into tabs on /sessions and /settings respectively.
expect(screen.getByText("Settings")).toBeInTheDocument();
expect(screen.queryByText("Activity Feed")).toBeNull();
});
// The live/version line lives in the status bar and the update check in the
// connection details modal; language and theme live in Settings.
it("has no footer, language or theme controls", () => {
for (const collapsed of [false, true]) {
const { unmount } = renderSidebar(true, collapsed);
expect(screen.queryByText("Live")).toBeNull();
expect(screen.queryByRole("button", { name: "Connection details" })).toBeNull();
expect(screen.queryByRole("button", { name: "English" })).toBeNull();
expect(screen.queryByRole("button", { name: "Dark" })).toBeNull();
expect(screen.queryByText("Up to date")).toBeNull();
unmount();
}
});
it("collapses and expands from a small button in the brand's corner", async () => {
const onToggle = vi.fn();
const user = userEvent.setup();
const { unmount } = renderSidebar(true, false, { onToggle });
await user.click(screen.getByRole("button", { name: "Collapse sidebar" }));
unmount();
renderSidebar(true, true, { onToggle });
await user.click(screen.getByRole("button", { name: "Expand sidebar" }));
expect(onToggle).toHaveBeenCalledTimes(2);
});
it("renders the workspace list when expanded and its icon in the rail", () => {
renderSidebar(true);
expect(screen.getByTestId("workspace-tree")).toBeInTheDocument();
renderSidebar(true, true);
expect(screen.getAllByRole("button", { name: "Workspaces" }).length).toBeGreaterThan(0);
});
it("should have correct navigation hrefs", () => {
renderSidebar(true);
const links = screen.getAllByRole("link");
const hrefs = links.map((link) => link.getAttribute("href"));
expect(hrefs).toContain("/");
expect(hrefs).toContain("/sessions");
expect(hrefs).toContain("/settings");
expect(hrefs).not.toContain("/activity");
// The file manager serves a shared server; a local dashboard has none.
expect(hrefs).not.toContain("/files");
});
describe("update status line, in the connection details modal", () => {
const plugin = {
git_repo: false,
install_kind: "plugin",
situation: "plugin_managed",
local_version: "1.8.10",
};
const publish = (data: object) =>
act(() => eventBus.publish({ type: "update_status", data } as never));
it("does not claim up to date when a plugin install could not read the release", () => {
renderSidebar(true, false, { connectionOpen: true });
publish({ ...plugin, update_available: false, release_check: "unreachable" });
expect(screen.queryByText("Up to date")).toBeNull();
expect(screen.getByTestId("update-status")).toHaveTextContent("Could not check for updates");
});
it("names a build installed beside the running one", () => {
renderSidebar(true, false, { connectionOpen: true });
publish({
...plugin,
update_available: true,
release_check: "unreachable",
installed_not_running: { root: "/x", version: "1.8.12" },
});
expect(screen.getByTestId("update-status")).toHaveTextContent(
"1.8.12 installed, not running"
);
});
it("still says up to date when the release was read and matches", () => {
renderSidebar(true, false, { connectionOpen: true });
publish({
...plugin,
update_available: false,
release_check: "ok",
latest_release: "v1.8.10",
});
const dialog = screen.getByRole("dialog");
expect(within(dialog).getByTestId("update-status")).toHaveTextContent("Up to date");
expect(within(dialog).getByRole("button", { name: "Check for updates" })).toBeEnabled();
});
});
});