Project import from github
All checks were successful
Production Build and Deploy / Build (push) Successful in 1m7s
Production Build and Deploy / Deploy (push) Successful in 21s

This commit is contained in:
Liviu Burcusel 2026-01-14 16:29:05 +01:00
commit 0add58254d
Signed by: liviu
GPG key ID: 6CDB37A4AD2C610C
179 changed files with 23756 additions and 0 deletions

View file

@ -0,0 +1,268 @@
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";
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,
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("v1.0.0");
});
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");
});
});

View file

@ -0,0 +1,336 @@
import { mount } from "@vue/test-utils";
import { describe, expect, it, vi } from "vitest";
import SidebarFooterComponent from "~/layouts/default/SidebarFooter.vue";
import type * as SidebarUI from "~/components/ui/sidebar";
const { useSidebarMock } = vi.hoisted(() => ({
useSidebarMock: vi.fn(),
}));
// Mock navigateTo
const navigateToMock = vi.fn();
vi.stubGlobal("navigateTo", navigateToMock);
// Mock UI components
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,
};
useSidebarMock.mockReturnValue({
isMobile: ref(false), // Ensure isMobile is a ref
state: ref("expanded"),
openMobile: ref(false),
setOpenMobile: vi.fn(),
});
return {
...actual,
SidebarFooter: MockComponent,
SidebarMenu: MockComponent,
SidebarMenuItem: MockComponent,
SidebarMenuButton: MockComponent,
useSidebar: useSidebarMock,
};
});
vi.mock("~/components/ui/avatar", () => {
const MockComponent = { template: "<div><slot /></div>" };
return {
Avatar: MockComponent,
AvatarFallback: MockComponent,
AvatarImage: MockComponent,
};
});
vi.mock("~/components/ui/dropdown-menu", () => {
const MockComponent = { template: "<div><slot /></div>" };
const MockContent = {
template: '<div data-testid="dropdown-content"><slot /></div>',
name: "DropdownMenuContent",
};
const DropdownMenuItem = {
name: "DropdownMenuItem",
template: '<div data-testid="dropdown-item" @click="$emit(\'click\')"><slot /></div>',
emits: ["click"],
};
return {
DropdownMenu: MockComponent,
DropdownMenuTrigger: MockComponent,
DropdownMenuContent: MockContent,
DropdownMenuGroup: MockComponent,
DropdownMenuItem: DropdownMenuItem,
DropdownMenuLabel: MockComponent,
DropdownMenuSeparator: MockComponent,
};
});
vi.mock("lucide-vue-next", () => {
const MockIcon = { template: '<svg class="lucide-mock" />' };
return {
BadgeCheck: MockIcon,
Bell: MockIcon,
ChevronsUpDown: MockIcon,
CreditCard: MockIcon,
LogIn: MockIcon,
LogOut: MockIcon,
};
});
describe("SidebarFooter.vue", () => {
const user = {
name: "Liviu Test",
email: "test@example.com",
image: "avatar.png",
id: "123",
emailVerified: true,
createdAt: new Date(),
updatedAt: new Date(),
};
it("renders user information correctly when user is provided", () => {
const wrapper = mount(SidebarFooterComponent, {
props: { user },
global: {
stubs: {
ClientOnly: { template: "<div><slot /></div>" },
},
},
});
expect(wrapper.text()).toContain("Liviu Test");
expect(wrapper.text()).toContain("test@example.com");
// Initials "Liviu Test" -> "LT"
expect(wrapper.text()).toContain("LT");
});
it("renders anonymous view correctly when user is not provided (null)", () => {
const wrapper = mount(SidebarFooterComponent, {
props: { user: null },
global: {
stubs: {
ClientOnly: { template: "<div><slot /></div>" },
},
},
});
expect(wrapper.text()).toContain("Anonymous");
expect(wrapper.text()).toContain("No email");
expect(wrapper.text()).toContain("Anon");
});
it("renders anonymous view correctly when user is undefined", () => {
const wrapper = mount(SidebarFooterComponent, {
props: { user: undefined },
global: {
stubs: {
ClientOnly: { template: "<div><slot /></div>" },
},
},
});
expect(wrapper.text()).toContain("Anonymous");
expect(wrapper.text()).toContain("No email");
expect(wrapper.text()).toContain("Anon");
});
it("renders 'Log out' option when user is logged in", () => {
const wrapper = mount(SidebarFooterComponent, {
props: { user },
global: {
stubs: {
ClientOnly: { template: "<div><slot /></div>" },
},
},
});
expect(wrapper.text()).toContain("Log out");
expect(wrapper.text()).not.toContain("Log in");
});
it("renders 'Log in' option when user is anonymous", () => {
const wrapper = mount(SidebarFooterComponent, {
props: { user: null },
global: {
stubs: {
ClientOnly: { template: "<div><slot /></div>" },
},
},
});
expect(wrapper.text()).toContain("Log in");
expect(wrapper.text()).not.toContain("Log out");
});
it("calls navigateTo('/auth/logout') when Log out is clicked", async () => {
const wrapper = mount(SidebarFooterComponent, {
props: { user },
global: {
stubs: {
ClientOnly: { template: "<div><slot /></div>" },
},
},
});
const logoutItem = wrapper.findAll('[data-testid="dropdown-item"]').find((item) => item.text().includes("Log out"));
expect(logoutItem).toBeDefined();
await logoutItem?.trigger("click");
expect(navigateToMock).toHaveBeenCalledWith("/auth/logout");
});
it("calls navigateTo('/auth/login') when Log in is clicked", async () => {
const wrapper = mount(SidebarFooterComponent, {
props: { user: null },
global: {
stubs: {
ClientOnly: { template: "<div><slot /></div>" },
},
},
});
const loginItem = wrapper.findAll('[data-testid="dropdown-item"]').find((item) => item.text().includes("Log in"));
expect(loginItem).toBeDefined();
await loginItem?.trigger("click");
expect(navigateToMock).toHaveBeenCalledWith("/auth/login");
});
it("computes initials correctly for single name", () => {
const singleNameUser = { ...user, name: "Liviu" };
const wrapper = mount(SidebarFooterComponent, {
props: { user: singleNameUser },
global: {
stubs: {
ClientOnly: { template: "<div><slot /></div>" },
},
},
});
// "Liviu" -> "L"
expect(wrapper.text()).toContain("L");
});
it("renders correctly when user has no image", () => {
const userNoImage = { ...user, image: null as unknown as string };
const wrapper = mount(SidebarFooterComponent, {
props: { user: userNoImage },
global: {
stubs: {
ClientOnly: { template: "<div><slot /></div>" },
},
},
});
expect(wrapper.text()).toContain("Liviu Test");
expect(wrapper.text()).toContain("test@example.com");
expect(wrapper.text()).toContain("LT");
});
it("sets side to 'bottom' when isMobile is true", () => {
const { ref } = require("vue");
useSidebarMock.mockReturnValue({
isMobile: ref(true),
state: ref("expanded"),
openMobile: ref(false),
setOpenMobile: vi.fn(),
});
const wrapper = mount(SidebarFooterComponent, {
props: { user },
global: {
stubs: {
ClientOnly: { template: "<div><slot /></div>" },
},
},
});
const dropdownContent = wrapper.findComponent({ name: "DropdownMenuContent" });
expect(dropdownContent.attributes("side")).toBe("bottom");
});
it("sets side to 'right' when isMobile is false", () => {
const { ref } = require("vue");
useSidebarMock.mockReturnValue({
isMobile: ref(false),
state: ref("expanded"),
openMobile: ref(false),
setOpenMobile: vi.fn(),
});
const wrapper = mount(SidebarFooterComponent, {
props: { user },
global: {
stubs: {
ClientOnly: { template: "<div><slot /></div>" },
},
},
});
const dropdownContent = wrapper.findComponent({ name: "DropdownMenuContent" });
expect(dropdownContent.attributes("side")).toBe("right");
});
it("sets side to 'bottom' when isMobile is true and user is anonymous", () => {
const { ref } = require("vue");
useSidebarMock.mockReturnValue({
isMobile: ref(true),
state: ref("expanded"),
openMobile: ref(false),
setOpenMobile: vi.fn(),
});
const wrapper = mount(SidebarFooterComponent, {
props: { user: null },
global: {
stubs: {
ClientOnly: { template: "<div><slot /></div>" },
},
},
});
const dropdownContent = wrapper.findComponent({ name: "DropdownMenuContent" });
expect(dropdownContent.attributes("side")).toBe("bottom");
});
it("sets side to 'right' when isMobile is false and user is anonymous", () => {
const { ref } = require("vue");
useSidebarMock.mockReturnValue({
isMobile: ref(false),
state: ref("expanded"),
openMobile: ref(false),
setOpenMobile: vi.fn(),
});
const wrapper = mount(SidebarFooterComponent, {
props: { user: null },
global: {
stubs: {
ClientOnly: { template: "<div><slot /></div>" },
},
},
});
const dropdownContent = wrapper.findComponent({ name: "DropdownMenuContent" });
expect(dropdownContent.attributes("side")).toBe("right");
});
it("returns empty string for userInititials when user is undefined (covering line 24)", () => {
const wrapper = mount(SidebarFooterComponent, {
props: { user: undefined },
global: {
stubs: {
ClientOnly: { template: "<div><slot /></div>" },
},
},
});
// Access the component's internal state/computed properties
expect((wrapper.vm as any).userInititials).toBe("");
});
});