-
+
+
+
+ Last clicked button:
+ {{ lastClicked }}
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/layouts/Default.test.ts b/tests/layouts/Default.test.ts
index c2b5893..b3257b9 100644
--- a/tests/layouts/Default.test.ts
+++ b/tests/layouts/Default.test.ts
@@ -1,11 +1,38 @@
-import { describe, expect, it } from "vitest";
-import { mount, type VueWrapper } from "@vue/test-utils";
+import { afterEach, describe, expect, it, vi } from "vitest";
+import { mount } from "@vue/test-utils";
import DefaultLayout from "~/layouts/Default.vue";
describe("Default.vue", () => {
- const wrapper: VueWrapper = mount(DefaultLayout);
+ afterEach(() => {
+ vi.useRealTimers();
+ });
it("loads without crashing", () => {
+ const wrapper = mount(DefaultLayout);
expect(wrapper.exists()).toBe(true);
});
+
+ describe("Footer", () => {
+ it("footer is displayed", () => {
+ const wrapper = mount(DefaultLayout);
+ const footer = wrapper.find("[data-testid='footer']");
+ expect(footer.exists()).toBe(true);
+ });
+
+ it("footer shows only 2025 when current year is 2025", () => {
+ vi.useFakeTimers();
+ vi.setSystemTime(new Date(2025, 0, 1));
+ const wrapper = mount(DefaultLayout);
+ const footer = wrapper.find("[data-testid='footer']");
+ expect(footer.text()).toBe("Glowing Fiesta 2025");
+ });
+
+ it("footer shows range when current year is not 2025", () => {
+ vi.useFakeTimers();
+ vi.setSystemTime(new Date(2069, 0, 1));
+ const wrapper = mount(DefaultLayout);
+ const footer = wrapper.find("[data-testid='footer']");
+ expect(footer.text()).toBe("Glowing Fiesta 2025 - 2069");
+ });
+ });
});
diff --git a/tests/layouts/default/Sidebar.test.ts b/tests/layouts/default/Sidebar.test.ts
new file mode 100644
index 0000000..ba04808
--- /dev/null
+++ b/tests/layouts/default/Sidebar.test.ts
@@ -0,0 +1,191 @@
+import { mount } from "@vue/test-utils";
+import { describe, it, expect, vi } 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(),
+}));
+
+// 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,
+ HandCoins: MockIcon,
+ LogOut: MockIcon,
+ Settings2: MockIcon,
+ Sparkles: MockIcon,
+ SquareTerminal: MockIcon,
+ };
+});
+
+describe("SidebarLayout", () => {
+ it("renders the header correctly", () => {
+ useSidebarMock.mockReturnValue({
+ isMobile: ref(false),
+ state: ref("expanded"),
+ openMobile: ref(false),
+ setOpenMobile: vi.fn(),
+ });
+
+ const wrapper = mount(SidebarLayout, {
+ props: {
+ collapsible: "icon",
+ },
+ });
+ expect(wrapper.text()).toContain("Glowing Fiesta");
+ expect(wrapper.text()).toContain("v1.0.0");
+ });
+
+ it("renders navigation groups", () => {
+ const wrapper = mount(SidebarLayout);
+ const text = wrapper.text();
+ expect(text).toContain("Playground");
+ expect(text).toContain("Documentation");
+ expect(text).toContain("Settings");
+ });
+
+ it("renders user information", () => {
+ const wrapper = mount(SidebarLayout);
+ expect(wrapper.text()).toContain("Liviu");
+ expect(wrapper.text()).toContain("x.liviu@gmail.com");
+ });
+
+ it("renders sub-items in navigation", () => {
+ const wrapper = mount(SidebarLayout);
+ const text = wrapper.text();
+ // Checking sub-items of Playground
+ expect(text).toContain("History");
+ expect(text).toContain("Starred");
+ // Checking sub-items of Documentation
+ expect(text).toContain("Introduction");
+ expect(text).toContain("Get Started");
+ });
+
+ it("does not render icon if item.icon is missing", () => {
+ const wrapper = mount(SidebarLayout, {
+ props: {
+ navItems: [
+ {
+ title: "No Icon Item",
+ url: "#",
+ items: [],
+ },
+ ],
+ },
+ });
+
+ expect(wrapper.text()).toContain("No Icon Item");
+ expect(wrapper.find('[data-testid="sidebar-icon"]').exists()).toBe(false);
+ });
+
+ it("renders correctly in mobile view", () => {
+ useSidebarMock.mockReturnValue({
+ isMobile: ref(true),
+ state: ref("expanded"),
+ openMobile: ref(true),
+ setOpenMobile: vi.fn(),
+ });
+
+ const wrapper = mount(SidebarLayout);
+
+ // 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");
+ });
+});
diff --git a/tests/pages/index.test.ts b/tests/pages/index.test.ts
index 55dccf9..f88fbd7 100644
--- a/tests/pages/index.test.ts
+++ b/tests/pages/index.test.ts
@@ -1,11 +1,52 @@
-import { describe, expect, it } from "vitest";
+import { describe, expect, it, beforeEach } from "vitest";
import { mount, type VueWrapper } from "@vue/test-utils";
import IndexPage from "~/pages/index.vue";
describe("pages/index.vue", () => {
- const wrapper: VueWrapper = mount(IndexPage);
+ let wrapper: VueWrapper;
+
+ beforeEach(() => {
+ wrapper = mount(IndexPage);
+ });
it("loads without crashing", () => {
expect(wrapper.exists()).toBe(true);
});
+
+ it("displays initial state correctly", () => {
+ const displayElement = wrapper.find(".text-lime-500");
+ expect(displayElement.exists()).toBe(true);
+ expect(displayElement.text()).toBe("None");
+ });
+
+ it("updates text when Default button is clicked", async () => {
+ // Find button method 1: by text content inside button elements
+ const buttons = wrapper.findAll("button");
+ const defaultBtn = buttons.find((b) => b.text() === "Default");
+
+ expect(defaultBtn?.exists()).toBe(true);
+
+ await defaultBtn?.trigger("click");
+ expect(wrapper.find(".text-lime-500").text()).toBe("default");
+ });
+
+ it("updates text when other buttons are clicked", async () => {
+ const testCases = [
+ { label: "Outline", expected: "outline" },
+ { label: "Ghost", expected: "ghost" },
+ { label: "Link", expected: "link" },
+ { label: "Secondary", expected: "secondary" },
+ { label: "Destructive", expected: "destructive" },
+ ];
+
+ for (const { label, expected } of testCases) {
+ const buttons = wrapper.findAll("button");
+ const btn = buttons.find((b) => b.text() === label);
+
+ expect(btn?.exists(), `Button with label ${label} should exist`).toBe(true);
+ await btn?.trigger("click");
+
+ expect(wrapper.find(".text-lime-500").text()).toBe(expected);
+ }
+ });
});
diff --git a/vitest.config.ts b/vitest.config.ts
index 07f384e..4bdb98f 100644
--- a/vitest.config.ts
+++ b/vitest.config.ts
@@ -32,6 +32,9 @@ export default defineConfig({
// Exclude config files
"*.config.*",
"assets/icons/**",
+
+ // Exclude UI components
+ "app/components/ui/**",
],
},
name: "GFiesta",