diff --git a/apps/web/components/app-sidebar/app-sidebar-footer.test.tsx b/apps/web/components/app-sidebar/app-sidebar-footer.test.tsx new file mode 100644 index 0000000000..0c42338e63 --- /dev/null +++ b/apps/web/components/app-sidebar/app-sidebar-footer.test.tsx @@ -0,0 +1,102 @@ +import { cleanup, fireEvent, render, screen } from "@testing-library/react"; +import { TooltipProvider } from "@kandev/ui/tooltip"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +const mocks = vi.hoisted(() => ({ + routerPush: vi.fn(), + toggleSettingsMode: vi.fn(), +})); + +const state = { + workspaces: { activeId: "ws-1" as string | null }, + appSidebar: { settingsMode: false }, + toggleAppSidebarSettingsMode: mocks.toggleSettingsMode, +}; + +let officeEnabled = false; + +vi.mock("next/navigation", () => ({ + useRouter: () => ({ push: mocks.routerPush }), +})); + +vi.mock("@/components/state-provider", () => ({ + useAppStore: (selector: (s: typeof state) => unknown) => selector(state), +})); + +vi.mock("@/hooks/domains/features/use-feature", () => ({ + useFeature: () => officeEnabled, +})); + +vi.mock("@/hooks/use-release-notes", () => ({ + useReleaseNotes: () => ({ + unseenEntries: [], + latestVersion: "0.0.0", + hasUnseen: false, + dialogOpen: false, + openDialog: vi.fn(), + closeDialog: vi.fn(), + hasNotes: false, + showTopbarButton: false, + }), +})); + +vi.mock("@/components/improve-kandev-dialog", () => ({ + ImproveKandevDialog: () => null, +})); + +vi.mock("@/components/release-notes/release-notes-dialog", () => ({ + ReleaseNotesDialog: () => null, +})); + +vi.mock("@/components/theme-toggle", () => ({ + ThemeToggle: () => , +})); + +import { AppSidebarFooter } from "./app-sidebar-footer"; + +function renderFooter() { + return render( + + + , + ); +} + +describe("AppSidebarFooter", () => { + beforeEach(() => { + officeEnabled = false; + state.appSidebar.settingsMode = false; + mocks.routerPush.mockClear(); + mocks.toggleSettingsMode.mockClear(); + }); + + afterEach(() => cleanup()); + + it("renders navigation icons as buttons so hover does not expose link URLs", () => { + officeEnabled = true; + + renderFooter(); + + const statsButton = screen.getByRole("button", { name: "Stats" }); + const officeButton = screen.getByRole("button", { name: "Office" }); + + expect(statsButton).toBeTruthy(); + expect(officeButton).toBeTruthy(); + expect(statsButton.getAttribute("href")).toBeNull(); + expect(officeButton.getAttribute("href")).toBeNull(); + expect(screen.queryByRole("link", { name: "Stats" })).toBeNull(); + expect(screen.queryByRole("link", { name: "Office" })).toBeNull(); + }); + + it("navigates from the Stats and Office footer buttons", () => { + officeEnabled = true; + + renderFooter(); + + fireEvent.click(screen.getByRole("button", { name: "Stats" })); + fireEvent.click(screen.getByRole("button", { name: "Office" })); + + expect(mocks.routerPush).toHaveBeenNthCalledWith(1, "/stats"); + expect(mocks.routerPush).toHaveBeenNthCalledWith(2, "/office"); + }); +}); diff --git a/apps/web/components/app-sidebar/app-sidebar-footer.tsx b/apps/web/components/app-sidebar/app-sidebar-footer.tsx index 84870e4532..873daf7678 100644 --- a/apps/web/components/app-sidebar/app-sidebar-footer.tsx +++ b/apps/web/components/app-sidebar/app-sidebar-footer.tsx @@ -1,7 +1,6 @@ "use client"; import { useState } from "react"; -import Link from "next/link"; import { useRouter } from "next/navigation"; import { IconBuildings, @@ -31,7 +30,6 @@ type FooterIconButtonProps = { label: string; collapsed: boolean; onClick?: () => void; - href?: string; badge?: boolean; testId?: string; /** Toggle state: rotates the icon a half-turn (spins back out when cleared). */ @@ -43,7 +41,6 @@ function FooterIconButton({ label, collapsed, onClick, - href, badge, testId, active, @@ -68,14 +65,9 @@ function FooterIconButton({ ); - const trigger = href ? ( - - ) : ( + const trigger = (