@oruga-ui/oruga-next
Version:
UI components for Vue.js and CSS framework agnostic
606 lines (496 loc) • 24 kB
text/typescript
import { afterEach, describe, expect, test } from "vitest";
import { enableAutoUnmount, mount } from "@vue/test-utils";
import { nextTick } from "vue";
import { setTimeout } from "timers/promises";
import type { ListboxOptions, ListItemProps } from "../props";
import type { OptionsProp } from "@/composables";
import OListbox from "@/components/listbox/Listbox.vue";
import OListItem from "@/components/listbox/ListItem.vue";
describe("OListbox tests", () => {
enableAutoUnmount(afterEach);
const options: ListboxOptions<string> = [
{ label: "New York", value: "NY" },
{ label: "Rome", value: "RM" },
{ label: "London", value: "LDN" },
{ label: "Istanbul", value: "IST" },
{ label: "Paris", value: "PRS" },
];
test("render correctly with options", () => {
const wrapper = mount(OListbox, {
props: { options },
});
expect(!!wrapper.vm).toBeTruthy();
expect(wrapper.exists()).toBeTruthy();
expect(wrapper.attributes("data-oruga")).toBe("listbox");
expect(wrapper.html()).toMatchSnapshot();
expect(wrapper.classes("o-listbox")).toBeTruthy();
const items = wrapper.findAll('[data-oruga="listbox-item"]');
expect(items).toHaveLength(options.length);
items.forEach((item, idx) => {
expect(item.attributes("data-oruga")).toBe("listbox-item");
expect(item.classes("o-listbox__item")).toBeTruthy();
expect(item.text()).toBe(options[idx].label);
});
});
test("render correctly with items", async () => {
const component = {
components: { OListbox, OListItem },
props: ["options"],
template: `<o-listbox>
<template #trigger="{ active }">
<button :class="{ active }">Component Trigger Label</button>
</template>
<o-list-item
v-for="el in options"
:key=" el"
:value="el.value">
{{ el.label }}
</o-list-item>
</o-listbox>`,
};
const wrapper = mount(component, {
props: { options: options },
});
await nextTick(); // wait for next tick to ensure all components are rendered
expect(!!wrapper.vm).toBeTruthy();
expect(wrapper.exists()).toBeTruthy();
expect(wrapper.attributes("data-oruga")).toBe("listbox");
expect(wrapper.html()).toMatchSnapshot();
expect(wrapper.classes("o-listbox")).toBeTruthy();
const items = wrapper.findAll('[data-oruga="listbox-item"]');
expect(items.length).toBe(options.length);
items.forEach((item, idx) => {
expect(item.attributes("data-oruga")).toBe("listbox-item");
expect(item.classes("o-listbox__item")).toBeTruthy();
expect(item.text()).toBe(options[idx].label);
});
});
test("render header and footer correctly", () => {
const wrapper = mount(OListbox, {
slots: {
header: "<h1 class='header'>SLOT HEADER</h1>",
footer: "<h1 class='footer'>SLOT FOOTER</h1>",
},
});
const header = wrapper.find(".header");
const footer = wrapper.find(".footer");
expect(header.exists()).toBeTruthy();
expect(footer.exists()).toBeTruthy();
});
test("set value correct when two-way-binded", async () => {
const wrapper = mount(OListbox, {
props: {
options,
modelValue: options[2].value,
"onUpdate:modelValue": (modelValue) => {
void wrapper.setProps({ modelValue });
},
},
});
const items = wrapper.findAll('[data-oruga="listbox-item"]');
expect(items).toHaveLength(options.length);
// check default selected value
expect(items[2].attributes("aria-selected")).toBeTruthy();
// chenge selection
await wrapper.setProps({ modelValue: options[0].value });
// check new selected value
expect(items[0].attributes("aria-selected")).toBeTruthy();
});
test("has configurable list tag", () => {
const wrapper = mount(OListbox, { props: { listTag: "ul" } });
expect(wrapper.find("ul.o-listbox__list").exists()).toBeTruthy();
});
describe("handle options props correctly", () => {
test("react accordingly when is using objects values", async () => {
const wrapper = mount(OListbox, {
props: { options, selectable: true },
});
const items = wrapper.findAll('[data-oruga="listbox-item"]');
expect(items.length).toBe(options.length);
items.forEach((item, index) => {
expect(item.text()).toEqual(options[index].label);
});
await items[1].trigger("click");
expect(items[0].classes("o-listbox__item--selected")).toBeFalsy();
expect(items[1].classes("o-listbox__item--selected")).toBeTruthy();
expect(items[2].classes("o-listbox__item--selected")).toBeFalsy();
expect(wrapper.emitted("update:modelValue")).toHaveLength(1);
expect(wrapper.emitted("update:modelValue")?.[0][0]).toStrictEqual(
options[1].value,
);
expect(wrapper.emitted("select")).toHaveLength(1);
expect(wrapper.emitted("select")?.[0][0]).toStrictEqual(
options[1].value,
);
});
test("handle options as primitves correctly", () => {
const options: OptionsProp = ["Flint", "Silver", "Vane", 0, 1, 2];
const wrapper = mount(OListbox, { props: { options } });
const items = wrapper.findAll('[data-oruga="listbox-item"]');
expect(items).toHaveLength(options.length);
items.forEach((el, idx) => {
expect(el.text()).toBe(String(options[idx]));
expect(el.attributes("aria-disabled")).toBe("false");
expect(el.attributes("aria-hidden")).toBe("false");
expect(el.attributes("aria-selected")).toBe("false");
});
});
test("handle options as object correctly", () => {
const options: OptionsProp = {
flint: "Flint",
silver: "Silver",
vane: "Vane",
0: "Zero",
1: "One",
2: "Two",
};
const wrapper = mount(OListbox, { props: { options } });
const items = wrapper.findAll('[data-oruga="listbox-item"]');
expect(items).toHaveLength(Object.keys(options).length);
items.forEach((el, idx) => {
expect(el.text()).toBe(Object.entries(options)[idx][1]);
expect(el.attributes("aria-disabled")).toBe("false");
expect(el.attributes("aria-hidden")).toBe("false");
expect(el.attributes("aria-selected")).toBe("false");
});
});
test("handle options as options array correctly", () => {
const options: ListboxOptions<string | number> = [
{ label: "Flint", value: "flint" },
{ label: "Silver", value: "silver", disabled: true },
{ label: "Vane", value: "vane" },
{ label: "Zero", value: 0 },
{ label: "One", value: 1 },
{ label: "Two", value: 2, disabled: true },
];
const wrapper = mount(OListbox, { props: { options } });
const items = wrapper.findAll('[data-oruga="listbox-item"]');
expect(items).toHaveLength(options.length);
items.forEach((el, idx) => {
expect(el.text()).toBe(options[idx].label);
expect(el.attributes("aria-disabled")).toBe(
options[idx].disabled ? "true" : "false",
);
expect(el.attributes("aria-hidden")).toBe("false");
expect(el.attributes("aria-selected")).toBe("false");
});
});
test("handle grouped options correctly", () => {
const options: ListboxOptions<string | number | object> = [
{
label: "Black Sails",
options: [
{ label: "Flint", value: "flint" },
{ label: "Silver", value: "silver" },
{ label: "Vane", value: "vane" },
{ label: "Billy", value: "billy" },
],
},
{
label: "Breaking Bad",
options: {
heisenberg: "Heisenberg",
jesse: "Jesse",
saul: "Saul",
mike: "Mike",
},
},
{
label: "Game of Thrones",
disabled: true,
options: [
"Tyrion Lannister",
"Jamie Lannister",
"Daenerys Targaryen",
"Jon Snow",
],
},
];
const wrapper = mount(OListbox, { props: { options } });
const items = wrapper.findAll('[data-oruga="listbox-item"]');
expect(items).toHaveLength(15);
items.forEach((el, idx) => {
const isGroup = idx % 5 == 0;
const g_idx = Math.floor(idx / 5);
const o_idx = (idx % 5) - 1;
const option = options[g_idx];
if (isGroup) {
expect(el.text()).toBe(option.label);
expect(el.attributes("aria-disabled")).toBe("true");
expect(el.attributes("aria-hidden")).toBe("false");
expect(el.attributes("aria-selected")).toBe("false");
} else {
const g_options = option.options;
let optionLabel;
if (idx < 5) {
optionLabel =
(g_options[o_idx] as ListItemProps<string>).label ||
g_options[o_idx];
} else if (idx < 10) {
optionLabel = Object.entries(g_options)[o_idx][1];
} else {
optionLabel = g_options[o_idx];
}
expect(el.text()).toBe(optionLabel);
expect(el.attributes("aria-disabled")).toBe("false");
expect(el.attributes("aria-hidden")).toBe("false");
expect(el.attributes("aria-selected")).toBe("false");
}
});
});
});
describe("test selectable", () => {
test("react accordingly when new item is selected", async () => {
const wrapper = mount(OListbox, {
props: {
options,
selectable: true,
modelValue: options[0].value,
},
});
expect(wrapper.classes("o-listbox--selectable")).toBeTruthy();
const items = wrapper.findAll(".o-listbox__item");
expect(items.length).toBe(options.length);
expect(items[0].classes("o-listbox__item--selected")).toBeTruthy();
expect(items[1].classes("o-listbox__item--selected")).toBeFalsy();
expect(items[2].classes("o-listbox__item--selected")).toBeFalsy();
await items[2].trigger("click");
expect(items[0].classes("o-listbox__item--selected")).toBeFalsy();
expect(items[1].classes("o-listbox__item--selected")).toBeFalsy();
expect(items[2].classes("o-listbox__item--selected")).toBeTruthy();
expect(wrapper.emitted("update:modelValue")).toHaveLength(1);
expect(wrapper.emitted("update:modelValue")?.[0][0]).toBe(
options[2].value,
);
expect(wrapper.emitted("select")).toHaveLength(1);
expect(wrapper.emitted("select")?.[0][0]).toBe(options[2].value);
});
test("react accordingly when same item is deselected", async () => {
const wrapper = mount(OListbox, {
props: {
options,
modelValue: options[0].value,
selectable: true,
},
});
const items = wrapper.findAll(".o-listbox__item");
expect(items.length).toBe(options.length);
expect(items[0].classes("o-listbox__item--selected")).toBeTruthy();
expect(items[1].classes("o-listbox__item--selected")).toBeFalsy();
expect(items[2].classes("o-listbox__item--selected")).toBeFalsy();
await items[0].trigger("click");
expect(items[0].classes("o-listbox__item--selected")).toBeFalsy();
expect(items[1].classes("o-listbox__item--selected")).toBeFalsy();
expect(items[2].classes("o-listbox__item--selected")).toBeFalsy();
expect(wrapper.emitted("update:modelValue")).toHaveLength(1);
expect(wrapper.emitted("update:modelValue")?.[0][0]).toBe(
undefined,
);
expect(wrapper.emitted("select")).toBeUndefined();
});
test("react accordingly when an item is selected with multiple prop", async () => {
const wrapper = mount(OListbox, {
props: {
options,
selectable: true,
multiple: true,
},
});
expect(wrapper.classes("o-listbox--multiple")).toBeTruthy();
const items = wrapper.findAll(".o-listbox__item");
expect(items.length).toBe(options.length);
expect(items[0].classes("o-listbox__item--selected")).toBeFalsy();
expect(items[1].classes("o-listbox__item--selected")).toBeFalsy();
expect(items[2].classes("o-listbox__item--selected")).toBeFalsy();
await items[0].trigger("click");
expect(items[0].classes("o-listbox__item--selected")).toBeTruthy();
expect(items[1].classes("o-listbox__item--selected")).toBeFalsy();
expect(items[2].classes("o-listbox__item--selected")).toBeFalsy();
expect(wrapper.emitted("select")).toHaveLength(1);
expect(wrapper.emitted("select")?.[0]).toContain(options[0].value);
expect(wrapper.emitted("update:modelValue")).toHaveLength(1);
expect(wrapper.emitted("update:modelValue")?.[0][0]).toHaveLength(
1,
);
expect(wrapper.emitted("update:modelValue")?.[0][0]).toContain(
options[0].value,
);
await items[2].trigger("click");
expect(items[0].classes("o-listbox__item--selected")).toBeTruthy();
expect(items[1].classes("o-listbox__item--selected")).toBeFalsy();
expect(items[2].classes("o-listbox__item--selected")).toBeTruthy();
expect(wrapper.emitted("select")).toHaveLength(2);
expect(wrapper.emitted("select")?.[1]).toContain(options[2].value);
expect(wrapper.emitted("update:modelValue")).toHaveLength(2);
expect(wrapper.emitted("update:modelValue")?.[1][0]).toHaveLength(
2,
);
expect(wrapper.emitted("update:modelValue")?.[1][0]).toContain(
options[0].value,
);
expect(wrapper.emitted("update:modelValue")?.[1][0]).toContain(
options[2].value,
);
await items[0].trigger("click");
expect(items[0].classes("o-listbox__item--selected")).toBeFalsy();
expect(items[1].classes("o-listbox__item--selected")).toBeFalsy();
expect(items[2].classes("o-listbox__item--selected")).toBeTruthy();
expect(wrapper.emitted("select")).toHaveLength(2);
expect(wrapper.emitted("update:modelValue")).toHaveLength(3);
expect(wrapper.emitted("update:modelValue")?.[2][0]).toHaveLength(
1,
);
expect(wrapper.emitted("update:modelValue")?.[2][0]).not.toContain(
options[0].value,
);
expect(wrapper.emitted("update:modelValue")?.[2][0]).toContain(
options[2].value,
);
});
test("react accordingly when is disabled", async () => {
const wrapper = mount(OListbox, {
props: { options: options, disabled: true },
});
expect(wrapper.classes("o-listbox--disabled")).toBeTruthy();
const items = wrapper.findAll(".o-listbox__item");
expect(items.length).toBe(options.length);
items.forEach((item) => {
expect(item.classes("o-listbox__item--selected")).toBeFalsy();
});
await items[0].trigger("click");
items.forEach((item) => {
expect(item.classes("o-listbox__item--selected")).toBeFalsy();
});
expect(wrapper.emitted("update:modelValue")).toBeUndefined();
expect(wrapper.emitted("select")).toBeUndefined();
expect(wrapper.classes("o-listbox--disabled")).toBeTruthy();
});
test("react accordingly when selected with keydown", async () => {
const wrapper = mount(OListbox, {
props: { options, selectable: true },
});
const list = wrapper.find("ul");
expect(list.exists()).toBeTruthy();
await list.trigger("keydown", { code: "ArrowDown", key: "Down" });
await list.trigger("keydown", { code: "Enter", key: "Enter" });
expect(wrapper.emitted("select")).toStrictEqual([
[options[0].value],
]);
expect(wrapper.emitted("update:modelValue")).toStrictEqual([
[options[0].value],
]);
});
});
describe("test filterable", () => {
test("should have correct custom icon", () => {
const wrapper = mount(OListbox, {
props: {
options,
filterable: true,
filterIcon: "pi pi-discord",
},
});
const input = wrapper.find('[data-oruga="input"]');
expect(input.exists()).toBeTruthy();
const icon = input.find('[data-oruga="icon"] i');
expect(icon.exists()).toBeTruthy();
expect(icon.classes()).toContain("pi-discord");
});
test("should correctly filter", async () => {
const wrapper = mount(OListbox, {
props: { options, filterable: true },
});
const filterInput = wrapper.find('[data-oruga="input"] input');
expect(filterInput.exists()).toBeTruthy();
await filterInput.setValue("is");
await filterInput.trigger("input");
await setTimeout(500); // await event debounce
const items = wrapper.findAll('[data-oruga="listbox-item"]');
expect(items.length).toBe(options.length);
// check the hidden states
expect(items[0].attributes("aria-hidden")).toBe("true");
expect(items[1].attributes("aria-hidden")).toBe("true");
expect(items[2].attributes("aria-hidden")).toBe("true");
expect(items[3].attributes("aria-hidden")).toBe("false");
expect(items[4].attributes("aria-hidden")).toBe("true");
expect(wrapper.emitted("filter")).toBeDefined();
expect(wrapper.emitted("filter")?.[0][0]).contain("is");
});
test("should correctly filter groups", async () => {
const wrapper = mount(OListbox, {
props: {
filterable: true,
options: [
{
label: "Germany",
value: "DE",
options: [
{ label: "Berlin", value: "Berlin" },
{ label: "Frankfurt", value: "Frankfurt" },
{ label: "Hamburg", value: "Hamburg" },
{ label: "Munich", value: "Munich" },
],
},
{
label: "USA",
value: "US",
options: [
{ label: "Chicago", value: "Chicago" },
{ label: "Los Angeles", value: "Los Angeles" },
{ label: "New York", value: "New York" },
{
label: "San Francisco",
value: "San Francisco",
},
],
},
],
},
});
const items = wrapper.findAll('[data-oruga="listbox-item"]');
expect(items).toHaveLength(10);
items.forEach((item) => {
expect(item.attributes("aria-hidden")).toBe("false");
});
expect(items[0].attributes("aria-disabled")).toBe("true");
expect(items[5].attributes("aria-disabled")).toBe("true");
const filterInput = wrapper.find('[data-oruga="input"] input');
expect(filterInput.exists()).toBeTruthy();
await filterInput.setValue("ch");
await filterInput.trigger("input");
await setTimeout(500); // await event debounce
expect(items[0].attributes("aria-disabled")).toBe("true");
expect(items[5].attributes("aria-disabled")).toBe("true");
items.forEach((item, idx) => {
// check only "Chicago" is shown
if (idx === 6)
expect(item.attributes("aria-hidden")).toBe("false");
else expect(item.attributes("aria-hidden")).toBe("true");
});
expect(wrapper.emitted("filter")).toBeDefined();
expect(wrapper.emitted("filter")?.[0][0]).contain("ch");
});
test("do not sort when `backend-filtering` is given", async () => {
const wrapper = mount(OListbox, {
props: { options, filterable: true, backendFiltering: true },
});
const items = wrapper.findAll('[data-oruga="listbox-item"]');
expect(items).toHaveLength(options.length);
items.forEach((item) => {
expect(item.attributes("disabled")).toBeFalsy();
});
const filterInput = wrapper.find('[data-oruga="input"] input');
expect(filterInput.exists()).toBeTruthy();
await filterInput.setValue(options[2].value);
await filterInput.trigger("input");
await setTimeout(500); // await event debounce
// check that there are no items hidden
expect(items).toHaveLength(options.length);
items.forEach((item) => {
expect(item.attributes("disabled")).toBeFalsy();
});
expect(wrapper.emitted("filter")).toBeDefined();
expect(wrapper.emitted("filter")?.[0][0]).contain(options[2].value);
});
});
});