@oruga-ui/oruga-next
Version:
UI components for Vue.js and CSS framework agnostic
769 lines (663 loc) • 30.3 kB
text/typescript
import { afterEach, describe, expect, test } from "vitest";
import { enableAutoUnmount, mount } from "@vue/test-utils";
import { nextTick, type ComponentPublicInstance, type PropType } from "vue";
import OTree from "@/components/tree/Tree.vue";
import OTreeItem from "@/components/tree/TreeItem.vue";
import type { OptionsProp } from "@/composables";
import type { TreeItemProps, TreeOptions } from "../props";
describe("OTree tests", () => {
enableAutoUnmount(afterEach);
const options: TreeOptions<string> = [
{
label: "Documents",
icon: "folder",
options: [
{
label: "Work",
value: "work",
icon: "cog",
options: [
{
label: "Expenses.doc",
value: "expenses",
icon: "file",
},
{
label: "Resume.doc",
value: "resume",
icon: "file",
},
],
},
{
label: "Home",
value: "home",
icon: "home",
options: [
{
label: "Invoices.txt",
value: "invoices",
icon: "file",
},
],
},
],
},
{
label: "Events",
value: "events",
icon: "calendar",
options: [
{
label: "Meeting",
value: "meeting",
icon: "calendar-plus",
},
{
label: "Product Launch",
value: "product-launch",
icon: "calendar-plus",
},
{
label: "Report Review",
value: "report-review",
icon: "calendar-plus",
},
],
},
{
label: "Movies",
value: "movies",
icon: "star",
options: [
{
label: "Al Pacino",
value: "al-pacion",
icon: "star",
options: [
{
label: "Scarface",
value: "scarface",
icon: "video",
},
{
label: "Serpico",
value: "serpico",
icon: "video",
},
],
},
{
label: "Robert De Niro",
value: "robert-de-niro",
icon: "star",
options: [
{
label: "Goodfellas",
value: "goodfellas",
icon: "video",
},
{
label: "Untouchables",
value: "untouchables",
icon: "video",
},
],
},
],
},
];
const componentWrapper = {
components: {
OTree,
OTreeItem,
},
props: {
items: {
type: Array as PropType<TreeOptions<string>>,
required: true,
},
},
template: `
<o-tree v-bind="$attrs">
<o-tree-item v-for="el in items" :key="el.label" v-bind="el" />
</o-tree>
`,
};
test("render correctly", () => {
const wrapper = mount(OTree);
expect(!!wrapper.vm).toBeTruthy();
expect(wrapper.exists()).toBeTruthy();
expect(wrapper.attributes("data-oruga")).toBe("tree");
expect(wrapper.html()).toMatchSnapshot();
expect(wrapper.classes("o-tree")).toBeTruthy();
});
test("render empty list correctly", () => {
const wrapper = mount(OTree, { props: { options: [] } });
expect(wrapper.attributes("data-oruga")).toBe("tree");
expect(wrapper.html()).toMatchSnapshot();
const list = wrapper.find(".o-tree__list");
expect(list.exists()).toBeTruthy();
const items = list.findAll("*");
expect(items.length).toBe(1);
});
test("render correctly with options", () => {
const items = [
{ label: "label1", icon: "user" },
{ label: "label2", icon: "mobile" },
];
const wrapper = mount(componentWrapper, {
props: { items },
});
expect(wrapper.attributes("data-oruga")).toBe("tree");
expect(wrapper.html()).toMatchSnapshot();
const itemComps = wrapper.findAllComponents(OTreeItem);
expect(itemComps.length).toBe(items.length);
items.forEach((value, idx) => {
expect(itemComps[idx].attributes("data-oruga")).toBe("tree-item");
expect(itemComps[idx].text()).toBe(value.label);
expect(itemComps[idx].classes()).toContain("o-tree__item");
});
});
test("render correctly with deep options", () => {
const wrapper = mount(OTree, { props: { options } });
expect(!!wrapper.vm).toBeTruthy();
expect(wrapper.exists()).toBeTruthy();
expect(wrapper.attributes("data-oruga")).toBe("tree");
expect(wrapper.html()).toMatchSnapshot();
expect(wrapper.classes("o-tree")).toBeTruthy();
const items = wrapper.findAllComponents(OTreeItem);
expect(items.length).toBe(17);
items.forEach((value, idx) => {
expect(items[idx].attributes("data-oruga")).toBe("tree-item");
expect(items[idx].classes()).toContain("o-tree__item");
});
});
describe("handle options props correctly", () => {
const options: TreeOptions<string> = [
{ label: "New York", value: "NY" },
{ label: "Rome", value: "RM" },
{ label: "London", value: "LDN" },
{ label: "Istanbul", value: "IST" },
{ label: "Paris", value: "PRS" },
];
test("react accordingly when is using objects values", async () => {
const wrapper = mount(OTree, {
props: { options, selectable: true },
});
const items = wrapper.findAll('[data-oruga="tree-item"]');
expect(items.length).toBe(options.length);
items.forEach((item, index) => {
expect(item.text()).toEqual(options[index].label);
});
const itemLabel = items[1].find(".o-tree__item-label");
expect(itemLabel.exists()).toBeTruthy();
await itemLabel.trigger("click");
expect(items[0].classes("o-tree__item--selected")).toBeFalsy();
expect(items[1].classes("o-tree__item--selected")).toBeTruthy();
expect(items[2].classes("o-tree__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(OTree, { props: { options } });
const items = wrapper.findAll('[data-oruga="tree-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(OTree, { props: { options } });
const items = wrapper.findAll('[data-oruga="tree-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: TreeOptions<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(OTree, { props: { options } });
const items = wrapper.findAll('[data-oruga="tree-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: TreeOptions<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(OTree, { props: { options } });
const items = wrapper.findAll('[data-oruga="tree-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) {
const label = el.find(".o-tree__item-label");
expect(label.exists()).toBeTruthy();
expect(label.text()).toBe(option.label);
expect(el.attributes("aria-disabled")).toBe(
option.disabled ? "true" : "false",
);
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 TreeItemProps<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 collapsable", () => {
test("react accordingly when tree is not collapsable", async () => {
const wrapper = mount(OTree, {
props: { options, collapsable: false },
});
const items =
wrapper.findAllComponents<ComponentPublicInstance>(OTreeItem);
expect(items.length).toBe(17);
const itemOne = items[0]; // Documents item
expect(itemOne.find(".o-tree__item-label").text()).toBe(
"Documents",
);
const itemTwo = items[6]; // Events item
expect(itemTwo.find(".o-tree__item-label").text()).toBe("Events");
const itemThree = items[10]; // Movies item
expect(itemThree.find(".o-tree__item-label").text()).toBe("Movies");
await itemOne.trigger("click");
expect(itemOne.emitted("open")).toBeUndefined();
expect(itemOne.emitted("close")).toBeUndefined();
await itemTwo.trigger("click");
expect(itemTwo.emitted("open")).toBeUndefined();
expect(itemTwo.emitted("close")).toBeUndefined();
await itemThree.trigger("click");
expect(itemThree.emitted("open")).toBeUndefined();
expect(itemThree.emitted("close")).toBeUndefined();
});
test("react accordingly when tree is collapsable and has no toggle icon", async () => {
const wrapper = mount(OTree, {
props: { options, collapsable: true, toggleIcon: "" },
});
await nextTick(); // await child component rendering
const items =
wrapper.findAllComponents<ComponentPublicInstance>(OTreeItem);
expect(items.length).toBe(17);
// Documents item
const itemOne = items[0];
const itemOneLabel = itemOne.find(".o-tree__item-label");
expect(itemOneLabel.exists()).toBeTruthy();
const itemOneToggle = itemOneLabel.find(
".o-tree__item-toggle-icon",
);
expect(itemOneToggle.exists()).toBeFalsy();
expect(itemOneLabel.text()).toBe("Documents");
// Events item
const itemTwo = items[6];
const itemTwoLabel = itemTwo.find(".o-tree__item-label");
expect(itemTwoLabel.exists()).toBeTruthy();
const itemTwoToggle = itemTwoLabel.find(
".o-tree__item-toggle-icon",
);
expect(itemTwoToggle.exists()).toBeFalsy();
expect(itemTwoLabel.text()).toBe("Events");
// Movies item
const itemThree = items[10];
const itemThreeLabel = itemThree.find(".o-tree__item-label");
expect(itemThreeLabel.exists()).toBeTruthy();
const itemThreeToggle = itemThreeLabel.find(
".o-tree__item-toggle-icon",
);
expect(itemThreeToggle.exists()).toBeFalsy();
expect(itemThreeLabel.text()).toBe("Movies");
// open tree sections
await itemOneLabel.trigger("click");
expect(itemOne.emitted("open")).toBeDefined();
expect(itemOne.emitted("close")).toBeUndefined();
await itemTwoLabel.trigger("click");
expect(itemTwo.emitted("open")).toBeDefined();
expect(itemTwo.emitted("close")).toBeUndefined();
await itemThreeLabel.trigger("click");
expect(itemThree.emitted("open")).toBeDefined();
expect(itemThree.emitted("close")).toBeUndefined();
// close all tree sections
await itemOneLabel.trigger("click");
expect(itemOne.emitted("open")).toBeDefined();
expect(itemOne.emitted("close")).toBeDefined();
await itemTwoLabel.trigger("click");
expect(itemTwo.emitted("open")).toBeDefined();
expect(itemTwo.emitted("close")).toBeDefined();
await itemThreeLabel.trigger("click");
expect(itemThree.emitted("open")).toBeDefined();
expect(itemThree.emitted("close")).toBeDefined();
});
test("react accordingly when tree is collapsable and has toggle icon", async () => {
const wrapper = mount(OTree, {
props: {
options,
collapsable: true,
toggleIcon: "chevron-left",
},
});
await nextTick(); // await child component rendering
const items =
wrapper.findAllComponents<ComponentPublicInstance>(OTreeItem);
expect(items.length).toBe(17);
// Documents item
const itemOne = items[0];
const itemOneLabel = itemOne.find(".o-tree__item-label");
expect(itemOneLabel.exists()).toBeTruthy();
const itemOneToggle = itemOneLabel.find(
".o-tree__item-toggle-icon",
);
expect(itemOneToggle.exists()).toBeTruthy();
expect(itemOneLabel.text()).toBe("Documents");
// Events item
const itemTwo = items[6];
const itemTwoLabel = itemTwo.find(".o-tree__item-label");
expect(itemTwoLabel.exists()).toBeTruthy();
const itemTwoToggle = itemTwoLabel.find(
".o-tree__item-toggle-icon",
);
expect(itemTwoToggle.exists()).toBeTruthy();
expect(itemTwoLabel.text()).toBe("Events");
// Movies item
const itemThree = items[10];
const itemThreeLabel = itemThree.find(".o-tree__item-label");
expect(itemThreeLabel.exists()).toBeTruthy();
const itemThreeToggle = itemThreeLabel.find(
".o-tree__item-toggle-icon",
);
expect(itemThreeToggle.exists()).toBeTruthy();
expect(itemThreeLabel.text()).toBe("Movies");
// open tree sections
await itemOneToggle.trigger("click");
expect(itemOne.emitted("open")).toBeDefined();
expect(itemOne.emitted("close")).toBeUndefined();
await itemTwoToggle.trigger("click");
expect(itemTwo.emitted("open")).toBeDefined();
expect(itemTwo.emitted("close")).toBeUndefined();
await itemThreeToggle.trigger("click");
expect(itemThree.emitted("open")).toBeDefined();
expect(itemThree.emitted("close")).toBeUndefined();
// close all tree sections
await itemOneToggle.trigger("click");
expect(itemOne.emitted("open")).toBeDefined();
expect(itemOne.emitted("close")).toBeDefined();
await itemTwoToggle.trigger("click");
expect(itemTwo.emitted("open")).toBeDefined();
expect(itemTwo.emitted("close")).toBeDefined();
await itemThreeToggle.trigger("click");
expect(itemThree.emitted("open")).toBeDefined();
expect(itemThree.emitted("close")).toBeDefined();
});
});
describe("test selectable", () => {
const options: TreeOptions<string> = [
{ label: "New York", value: "NY" },
{ label: "Rome", value: "RM" },
{ label: "London", value: "LDN" },
{ label: "Istanbul", value: "IST" },
{ label: "Paris", value: "PRS" },
];
test("react accordingly when new item is selected", async () => {
const wrapper = mount(OTree, {
props: {
options,
selectable: true,
modelValue: options[0].value,
},
});
expect(wrapper.classes("o-tree--selectable")).toBeTruthy();
const items = wrapper.findAll(".o-tree__item");
expect(items.length).toBe(options.length);
expect(items[0].classes("o-tree__item--selected")).toBeTruthy();
expect(items[1].classes("o-tree__item--selected")).toBeFalsy();
expect(items[2].classes("o-tree__item--selected")).toBeFalsy();
const itemLabel = items[2].find(".o-tree__item-label");
expect(itemLabel.exists()).toBeTruthy();
await itemLabel.trigger("click");
expect(items[0].classes("o-tree__item--selected")).toBeFalsy();
expect(items[1].classes("o-tree__item--selected")).toBeFalsy();
expect(items[2].classes("o-tree__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 item is clicked without selectable", async () => {
const wrapper = mount(OTree, {
props: {
options,
selectable: false,
},
});
const itemComps =
wrapper.findAllComponents<ComponentPublicInstance>(
'[data-oruga="tree-item"]',
);
expect(itemComps.length).toBe(options.length);
const treeItems = wrapper.findAll('[role="treeitem"]');
expect(treeItems.length).toBe(options.length);
await treeItems[0].trigger("click");
await treeItems[1].trigger("click");
expect(wrapper.emitted("update:modelValue")).toBeUndefined();
});
test("react accordingly when same item is deselected", async () => {
const wrapper = mount(OTree, {
props: {
options,
modelValue: options[0].value,
selectable: true,
},
});
const items = wrapper.findAll(".o-tree__item");
expect(items.length).toBe(options.length);
expect(items[0].classes("o-tree__item--selected")).toBeTruthy();
expect(items[1].classes("o-tree__item--selected")).toBeFalsy();
expect(items[2].classes("o-tree__item--selected")).toBeFalsy();
const itemLabel = items[0].find(".o-tree__item-label");
expect(itemLabel.exists()).toBeTruthy();
await itemLabel.trigger("click");
expect(items[0].classes("o-tree__item--selected")).toBeFalsy();
expect(items[1].classes("o-tree__item--selected")).toBeFalsy();
expect(items[2].classes("o-tree__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(OTree, {
props: {
options,
selectable: true,
multiple: true,
},
});
expect(wrapper.classes("o-tree--multiple")).toBeTruthy();
const items = wrapper.findAll(".o-tree__item");
expect(items.length).toBe(options.length);
expect(items[0].classes("o-tree__item--selected")).toBeFalsy();
expect(items[1].classes("o-tree__item--selected")).toBeFalsy();
expect(items[2].classes("o-tree__item--selected")).toBeFalsy();
let itemLabel = items[0].find(".o-tree__item-label");
expect(itemLabel.exists()).toBeTruthy();
await itemLabel.trigger("click");
expect(items[0].classes("o-tree__item--selected")).toBeTruthy();
expect(items[1].classes("o-tree__item--selected")).toBeFalsy();
expect(items[2].classes("o-tree__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,
);
itemLabel = items[2].find(".o-tree__item-label");
expect(itemLabel.exists()).toBeTruthy();
await itemLabel.trigger("click");
expect(items[0].classes("o-tree__item--selected")).toBeTruthy();
expect(items[1].classes("o-tree__item--selected")).toBeFalsy();
expect(items[2].classes("o-tree__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,
);
itemLabel = items[0].find(".o-tree__item-label");
expect(itemLabel.exists()).toBeTruthy();
await itemLabel.trigger("click");
expect(items[0].classes("o-tree__item--selected")).toBeFalsy();
expect(items[1].classes("o-tree__item--selected")).toBeFalsy();
expect(items[2].classes("o-tree__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 tree is disabled", async () => {
const wrapper = mount(OTree, {
props: { options: options, disabled: true },
});
expect(wrapper.classes("o-tree--disabled")).toBeTruthy();
const items = wrapper.findAll(".o-tree__item");
expect(items.length).toBe(options.length);
items.forEach((item) => {
expect(item.classes("o-tree__item--selected")).toBeFalsy();
});
await items[0].trigger("click");
items.forEach((item) => {
expect(item.classes("o-tree__item--selected")).toBeFalsy();
});
expect(wrapper.emitted("update:modelValue")).toBeUndefined();
expect(wrapper.emitted("select")).toBeUndefined();
expect(wrapper.classes("o-tree--disabled")).toBeTruthy();
});
test("react accordingly when item has disabled prop", () => {
const items: TreeOptions<string> = [
{ label: "label1", disabled: true },
{ label: "label2", disabled: false },
];
const wrapper = mount(componentWrapper, { props: { items } });
const itemComps =
wrapper.findAllComponents<ComponentPublicInstance>(OTreeItem);
expect(itemComps.length).toBe(items.length);
const treeItems = wrapper.findAll('[role="treeitem"]');
expect(treeItems.length).toBe(items.length);
expect(treeItems[0].attributes("aria-disabled")).toBe("true");
expect(itemComps[0].classes()).toContain("o-tree__item--disabled");
expect(itemComps[1].attributes("aria-disabled")).toBe("false");
});
test("react accordingly when selected with keydown", async () => {
const wrapper = mount(OTree, {
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],
]);
});
});
});