vueless
Version:
Vue Styleless UI Component Library, powered by Tailwind CSS.
411 lines (323 loc) • 12.3 kB
text/typescript
import { mount } from "@vue/test-utils";
import { describe, it, expect } from "vitest";
import UTabs from "../UTabs.vue";
import UTab from "../../ui.navigation-tab/UTab.vue";
import UButton from "../../ui.button/UButton.vue";
import type { Props, UTabsOption } from "../types";
describe("UTabs.vue", () => {
// Global options definition
const options: UTabsOption[] = [
{ value: "tab1", label: "Tab 1" },
{ value: "tab2", label: "Tab 2" },
{ value: "tab3", label: "Tab 3" },
];
describe("Props", () => {
it("ModelValue – correctly sets the selected tab", () => {
const modelValue = "tab2";
const expectedActiveClass = "border-primary";
const component = mount(UTabs, {
props: {
options,
modelValue,
},
});
// Find all UTab components
const tabs = component.findAllComponents(UTab);
// Check that the correct tab is active
expect(tabs.length).toBe(options.length);
// The second tab should be active
const activeTab = tabs[1];
expect(activeTab.classes()).toContain(expectedActiveClass);
});
it("Options – renders the correct number of tabs from options", () => {
const component = mount(UTabs, {
props: {
options,
},
});
// Find all UTab components
const tabs = component.findAllComponents(UTab);
// Check that the correct number of tabs is rendered
expect(tabs.length).toBe(options.length);
// Check that the tabs have the correct labels
options.forEach((option, index) => {
expect(tabs[index].text()).toBe(option.label);
});
});
it("Size – applies the correct size to tabs", () => {
const sizes = ["2xs", "xs", "sm", "md", "lg", "xl"];
sizes.forEach((size) => {
const component = mount(UTabs, {
props: {
options: [{ value: "tab1", label: "Tab 1" }],
size: size as Props["size"],
},
});
// Find the UTab component
const tab = component.findComponent(UTab);
// Find the UButton inside the UTab
const button = tab.findComponent(UButton);
// Check that the button has the correct size
expect(button.props("size")).toBe(size);
});
});
it("Scrollable – applies scrollable class when scrollable prop is true", () => {
const scrollable = true;
const component = mount(UTabs, {
props: {
options,
scrollable,
},
});
// Find the tabs container
const tabsContainer = component.find(`[vl-key="tabs"]`);
// Check that the container has the scrollable class
expect(tabsContainer.classes()).toContain("scroll-smooth");
});
it("Scroll – shows scroll buttons when scrollable and content overflows", async () => {
const manyOptions: UTabsOption[] = Array.from({ length: 10 }, (_, i) => ({
value: `tab${i}`,
label: `Tab ${i}`,
}));
const component = mount(UTabs, {
props: {
options: manyOptions,
scrollable: true,
},
});
// Mock the scroll position to show both arrows
const scrollContainer = component.find(`[vl-key="tabs"]`).element;
Object.defineProperty(scrollContainer, "scrollLeft", { value: 10 });
Object.defineProperty(scrollContainer, "scrollWidth", { value: 1000 });
Object.defineProperty(scrollContainer, "clientWidth", { value: 500 });
// Trigger scroll event
await component.find("[vl-key='tabs']").trigger("scroll");
// Both arrows should be visible
const buttons = component.findAllComponents(UButton);
// Check that at least two buttons are rendered
expect(buttons.length).toBeGreaterThanOrEqual(2);
// Find the buttons with the correct icons
const prevButton = buttons.find((button) => button.props("icon") === "chevron_left");
const nextButton = buttons.find((button) => button.props("icon") === "chevron_right");
expect(prevButton).toBeDefined();
expect(nextButton).toBeDefined();
});
it("Block – provides block value to tabs", () => {
const block = true;
const component = mount(UTabs, {
props: {
options: [{ value: "tab1", label: "Tab 1" }],
block,
},
});
// Find the UTab component
const tab = component.findComponent(UTab);
// Find the UButton inside the UTab
const button = tab.findComponent(UButton);
// Check that the button has the block prop
expect(button.props("block")).toBe(block);
});
it("Square – provides square value to tabs", () => {
const square = true;
const component = mount(UTabs, {
props: {
options: [{ value: "tab1", label: "Tab 1" }],
square,
},
});
// Find the UTab component
const tab = component.findComponent(UTab);
// Find the UButton inside the UTab
const button = tab.findComponent(UButton);
// Check that the button has the square prop
expect(button.props("square")).toBe(square);
});
it("DataTest – applies the correct data-test attribute", () => {
const dataTest = "test-tabs";
const singleOption = [options[0]];
const component = mount(UTabs, {
props: {
options: singleOption,
dataTest,
},
});
// Find the tabs container
const tabsContainer = component.find(`[data-test="${dataTest}"]`);
// Check that the container has the data-test attribute
expect(tabsContainer.exists()).toBe(true);
// Check that the tab has the correct data-test attribute
const tab = component.find(`[data-test="${dataTest}-item-0"]`);
expect(tab.exists()).toBe(true);
});
});
describe("Slots", () => {
it("Default – renders content from default slot", () => {
const slotContent = "Custom Tabs";
const slotClass = "custom-tabs";
const component = mount(UTabs, {
slots: {
default: `<div class="${slotClass}">${slotContent}</div>`,
},
});
expect(component.find(`.${slotClass}`).exists()).toBe(true);
expect(component.find(`.${slotClass}`).text()).toBe(slotContent);
});
it("Prev – renders content from prev slot when scrollable", async () => {
const slotContent = "Prev";
const slotClass = "prev-content";
const dataTest = "test-tabs";
const manyOptions: UTabsOption[] = Array.from({ length: 10 }, (_, i) => ({
value: `tab${i}`,
label: `Tab ${i}`,
}));
const component = mount(UTabs, {
props: {
options: manyOptions,
scrollable: true,
dataTest,
},
slots: {
prev: `<div class="${slotClass}">${slotContent}</div>`,
},
});
// Mock the scroll position to show left arrow
const scrollContainer = component.find(`[vl-key="tabs"]`).element;
Object.defineProperty(scrollContainer, "scrollLeft", { value: 10 });
Object.defineProperty(scrollContainer, "scrollWidth", { value: 1000 });
Object.defineProperty(scrollContainer, "clientWidth", { value: 500 });
// Trigger scroll event
await component.find("[vl-key='tabs']").trigger("scroll");
// Now the left arrow should be visible
expect(component.find(`.${slotClass}`).exists()).toBe(true);
expect(component.find(`.${slotClass}`).text()).toBe(slotContent);
});
it("Next – renders content from next slot when scrollable", async () => {
const slotContent = "Next";
const slotClass = "next-content";
const dataTest = "test-tabs";
const manyOptions: UTabsOption[] = Array.from({ length: 10 }, (_, i) => ({
value: `tab${i}`,
label: `Tab ${i}`,
}));
const component = mount(UTabs, {
props: {
options: manyOptions,
scrollable: true,
dataTest,
},
slots: {
next: `<div class="${slotClass}">${slotContent}</div>`,
},
});
// Mock the scroll position to show right arrow
const scrollContainer = component.find(`[vl-key="tabs"]`).element;
Object.defineProperty(scrollContainer, "scrollLeft", { value: 0 });
Object.defineProperty(scrollContainer, "scrollWidth", { value: 1000 });
Object.defineProperty(scrollContainer, "clientWidth", { value: 500 });
// Trigger scroll event
await component.find("[vl-key='tabs']").trigger("scroll");
// Now the right arrow should be visible
expect(component.find(`.${slotClass}`).exists()).toBe(true);
expect(component.find(`.${slotClass}`).text()).toBe(slotContent);
});
it("Left – renders content from left slot for each tab", () => {
const slotText = "Left";
const slotClass = "left-content";
const component = mount(UTabs, {
props: {
options,
},
slots: {
left: `<span class="${slotClass}">${slotText}</span>`,
},
});
const leftContents = component.findAll(`.${slotClass}`);
expect(leftContents.length).toBe(options.length);
leftContents.forEach((left) => {
expect(left.text()).toBe(slotText);
});
});
it("Label – renders content from label slot instead of default label", () => {
const testOptions: UTabsOption[] = [{ value: "tab1", label: "Tab 1" }];
const slotText = "Custom Label";
const slotClass = "label-content";
const component = mount(UTabs, {
props: {
options: testOptions,
},
slots: {
label: `<span class="${slotClass}">${slotText}</span>`,
},
});
expect(component.find(`.${slotClass}`).exists()).toBe(true);
expect(component.find(`.${slotClass}`).text()).toBe(slotText);
expect(component.text()).not.toContain(testOptions[0].label);
});
it("Right – renders content from right slot", () => {
const slotText = "Right";
const slotClass = "right-content";
const component = mount(UTabs, {
props: {
options,
},
slots: {
right: `<span class="${slotClass}">${slotText}</span>`,
},
});
expect(component.findAll(`.${slotClass}`).length).toBe(options.length);
expect(component.find(`.${slotClass}`).text()).toBe(slotText);
});
it("Slot – passes item, index and active state to slots", () => {
const modelValue = "tab2";
const component = mount(UTabs, {
props: {
options,
modelValue,
},
slots: {
left: `
<div
:data-value="params.item.value"
:data-index="params.index"
:data-active="params.active"
/>
`,
},
});
options.forEach((option, index) => {
const el = component.find(`[data-value="${option.value}"][data-index="${index}"]`);
expect(el.exists()).toBe(true);
});
const activeEl = component.find('[data-active="true"]');
expect(activeEl.exists()).toBe(true);
expect(activeEl.attributes("data-value")).toBe(modelValue);
});
});
describe("Events", () => {
it("Update:modelValue – emits update:modelValue event when tab is clicked", async () => {
const component = mount(UTabs, {
props: {
options,
modelValue: "tab1",
},
});
// Find the second tab and click it
const secondTab = component.findAllComponents(UTab)[1];
await secondTab.trigger("click");
// Check that the update:modelValue event was emitted with the correct value
expect(component.emitted("update:modelValue")).toBeTruthy();
expect(component.emitted("update:modelValue")?.[0]).toEqual(["tab2"]);
});
});
describe("Exposed refs", () => {
it("wrapperRef – exposes wrapperRef", () => {
const singleOption = [options[0]];
const component = mount(UTabs, {
props: {
options: singleOption,
},
});
expect(component.vm.wrapperRef).toBeDefined();
});
});
});