vueless
Version:
Vue Styleless UI Component Library, powered by Tailwind CSS.
632 lines (516 loc) • 17.4 kB
text/typescript
import { mount } from "@vue/test-utils";
import { describe, it, expect } from "vitest";
import { nextTick } from "vue";
import UDropdown from "../UDropdown.vue";
import UListbox from "../../ui.form-listbox/UListbox.vue";
describe("UDropdown.vue", () => {
const defaultOptions = [
{ value: 1, label: "Option 1" },
{ value: 2, label: "Option 2" },
{ value: 3, label: "Option 3" },
];
describe("Props", () => {
it("ModelValue – selects the correct option based on modelValue", async () => {
const modelValue = 2;
const component = mount(UDropdown, {
props: {
modelValue,
options: defaultOptions,
},
slots: {
default: `<button>Trigger</button>`,
},
});
expect(component.vm.selectedOptions[0]?.value).toBe(modelValue);
});
it("Multiple – handles multiple selections correctly", async () => {
const modelValue = [1, 3];
const component = mount(UDropdown, {
props: {
modelValue,
multiple: true,
options: defaultOptions,
},
slots: {
default: `<button>Trigger</button>`,
},
});
expect(component.vm.selectedOptions.length).toBe(2);
expect(component.vm.selectedOptions[0]?.value).toBe(1);
expect(component.vm.selectedOptions[1]?.value).toBe(3);
});
it("Disabled – applies disabled state correctly", async () => {
const component = mount(UDropdown, {
props: {
disabled: true,
options: defaultOptions,
dataTest: "dropdown",
},
slots: {
default: `<button>Click me</button>`,
},
});
const collapsible = component.findComponent({ name: "UCollapsible" });
expect(collapsible.props("disabled")).toBe(true);
await component.find("button").trigger("click");
await nextTick();
expect(component.findComponent(UListbox).exists()).toBe(false);
});
it("Searchable – renders searchable dropdown", async () => {
const component = mount(UDropdown, {
props: {
searchable: true,
options: defaultOptions,
},
slots: {
default: `<button>Trigger</button>`,
},
});
await component.find("button").trigger("click");
await nextTick();
expect(component.findComponent(UListbox).props("searchable")).toBe(true);
});
it("Size – applies the correct size to listbox", async () => {
const sizes = ["sm", "md", "lg"] as const;
for (const size of sizes) {
const component = mount(UDropdown, {
props: {
size,
options: defaultOptions,
},
slots: {
default: `<button>Trigger</button>`,
},
});
await component.find("button").trigger("click");
await nextTick();
expect(component.findComponent(UListbox).props("size")).toBe(size);
}
});
it("Color – applies the correct color to listbox", async () => {
const colors = [
"primary",
"secondary",
"error",
"warning",
"success",
"info",
"notice",
"neutral",
"grayscale",
] as const;
for (const color of colors) {
const component = mount(UDropdown, {
props: {
color,
options: defaultOptions,
},
slots: {
default: `<button>Trigger</button>`,
},
});
await component.find("button").trigger("click");
await nextTick();
expect(component.findComponent(UListbox).props("color")).toBe(color);
}
});
it("XPosition – passes xPosition prop to collapsible", () => {
const component = mount(UDropdown, {
props: {
xPosition: "right",
options: defaultOptions,
},
slots: {
default: `<button>Trigger</button>`,
},
});
const collapsible = component.findComponent({ name: "UCollapsible" });
expect(collapsible.props("xPosition")).toBe("right");
});
it("YPosition – passes yPosition prop to collapsible", () => {
const component = mount(UDropdown, {
props: {
yPosition: "top",
options: defaultOptions,
},
slots: {
default: `<button>Trigger</button>`,
},
});
const collapsible = component.findComponent({ name: "UCollapsible" });
expect(collapsible.props("yPosition")).toBe("top");
});
it("LabelKey – uses custom label key for options", async () => {
const customOptions = [
{ id: 1, name: "First" },
{ id: 2, name: "Second" },
];
const component = mount(UDropdown, {
props: {
options: customOptions,
labelKey: "name",
valueKey: "id",
modelValue: 1,
},
slots: {
default: `<button>Trigger</button>`,
},
});
await component.find("button").trigger("click");
await nextTick();
expect(component.findComponent(UListbox).props("labelKey")).toBe("name");
});
it("ValueKey – uses custom value key for options", async () => {
const customOptions = [
{ id: 1, name: "First" },
{ id: 2, name: "Second" },
];
const component = mount(UDropdown, {
props: {
options: customOptions,
labelKey: "name",
valueKey: "id",
modelValue: 1,
},
slots: {
default: `<button>Trigger</button>`,
},
});
await component.find("button").trigger("click");
await nextTick();
expect(component.findComponent(UListbox).props("valueKey")).toBe("id");
});
it("GroupValueKey – passes groupValueKey prop to listbox", async () => {
const component = mount(UDropdown, {
props: {
groupValueKey: "items",
options: defaultOptions,
},
slots: {
default: `<button>Trigger</button>`,
},
});
await component.find("button").trigger("click");
await nextTick();
expect(component.findComponent(UListbox).props("groupValueKey")).toBe("items");
});
it("CloseOnSelect – closes dropdown when option is selected and closeOnSelect is true", async () => {
const component = mount(UDropdown, {
props: {
closeOnSelect: true,
options: defaultOptions,
},
slots: {
default: `<button>Trigger</button>`,
},
});
await component.find("button").trigger("click");
await nextTick();
expect(component.findComponent(UListbox).exists()).toBe(true);
const listbox = component.findComponent(UListbox);
await listbox.vm.$emit("click-option", defaultOptions[0]);
await nextTick();
// Give time for the hide function to execute
await new Promise((resolve) => setTimeout(resolve, 20));
await nextTick();
expect(component.findComponent(UListbox).exists()).toBe(false);
});
it("CloseOnSelect – keeps dropdown open when option is selected and closeOnSelect is false", async () => {
const component = mount(UDropdown, {
props: {
closeOnSelect: false,
options: defaultOptions,
},
slots: {
default: `<button>Trigger</button>`,
},
});
await component.find("button").trigger("click");
await nextTick();
expect(component.findComponent(UListbox).exists()).toBe(true);
const listbox = component.findComponent(UListbox);
await listbox.vm.$emit("click-option", defaultOptions[0]);
await nextTick();
expect(component.findComponent(UListbox).exists()).toBe(true);
});
});
describe("Slots", () => {
it("Default – renders default slot with correct bindings", () => {
const component = mount(UDropdown, {
props: {
options: defaultOptions,
},
slots: {
default: `
<template #default="{ opened }">
<button>{{ opened ? 'Opened' : 'Closed' }}</button>
</template>
`,
},
});
expect(component.find("button").exists()).toBe(true);
expect(component.text()).toContain("Closed");
});
it("Empty – renders empty slot when no options are provided", async () => {
const component = mount(UDropdown, {
props: {
options: [],
},
slots: {
default: `<button>Open</button>`,
empty: `<div class="empty-state">No options available</div>`,
},
});
await component.find("button").trigger("click");
await nextTick();
expect(component.find(".empty-state").exists()).toBe(true);
expect(component.text()).toContain("No options available");
});
it("Before-option – renders before-option slot", async () => {
const component = mount(UDropdown, {
props: {
options: defaultOptions,
},
slots: {
default: `<button>Open</button>`,
"before-option": `<span class="before-icon">→</span>`,
},
});
await component.find("button").trigger("click");
await nextTick();
expect(component.findAll(".before-icon").length).toBeGreaterThan(0);
});
it("Option – renders option slot with custom content", async () => {
const component = mount(UDropdown, {
props: {
options: defaultOptions,
},
slots: {
default: `<button>Open</button>`,
option: `<template #option="{ option }"><div class="custom-option">{{ option.label }}</div></template>`,
},
});
await component.find("button").trigger("click");
await nextTick();
expect(component.findAll(".custom-option").length).toBeGreaterThan(0);
});
it("After-option – renders after-option slot", async () => {
const component = mount(UDropdown, {
props: {
options: defaultOptions,
},
slots: {
default: `<button>Open</button>`,
"after-option": `<span class="after-icon">✓</span>`,
},
});
await component.find("button").trigger("click");
await nextTick();
expect(component.findAll(".after-icon").length).toBeGreaterThan(0);
});
it("Dropdown – renders custom dropdown content slot", async () => {
const component = mount(UDropdown, {
props: {
options: defaultOptions,
},
slots: {
default: `<button>Open</button>`,
dropdown: `<div class="custom-dropdown">Custom dropdown content</div>`,
},
});
await component.find("button").trigger("click");
await nextTick();
expect(component.find(".custom-dropdown").exists()).toBe(true);
expect(component.text()).toContain("Custom dropdown content");
});
});
describe("Events", () => {
it("Update:modelValue – emits update:modelValue when an option is selected", async () => {
const component = mount(UDropdown, {
props: {
options: defaultOptions,
},
slots: {
default: `<button>Open</button>`,
},
});
await component.find("button").trigger("click");
await nextTick();
const listbox = component.findComponent(UListbox);
await listbox.vm.$emit("update:modelValue", defaultOptions[0].value);
expect(component.emitted("update:modelValue")).toBeTruthy();
expect(component.emitted("update:modelValue")![0][0]).toBe(defaultOptions[0].value);
});
it("Open – emits open event when dropdown is opened", async () => {
const component = mount(UDropdown, {
props: {
options: defaultOptions,
},
slots: {
default: `<button>Open</button>`,
},
});
await component.find("button").trigger("click");
await nextTick();
expect(component.emitted("open")).toBeTruthy();
});
it("Close – emits close event when dropdown is closed", async () => {
const component = mount(UDropdown, {
props: {
options: defaultOptions,
},
slots: {
default: `<button>Open</button>`,
},
});
await component.find("button").trigger("click");
await nextTick();
const hide = component.vm.hide as () => void;
hide();
await nextTick();
expect(component.emitted("close")).toBeTruthy();
});
it("ClickOption – emits click-option event when an option is clicked", async () => {
const component = mount(UDropdown, {
props: {
options: defaultOptions,
},
slots: {
default: `<button>Open</button>`,
},
});
await component.find("button").trigger("click");
await nextTick();
const listbox = component.findComponent(UListbox);
await listbox.vm.$emit("click-option", defaultOptions[0]);
expect(component.emitted("click-option")).toBeTruthy();
});
it("SearchChange – emits search-change event when search value changes", async () => {
const component = mount(UDropdown, {
props: {
searchable: true,
options: defaultOptions,
},
slots: {
default: `<button>Open</button>`,
},
});
await component.find("button").trigger("click");
await nextTick();
const listbox = component.findComponent(UListbox);
await listbox.vm.$emit("search-change", "test query");
expect(component.emitted("search-change")).toBeTruthy();
expect(component.emitted("search-change")![0][0]).toBe("test query");
});
it("Update:search – emits update:search event when search value updates", async () => {
const component = mount(UDropdown, {
props: {
searchable: true,
options: defaultOptions,
},
slots: {
default: `<button>Open</button>`,
},
});
await component.find("button").trigger("click");
await nextTick();
const listbox = component.findComponent(UListbox);
await listbox.vm.$emit("update:search", "new search");
expect(component.emitted("update:search")).toBeTruthy();
expect(component.emitted("update:search")![0][0]).toBe("new search");
});
});
describe("Exposed methods", () => {
it("Toggle – exposes toggle method", () => {
const component = mount(UDropdown, {
props: {
options: defaultOptions,
},
slots: {
default: `<button>Open</button>`,
},
});
expect(component.vm.toggle).toBeDefined();
expect(typeof component.vm.toggle).toBe("function");
});
it("Hide – exposes hide method", () => {
const component = mount(UDropdown, {
props: {
options: defaultOptions,
},
slots: {
default: `<button>Open</button>`,
},
});
expect(component.vm.hide).toBeDefined();
expect(typeof component.vm.hide).toBe("function");
});
it("WrapperRef – exposes wrapperRef", () => {
const component = mount(UDropdown, {
props: {
options: defaultOptions,
},
slots: {
default: `<button>Open</button>`,
},
});
expect(component.vm.wrapperRef).toBeDefined();
});
it("IsOpened – exposes isOpened property", async () => {
const component = mount(UDropdown, {
props: {
options: defaultOptions,
},
slots: {
default: `<button>Open</button>`,
},
});
expect(component.vm.isOpened).toBe(false);
await component.find("button").trigger("click");
await nextTick();
expect(component.vm.isOpened).toBe(true);
});
it("SelectedOptions – exposes selectedOptions property", () => {
const component = mount(UDropdown, {
props: {
options: defaultOptions,
modelValue: 2,
},
slots: {
default: `<button>Open</button>`,
},
});
expect(component.vm.selectedOptions).toBeDefined();
expect(component.vm.selectedOptions.length).toBe(1);
expect(component.vm.selectedOptions[0].value).toBe(2);
});
it("DisplayLabel – exposes displayLabel property", () => {
const component = mount(UDropdown, {
props: {
options: defaultOptions,
modelValue: 2,
labelDisplayCount: 1,
},
slots: {
default: `<button>Open</button>`,
},
});
expect(component.vm.displayLabel).toBeDefined();
expect(component.vm.displayLabel).toBe("Option 2");
});
it("FullLabel – exposes fullLabel property", () => {
const component = mount(UDropdown, {
props: {
options: defaultOptions,
modelValue: [1, 2],
multiple: true,
},
slots: {
default: `<button>Open</button>`,
},
});
expect(component.vm.fullLabel).toBeDefined();
expect(component.vm.fullLabel).toBe("Option 1, Option 2");
});
});
});