import { describe, test, expect, vi, afterEach } from "vitest";
import { enableAutoUnmount, mount } from "@vue/test-utils";
import { nextTick, type ComponentPublicInstance } from "vue";
import { setTimeout } from "timers/promises";

import type { OptionsProp } from "@/composables";
import type { DropdownItemProps, DropdownOptions } from "../props";

import ODropdown from "@/components/dropdown/Dropdown.vue";
import ODropdownItem from "@/components/dropdown/DropdownItem.vue";

describe("ODropdown tests", () => {
    const options: DropdownOptions<number> = [
        { label: "Item 1", value: 1 },
        { label: "Item 2", value: 2 },
        { label: "Item 3", value: 3 },
    ];
    const simpleOptions: OptionsProp = ["A", "B", "C"];

    enableAutoUnmount(afterEach);

    test("render correctly with options", async () => {
        const wrapper = mount(ODropdown, {
            props: { options, label: "Some Trigger Label" },
        });
        await nextTick(); // await dropdown items rendered

        expect(!!wrapper.vm).toBeTruthy();
        expect(wrapper.exists()).toBeTruthy();
        expect(wrapper.attributes("data-oruga")).toBe("dropdown");
        expect(wrapper.html()).toMatchSnapshot();
        expect(wrapper.classes("o-dropdown")).toBeTruthy();

        const items = wrapper.findAllComponents(ODropdownItem);
        expect(items.length).toBe(options.length);
        options.forEach((option, idx) => {
            expect(items[idx].attributes("data-oruga")).toBe("dropdown-item");
            expect(items[idx].classes("o-dropdown__item")).toBeTruthy();
            expect(items[idx].text()).toBe(option.label);
        });
    });

    test("render correctly with items", async () => {
        const component = {
            components: { ODropdown, ODropdownItem },
            props: ["options"],
            template: `<o-dropdown>
                <template #trigger="{ active }">
                    <button :class="{ active }">Component Trigger Label</button>
                </template>

                <o-dropdown-item
                    v-for="el in options"
                    :key=" el"
                    :value="el">
                    {{ el }}
                </o-dropdown-item>
            </o-dropdown>`,
        };

        const wrapper = mount(component, {
            props: { options: simpleOptions, selectable: true },
        });
        await nextTick(); // await dropdown items rendered

        expect(!!wrapper.vm).toBeTruthy();
        expect(wrapper.exists()).toBeTruthy();
        expect(wrapper.attributes("data-oruga")).toBe("dropdown");
        expect(wrapper.html()).toMatchSnapshot();
        expect(wrapper.classes("o-dropdown")).toBeTruthy();

        const items = wrapper.findAllComponents(ODropdownItem);
        expect(items.length).toBe(options.length);
        simpleOptions.forEach((option, idx) => {
            expect(items[idx].attributes("data-oruga")).toBe("dropdown-item");
            expect(items[idx].classes("o-dropdown__item")).toBeTruthy();
            expect(items[idx].text()).toBe(option);
        });
    });

    test("has configurable menu tag", () => {
        const wrapper = mount(ODropdown, { props: { menuTag: "ul" } });
        expect(wrapper.find("ul.o-dropdown__menu").exists()).toBeTruthy();
    });

    test("has configurable trigger tag", () => {
        const wrapper = mount(ODropdown, { props: { triggerTag: "a" } });
        expect(wrapper.find("a.o-dropdown__trigger").exists()).toBeTruthy();
    });

    test("reset events before destroy", async () => {
        const documentDummyListener = vi.fn();
        const windowDummyListener = vi.fn();
        document.removeEventListener = documentDummyListener;
        window.removeEventListener = windowDummyListener;

        const wrapper = mount(ODropdown, { props: { active: true } });
        await setTimeout(); // await event handler get set

        wrapper.unmount();

        expect(documentDummyListener).toHaveBeenCalledTimes(1);
        // remove scroll listener
        expect(documentDummyListener).toHaveBeenCalledWith(
            "scroll",
            expect.any(Function),
        );

        expect(windowDummyListener).toHaveBeenCalledTimes(2);
        // remove position listener
        expect(windowDummyListener).toHaveBeenCalledWith(
            "resize",
            expect.any(Function),
        );
        // remove click outside listener
        expect(windowDummyListener).toHaveBeenCalledWith(
            "click",
            expect.any(Function),
            expect.any(Object),
        );
    });

    describe("test trigger", () => {
        test("render trigger slot correctly", () => {
            const triggerHTML = '<button class="trigger">trigger</button>';
            const wrapper = mount(ODropdown, {
                props: { options: simpleOptions },
                slots: {
                    trigger: triggerHTML,
                },
            });
            const trigger = wrapper.find(".trigger");
            expect(trigger.html()).toBe(triggerHTML);
            expect(trigger.text()).toBe("trigger");
        });

        test("render trigger label correctly", () => {
            const triggerLabel = "MyTriggerLabel";
            const wrapper = mount(ODropdown, {
                props: { options: simpleOptions, label: triggerLabel },
            });
            const trigger = wrapper.find(".o-dropdown__trigger");
            expect(trigger.exists()).toBeTruthy();
            expect(trigger.text()).contain(triggerLabel);
        });
    });

    describe("test interactions", () => {
        test("react accordingly when clicking item", async () => {
            const wrapper = mount(ODropdown, {
                props: { options: simpleOptions, active: true },
                attachTo: document.body,
            });
            await nextTick(); // await event handler get set

            const menu = wrapper.find(".o-dropdown__menu");

            expect(wrapper.classes("o-dropdown--active")).toBeTruthy();
            expect(menu.isVisible()).toBeTruthy();

            const items = wrapper.findAll(".o-dropdown__item");
            expect(items.length).toBe(simpleOptions.length);

            await items[1].trigger("click");

            const dropdown = wrapper.findComponent<ComponentPublicInstance>(
                '[data-oruga="dropdown"]',
            );
            expect(dropdown.emitted("select")).toHaveLength(1);
            expect(dropdown.emitted("select")?.[0][0]).toBe(simpleOptions[1]);
            expect(dropdown.emitted("close")).toHaveLength(1);

            expect(wrapper.classes("o-dropdown--active")).toBeFalsy();
            expect(menu.isVisible()).toBeFalsy();
        });

        test("react accordingly when clicking outside", async () => {
            const wrapper = mount(ODropdown, {
                props: { active: true },
                attachTo: document.body,
            });
            await setTimeout(); // await event handler get set

            const menu = wrapper.find(".o-dropdown__menu");

            expect(wrapper.classes("o-dropdown--active")).toBeTruthy();
            expect(menu.isVisible()).toBeTruthy();

            // click outside
            window.dispatchEvent(new Event("click"));
            await nextTick(); // await dom update

            // check dropdown closed
            const activeEmits = wrapper.emitted("update:active");
            expect(activeEmits).toHaveLength(1);
            expect(activeEmits?.[0][0]).toBeFalsy();
            expect(wrapper.emitted("close")).toHaveLength(1);
            expect(wrapper.classes("o-dropdown--active")).toBeFalsy();
            expect(menu.isVisible()).toBeFalsy();
        });

        test("react accordingly when clicking outside with inline", async () => {
            const wrapper = mount(ODropdown, {
                props: { active: true, inline: true },
            });
            await setTimeout(); // await event handler get set

            const menu = wrapper.find(".o-dropdown__menu");

            expect(wrapper.classes("o-dropdown--active")).toBeTruthy();
            expect(menu.isVisible()).toBeTruthy();

            // click outside
            window.dispatchEvent(new Event("click"));
            await nextTick(); // await dom update

            // check dropdown closed
            expect(wrapper.emitted("update:active")).toBeUndefined();
            expect(wrapper.emitted("close")).toBeUndefined();
            expect(wrapper.classes("o-dropdown--active")).toBeTruthy();
            expect(menu.isVisible()).toBeTruthy();
        });

        test("react accordingly when clicking outside with closeable false", async () => {
            const wrapper = mount(ODropdown, {
                props: { active: true, closeOnOutside: false },
                attachTo: document.body,
            });
            await setTimeout(); // await event handler get set

            const menu = wrapper.find(".o-dropdown__menu");

            expect(wrapper.classes("o-dropdown--active")).toBeTruthy();
            expect(menu.isVisible()).toBeTruthy();

            // click outside
            window.dispatchEvent(new Event("click"));
            await nextTick(); // await dom update

            // check dropdown closed
            expect(wrapper.emitted("update:active")).toBeUndefined();
            expect(wrapper.emitted("close")).toBeUndefined();
            expect(wrapper.classes("o-dropdown--active")).toBeTruthy();
            expect(menu.isVisible()).toBeTruthy();
        });

        test("react accordingly when clicking trigger", async () => {
            const wrapper = mount(ODropdown, {
                props: { options: simpleOptions },
                attachTo: document.body,
            });
            await setTimeout(); // await event handler get set

            const menu = wrapper.find(".o-dropdown__menu");
            const trigger = wrapper.find(".o-dropdown__trigger");

            expect(wrapper.classes("o-dropdown--active")).toBeFalsy();
            expect(menu.isVisible()).toBeFalsy();

            // open on trigger click
            await trigger.trigger("click");
            expect(wrapper.classes("o-dropdown--active")).toBeTruthy();
            expect(menu.isVisible()).toBeTruthy();

            // close on trigger click
            await trigger.trigger("click");
            expect(wrapper.classes("o-dropdown--active")).toBeFalsy();
            expect(menu.isVisible()).toBeFalsy();
        });

        test("react accordingly when clicking trigger with disabled", async () => {
            const wrapper = mount(ODropdown, {
                props: { options: simpleOptions, disabled: true },
                attachTo: document.body,
            });
            const menu = wrapper.find(".o-dropdown__menu");
            const trigger = wrapper.find(".o-dropdown__trigger");

            expect(wrapper.classes("o-dropdown--active")).toBeFalsy();
            expect(menu.isVisible()).toBeFalsy();

            await trigger.trigger("click");

            expect(wrapper.classes("o-dropdown--active")).toBeFalsy();
            expect(menu.isVisible()).toBeFalsy();
        });

        test("react accordingly when pressing escape", async () => {
            const wrapper = mount(ODropdown, {
                props: { active: true },
                attachTo: document.body,
            });
            await setTimeout(); // await event handler get set

            expect(wrapper.classes("o-dropdown--active")).toBeTruthy();

            const trigger = wrapper.find(".o-dropdown__trigger");
            expect(trigger.exists()).toBeTruthy();
            const menu = wrapper.find(".o-dropdown__menu");
            expect(menu.exists()).toBeTruthy();
            expect(menu.isVisible()).toBeTruthy();

            // emit escape clicked event
            await trigger.trigger("keydown", { key: "Escape" });
            await nextTick(); // await dom update

            expect(wrapper.classes("o-dropdown--active")).toBeFalsy();
            expect(menu.isVisible()).toBeFalsy();
        });

        test("react accordingly when pressing escape with closeable", async () => {
            const wrapper = mount(ODropdown, {
                props: { active: true, closeable: false },
            });
            await setTimeout(); // await event handler get set

            const menu = wrapper.find(".o-dropdown__menu");

            expect(wrapper.classes("o-dropdown--active")).toBeTruthy();
            expect(menu.isVisible()).toBeTruthy();

            document.dispatchEvent(
                new KeyboardEvent("keyup", { key: "Escape" }),
            );
            await nextTick(); // await dom update

            expect(wrapper.classes("o-dropdown--active")).toBeTruthy();
            expect(menu.isVisible()).toBeTruthy();
        });

        test("react accordingly when mouse over without trigger", async () => {
            const wrapper = mount(ODropdown, {
                props: { openOnClick: true },
                attachTo: document.body,
            });

            const trigger = wrapper.find(".o-dropdown__trigger");
            await trigger.trigger("mouseenter");
            expect(wrapper.find(".o-dropdown__menu").isVisible()).toBeFalsy();

            await trigger.trigger("click");
            expect(wrapper.find(".o-dropdown__menu").isVisible()).toBeTruthy();
        });

        test("react accordingly when mouse over with trigger", async () => {
            const wrapper = mount(ODropdown, {
                props: { openOnHover: true },
                attachTo: document.body,
            });
            const trigger = wrapper.find(".o-dropdown__trigger");
            await trigger.trigger("mouseenter");
            expect(wrapper.find(".o-dropdown__menu").isVisible()).toBeTruthy();
        });

        test("react accordingly when having cotextmenu trigger", async () => {
            const wrapper = mount(ODropdown, {
                props: { openOnContextmenu: true },
                attachTo: document.body,
            });
            const trigger = wrapper.find(".o-dropdown__trigger");
            await trigger.trigger("contextmenu");
            expect(wrapper.find(".o-dropdown__menu").isVisible()).toBeTruthy();
        });

        test("react accordingly when page scolling", async () => {
            const wrapper = mount(ODropdown, {
                props: { active: true, closeOnScroll: true },
                attachTo: document.body,
            });
            await setTimeout(); // await event handler get set

            const menu = wrapper.find(".o-dropdown__menu");

            expect(wrapper.classes("o-dropdown--active")).toBeTruthy();
            expect(menu.isVisible()).toBeTruthy();

            // do scroll
            window.dispatchEvent(
                new CustomEvent("scroll", { detail: "anything" }),
            );
            await nextTick(); // await dom update

            // check dropdown closed
            const activeEmits = wrapper.emitted("update:active");
            expect(activeEmits).toHaveLength(1);
            expect(activeEmits?.[0][0]).toBeFalsy();
            expect(wrapper.emitted("close")).toHaveLength(1);
            expect(wrapper.classes("o-dropdown--active")).toBeFalsy();
            expect(menu.isVisible()).toBeFalsy();
        });
    });

    describe("test teleport", () => {
        test("react accordingly when using teleport to body", () => {
            const wrapper = mount(ODropdown, { props: { teleport: true } });

            expect(wrapper.find(".o-dropdown__menu").exists()).toBeFalsy();

            const menu = document.getElementsByClassName("o-dropdown__menu");
            expect(menu.length).toBe(1);
            const teleportWrapper = document.getElementsByClassName(
                "o-dropdown--teleport",
            );
            expect(teleportWrapper.length).toBe(1);
        });

        test("react accordingly when using teleport with element", () => {
            const wrapperDiv = document.createElement("div");
            const wrapperClass = "test-teleport-wrapper";
            wrapperDiv.className = wrapperClass;
            document.body.appendChild(wrapperDiv);

            const wrapper = mount(ODropdown, {
                props: { teleport: wrapperDiv },
            });

            expect(wrapper.find(".o-dropdown__menu").exists()).toBeFalsy();

            const menu = document.getElementsByClassName("o-dropdown__menu");
            expect(menu.length).toBe(1);
            const teleportWrapper = document.getElementsByClassName(
                "o-dropdown--teleport",
            );
            expect(teleportWrapper.length).toBe(1);
        });
    });

    describe("test selectable", () => {
        test("react accordingly when new item is selected", async () => {
            const wrapper = mount(ODropdown, {
                props: {
                    active: true,
                    options,
                    modelValue: options[0].value,
                    selectable: true,
                },
            });
            await nextTick(); // await dropdown item rendered

            const items = wrapper.findAll(".o-dropdown__item");
            expect(items.length).toBe(options.length);
            expect(items[0].classes("o-dropdown__item--active")).toBeTruthy();
            expect(items[1].classes("o-dropdown__item--active")).toBeFalsy();
            expect(items[2].classes("o-dropdown__item--active")).toBeFalsy();

            await items[2].trigger("click");

            expect(items[0].classes("o-dropdown__item--active")).toBeFalsy();
            expect(items[1].classes("o-dropdown__item--active")).toBeFalsy();
            expect(items[2].classes("o-dropdown__item--active")).toBeTruthy();

            const dropdown = wrapper.findComponent<ComponentPublicInstance>(
                '[data-oruga="dropdown"]',
            );
            expect(dropdown.emitted("update:modelValue")).toHaveLength(1);
            expect(dropdown.emitted("select")).toHaveLength(1);
            expect(dropdown.emitted("select")?.[0][0]).toBe(options[2].value);
            expect(dropdown.emitted("close")).toHaveLength(1);
        });

        test("react accordingly when same item is selected", async () => {
            const wrapper = mount(ODropdown, {
                props: {
                    active: true,
                    options,
                    modelValue: options[0].value,
                    selectable: true,
                },
            });
            await nextTick(); // await dropdown item rendered

            const items = wrapper.findAll(".o-dropdown__item");
            expect(items.length).toBe(options.length);
            expect(items[0].classes("o-dropdown__item--active")).toBeTruthy();
            expect(items[1].classes("o-dropdown__item--active")).toBeFalsy();
            expect(items[2].classes("o-dropdown__item--active")).toBeFalsy();

            await items[0].trigger("click");

            expect(items[0].classes("o-dropdown__item--active")).toBeTruthy();
            expect(items[1].classes("o-dropdown__item--active")).toBeFalsy();
            expect(items[2].classes("o-dropdown__item--active")).toBeFalsy();

            const dropdown = wrapper.findComponent<ComponentPublicInstance>(
                '[data-oruga="dropdown"]',
            );
            expect(dropdown.emitted("update:modelValue")).toBeUndefined();
            expect(dropdown.emitted("select")).toHaveLength(1);
            expect(dropdown.emitted("select")?.[0][0]).toBe(options[0].value);
            expect(dropdown.emitted("close")).toHaveLength(1);
        });

        test("react accordingly when an item is selected with multiple prop", async () => {
            const wrapper = mount(ODropdown, {
                props: {
                    active: true,
                    options,
                    selectable: true,
                    multiple: true,
                    keepOpen: true,
                },
                attachTo: document.body,
            });
            await nextTick(); // await dropdown item rendered

            expect(wrapper.classes("o-dropdown--active")).toBeTruthy();

            const menu = wrapper.find(".o-dropdown__menu");
            expect(menu.exists()).toBeTruthy();
            expect(menu.isVisible()).toBeTruthy();

            const items = wrapper.findAll(".o-dropdown__item");
            expect(items.length).toBe(options.length);
            expect(items[0].classes("o-dropdown__item--active")).toBeFalsy();
            expect(items[1].classes("o-dropdown__item--active")).toBeFalsy();
            expect(items[2].classes("o-dropdown__item--active")).toBeFalsy();

            await items[0].trigger("click");

            expect(items[0].classes("o-dropdown__item--active")).toBeTruthy();
            expect(items[1].classes("o-dropdown__item--active")).toBeFalsy();
            expect(items[2].classes("o-dropdown__item--active")).toBeFalsy();
            expect(menu.isVisible()).toBeTruthy();

            const dropdown = wrapper.findComponent<ComponentPublicInstance>(
                '[data-oruga="dropdown"]',
            );
            expect(dropdown.emitted("select")).toHaveLength(1);
            expect(dropdown.emitted("select")?.[0]).toContain(options[0].value);
            expect(dropdown.emitted("update:modelValue")).toHaveLength(1);
            expect(dropdown.emitted("update:modelValue")?.[0][0]).toHaveLength(
                1,
            );
            expect(dropdown.emitted("update:modelValue")?.[0][0]).toContain(
                options[0].value,
            );
            expect(dropdown.emitted("close")).toBeUndefined();

            await items[2].trigger("click");

            expect(items[0].classes("o-dropdown__item--active")).toBeTruthy();
            expect(items[1].classes("o-dropdown__item--active")).toBeFalsy();
            expect(items[2].classes("o-dropdown__item--active")).toBeTruthy();
            expect(menu.isVisible()).toBeTruthy();

            expect(dropdown.emitted("select")).toHaveLength(2);
            expect(dropdown.emitted("select")?.[1]).toContain(options[2].value);
            expect(dropdown.emitted("update:modelValue")).toHaveLength(2);
            expect(dropdown.emitted("update:modelValue")?.[1][0]).toHaveLength(
                2,
            );
            expect(dropdown.emitted("update:modelValue")?.[1][0]).toContain(
                options[0].value,
            );
            expect(dropdown.emitted("update:modelValue")?.[1][0]).toContain(
                options[2].value,
            );
            expect(dropdown.emitted("close")).toBeUndefined();

            await items[0].trigger("click");
            expect(items[0].classes("o-dropdown__item--active")).toBeFalsy();
            expect(items[1].classes("o-dropdown__item--active")).toBeFalsy();
            expect(items[2].classes("o-dropdown__item--active")).toBeTruthy();

            expect(dropdown.emitted("select")).toHaveLength(3);
            expect(dropdown.emitted("select")?.[2]).toContain(options[0].value);
            expect(dropdown.emitted("update:modelValue")).toHaveLength(3);
            expect(dropdown.emitted("update:modelValue")?.[2][0]).toHaveLength(
                1,
            );
            expect(dropdown.emitted("update:modelValue")?.[2][0]).toContain(
                options[2].value,
            );
            expect(dropdown.emitted("close")).toBeUndefined();
        });

        test("react accordingly when item is selected with keepOpen", async () => {
            const wrapper = mount(ODropdown, {
                props: {
                    active: true,
                    options,
                    keepOpen: true,
                    selectable: true,
                },
            });
            await nextTick(); // await dropdown item rendered

            const items = wrapper.findAll(".o-dropdown__item");
            expect(items.length).toBe(options.length);
            expect(items[0].classes("o-dropdown__item--active")).toBeFalsy();
            expect(items[1].classes("o-dropdown__item--active")).toBeFalsy();
            expect(items[2].classes("o-dropdown__item--active")).toBeFalsy();

            await items[0].trigger("click");

            expect(items[0].classes("o-dropdown__item--active")).toBeTruthy();
            expect(items[1].classes("o-dropdown__item--active")).toBeFalsy();
            expect(items[2].classes("o-dropdown__item--active")).toBeFalsy();

            const dropdown = wrapper.findComponent<ComponentPublicInstance>(
                '[data-oruga="dropdown"]',
            );
            expect(dropdown.emitted("update:modelValue")).toHaveLength(1);
            expect(dropdown.emitted("select")).toHaveLength(1);
            expect(dropdown.emitted("close")).toBeUndefined();
        });

        test("react accordingly when component has keepFirst", async () => {
            const wrapper = mount(ODropdown, {
                props: {
                    active: true,
                    options,
                    keepFirst: true,
                    selectable: true,
                },
            });
            await nextTick(); // await dropdown item rendered

            const dropdown = wrapper.find(".o-dropdown__menu");
            expect(dropdown.exists()).toBeTruthy();

            const focusedItem = wrapper.find(".o-dropdown__item--focused");
            expect(focusedItem.exists()).toBeTruthy();
            expect(focusedItem.text()).toContain(options[0].label);
        });

        test("react accordingly when is disabled", async () => {
            const wrapper = mount(ODropdown, {
                props: { options: simpleOptions, disabled: true, active: true },
                attachTo: document.body,
            });
            await nextTick(); // await dropdown item rendered

            expect(wrapper.classes("o-dropdown--disabled")).toBeTruthy();
            expect(wrapper.find(".o-dropdown__menu").isVisible()).toBeFalsy();

            const items = wrapper.findAll(".o-dropdown__item");
            expect(items.length).toBe(simpleOptions.length);
            expect(items[0].classes("o-dropdown__item--active")).toBeFalsy();
            expect(items[1].classes("o-dropdown__item--active")).toBeFalsy();
            expect(items[2].classes("o-dropdown__item--active")).toBeFalsy();

            await items[0].trigger("click");

            expect(items[0].classes("o-dropdown__item--active")).toBeFalsy();
            expect(items[1].classes("o-dropdown__item--active")).toBeFalsy();
            expect(items[2].classes("o-dropdown__item--active")).toBeFalsy();

            const dropdown = wrapper.findComponent<ComponentPublicInstance>(
                '[data-oruga="dropdown"]',
            );
            expect(dropdown.classes("o-dropdown--disabled")).toBeTruthy();
            expect(dropdown.emitted("update:modelValue")).toBeUndefined();
            expect(dropdown.emitted("select")).toBeUndefined();
            expect(dropdown.emitted("close")).toBeUndefined();
        });

        test("react accordingly when selected with keydown", async () => {
            const wrapper = mount(ODropdown, {
                props: {
                    options,
                    selectable: true,
                },
                attachTo: document.body,
            });

            const trigger = wrapper.find(".o-dropdown__trigger");
            expect(trigger.exists()).toBeTruthy();

            // open menu with trigger click
            await trigger.trigger("click");

            let dropdown = wrapper.find(".o-dropdown__menu");

            expect(dropdown.exists()).toBeTruthy();
            expect(dropdown.isVisible()).toBeTruthy();

            await trigger.trigger("keydown", { key: "Down" });
            await trigger.trigger("keydown", { key: "Enter" });

            expect(wrapper.emitted("select")).toStrictEqual([
                [options[0].value],
            ]);
            expect(wrapper.emitted("update:modelValue")).toStrictEqual([
                [options[0].value],
            ]);

            dropdown = wrapper.find(".o-dropdown__menu");

            expect(dropdown.exists()).toBeTruthy();
            expect(dropdown.isVisible()).toBeFalsy();
        });
    });

    describe("handle options props correctly", () => {
        test("react accordingly when is using objects values", async () => {
            const wrapper = mount(ODropdown, {
                props: { active: true, options, selectable: true },
                attachTo: document.body,
            });
            await nextTick(); // await dropdown item rendered

            const items = wrapper.findAll('[data-oruga="dropdown-item"]');
            expect(items.length).toBe(options.length);

            items.forEach((item, index) => {
                expect(item.text()).toEqual(options[index].label);
            });

            const item = items[1];
            await item.trigger("click");
            expect(items[0].classes("o-dropdown__item--active")).toBeFalsy();
            expect(items[1].classes("o-dropdown__item--active")).toBeTruthy();
            expect(items[2].classes("o-dropdown__item--active")).toBeFalsy();

            const dropdown = wrapper.findComponent<ComponentPublicInstance>(
                '[data-oruga="dropdown"]',
            );
            expect(dropdown.emitted("update:modelValue")).toHaveLength(1);
            expect(dropdown.emitted("update:modelValue")?.[0][0]).toStrictEqual(
                options[1].value,
            );
            expect(dropdown.emitted("select")).toHaveLength(1);
            expect(dropdown.emitted("select")?.[0][0]).toStrictEqual(
                options[1].value,
            );
            expect(dropdown.emitted("close")).toHaveLength(1);
        });

        test("handle options as primitves correctly", async () => {
            const options: OptionsProp = ["Flint", "Silver", "Vane", 0, 1, 2];

            const wrapper = mount(ODropdown, { props: { options } });
            await nextTick(); // await dropdown item rendered

            const optionElements = wrapper.findAll(
                '[data-oruga="dropdown-item"]',
            );
            expect(optionElements).toHaveLength(options.length);

            optionElements.forEach((el, idx) => {
                expect(el.text()).toBe(String(options[idx]));
                expect(el.attributes("aria-disabled")).toBe("false");
            });
        });

        test("handle options as object correctly", async () => {
            const options: OptionsProp = {
                flint: "Flint",
                silver: "Silver",
                vane: "Vane",
                0: "Zero",
                1: "One",
                2: "Two",
            };

            const wrapper = mount(ODropdown, { props: { options } });
            await nextTick(); // await dropdown item rendered

            const optionElements = wrapper.findAll(
                '[data-oruga="dropdown-item"]',
            );
            expect(optionElements).toHaveLength(Object.keys(options).length);

            optionElements.forEach((el, idx) => {
                expect(el.text()).toBe(Object.entries(options)[idx][1]);
                expect(el.attributes("aria-disabled")).toBe("false");
            });
        });

        test("handle options as options array correctly", async () => {
            const options: DropdownOptions<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(ODropdown, { props: { options } });
            await nextTick(); // await dropdown item rendered

            const optionElements = wrapper.findAll(
                '[data-oruga="dropdown-item"]',
            );
            expect(optionElements).toHaveLength(options.length);

            optionElements.forEach((el, idx) => {
                expect(el.text()).toBe(options[idx].label);
                expect(el.attributes("aria-disabled")).toBe(
                    options[idx].disabled ? "true" : "false",
                );
            });
        });

        test("handle grouped options correctly", async () => {
            const options: DropdownOptions<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(ODropdown, { props: { options } });
            await nextTick(); // await dropdown item rendered

            const optionElements = wrapper.findAll(
                '[data-oruga="dropdown-item"]',
            );
            expect(optionElements).toHaveLength(15);

            optionElements.forEach((el, idx) => {
                const isGroup = idx % 5 == 0;
                const g_idx = Math.floor(idx / 5);
                const o_idx = (idx % 5) - 1;

                if (isGroup) {
                    const option = options[g_idx];
                    expect(el.text()).toBe(option.label);
                    expect(el.attributes("aria-disabled")).toBe(
                        option.disabled ? "true" : "false",
                    );
                } else {
                    const g_options = options[g_idx].options;

                    let optionLabel;
                    if (idx < 5) {
                        optionLabel = (
                            g_options[o_idx] as DropdownItemProps<number>
                        ).label;
                    } else if (idx < 10) {
                        optionLabel = Object.entries(g_options)[o_idx][1];
                    } else {
                        optionLabel = g_options[o_idx];
                    }

                    expect(el.text()).toBe(optionLabel);
                }
            });
        });
    });
});
