import { mount, flushPromises } from "@vue/test-utils"; import { describe, expect, it, vi, beforeAll, afterAll } from "vitest"; import SidebarLayout from "~/layouts/default/Sidebar.vue"; import { ref } from "vue"; import type * as SidebarUI from "~/components/ui/sidebar"; import { useRuntimeConfig } from "#app"; const { useSidebarMock } = vi.hoisted(() => ({ useSidebarMock: vi.fn(), })); // ... (existing mocks) vi.mock("~/stores/auth", () => ({ useAuthStore: () => ({ user: { name: "Liviu", email: "x.liviu@gmail.com", image: "avatar.png", }, signOut: vi.fn(), init: vi.fn(), }), })); // Mock the UI components and hook vi.mock("~/components/ui/sidebar", async (importOriginal) => { const actual = await importOriginal(); const { ref } = await import("vue"); const MockComponent = { template: "
", inheritAttrs: false, }; // Default implementation useSidebarMock.mockReturnValue({ isMobile: ref(false), state: ref("expanded"), openMobile: ref(false), setOpenMobile: vi.fn(), }); return { ...actual, Sidebar: MockComponent, SidebarContent: MockComponent, SidebarFooter: MockComponent, SidebarGroup: MockComponent, SidebarHeader: MockComponent, SidebarMenu: MockComponent, SidebarMenuItem: MockComponent, SidebarMenuButton: MockComponent, SidebarRail: MockComponent, SidebarMenuSub: MockComponent, SidebarMenuSubButton: MockComponent, SidebarMenuSubItem: MockComponent, useSidebar: useSidebarMock, }; }); // Mock other UI components to avoid rendering complexity vi.mock("~/components/ui/dropdown-menu", () => { const MockComponent = { template: "
" }; return { DropdownMenu: MockComponent, DropdownMenuContent: MockComponent, DropdownMenuGroup: MockComponent, DropdownMenuItem: MockComponent, DropdownMenuLabel: MockComponent, DropdownMenuSeparator: MockComponent, DropdownMenuTrigger: MockComponent, }; }); vi.mock("~/components/ui/collapsible", () => { const MockComponent = { template: "
" }; return { Collapsible: MockComponent, CollapsibleContent: MockComponent, CollapsibleTrigger: MockComponent, }; }); vi.mock("~/components/ui/avatar", () => { const MockComponent = { template: "
" }; return { Avatar: MockComponent, AvatarFallback: MockComponent, AvatarImage: MockComponent, }; }); vi.mock("@/components/ui/sheet", () => { const MockComponent = { template: "
" }; return { Sheet: MockComponent, SheetContent: MockComponent, }; }); vi.mock("@/components/ui/sheet/SheetDescription.vue", () => ({ default: { template: "
" }, })); vi.mock("@/components/ui/sheet/SheetHeader.vue", () => ({ default: { template: "
" }, })); vi.mock("lucide-vue-next", () => { const MockIcon = { template: '' }; return { BadgeCheck: MockIcon, Bell: MockIcon, ChevronRight: MockIcon, ChevronsUpDown: MockIcon, CreditCard: MockIcon, BookOpen: MockIcon, BookOpenText: MockIcon, HandCoins: MockIcon, LogIn: MockIcon, LogOut: MockIcon, Settings2: MockIcon, Sparkles: MockIcon, SquareTerminal: MockIcon, }; }); describe("SidebarLayout", () => { beforeAll(() => { const shouldSuppress = (args: string[]) => { const msg = args.join(" "); return msg.includes(" is an experimental feature"); }; const spyMethods = ["warn", "error", "log", "info"] as const; for (const method of spyMethods) { const original = console[method]; vi.spyOn(console, method).mockImplementation((...args) => { if (shouldSuppress(args)) return; original(...args); }); } }); afterAll(() => { vi.restoreAllMocks(); }); it("renders the header correctly", async () => { useSidebarMock.mockReturnValue({ isMobile: ref(false), state: ref("expanded"), openMobile: ref(false), setOpenMobile: vi.fn(), }); const wrapper = mount({ components: { SidebarLayout }, template: "", setup() { return { props: { collapsible: "icon", }, }; }, }); await flushPromises(); expect(wrapper.text()).toContain("Glowing Fiesta"); expect(wrapper.text()).toContain(`v${useRuntimeConfig().public.appVersion as string}`); }); it("renders sidebar content correctly", async () => { const user = { name: "Liviu", email: "x.liviu@gmail.com", image: "avatar.png", id: "123", emailVerified: true, createdAt: new Date(), updatedAt: new Date(), }; const wrapper = mount({ components: { SidebarLayout }, template: "", setup() { return { user }; }, }); await flushPromises(); const text = wrapper.text(); // Navigation groups expect(text).toContain("Playground"); expect(text).toContain("Documentation"); expect(text).toContain("Settings"); // User information expect(text).toContain("Liviu"); expect(text).toContain("x.liviu@gmail.com"); // Sub-items expect(text).toContain("History"); expect(text).toContain("Starred"); expect(text).toContain("Introduction"); expect(text).toContain("Get Started"); }); it("does not render icon if item.icon is missing", async () => { const wrapper = mount({ components: { SidebarLayout }, template: "", setup() { return { props: { navItems: [ { title: "No Icon Item", url: "#", items: [], }, ], }, }; }, }); await flushPromises(); expect(wrapper.text()).toContain("No Icon Item"); expect(wrapper.find('[data-testid="sidebar-icon"]').exists()).toBe(false); }); it("renders correctly in mobile view", async () => { useSidebarMock.mockReturnValue({ isMobile: ref(true), state: ref("expanded"), openMobile: ref(true), setOpenMobile: vi.fn(), }); const user = { name: "Liviu", email: "x.liviu@gmail.com", image: "avatar.png", id: "123", emailVerified: true, createdAt: new Date(), updatedAt: new Date(), }; const wrapper = mount({ components: { SidebarLayout }, template: "", setup() { return { user }; }, }); await flushPromises(); // When isMobile is true, it renders a Sheet. // Since we mocked Sheet components as simple divs with slots, the content should still be present. // However, the structure is different. // We can verify that the content is still rendered (passed through the slot). const text = wrapper.text(); expect(text).toContain("Glowing Fiesta"); expect(text).toContain("Liviu"); // Check specific specific mock interaction if needed, or just that it doesn't crash // and renders the menu items. expect(text).toContain("Playground"); }); });