All checks were successful
Production PR / QA Tests (pull_request) Successful in 45s
270 lines
7.2 KiB
TypeScript
270 lines
7.2 KiB
TypeScript
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<typeof SidebarUI>();
|
|
const { ref } = await import("vue");
|
|
const MockComponent = {
|
|
template: "<div><slot /></div>",
|
|
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: "<div><slot /></div>" };
|
|
return {
|
|
DropdownMenu: MockComponent,
|
|
DropdownMenuContent: MockComponent,
|
|
DropdownMenuGroup: MockComponent,
|
|
DropdownMenuItem: MockComponent,
|
|
DropdownMenuLabel: MockComponent,
|
|
DropdownMenuSeparator: MockComponent,
|
|
DropdownMenuTrigger: MockComponent,
|
|
};
|
|
});
|
|
|
|
vi.mock("~/components/ui/collapsible", () => {
|
|
const MockComponent = { template: "<div><slot /></div>" };
|
|
return {
|
|
Collapsible: MockComponent,
|
|
CollapsibleContent: MockComponent,
|
|
CollapsibleTrigger: MockComponent,
|
|
};
|
|
});
|
|
|
|
vi.mock("~/components/ui/avatar", () => {
|
|
const MockComponent = { template: "<div><slot /></div>" };
|
|
return {
|
|
Avatar: MockComponent,
|
|
AvatarFallback: MockComponent,
|
|
AvatarImage: MockComponent,
|
|
};
|
|
});
|
|
|
|
vi.mock("@/components/ui/sheet", () => {
|
|
const MockComponent = { template: "<div><slot /></div>" };
|
|
return {
|
|
Sheet: MockComponent,
|
|
SheetContent: MockComponent,
|
|
};
|
|
});
|
|
|
|
vi.mock("@/components/ui/sheet/SheetDescription.vue", () => ({
|
|
default: { template: "<div><slot /></div>" },
|
|
}));
|
|
vi.mock("@/components/ui/sheet/SheetHeader.vue", () => ({
|
|
default: { template: "<div><slot /></div>" },
|
|
}));
|
|
vi.mock("lucide-vue-next", () => {
|
|
const MockIcon = { template: '<svg class="lucide-mock" />' };
|
|
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("<Suspense> 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: "<Suspense><SidebarLayout v-bind='props' /></Suspense>",
|
|
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: "<Suspense><SidebarLayout :user='user' /></Suspense>",
|
|
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: "<Suspense><SidebarLayout v-bind='props' /></Suspense>",
|
|
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: "<Suspense><SidebarLayout :user='user' /></Suspense>",
|
|
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");
|
|
});
|
|
});
|