vueless
Version:
Vue Styleless UI Component Library, powered by Tailwind CSS.
768 lines (599 loc) • 22.1 kB
text/typescript
import { mount } from "@vue/test-utils";
import { describe, it, expect } from "vitest";
import UDropdownLink from "../UDropdownLink.vue";
import ULink from "../../ui.button-link/ULink.vue";
import UIcon from "../../ui.image-icon/UIcon.vue";
import UListbox from "../../ui.form-listbox/UListbox.vue";
import type { Props } from "../types";
describe("UDropdownLink.vue", () => {
const defaultOptions = [
{ value: 1, label: "Option 1" },
{ value: 2, label: "Option 2" },
{ value: 3, label: "Option 3" },
];
describe("Props", () => {
it("Label – renders the correct label text", () => {
const label = "Dropdown Link";
const component = mount(UDropdownLink, {
props: {
label,
options: defaultOptions,
},
});
expect(component.findComponent(ULink).props("label")).toBe(label);
});
it("ModelValue – selects the correct option based on modelValue", async () => {
const modelValue = 2;
const component = mount(UDropdownLink, {
props: {
modelValue,
options: defaultOptions,
},
});
// Find the selected option's label
const selectedOption = defaultOptions.find((option) => option.value === modelValue);
expect(component.findComponent(ULink).props("label")).toBe(selectedOption?.label);
});
it("Multiple – handles multiple selections correctly", async () => {
const modelValue = [1, 3];
const component = mount(UDropdownLink, {
props: {
modelValue,
multiple: true,
options: defaultOptions,
},
});
// Find the selected options' labels
const selectedOptions = defaultOptions.filter((option) => modelValue.includes(option.value));
const expectedLabel = selectedOptions.map((option) => option.label).join(", ");
expect(component.findComponent(ULink).props("label")).toBe(expectedLabel);
});
it("LabelDisplayCount – limits displayed labels based on labelDisplayCount", async () => {
const modelValue = [1, 2, 3];
const labelDisplayCount = 1;
// Should show the first label + count of remaining
const expectedLabel = "Option 1, +2";
const component = mount(UDropdownLink, {
props: {
modelValue,
multiple: true,
labelDisplayCount,
options: defaultOptions,
},
});
expect(component.findComponent(ULink).props("label")).toBe(expectedLabel);
});
it("LabelDisplayCount – displays label correctly with single value", async () => {
const modelValue = [1];
const labelDisplayCount = 1;
// Should show only the selected label without +X suffix
const expectedLabel = "Option 1";
const component = mount(UDropdownLink, {
props: {
modelValue,
multiple: true,
labelDisplayCount,
options: defaultOptions,
},
});
expect(component.findComponent(ULink).props("label")).toBe(expectedLabel);
});
it("Color – applies the correct color class", async () => {
const colors = [
"primary",
"secondary",
"error",
"warning",
"success",
"info",
"notice",
"neutral",
"grayscale",
];
colors.forEach((color) => {
const component = mount(UDropdownLink, {
props: {
color: color as Props["color"],
options: defaultOptions,
},
});
expect(component.findComponent(ULink).props("color")).toBe(color);
});
});
it("Size – applies the correct size class", async () => {
const sizes = ["sm", "md", "lg"];
sizes.forEach((size) => {
const component = mount(UDropdownLink, {
props: {
size: size as Props["size"],
options: defaultOptions,
},
});
expect(component.findComponent(ULink).props("size")).toBe(size);
});
});
it("Underlined – applies underlined class when underlined prop is true", () => {
const underlined = true;
const component = mount(UDropdownLink, {
props: {
underlined,
options: defaultOptions,
},
});
expect(component.findComponent(ULink).props("underlined")).toBe(underlined);
});
it("Dashed – applies dashed class when dashed prop is true", () => {
const dashed = true;
const component = mount(UDropdownLink, {
props: {
dashed,
options: defaultOptions,
},
});
expect(component.findComponent(ULink).props("dashed")).toBe(dashed);
});
it("Disabled – applies disabled attribute when disabled prop is true", () => {
const disabled = true;
const component = mount(UDropdownLink, {
props: {
disabled,
options: defaultOptions,
},
});
expect(component.findComponent(ULink).props("disabled")).toBe(disabled);
});
it("ToggleIcon – shows default toggle icon when toggleIcon is true", () => {
const toggleIcon = true;
const component = mount(UDropdownLink, {
props: {
toggleIcon,
options: defaultOptions,
},
});
const iconComponent = component.findComponent(UIcon);
expect(iconComponent.exists()).toBe(true);
expect(iconComponent.props("name")).toBe("keyboard_arrow_down");
});
it("ToggleIcon – hides toggle icon when toggleIcon is false", () => {
const toggleIcon = false;
const component = mount(UDropdownLink, {
props: {
toggleIcon,
options: defaultOptions,
},
});
const iconComponent = component.findComponent(UIcon);
expect(iconComponent.exists()).toBe(false);
});
it("ToggleIcon – shows custom toggle icon when toggleIcon is a string", () => {
const toggleIcon = "custom_icon";
const component = mount(UDropdownLink, {
props: {
toggleIcon,
options: defaultOptions,
},
});
const iconComponent = component.findComponent(UIcon);
expect(iconComponent.exists()).toBe(true);
expect(iconComponent.props("name")).toBe(toggleIcon);
});
it("ID – applies the correct id attribute", () => {
const id = "test-dropdown-id";
const component = mount(UDropdownLink, {
props: {
id,
options: defaultOptions,
},
});
const dropdown = component.findComponent({ name: "UDropdown" });
expect(dropdown.props("id")).toBe(id);
});
it("DataTest – applies the correct data-test attribute", () => {
const dataTest = "test-dropdown";
const component = mount(UDropdownLink, {
props: {
dataTest,
options: defaultOptions,
},
});
expect(component.findComponent(ULink).attributes("data-test")).toBe(dataTest);
});
it("OptionsLimit – passes optionsLimit prop to UListbox component", async () => {
const optionsLimit = 2;
const component = mount(UDropdownLink, {
props: {
optionsLimit,
options: defaultOptions,
},
});
await component.findComponent(ULink).trigger("click");
expect(component.findComponent(UListbox).props("optionsLimit")).toBe(optionsLimit);
});
it("VisibleOptions – passes visibleOptions prop to UListbox component", async () => {
const visibleOptions = 5;
const component = mount(UDropdownLink, {
props: {
visibleOptions,
options: defaultOptions,
},
});
await component.findComponent(ULink).trigger("click");
expect(component.findComponent(UListbox).props("visibleOptions")).toBe(visibleOptions);
});
it("GroupLabelKey – passes groupLabelKey prop to UListbox component", async () => {
const groupLabelKey = "category";
const groupedOptions = [
{ groupLabel: "Group 1", category: "group1" },
{ label: "Option 1", id: "option1", category: "group1" },
{ groupLabel: "Group 2", category: "group2" },
{ label: "Option 2", id: "option2", category: "group2" },
];
const component = mount(UDropdownLink, {
props: {
groupLabelKey,
options: groupedOptions,
},
});
await component.findComponent(ULink).trigger("click");
expect(component.findComponent(UListbox).props("groupLabelKey")).toBe(groupLabelKey);
});
it("Search v-model – passes search to UListbox and filters", async () => {
const component = mount(UDropdownLink, {
props: {
searchable: true,
options: defaultOptions,
search: "Option 3",
},
});
await component.findComponent(ULink).trigger("click");
const listbox = component.getComponent(UListbox);
const options = listbox.findAll("[vl-child-key='option']");
expect(options).toHaveLength(1);
expect(options[0].text()).toBe("Option 3");
});
it("CloseOnSelect – keeps dropdown open when closeOnSelect is false", async () => {
const component = mount(UDropdownLink, {
props: {
options: defaultOptions,
closeOnSelect: false,
},
});
// Open the dropdown
await component.findComponent(ULink).trigger("click");
expect(component.findComponent(UListbox).exists()).toBe(true);
// Find the listbox component
const listbox = component.findComponent(UListbox);
// Simulate selecting an option by emitting update:modelValue from the listbox
listbox.vm.$emit("update:modelValue", 2);
// Dropdown should remain open
expect(component.findComponent(UListbox).exists()).toBe(true);
});
it("XPosition – passes xPosition prop to dropdown", () => {
const component = mount(UDropdownLink, {
props: {
xPosition: "right",
options: defaultOptions,
},
});
const dropdown = component.findComponent({ name: "UDropdown" });
expect(dropdown.props("xPosition")).toBe("right");
});
it("YPosition – passes yPosition prop to dropdown", () => {
const component = mount(UDropdownLink, {
props: {
yPosition: "top",
options: defaultOptions,
},
});
const dropdown = component.findComponent({ name: "UDropdown" });
expect(dropdown.props("yPosition")).toBe("top");
});
it("LabelKey – uses custom label key for options", () => {
const customOptions = [
{ id: 1, name: "First" },
{ id: 2, name: "Second" },
];
const component = mount(UDropdownLink, {
props: {
options: customOptions,
labelKey: "name",
valueKey: "id",
},
});
const dropdown = component.findComponent({ name: "UDropdown" });
expect(dropdown.props("labelKey")).toBe("name");
});
it("ValueKey – uses custom value key for options", () => {
const customOptions = [
{ id: 1, name: "First" },
{ id: 2, name: "Second" },
];
const component = mount(UDropdownLink, {
props: {
options: customOptions,
labelKey: "name",
valueKey: "id",
},
});
const dropdown = component.findComponent({ name: "UDropdown" });
expect(dropdown.props("valueKey")).toBe("id");
});
it("GroupValueKey – passes groupValueKey prop to dropdown", () => {
const component = mount(UDropdownLink, {
props: {
groupValueKey: "items",
options: defaultOptions,
},
});
const dropdown = component.findComponent({ name: "UDropdown" });
expect(dropdown.props("groupValueKey")).toBe("items");
});
it("Searchable – passes searchable prop to dropdown", () => {
const component = mount(UDropdownLink, {
props: {
searchable: true,
options: defaultOptions,
},
});
const dropdown = component.findComponent({ name: "UDropdown" });
expect(dropdown.props("searchable")).toBe(true);
});
});
describe("Slots", () => {
it("Default – renders content from default slot", () => {
const slotContent = "Custom Content";
const label = "Dropdown Link";
const component = mount(UDropdownLink, {
props: {
label,
options: defaultOptions,
},
slots: {
default: slotContent,
},
});
expect(component.text()).toContain(slotContent);
});
it("Left – renders content from left slot", () => {
const label = "Dropdown Link";
const slotText = "Left";
const slotClass = "left-content";
const component = mount(UDropdownLink, {
props: {
label,
options: defaultOptions,
},
slots: {
left: `<span class='${slotClass}'>${slotText}</span>`,
},
});
expect(component.find(`.${slotClass}`).exists()).toBe(true);
expect(component.find(`.${slotClass}`).text()).toBe(slotText);
});
it("Toggle – renders content from toggle slot", () => {
const label = "Dropdown Link";
const slotText = "Toggle";
const slotClass = "toggle-content";
const component = mount(UDropdownLink, {
props: {
label,
options: defaultOptions,
},
slots: {
toggle: `<span class='${slotClass}'>${slotText}</span>`,
},
});
expect(component.find(`.${slotClass}`).exists()).toBe(true);
expect(component.find(`.${slotClass}`).text()).toBe(slotText);
});
it("Before – renders content from before-option slot", async () => {
const label = "Dropdown Link";
const slotText = "Before";
const slotClass = "before-option-content";
const component = mount(UDropdownLink, {
props: {
label,
options: defaultOptions,
},
slots: {
"before-option": `<span class='${slotClass}'>${slotText}</span>`,
},
});
await component.findComponent(ULink).trigger("click");
const listbox = component.findComponent(UListbox);
const beforeOptionSlot = listbox.find(`.${slotClass}`);
expect(beforeOptionSlot.exists()).toBe(true);
expect(beforeOptionSlot.text()).toBe(slotText);
});
it("Option – renders custom content from option slot", async () => {
const label = "Dropdown Link";
const slotClass = "custom-option-content";
const component = mount(UDropdownLink, {
props: {
label,
options: defaultOptions,
},
slots: {
option: `<span class='${slotClass}'>Custom {{ params.option.label }}</span>`,
},
});
await component.findComponent(ULink).trigger("click");
const listbox = component.findComponent(UListbox);
const customOptionSlot = listbox.find(`.${slotClass}`);
expect(customOptionSlot.exists()).toBe(true);
expect(customOptionSlot.text()).toBe("Custom Option 1");
});
it("After – renders content from after-option slot", async () => {
const label = "Dropdown Link";
const slotText = "After";
const slotClass = "after-option-content";
const component = mount(UDropdownLink, {
props: {
label,
options: defaultOptions,
},
slots: {
"after-option": `<span class='${slotClass}'>${slotText}</span>`,
},
});
await component.findComponent(ULink).trigger("click");
const listbox = component.findComponent(UListbox);
const afterOptionSlot = listbox.find(`.${slotClass}`);
expect(afterOptionSlot.exists()).toBe(true);
expect(afterOptionSlot.text()).toBe(slotText);
});
it("Empty – renders custom content from empty slot", async () => {
const label = "Dropdown Link";
const slotContent = "No options available";
const slotClass = "custom-empty";
const component = mount(UDropdownLink, {
props: {
label,
options: [],
},
slots: {
empty: `<span class='${slotClass}'>${slotContent}</span>`,
},
});
await component.findComponent(ULink).trigger("click");
const listbox = component.findComponent(UListbox);
const emptySlot = listbox.find(`.${slotClass}`);
expect(emptySlot.exists()).toBe(true);
expect(emptySlot.text()).toBe(slotContent);
});
});
describe("Events", () => {
it("Click – opens dropdown when link is clicked", async () => {
const component = mount(UDropdownLink, {
props: {
options: defaultOptions,
},
});
// Initially, dropdown should be closed
expect(component.findComponent(UListbox).exists()).toBe(false);
// Click the link
await component.findComponent(ULink).trigger("click");
// Dropdown should be open
expect(component.findComponent(UListbox).exists()).toBe(true);
});
it("update:modelValue – emits update:modelValue event when an option is selected", async () => {
const component = mount(UDropdownLink, {
props: {
options: defaultOptions,
},
});
// Open the dropdown
await component.findComponent(ULink).trigger("click");
// Find the listbox component
const listbox = component.findComponent(UListbox);
// Simulate selecting an option by emitting update:modelValue from the listbox
listbox.vm.$emit("update:modelValue", 2);
// Check if the event was emitted with the correct value
expect(component.emitted("update:modelValue")).toBeTruthy();
expect(component.emitted("update:modelValue")?.[0]).toEqual([2]);
});
it("click-option – emits click-option event when an option is clicked", async () => {
const component = mount(UDropdownLink, {
props: {
options: defaultOptions,
},
});
// Open the dropdown
await component.findComponent(ULink).trigger("click");
// Find the listbox component
const listbox = component.findComponent(UListbox);
// Simulate clicking an option by emitting click-option from the listbox
const option = defaultOptions[1];
listbox.vm.$emit("click-option", option);
// Check if the event was emitted with the correct value
expect(component.emitted("click-option")).toBeTruthy();
expect(component.emitted("click-option")?.[0]).toEqual([option]);
});
it("Close – closes dropdown when clicking outside", async () => {
const component = mount(UDropdownLink, {
props: {
options: defaultOptions,
},
});
// Open the dropdown
await component.findComponent(ULink).trigger("click");
expect(component.findComponent(UListbox).exists()).toBe(true);
// Directly call the hide function
// This is equivalent to what happens when clicking outside
component.vm.hide();
await component.vm.$nextTick();
// Dropdown should be closed
expect(component.findComponent(UListbox).exists()).toBe(false);
});
it("No – does not toggle dropdown when disabled", async () => {
const component = mount(UDropdownLink, {
props: {
disabled: true,
options: defaultOptions,
},
});
// Initially, dropdown should be closed
expect(component.findComponent(UListbox).exists()).toBe(false);
// Click the link
await component.findComponent(ULink).trigger("click");
// Dropdown should still be closed
expect(component.findComponent(UListbox).exists()).toBe(false);
});
it("Open – emits open event when dropdown is opened", async () => {
const component = mount(UDropdownLink, {
props: {
options: defaultOptions,
},
});
await component.findComponent(ULink).trigger("click");
expect(component.emitted("open")).toBeTruthy();
});
it("Close – emits close event when dropdown is closed", async () => {
const component = mount(UDropdownLink, {
props: {
options: defaultOptions,
},
});
await component.findComponent(ULink).trigger("click");
component.vm.hide();
await component.vm.$nextTick();
expect(component.emitted("close")).toBeTruthy();
});
it("SearchChange – emits search-change event when search value changes", async () => {
const component = mount(UDropdownLink, {
props: {
searchable: true,
options: defaultOptions,
},
});
await component.findComponent(ULink).trigger("click");
const dropdown = component.findComponent({ name: "UDropdown" });
dropdown.vm.$emit("search-change", "test query");
expect(component.emitted("search-change")).toBeTruthy();
expect(component.emitted("search-change")?.[0]).toEqual(["test query"]);
});
it("Update:search – emits update:search event when search value updates", async () => {
const component = mount(UDropdownLink, {
props: {
searchable: true,
options: defaultOptions,
},
});
await component.findComponent(ULink).trigger("click");
const dropdown = component.findComponent({ name: "UDropdown" });
dropdown.vm.$emit("update:search", "new search");
expect(component.emitted("update:search")).toBeTruthy();
expect(component.emitted("update:search")?.[0]).toEqual(["new search"]);
});
});
describe("Exposed refs", () => {
it("wrapperRef – exposes wrapperRef", () => {
const component = mount(UDropdownLink, {
props: {
options: defaultOptions,
},
});
expect(component.vm.wrapperRef).toBeDefined();
});
});
});