vueless
Version:
Vue Styleless UI Component Library, powered by Tailwind CSS.
725 lines (569 loc) • 20.7 kB
text/typescript
import { mount } from "@vue/test-utils";
import { describe, it, expect, vi } from "vitest";
import UDrawer from "../UDrawer.vue";
import UHeader from "../../ui.text-header/UHeader.vue";
import type { Props } from "../types";
describe("UDrawer", () => {
const modelValue = true;
// Wait for an async component to load
function sleep(ms: number = 0) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
// Props tests
describe("Props", () => {
it("ModelValue – renders when true", () => {
const component = mount(UDrawer, {
props: {
modelValue,
},
});
expect(component.isVisible()).toBe(modelValue);
});
it("ModelValue – does not render when false", () => {
const modelValue = false;
const component = mount(UDrawer, {
props: {
modelValue,
},
});
expect(component.find("[vl-key='overlay']").exists()).toBe(modelValue);
});
it("Title – renders with title prop", () => {
const title = "Drawer Title";
const component = mount(UDrawer, {
props: {
modelValue,
title,
},
});
const header = component.findComponent(UHeader);
expect(header.exists()).toBe(true);
expect(header.props("label")).toBe(title);
});
it("Description – renders with description prop", () => {
const title = "Drawer Title";
const description = "Drawer Description";
const component = mount(UDrawer, {
props: {
modelValue,
title,
description,
},
});
expect(component.text()).toContain(description);
});
it("Position – applies correct position classes", () => {
const positions = {
top: ["top-0", "w-full", "h-auto"],
bottom: ["bottom-0", "w-full", "h-auto"],
left: ["left-0", "w-max", "h-full"],
right: ["right-0", "w-max", "h-full"],
};
Object.entries(positions).forEach(([position, expectedClasses]) => {
const component = mount(UDrawer, {
props: {
modelValue,
position: position as Props["position"],
},
});
const drawerClasses = component.find("[vl-key='drawerWrapper']").attributes("class");
expectedClasses.forEach((expectedClass) => {
expect(drawerClasses).toContain(expectedClass);
});
});
});
it("Variant – applies correct variant classes", () => {
const variants = {
solid: "bg-default",
outlined: "bg-default",
subtle: "bg-muted",
soft: "bg-muted",
};
Object.entries(variants).forEach(([variant, expectedClasses]) => {
const component = mount(UDrawer, {
props: {
modelValue,
variant: variant as Props["variant"],
},
});
expect(component.find("[vl-key='drawerWrapper']").attributes("class")).toContain(
expectedClasses,
);
});
});
it("Handle – renders handle when prop is true", () => {
const handle = [true, false];
handle.forEach((value) => {
const component = mount(UDrawer, {
props: {
modelValue,
handle: value,
},
});
const handleWrapper = component.find("[vl-key='handleWrapper']");
const handleElement = component.find("[vl-key='handle']");
expect(handleWrapper.exists()).toBe(value);
expect(handleElement.exists()).toBe(value);
});
});
it("Inset – applies inset class when prop is true", () => {
const inset = true;
const expectedClass = "m-4";
const component = mount(UDrawer, {
props: {
modelValue,
inset,
},
});
const innerWrapper = component.find("[vl-key='innerWrapper']");
expect(innerWrapper.attributes("class")).toContain(expectedClass);
});
it("CloseOnOverlay – closes drawer when overlay is clicked", () => {
const closeOnOverlay = [true, false];
closeOnOverlay.forEach(async (value) => {
const component = mount(UDrawer, {
props: {
modelValue,
closeOnOverlay: value,
},
});
const innerWrapper = component.find('[vl-key="innerWrapper"]');
expect(innerWrapper.exists()).toBe(true);
await innerWrapper.trigger("mousedown");
await innerWrapper.trigger("click");
await sleep(1000);
const drawer = component.find('[vl-key="drawer"]');
expect(drawer.exists()).toBe(!value);
});
});
it("CloseOnEsc – closes drawer when escape key is pressed", () => {
const closeOnEsc = [true, false];
closeOnEsc.forEach(async (value) => {
const component = mount(UDrawer, {
props: {
modelValue,
closeOnEsc: value,
},
});
const wrapper = component.find("[vl-key='wrapper']");
await wrapper.trigger("keydown", { key: "Escape" });
await sleep(1000);
const drawer = component.find('[vl-key="drawer"]');
expect(drawer.exists()).toBe(!value);
});
});
it("DataTest – applies the correct data-test attribute", () => {
const dataTest = "drawer-test";
const component = mount(UDrawer, {
props: {
modelValue,
dataTest,
},
});
const drawerWrapper = component.find("[vl-key='wrapper']");
expect(drawerWrapper.attributes("data-test")).toBe(dataTest);
});
it("CloseOnCross – shows cross by default and closes on click when true", async () => {
const component = mount(UDrawer, {
props: {
modelValue: true,
title: "Drawer Title",
closeOnCross: true,
},
});
const closeButton = component.find('[vl-key="closeButton"]');
expect(closeButton.exists()).toBe(true);
await closeButton.trigger("click");
expect(component.emitted("update:modelValue")).toBeTruthy();
expect(component.emitted("update:modelValue")?.[0]).toEqual([false]);
expect(component.emitted("close")).toBeTruthy();
});
it("CloseOnCross – hides cross when false", () => {
const component = mount(UDrawer, {
props: {
modelValue: true,
title: "Drawer Title",
closeOnCross: false,
},
});
const closeButton = component.find('[vl-key="closeButton"]');
expect(closeButton.exists()).toBe(false);
});
});
// Slots tests
describe("Slots", () => {
it("Default – renders content in default slot", () => {
const slotClass = "default-content";
const slotContent = "Default Content";
const component = mount(UDrawer, {
props: { modelValue: true },
slots: {
default: `<div class="${slotClass}">${slotContent}</div>`,
},
});
expect(component.find(`.${slotClass}`).exists()).toBe(true);
expect(component.text()).toContain(slotContent);
});
it("Before Title – renders content in slot and shows header", () => {
const slotClass = "before-title";
const slotContent = "Before Title";
const component = mount(UDrawer, {
props: {
modelValue: true,
title: "Drawer Title",
},
slots: {
"before-title": `<div class="${slotClass}">${slotContent}</div>`,
},
});
expect(component.find(`.${slotClass}`).exists()).toBe(true);
expect(component.text()).toContain(slotContent);
// Check that header is shown when before-title slot is provided
const header = component.find("[vl-key='header']");
expect(header.exists()).toBe(true);
expect(header.text()).toContain(slotContent);
});
it("Title – renders custom content in slot and shows header", () => {
const slotClass = "custom-title";
const slotContent = "Custom Title";
const component = mount(UDrawer, {
props: {
modelValue: true,
title: "Drawer Title",
},
slots: {
title: `<div class="${slotClass}">${slotContent}</div>`,
},
});
expect(component.find(`.${slotClass}`).exists()).toBe(true);
expect(component.text()).toContain(slotContent);
expect(component.findComponent(UHeader).exists()).toBe(false);
// Check that header is shown when title slot is provided
const header = component.find("[vl-key='header']");
expect(header.exists()).toBe(true);
expect(header.text()).toContain(slotContent);
});
it("After Title – renders content in slot and shows header", () => {
const slotClass = "after-title";
const slotContent = "After Title";
const component = mount(UDrawer, {
props: {
modelValue: true,
title: "Drawer Title",
},
slots: {
"after-title": `<div class="${slotClass}">${slotContent}</div>`,
},
});
expect(component.find(`.${slotClass}`).exists()).toBe(true);
expect(component.text()).toContain(slotContent);
// Check that header is shown when after-title slot is provided
const header = component.find("[vl-key='header']");
expect(header.exists()).toBe(true);
expect(header.text()).toContain(slotContent);
});
it("Actions – renders custom content in slot and shows header", () => {
const slotClass = "actions";
const slotContent = "Actions";
const component = mount(UDrawer, {
props: {
modelValue: true,
title: "Drawer Title",
},
slots: {
actions: `<div class="${slotClass}">${slotContent}</div>`,
},
});
expect(component.find(`.${slotClass}`).exists()).toBe(true);
expect(component.text()).toContain(slotContent);
// Check that header is shown when actions slot is provided
const header = component.find("[vl-key='header']");
expect(header.exists()).toBe(true);
expect(header.text()).toContain(slotContent);
});
it("Header – does not show when no title or slots are provided", () => {
const component = mount(UDrawer, {
props: { modelValue },
});
const header = component.find("[vl-key='header']");
expect(header.exists()).toBe(false);
});
it("Actions – provides close binding to slot", () => {
const component = mount(UDrawer, {
props: {
modelValue: true,
title: "Drawer Title",
},
slots: {
actions: `
<template #default="{ close }">
<button class="custom-close" @click="close">Close</button>
</template>
`,
},
});
const closeButton = component.find(".custom-close");
expect(closeButton.exists()).toBe(true);
// Click the close button
closeButton.trigger("click");
// Check if the drawer emitted the update:modelValue event with false
expect(component.emitted("update:modelValue")).toBeTruthy();
expect(component.emitted("update:modelValue")?.[0]).toEqual([false]);
});
it("Handle – renders custom content in slot", () => {
const slotClass = "custom-handle";
const slotContent = "Custom Handle";
const component = mount(UDrawer, {
props: {
modelValue: true,
handle: true,
},
slots: {
handle: `<div class="${slotClass}">${slotContent}</div>`,
},
});
expect(component.find(`.${slotClass}`).exists()).toBe(true);
expect(component.text()).toContain(slotContent);
// Check that default handle element is not rendered when slot is used
const defaultHandle = component.find("[vl-key='handle']");
expect(defaultHandle.exists()).toBe(false);
});
it("Footer Left – renders content in slot and shows footer", () => {
const slotClass = "footer-left";
const slotContent = "Footer Left";
const component = mount(UDrawer, {
props: { modelValue: true },
slots: {
"footer-left": `<div class="${slotClass}">${slotContent}</div>`,
},
});
expect(component.find(`.${slotClass}`).exists()).toBe(true);
expect(component.text()).toContain(slotContent);
// Check that footer is shown when footer-left slot is provided
const footer = component.find("[vl-key='footer']");
expect(footer.exists()).toBe(true);
expect(footer.text()).toContain(slotContent);
});
it("Footer Right – renders content in slot and shows footer", () => {
const slotClass = "footer-right";
const slotContent = "Footer Right";
const component = mount(UDrawer, {
props: { modelValue: true },
slots: {
"footer-right": `<div class="${slotClass}">${slotContent}</div>`,
},
});
expect(component.find(`.${slotClass}`).exists()).toBe(true);
expect(component.text()).toContain(slotContent);
// Check that footer is shown when footer-right slot is provided
const footer = component.find("[vl-key='footer']");
expect(footer.exists()).toBe(true);
expect(footer.text()).toContain(slotContent);
});
it("Footer – does not show when no footer slots are provided", () => {
const component = mount(UDrawer, {
props: { modelValue },
});
const footer = component.find("[vl-key='footer']");
expect(footer.exists()).toBe(false);
});
});
// Events tests
describe("Events", () => {
it("Update:modelValue – emits event when drawer is closed", async () => {
const title = "Drawer Title";
const component = mount(UDrawer, {
props: {
modelValue,
title,
},
slots: {
actions: `
<template #default="{ close }">
<button class="close-btn" @click="close">Close</button>
</template>
`,
},
});
const closeButton = component.find(".close-btn");
await closeButton.trigger("click");
expect(component.emitted("update:modelValue")).toBeTruthy();
expect(component.emitted("update:modelValue")?.[0]).toEqual([false]);
});
it("Close – emits event when drawer is closed", async () => {
const title = "Drawer Title";
const component = mount(UDrawer, {
props: {
modelValue,
title,
},
slots: {
actions: `
<template #default="{ close }">
<button class="close-btn" @click="close">Close</button>
</template>
`,
},
});
const closeButton = component.find(".close-btn");
await closeButton.trigger("click");
expect(component.emitted("close")).toBeTruthy();
});
it("CloseOnOverlay – emits events when overlay is clicked based on prop", async () => {
const closeOnOverlay = [true, false];
for (const value of closeOnOverlay) {
const component = mount(UDrawer, {
props: {
modelValue,
closeOnOverlay: value,
},
});
const innerWrapper = component.find("[vl-key='innerWrapper']");
await innerWrapper.trigger("mousedown");
await innerWrapper.trigger("click");
expect(component.emitted("update:modelValue")).toEqual(value ? [[false]] : undefined);
expect(Boolean(component.emitted("close")?.length)).toBe(value);
}
});
it("CloseOnOverlay – does not close when mousedown on drawer and mouseup on overlay", async () => {
const component = mount(UDrawer, {
props: {
modelValue: true,
closeOnOverlay: true,
},
});
const drawer = component.find("[vl-key='drawer']");
const innerWrapper = component.find("[vl-key='innerWrapper']");
// Mousedown on drawer content
await drawer.trigger("mousedown");
// Click (mouseup) on overlay
await innerWrapper.trigger("click");
// Drawer should NOT close
expect(component.emitted("update:modelValue")).toBeFalsy();
expect(component.emitted("close")).toBeFalsy();
});
it("CloseOnEsc – emits events when escape key is pressed based on prop", async () => {
const closeOnEsc = [true, false];
for (const value of closeOnEsc) {
const component = mount(UDrawer, {
props: {
modelValue,
closeOnEsc: value,
},
});
const wrapper = component.find("[vl-key='wrapper']");
await wrapper.trigger("keydown", { key: "Escape" });
expect(component.emitted("update:modelValue")).toEqual(value ? [[false]] : undefined);
expect(Boolean(component.emitted("close")?.length)).toBe(value);
}
});
});
// Drag functionality tests
describe("Drag Functionality", () => {
it("Cursor – applies drag cursor classes when handle is enabled", () => {
const component = mount(UDrawer, {
props: {
modelValue: true,
handle: true,
},
});
const handleWrapper = component.find("[vl-key='handleWrapper']");
expect(handleWrapper.attributes("class")).toContain("cursor-grab");
});
it("Mouse Drag – handles drag start from handle", async () => {
const component = mount(UDrawer, {
props: {
modelValue: true,
handle: true,
},
});
const drawer = component.find("[vl-key='drawerWrapper']");
const handleWrapper = component.find("[vl-key='handleWrapper']");
// Mock getBoundingClientRect
const mockRect = {
width: 300,
height: 400,
top: 0,
left: 0,
right: 300,
bottom: 400,
};
vi.spyOn(drawer.element, "getBoundingClientRect").mockReturnValue(mockRect as DOMRect);
await handleWrapper.trigger("mousedown", {
clientX: 100,
clientY: 100,
});
// Check if handle has drag cursor class
expect(handleWrapper.attributes("class")).toContain("cursor-grab");
});
it("Touch Drag – handles drag start from handle", async () => {
const component = mount(UDrawer, {
props: {
modelValue: true,
handle: true,
},
});
const drawer = component.find("[vl-key='drawerWrapper']");
const handleWrapper = component.find("[vl-key='handleWrapper']");
// Mock getBoundingClientRect
const mockRect = {
width: 300,
height: 400,
top: 0,
left: 0,
right: 300,
bottom: 400,
};
vi.spyOn(drawer.element, "getBoundingClientRect").mockReturnValue(mockRect as DOMRect);
await handleWrapper.trigger("touchstart", {
touches: [{ clientX: 100, clientY: 100 }],
});
// Check if handle has drag cursor class
expect(handleWrapper.attributes("class")).toContain("cursor-grab");
});
it("Transform – applies drag transform styles during drag", async () => {
const component = mount(UDrawer, {
props: {
modelValue: true,
position: "left",
},
});
const drawer = component.find("[vl-key='drawerWrapper']");
// Mock getBoundingClientRect
const mockRect = {
width: 300,
height: 400,
top: 0,
left: 0,
right: 300,
bottom: 400,
};
vi.spyOn(drawer.element, "getBoundingClientRect").mockReturnValue(mockRect as DOMRect);
// Start drag
await drawer.trigger("mousedown", {
clientX: 100,
clientY: 100,
});
// Simulate drag movement
const mouseMoveEvent = new MouseEvent("mousemove", {
clientX: 150, // 50px movement
clientY: 100,
});
document.dispatchEvent(mouseMoveEvent);
// Check if transform is applied
const drawerElement = component.find("[vl-key='drawerWrapper']");
const style = drawerElement.attributes("style");
expect(style ? style.includes("transform") : drawerElement.exists()).toBe(true);
});
});
// Exposed refs tests
describe("Exposed refs", () => {
it("WrapperRef – exposes wrapper element ref", () => {
const component = mount(UDrawer, {
props: { modelValue },
});
expect(component.vm.wrapperRef).toBeDefined();
expect(component.vm.wrapperRef instanceof HTMLDivElement).toBe(true);
});
});
});