UNPKG

@oruga-ui/oruga-next

Version:

UI components for Vue.js and CSS framework agnostic

606 lines (496 loc) 24 kB
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); }); }); });