diff --git a/src/App.test.tsx b/src/App.test.tsx
index 8593b0c7..83f745c4 100644
--- a/src/App.test.tsx
+++ b/src/App.test.tsx
@@ -56,14 +56,4 @@ describe("App", () => {
).toBeVisible(),
);
});
-
- it("should render breadcrumb", async () => {
- render();
- await waitFor(() =>
- expect(
- screen.getByRole("link", { name: "CodeGate Dashboard" }),
- ).toBeVisible(),
- );
- expect(screen.getByRole("link", { name: "Dashboard" })).toBeVisible();
- });
});
diff --git a/src/App.tsx b/src/App.tsx
index f3ce84c1..21153c5f 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,17 +1,9 @@
import { Header } from "./components/Header";
import { PromptList } from "./components/PromptList";
-import { Routes, Route, Link } from "react-router-dom";
+import { Routes, Route } from "react-router-dom";
import { usePromptsData } from "./hooks/usePromptsData";
import { Sidebar } from "./components/Sidebar";
import { useSse } from "./hooks/useSse";
-import {
- Breadcrumb,
- BreadcrumbList,
- BreadcrumbItem,
- BreadcrumbSeparator,
- BreadcrumbPage,
-} from "./components/ui/breadcrumb";
-import { useBreadcrumb } from "./hooks/useBreadcrumb";
import { RouteWorkspace } from "./routes/route-workspace";
import { RouteWorkspaces } from "./routes/route-workspaces";
import { RouteCertificates } from "./routes/route-certificates";
@@ -23,7 +15,6 @@ import { RouteCertificateSecurity } from "./routes/route-certificate-security";
function App() {
const { data: prompts, isLoading } = usePromptsData();
useSse();
- const breadcrumb = useBreadcrumb();
return (
@@ -33,27 +24,7 @@ function App() {
-
-
-
-
- Dashboard
-
- {breadcrumb && (
- <>
-
-
-
- {breadcrumb}
-
-
- >
- )}
-
-
-
-
-
+
} />
} />
diff --git a/src/components/BreadcrumbHome.tsx b/src/components/BreadcrumbHome.tsx
new file mode 100644
index 00000000..86d69b21
--- /dev/null
+++ b/src/components/BreadcrumbHome.tsx
@@ -0,0 +1,5 @@
+import { Breadcrumb } from "@stacklok/ui-kit";
+
+export function BreadcrumbHome() {
+ return Dashboard;
+}
diff --git a/src/components/ui/breadcrumb.tsx b/src/components/ui/breadcrumb.tsx
deleted file mode 100644
index 314443ff..00000000
--- a/src/components/ui/breadcrumb.tsx
+++ /dev/null
@@ -1,114 +0,0 @@
-import * as React from "react";
-import { Slot } from "@radix-ui/react-slot";
-import { ChevronRight, MoreHorizontal } from "lucide-react";
-import { twMerge } from "tailwind-merge";
-
-const Breadcrumb = React.forwardRef<
- HTMLElement,
- React.ComponentPropsWithoutRef<"nav"> & {
- separator?: React.ReactNode;
- }
->(({ ...props }, ref) => );
-Breadcrumb.displayName = "Breadcrumb";
-
-const BreadcrumbList = React.forwardRef<
- HTMLOListElement,
- React.ComponentPropsWithoutRef<"ol">
->(({ className, ...props }, ref) => (
-
-));
-BreadcrumbList.displayName = "BreadcrumbList";
-
-const BreadcrumbItem = React.forwardRef<
- HTMLLIElement,
- React.ComponentPropsWithoutRef<"li">
->(({ className, ...props }, ref) => (
-
-));
-BreadcrumbItem.displayName = "BreadcrumbItem";
-
-const BreadcrumbLink = React.forwardRef<
- HTMLAnchorElement,
- React.ComponentPropsWithoutRef<"a"> & {
- asChild?: boolean;
- }
->(({ asChild, className, ...props }, ref) => {
- const Comp = asChild ? Slot : "a";
-
- return (
-
- );
-});
-BreadcrumbLink.displayName = "BreadcrumbLink";
-
-const BreadcrumbPage = React.forwardRef<
- HTMLSpanElement,
- React.ComponentPropsWithoutRef<"span">
->(({ className, ...props }, ref) => (
-
-));
-BreadcrumbPage.displayName = "BreadcrumbPage";
-
-const BreadcrumbSeparator = ({
- children,
- className,
- ...props
-}: React.ComponentProps<"li">) => (
- svg]:size-3.5", className)}
- {...props}
- >
- {children ?? }
-
-);
-BreadcrumbSeparator.displayName = "BreadcrumbSeparator";
-
-const BreadcrumbEllipsis = ({
- className,
- ...props
-}: React.ComponentProps<"span">) => (
-
-
- More
-
-);
-BreadcrumbEllipsis.displayName = "BreadcrumbElipssis";
-
-export {
- Breadcrumb,
- BreadcrumbList,
- BreadcrumbItem,
- BreadcrumbLink,
- BreadcrumbPage,
- BreadcrumbSeparator,
- BreadcrumbEllipsis,
-};
diff --git a/src/hooks/__tests__/useBreadcrumb.test.tsx b/src/hooks/__tests__/useBreadcrumb.test.tsx
deleted file mode 100644
index c3dd168d..00000000
--- a/src/hooks/__tests__/useBreadcrumb.test.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import { renderHook } from "@testing-library/react";
-import { vi } from "vitest";
-import { useBreadcrumb } from "../useBreadcrumb";
-import { MemoryRouter } from "react-router-dom";
-import { TestQueryClientProvider } from "@/lib/test-utils";
-
-vi.mock("../usePromptsData", () => ({
- usePromptsData: vi.fn(() => ({
- data: [
- {
- chat_id: "test-chat-id",
- question_answers: [
- {
- question: { message: "Fake question" },
- answer: { message: "Fake answer" },
- },
- ],
- },
- ],
- })),
-}));
-
-vi.mock("../useCurrentPromptStore", () => ({
- useCurrentPromptStore: vi.fn(() => ({
- currentPromptId: "test-chat-id",
- })),
-}));
-
-describe("useBreadcrumb", () => {
- afterEach(() => {
- vi.clearAllMocks();
- });
-
- it.each([
- { path: "/certificates/security", expected: "Certificate Security" },
- { path: "/certificates", expected: "Certificate Download" },
- { path: "/help/continue-setup", expected: "Continue Setup" },
- { path: "/help/copilot-setup", expected: "Copilot Setup" },
- { path: "/prompt/", expected: "Fake question" },
- { path: "/", expected: "" },
- ])("returns breadcrumb for path $path", ({ path, expected }) => {
- const wrapper = ({ children }: { children: React.ReactNode }) => (
-
- {children}
-
- );
-
- const { result } = renderHook(() => useBreadcrumb(), { wrapper });
-
- expect(result.current).toBe(expected);
- });
-});
diff --git a/src/hooks/useBreadcrumb.ts b/src/hooks/useBreadcrumb.ts
deleted file mode 100644
index bedcd6ec..00000000
--- a/src/hooks/useBreadcrumb.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import { extractTitleFromMessage, sanitizeQuestionPrompt } from "@/lib/utils";
-import { useLocation } from "react-router-dom";
-import { useCurrentPromptStore } from "./useCurrentPromptStore";
-import { usePromptsData } from "./usePromptsData";
-
-const routes = [
- { path: "/certificates/security", breadcrumb: "Certificate Security" },
- { path: "/certificates", breadcrumb: "Certificate Download" },
- { path: "/help/continue-setup", breadcrumb: "Continue Setup" },
- { path: "/help/copilot-setup", breadcrumb: "Copilot Setup" },
- { path: "/prompt/", breadcrumb: "Dashboard" },
- { path: "/", breadcrumb: "" },
-];
-
-export function useBreadcrumb() {
- const { pathname } = useLocation();
- const { currentPromptId } = useCurrentPromptStore();
- const { data: prompts } = usePromptsData();
-
- const match = routes.find((route) => pathname.startsWith(route.path));
- if (match?.path === "/prompt/") {
- try {
- const chat = prompts?.find(
- (prompt) => prompt.chat_id === currentPromptId,
- );
- const title = chat?.question_answers?.[0]?.question?.message ?? "";
-
- const sanitized = sanitizeQuestionPrompt({
- question: title,
- answer: chat?.question_answers?.[0]?.answer?.message ?? "",
- });
- return extractTitleFromMessage(sanitized) ?? "";
- } catch {
- return "";
- }
- }
-
- return match ? match.breadcrumb : "";
-}
diff --git a/src/routes/__tests__/route-certificate-security.test.tsx b/src/routes/__tests__/route-certificate-security.test.tsx
new file mode 100644
index 00000000..1ef52b0d
--- /dev/null
+++ b/src/routes/__tests__/route-certificate-security.test.tsx
@@ -0,0 +1,19 @@
+import { render } from "@/lib/test-utils";
+import { screen, within } from "@testing-library/react";
+import { describe, expect, it } from "vitest";
+import { RouteCertificateSecurity } from "../route-certificate-security";
+
+describe("Certificate security", () => {
+ it("has breadcrumbs", () => {
+ render();
+
+ const breadcrumbs = screen.getByRole("list", { name: "Breadcrumbs" });
+ expect(breadcrumbs).toBeVisible();
+ expect(
+ within(breadcrumbs).getByRole("link", { name: "Dashboard" }),
+ ).toHaveAttribute("href", "/");
+ expect(
+ within(breadcrumbs).getByText(/certificate security/i),
+ ).toBeVisible();
+ });
+});
diff --git a/src/routes/__tests__/route-certificates.test.tsx b/src/routes/__tests__/route-certificates.test.tsx
index 3ed7d935..46a4ecf1 100644
--- a/src/routes/__tests__/route-certificates.test.tsx
+++ b/src/routes/__tests__/route-certificates.test.tsx
@@ -1,5 +1,5 @@
import { render } from "@/lib/test-utils";
-import { screen } from "@testing-library/react";
+import { screen, within } from "@testing-library/react";
import { describe, expect, it } from "vitest";
import userEvent from "@testing-library/user-event";
import { RouteCertificates } from "../route-certificates";
@@ -27,6 +27,17 @@ describe("Certificates", () => {
expect(screen.getByText("Linux")).toBeVisible();
});
+ it("has breadcrumbs", () => {
+ render();
+
+ const breadcrumbs = screen.getByRole("list", { name: "Breadcrumbs" });
+ expect(breadcrumbs).toBeVisible();
+ expect(
+ within(breadcrumbs).getByRole("link", { name: "Dashboard" }),
+ ).toHaveAttribute("href", "/");
+ expect(within(breadcrumbs).getByText(/certificates/i)).toBeVisible();
+ });
+
it("should render macOS certificate installation", async () => {
render();
diff --git a/src/routes/__tests__/route-chat.test.tsx b/src/routes/__tests__/route-chat.test.tsx
index 8c8acd13..f90f38ad 100644
--- a/src/routes/__tests__/route-chat.test.tsx
+++ b/src/routes/__tests__/route-chat.test.tsx
@@ -49,7 +49,7 @@ vi.mock("@/hooks/usePromptsData", () => ({
}));
describe("Chat", () => {
- it("should render secret issue chat", () => {
+ it("render secret issue chat", () => {
render(, {
routeConfig: {
initialEntries: ["/prompt/chatcmpl-7d87679de7ed41639eb91d8ebbaa6f72"],
@@ -60,9 +60,6 @@ describe("Chat", () => {
expect(screen.getByText(/REDACTED {
.getByRole("code")
.className.includes("language-tsx"),
).toBeTruthy();
+
+ const breadcrumbs = screen.getByRole("list", { name: "Breadcrumbs" });
+ expect(breadcrumbs).toBeVisible();
+ expect(
+ within(breadcrumbs).getByRole("link", { name: "Dashboard" }),
+ ).toHaveAttribute("href", "/");
+ expect(
+ within(breadcrumbs).getByText(/do you see any security issue/i),
+ ).toBeVisible();
});
});
diff --git a/src/routes/__tests__/route-help.test.tsx b/src/routes/__tests__/route-help.test.tsx
new file mode 100644
index 00000000..6fa347b0
--- /dev/null
+++ b/src/routes/__tests__/route-help.test.tsx
@@ -0,0 +1,21 @@
+import { render } from "@/lib/test-utils";
+import { screen, within } from "@testing-library/react";
+import { describe, expect, it } from "vitest";
+import { RouteHelp } from "../route-help";
+
+describe("Help page", () => {
+ it("has breadcrumbs", () => {
+ render(, {
+ routeConfig: {
+ initialEntries: ["/help/continue-setup"],
+ },
+ pathConfig: "/help/:id",
+ });
+ const breadcrumbs = screen.getByRole("list", { name: "Breadcrumbs" });
+ expect(breadcrumbs).toBeVisible();
+ expect(
+ within(breadcrumbs).getByRole("link", { name: "Dashboard" }),
+ ).toHaveAttribute("href", "/");
+ expect(within(breadcrumbs).getByText(/help/i)).toBeVisible();
+ });
+});
diff --git a/src/routes/__tests__/route-workspace.test.tsx b/src/routes/__tests__/route-workspace.test.tsx
index 1d13d534..5d7a301a 100644
--- a/src/routes/__tests__/route-workspace.test.tsx
+++ b/src/routes/__tests__/route-workspace.test.tsx
@@ -1,4 +1,4 @@
-import { render } from "@/lib/test-utils";
+import { render, within } from "@/lib/test-utils";
import { test, expect } from "vitest";
import { RouteWorkspace } from "../route-workspace";
@@ -37,3 +37,17 @@ test("renders system prompt editor", () => {
expect(getByTestId("system-prompt-editor")).toBeVisible();
});
+
+test("has breadcrumbs", () => {
+ const { getByRole } = renderComponent();
+
+ const breadcrumbs = getByRole("list", { name: "Breadcrumbs" });
+ expect(breadcrumbs).toBeVisible();
+ expect(
+ within(breadcrumbs).getByRole("link", { name: "Dashboard" }),
+ ).toHaveAttribute("href", "/");
+ expect(
+ within(breadcrumbs).getByRole("link", { name: /manage workspaces/i }),
+ ).toHaveAttribute("href", "/workspaces");
+ expect(within(breadcrumbs).getByText(/workspace settings/i)).toBeVisible();
+});
diff --git a/src/routes/__tests__/route-workspaces.test.tsx b/src/routes/__tests__/route-workspaces.test.tsx
index 59db5d0b..a543de28 100644
--- a/src/routes/__tests__/route-workspaces.test.tsx
+++ b/src/routes/__tests__/route-workspaces.test.tsx
@@ -8,6 +8,15 @@ describe("Workspaces page", () => {
render();
});
+ it("has breadcrumbs", () => {
+ const breadcrumbs = screen.getByRole("list", { name: "Breadcrumbs" });
+ expect(breadcrumbs).toBeVisible();
+ expect(
+ within(breadcrumbs).getByRole("link", { name: "Dashboard" }),
+ ).toHaveAttribute("href", "/");
+ expect(within(breadcrumbs).getByText(/manage workspaces/i)).toBeVisible();
+ });
+
it("has a title", () => {
expect(
screen.getByRole("heading", { name: /manage workspaces/i }),
diff --git a/src/routes/route-certificate-security.tsx b/src/routes/route-certificate-security.tsx
index d43e40a7..d2b2c169 100644
--- a/src/routes/route-certificate-security.tsx
+++ b/src/routes/route-certificate-security.tsx
@@ -1,4 +1,5 @@
-import { Card, CardBody } from "@stacklok/ui-kit";
+import { BreadcrumbHome } from "@/components/BreadcrumbHome";
+import { Breadcrumb, Breadcrumbs, Card, CardBody } from "@stacklok/ui-kit";
const SecurityShieldIcon = () => (