import { AsyncChildrenApp, TestApp, type TestAppProps, type UnitSetupProps, runAllSuites, } from '@wix/unidriver-common/testing'; import React, {act, createElement} from 'react'; import {createRoot} from 'react-dom/client'; import {jsdomReactUniDriver} from '../jsdomReactUniDriver'; const renderTestApp = (element: HTMLElement, props?: TestAppProps) => { const comp = createElement(TestApp, props); const root = createRoot(element); act(() => { root.render(comp); }); return () => { act(() => { root.unmount(); }); }; }; const render = (jsx: React.JSX.Element, options = {attachToBody: true}) => { const elem = document.createElement('div'); if (options.attachToBody) { document.body.appendChild(elem); } const root = createRoot(elem); act(() => { root.render(jsx); }); const rerender = (jsxEl: React.JSX.Element) => { act(() => { root.render(jsxEl); }); }; return {container: elem, rerender}; }; // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access (globalThis as any).IS_REACT_ACT_ENVIRONMENT = true; describe('Common Test Suite', () => { runAllSuites({ setup: async (params) => { const div = document.createElement('div'); document.body.appendChild(div); const cleanApp = renderTestApp(div, params); const driver = jsdomReactUniDriver(div); const tearDown = () => { cleanApp(); return Promise.resolve(); }; return {driver, tearDown}; }, }); }); describe('Adapter Specific', () => { beforeEach(() => { document.body.innerHTML = ''; }); describe('click', () => { it('sends event data properly on simulated events when element is not attached to body', async () => { const s = vi.fn(); const {container} = render(); const driver = jsdomReactUniDriver(container); await driver.$('button').click(); expect(s).toHaveBeenCalledTimes(1); }); it('sends event data properly on simulated events when element is attached to body', async () => { const s = vi.fn(); const {container} = render(); const driver = jsdomReactUniDriver(container); await driver.$('button').click(); expect(s).toHaveBeenCalledTimes(1); }); it('should trigger [mouseDown, mouseUp, click] in this order and with default main-button(0) when clicked', async () => { const mouseDown = vi.fn(); const mouseUp = vi.fn(); const click = vi.fn(); const {container} = render( , ); const driver = jsdomReactUniDriver(container); await driver.$('button').click(); expect(mouseDown).toHaveBeenCalledTimes(1); expect(mouseUp).toHaveBeenCalledTimes(1); expect(click).toHaveBeenCalledTimes(1); }); it('should trigger [focus] on click', async () => { const focus = vi.fn(); const {container} = render(, { attachToBody: false, }); const driver = jsdomReactUniDriver(container); await driver.$('button').click(); expect(focus).toHaveBeenCalledTimes(1); }); it('should trigger [mousedown, focus, mouseup, click] events on click', async () => { // https://jsbin.com/larubagiwu/1/edit?html,js,console,output // https://github.com/wix-incubator/unidriver/pull/86#issuecomment-516809527 const mousedown = vi.fn(); const focus = vi.fn(); const mouseup = vi.fn(); const click = vi.fn(); const {container} = render( , {attachToBody: false}, ); const driver = jsdomReactUniDriver(container); await driver.$('button').click(); expect(mousedown).toHaveBeenCalledTimes(1); expect(focus).toHaveBeenCalledTimes(1); expect(mouseup).toHaveBeenCalledTimes(1); expect(click).toHaveBeenCalledTimes(1); }); describe('on input[type=checkbox]', () => { it('should trigger [mousedown, focus, mouseup, click, input, change] events', async () => { const mousedown = vi.fn(); const focus = vi.fn(); const mouseup = vi.fn(); const click = vi.fn(); const input = vi.fn(); const change = vi.fn(); const {container} = render( , {attachToBody: false}, ); const driver = jsdomReactUniDriver(container); await driver.$('input').click(); expect(mousedown).toHaveBeenCalledTimes(1); expect(focus).toHaveBeenCalledTimes(1); expect(mouseup).toHaveBeenCalledTimes(1); expect(click).toHaveBeenCalledTimes(1); expect(input).toHaveBeenCalledTimes(1); expect(change).toHaveBeenCalledTimes(1); }); it('should trigger change event with a target object containing correct `checked` property', async () => { const change = vi.fn(); const {container} = render(); const driver = jsdomReactUniDriver(container); await driver.$('input').click(); expect(change).toHaveBeenCalledExactlyOnceWith( expect.objectContaining({ target: expect.objectContaining({checked: true}), }), ); }); }); it('should trigger [focusA, blurA, focusB] when clicking two buttons', async () => { const focusA = vi.fn(); const focusB = vi.fn(); const blurA = vi.fn(); const {container} = render(
, ); const driver = jsdomReactUniDriver(container); await driver.$('button#A').click(); await driver.$('button#B').click(); expect(focusA).toHaveBeenCalledTimes(2); // TODO - should be 1 expect(blurA).toHaveBeenCalledTimes(2); // TODO - should be 1 expect(focusB).toHaveBeenCalledTimes(2); // TODO - should be 1 }); it('should trigger [focusA, blurA] when clicking enabled and disabled button', async () => { const focusA = vi.fn(); const focusB = vi.fn(); const blurA = vi.fn(); const {container} = render(
, ); const driver = jsdomReactUniDriver(container); await driver.$('button#A').click(); await driver.$('button#B').click(); expect(focusA).toHaveBeenCalledTimes(2); // TODO - should be 1 expect(blurA).toHaveBeenCalledTimes(1); expect(focusB).not.toHaveBeenCalled(); }); it('should trigger blur on active element when clicking an svg', async () => { const blurA = vi.fn(); const {container} = render(
, ); const driver = jsdomReactUniDriver(container); await driver.$('button#A').click(); await driver.$('svg#B').click(); expect(blurA).toHaveBeenCalledTimes(1); }); }); describe('enterValue', () => { it('should change input value when entering', async () => { const onChange = vi.fn(); const {container} = render( { onChange(event.target.value); }} />, ); const driver = jsdomReactUniDriver(container); await driver.$('input').enterValue('some keywords'); const value = onChange.mock.calls.at(0)?.[0]; expect(value).toBe('some keywords'); }); it('should change input value when given uncontrolled input', async () => { const {container} = render(); const driver = jsdomReactUniDriver(container); const input = driver.$('input'); await input.enterValue('some keywords'); expect(await input.value()).toBe('some keywords'); }); test.todo('should type with a delay if delay prop provided'); }); describe('enterText', () => { it('should change input value when entering', async () => { const onChange = vi.fn(); const {container} = render( { onChange(event.target.value); }} />, ); const driver = jsdomReactUniDriver(container); await driver.$('input').enterText('some keywords'); const value = onChange.mock.calls.at(0)?.[0]; expect(value).toBe('some keywords'); }); it('should change input value when given uncontrolled input', async () => { const {container} = render(); const driver = jsdomReactUniDriver(container); const input = driver.$('input'); await input.enterText('some keywords'); expect(await input.value()).toBe('some keywords'); }); test.todo('should type with a delay if delay prop provided'); }); describe('wait', () => { it('should throw immediately if element is not found when passed zero timeout', async () => { const {container} = render(
); const driver = jsdomReactUniDriver(container); const firstCompletedTask = await Promise.race([ driver .$('button') .wait(0) .catch(() => { return 'wait'; }), new Promise((resolve) => setTimeout(() => { resolve('timeout'); }, 100), ), ]); expect(firstCompletedTask).toBe('wait'); }); it('should resolve promise only after the element is in the DOM', async () => { const {container, rerender} = render(
); const driver = jsdomReactUniDriver(container); const selector = '[data-testid="test-element"]'; expect(await driver.$(selector).exists()).toBe(false); const originalConsoleError = console.error.bind(console); vi.spyOn(console, 'error').mockImplementation((...args: any[]) => { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unused-expressions args[0]?.includes('Warning: The current testing environment is not configured to support act') ? undefined : // eslint-disable-next-line @typescript-eslint/no-unsafe-argument originalConsoleError(...args); }); const waitPromise = driver.$(selector).wait(); rerender(
); expect(await driver.$(selector).exists()).toBe(true); await expect(waitPromise).resolves.toBe(undefined); }); it('should reject promise if element does not appear in the DOM', async () => { const {container} = render(
); const driver = jsdomReactUniDriver(container); await expect(driver.$('button').wait()).rejects.toThrowError(); }); }); describe('$', () => { describe('awaited', () => { it('should have basic support', async () => { const onClick = vi.fn(); const {container} = render(