import * as React from 'react';
import Popover from '../Popover';
import { PopoverCore } from '../PopoverCore/PopoverCore';
import { act, createAsyncRendererWithUniDriver, waitFor, } from '../../utils/test-utils/unit';
import { popoverUniDriverFactory } from '../Popover.uni.driver';
import WixDesignSystemProvider from '../../WixDesignSystemProvider';
const renderPopover = (props, content = 'Content') => (React.createElement(Popover, { ...props },
    React.createElement(Popover.Element, null,
        React.createElement("div", null, "Element")),
    React.createElement(Popover.Content, null,
        React.createElement("div", null, content))));
describe('Popover', () => {
    const render = createAsyncRendererWithUniDriver(popoverUniDriverFactory);
    describe('props', () => {
        describe('shown', () => {
            it(`doesn't display popup when shown={false}`, async () => {
                const props = {
                    placement: 'bottom',
                    shown: false,
                };
                const { driver } = await render(renderPopover(props));
                expect(await driver.isTargetElementExists()).toBe(true);
                expect(await driver.isContentElementExists()).toBe(false);
            });
            it(`displays popup when shown={true}`, async () => {
                const props = {
                    placement: 'bottom',
                    shown: true,
                };
                const { driver } = await render(renderPopover(props));
                await waitFor(async () => {
                    expect(await driver.isContentElementExists()).toBe(true);
                });
            });
        });
        describe('onMouseEnter & onMouseLeave', () => {
            it(`calls mouseEnter and mouseLeave callbacks`, async () => {
                const onMouseEnter = vi.fn();
                const onMouseLeave = vi.fn();
                const props = { onMouseEnter, onMouseLeave };
                const { driver } = await render(renderPopover(props));
                await driver.mouseEnter();
                expect(onMouseEnter).toHaveBeenCalled();
                await driver.mouseLeave();
                expect(onMouseLeave).toHaveBeenCalled();
            });
        });
        describe('onClick', () => {
            it(`calls onClick callback`, async () => {
                const onClick = vi.fn();
                const props = { onClick };
                const { driver } = await render(renderPopover(props));
                await driver.click();
                expect(onClick).toHaveBeenCalled();
            });
        });
        describe('onClickOutside', () => {
            it('should be triggered when outside of the popover is clicked', async () => {
                const onClickOutside = vi.fn();
                const props = { onClickOutside, shown: true };
                const { driver } = await render(renderPopover(props));
                await driver.clickOutside();
                await waitFor(async () => expect(onClickOutside).toHaveBeenCalled());
            });
            it('should not trigger onClickOutside when clicking inside with an excluded class', async () => {
                const onClickOutside = vi.fn();
                const props = {
                    onClickOutside,
                    shown: true,
                    excludeClass: 'excludeClass',
                };
                const { driver } = await render(renderPopover(props));
                await driver.click();
                expect(onClickOutside).not.toHaveBeenCalled();
            });
            it('should not trigger onClickOutside when outside of the popover is clicked and the popover is *not* shown', async () => {
                const onClickOutside = vi.fn();
                const props = {
                    onClickOutside,
                    shown: false,
                };
                const { driver } = await render(renderPopover(props));
                await waitFor(async () => {
                    await driver.clickOutside();
                });
                expect(onClickOutside).not.toHaveBeenCalled();
            });
        });
        describe('disableClickOutsideWhenClosed', () => {
            it('should not trigger onClickOutside when outside of the popover is clicked and the popover is *not* shown if disableClickOutsideWhenClosed is *false*', async () => {
                const onClickOutside = vi.fn();
                const props = {
                    shown: false,
                    onClickOutside,
                    disableClickOutsideWhenClosed: false,
                };
                const { driver } = await render(renderPopover(props));
                await driver.clickOutside();
                expect(onClickOutside).toHaveBeenCalled();
            });
        });
        describe('dataHook', () => {
            it('should be found on target element container', async () => {
                const props = {
                    shown: true,
                    appendTo: 'window',
                    dataHook: 'random',
                };
                const { driver } = await render(renderPopover(props));
                await waitFor(async () => {
                    const target = await driver.getTargetElement();
                    expect(target.parentNode.getAttribute('data-hook')).toBe('random');
                });
            });
            it('should construct data-content-hook', async () => {
                const props = {
                    shown: true,
                    appendTo: 'window',
                    dataHook: 'random',
                };
                const { driver } = await render(renderPopover(props));
                await waitFor(async () => {
                    const target = await driver.getTargetElement();
                    expect(target.parentNode.getAttribute('data-content-hook')).toMatch(/popover-content-random-/);
                });
            });
            it('should apply data-content-element on content element', async () => {
                const props = {
                    shown: true,
                    appendTo: 'window',
                    dataHook: 'random',
                };
                const { driver } = await render(renderPopover(props));
                await waitFor(async () => {
                    expect(await driver.isContentElementExists()).toBe(true);
                });
                const content = await driver.getContentElement();
                expect(content.getAttribute('data-content-element')).toMatch(/popover-content-random-/);
            });
            it('should not override portal component data-hook', async () => {
                const props = {
                    shown: true,
                    appendTo: 'window',
                    dataHook: 'random',
                };
                const { driver } = await render(renderPopover(props));
                await waitFor(async () => {
                    expect(await driver.isContentElementExists()).toBe(true);
                });
                const content = await driver.getContentElement();
                expect(content.parentNode.getAttribute('data-hook')).toBe('popover-portal');
            });
        });
        describe('customArrow', () => {
            it('should display a custom arrow element', async () => {
                const customArrow = (placement, arrowProps) => (React.createElement("p", { "data-test": "custom-arrow", "data-placement": placement, ...arrowProps }));
                const props = {
                    shown: true,
                    showArrow: true,
                    customArrow,
                    placement: 'top',
                };
                const { driver, container } = await render(renderPopover(props));
                await waitFor(async () => {
                    expect(await driver.isContentElementExists()).toBe(true);
                });
                const arrowElement = container.querySelector('[data-test="custom-arrow"]');
                expect(!!arrowElement).toBe(true);
            });
        });
        describe('moveArrowTo', () => {
            it(`offsets the popup arrow by specified amount`, async () => {
                const props = {
                    shown: true,
                    showArrow: true,
                    moveArrowTo: 10,
                };
                const { driver } = await render(renderPopover(props));
                await waitFor(async () => {
                    expect((await driver.getArrowOffset()).left).toBe('10px');
                });
            });
        });
        describe('timeout', () => {
            beforeEach(() => {
                vi.useFakeTimers();
            });
            afterEach(() => {
                vi.useRealTimers();
            });
            it(`remove timeout on close [when] given a timeout={0}`, async () => {
                const timeout = { timeout: 0 };
                const props = { shown: true, ...timeout };
                const { driver, rerender } = await render(renderPopover(props));
                await waitFor(async () => {
                    expect(await driver.isContentElementExists()).toBe(true);
                });
                await rerender(renderPopover({ ...props, shown: false }));
                expect(await driver.isContentElementExists()).toBe(false);
            });
            it('adds timeout of 100ms on close [when] given a timeout={100}', async () => {
                const timeout = 100;
                const props = { shown: true, timeout };
                const { driver, rerender } = await render(renderPopover(props));
                await waitFor(async () => {
                    expect(await driver.isContentElementExists()).toBe(true);
                });
                await rerender(renderPopover({ ...props, shown: false }));
                expect(await driver.isContentElementExists()).toBe(true);
                await act(async () => {
                    vi.advanceTimersByTime(timeout);
                });
                expect(await driver.isContentElementExists()).toBe(false);
            });
        });
        describe('hideDelay', () => {
            beforeEach(() => {
                vi.useFakeTimers();
            });
            afterEach(() => {
                vi.useRealTimers();
            });
            it(`should close after hideDelay`, async () => {
                const timeout = 500;
                const props = { shown: true, hideDelay: timeout };
                const { driver, rerender } = await render(renderPopover(props));
                await waitFor(async () => {
                    expect(await driver.isContentElementExists()).toBe(true);
                });
                await rerender(renderPopover({ ...props, shown: false }));
                expect(await driver.isContentElementExists()).toBe(true);
                await act(async () => {
                    vi.advanceTimersByTime(timeout);
                });
                expect(await driver.isContentElementExists()).toBe(false);
            });
            it(`should not update delay until the popover visibility has fully changed`, async () => {
                const timeout = 500;
                const props = { shown: true, hideDelay: timeout };
                const { driver, rerender } = await render(renderPopover(props));
                await waitFor(async () => {
                    expect(await driver.isContentElementExists()).toBe(true);
                });
                await rerender(renderPopover({ ...props, shown: false, hideDelay: 100 }));
                expect(await driver.isContentElementExists()).toBe(true);
                await act(async () => {
                    vi.advanceTimersByTime(timeout);
                });
                expect(await driver.isContentElementExists()).toBe(false);
            });
        });
        describe('showDelay', () => {
            it(`should show the content immediately on first render`, async () => {
                const props = { shown: true, showDelay: 100 };
                const { driver } = await render(renderPopover(props));
                await waitFor(async () => {
                    expect(await driver.isContentElementExists()).toBe(true);
                });
            });
            it(`should open after showDelay`, async () => {
                const timeout = { timeout: 500 };
                const props = { shown: false, showDelay: 100 };
                const { driver, rerender } = await render(renderPopover(props));
                expect(await driver.isContentElementExists()).toBe(false);
                await rerender(renderPopover({ ...props, shown: true }));
                expect(await driver.isContentElementExists()).toBe(false);
                await waitFor(async () => {
                    expect(await driver.isContentElementExists()).toBe(true);
                }, timeout);
            });
        });
        describe('onShow', () => {
            beforeEach(() => {
                vi.useFakeTimers();
            });
            afterEach(() => {
                vi.useRealTimers();
            });
            it('should call the onShow callback when popover is shown', async () => {
                const onShow = vi.fn();
                const props = { shown: false, onShow };
                const { driver, rerender } = await render(renderPopover(props));
                expect(await driver.isContentElementExists()).toBe(false);
                expect(onShow).not.toHaveBeenCalled();
                await act(async () => {
                    rerender(renderPopover({ ...props, shown: true }));
                });
                expect(await driver.isContentElementExists()).toBe(true);
                expect(onShow).toHaveBeenCalledTimes(1);
            });
            it('should call the onShow callback when popover is shown after a delay', async () => {
                const onShow = vi.fn();
                const showDelay = 100;
                const props = { shown: false, showDelay, onShow };
                const { driver, rerender } = await render(renderPopover(props));
                expect(await driver.isContentElementExists()).toBe(false);
                expect(onShow).not.toHaveBeenCalled();
                rerender(renderPopover({ ...props, shown: true }));
                expect(await driver.isContentElementExists()).toBe(false);
                expect(onShow).not.toHaveBeenCalled();
                await act(async () => {
                    vi.advanceTimersByTime(showDelay + 10);
                });
                expect(await driver.isContentElementExists()).toBe(true);
                expect(onShow).toHaveBeenCalledTimes(1);
            });
        });
    });
    describe('Accessibility', () => {
        it('should pass aria attrs to content wrapper', async () => {
            const role = 'someRole';
            const ariaAttributes = {
                'aria-label': 'someAriaLabel',
                'aria-labelledby': 'someAriaLabelledby',
                'aria-describedby': 'someAriaDescribedby',
            };
            const props = {
                shown: true,
                role,
                ...ariaAttributes,
            };
            const { driver } = await render(renderPopover(props));
            await waitFor(async () => {
                expect(await driver.isContentElementExists()).toBe(true);
            });
            const contentElement = await driver.getContentElement();
            expect(contentElement.getAttribute('role')).toBe(role);
            for (const [ariaAttr, ariaAttrValue] of Object.entries(ariaAttributes)) {
                expect(contentElement.getAttribute(ariaAttr)).toBe(ariaAttrValue);
            }
        });
        it('should apply id attribute only on popover content', async () => {
            const ariaId = 'test-labelled';
            const { driver, container } = await render(renderPopover({ id: ariaId, shown: true }));
            await waitFor(async () => {
                expect(container.querySelectorAll(`[id="${ariaId}"]`)).toHaveLength(1);
            });
            expect(await driver.getContentElement()).toHaveProperty('id', ariaId);
        });
    });
    describe('Popper update behaviour', () => {
        it(`should update popper's position when props are changing`, async () => {
            const updatePositionSpy = vi.spyOn(PopoverCore.prototype, 'updatePosition');
            const { rerender } = await render(renderPopover({ shown: true }));
            await rerender(renderPopover({ shown: true }, 'New Content'));
            expect(updatePositionSpy).toHaveBeenCalledTimes(2);
            updatePositionSpy.mockRestore();
        });
        it(`should not directly update popper's position when the visibility hasn't changed`, async () => {
            const updatePositionSpy = vi.spyOn(PopoverCore.prototype, 'updatePosition');
            const { rerender } = await render(renderPopover({ hideDelay: 10, showDelay: 10, shown: false }));
            await rerender(renderPopover({ hideDelay: 10, showDelay: 10, shown: true }));
            await rerender(renderPopover({ hideDelay: 10, showDelay: 10, shown: false }));
            expect(updatePositionSpy).toHaveBeenCalledTimes(1);
            updatePositionSpy.mockRestore();
        });
        it('should append popover to different container when appendTo changes', async () => {
            const PopoverWithDynamicAppendTo = () => {
                const [ref, setRef] = React.useState(null);
                return (React.createElement("div", { id: "target-container", ref: setRef },
                    React.createElement("div", { id: "parent-container" }, renderPopover({
                        shown: true,
                        appendTo: ref,
                    }))));
            };
            await render(React.createElement(PopoverWithDynamicAppendTo, null));
            expect(document.querySelector('#target-container > [data-hook="popover-portal"]')).toBeTruthy();
        });
    });
    describe('Portal and containment', () => {
        it('should render content inside portal [when] appendTo = window', async () => {
            const props = { shown: true, appendTo: 'window' };
            const { driver } = await render(renderPopover(props));
            await waitFor(async () => {
                expect(await driver.isContentElementExists()).toBe(true);
            });
            const portalElement = document.body.querySelector('[data-hook="popover-portal"]');
            expect(!!portalElement).toBe(true);
        });
        it(`renders an empty portal when closed`, async () => {
            const props = { shown: false, appendTo: 'window' };
            await render(renderPopover(props));
            const portalElement = document.body.querySelector('[data-hook="popover-portal"]');
            expect(!!portalElement).toBe(true);
        });
        it(`removes portal on unmount`, async () => {
            const props = { shown: false, appendTo: 'window' };
            const { unmount } = await render(renderPopover(props));
            const portalElement = document.body.querySelector('[data-hook="popover-portal"]');
            expect(!!portalElement).toBe(true);
            unmount();
            const portalElementUnmounted = document.body.querySelector('[data-hook="popover-portal"]');
            expect(!!portalElementUnmounted).toBe(false);
        });
        it(`adds the portal to the closest scrollable element when appendTo="scrollParent"`, async () => {
            const srollableHook = 'scrollable-element';
            const props = { shown: false, appendTo: 'scrollParent' };
            await render(React.createElement("div", { "data-hook": srollableHook, style: { overflow: 'scroll' } },
                React.createElement("div", { style: { overflow: 'visible' } }, renderPopover(props))));
            const parentElementHook = document.body
                .querySelector('[data-hook="popover-portal"]')
                ?.parentElement?.getAttribute('data-hook');
            expect(parentElementHook).toBe(srollableHook);
        });
        it(`adds the portal to custom element when using appendTo predicate`, async () => {
            const targetId = 'target';
            const props = {
                shown: false,
                appendTo: el => el.getAttribute('id') === targetId,
            };
            await render(React.createElement("span", { id: targetId },
                React.createElement("div", null,
                    React.createElement("div", null, renderPopover(props)))));
            const parentElementId = document.body
                .querySelector('[data-hook="popover-portal"]')
                ?.parentElement?.getAttribute('id');
            expect(parentElementId).toBe(targetId);
        });
        it(`should update the portal's styles when updated`, async () => {
            const className = 'some-class';
            const props = {
                shown: true,
                appendTo: 'window',
                className,
            };
            await render(renderPopover(props));
            const portalElement = document.body.querySelector('[data-hook="popover-portal"]');
            await waitFor(async () => {
                expect(portalElement?.classList).toContain(className);
            });
        });
        describe('contextClassName', () => {
            it(`should set class on portal [when] contextClassName is provided`, async () => {
                const contextClassName = 'custom-context-class';
                const props = {
                    shown: true,
                    appendTo: 'window',
                };
                await render(React.createElement(WixDesignSystemProvider, { contextClassName: contextClassName }, renderPopover(props)));
                const portalElement = document.body.querySelector('[data-hook="popover-portal"]');
                expect(portalElement?.classList.contains(contextClassName)).toBe(true);
            });
            it(`should not set class on portal [when] contextClassName is empty`, async () => {
                const props = {
                    shown: true,
                    appendTo: 'window',
                };
                await render(React.createElement(WixDesignSystemProvider, null, renderPopover(props)));
                const portalElement = document.body.querySelector('[data-hook="popover-portal"]');
                expect(portalElement?.classList.contains('custom-context-class')).toBeFalsy();
            });
        });
    });
});
//# sourceMappingURL=Popover.spec.js.map