import {cleanup, render, waitFor} from '@testing-library/react'; import {AsyncChildrenApp, TestApp, type UnitSetupProps, runAllSuites} from '@wix/unidriver-common/testing'; import {delay} from '@wix/unidriver-common'; import React, {type ChangeEvent} from 'react'; import {jsdomReactUniDriver} from '../jsdomReactUniDriver'; describe('Common Test Suite', () => { runAllSuites({ setup: async (props: UnitSetupProps) => { const {container, unmount} = render(); const driver = jsdomReactUniDriver(container); const tearDown = async () => { unmount(); cleanup(); }; return {driver, tearDown}; }, }); }); describe('Adapter Specific', () => { afterEach(() => { vi.useRealTimers(); cleanup(); }); describe('click', () => { // TODO: check order of fired events it('should trigger button events when clicked', async () => { const onMouseUp = vi.fn(); const onClick = vi.fn(); const onMouseDown = vi.fn(); const onFocus = vi.fn(); const {container} = render( , ); const driver = jsdomReactUniDriver(container); await driver.$('#A').click(); expect(onFocusA).toHaveBeenCalledTimes(1); expect(onBlurA).toHaveBeenCalledTimes(0); expect(onFocusB).toHaveBeenCalledTimes(0); await driver.$('#B').click(); expect(onBlurA).toHaveBeenCalledTimes(1); expect(onFocusB).toHaveBeenCalledTimes(1); }); it('should not call onFocus when given disabled button', async () => { const onFocusA = vi.fn(); const {container} = render( , ); const driver = jsdomReactUniDriver(container); await driver.$('button#A').click(); expect(onFocusA).toHaveBeenCalledTimes(0); }); it('should work with fake timers', async () => { vi.useFakeTimers(); const {container} = render(); const driver = jsdomReactUniDriver(container); // eslint-disable-next-line @typescript-eslint/no-confusing-void-expression expect(await driver.$('button').click()).toBe(undefined); }); it(`should trigger double click if {type: 'double'} is passed`, async () => { const onDoubleClick = vi.fn(); const {container} = render(); const driver = jsdomReactUniDriver(container); await driver.$('input').click({type: 'double'}); expect(onDoubleClick).toHaveBeenCalledTimes(1); }); it('should work with pointer-events: none', async () => { const {container} = render(); const driver = jsdomReactUniDriver(container); // eslint-disable-next-line @typescript-eslint/no-confusing-void-expression expect(await driver.$('button').click()).toBe(undefined); }); }); 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 firstLetter: string = onChange.mock.calls.at(0)?.[0]; expect(firstLetter).toBe('s'); const value: string = onChange.mock.calls.at(-1)?.[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'); }); it('should type with a delay if delay prop provided', async () => { const {container} = render(); const driver = jsdomReactUniDriver(container); const input = driver.$('input'); const inputPromise = input.enterText('some keywords', {delay: 100}); const assertionPromise = delay(300).then(async () => { const value = await driver.$('input').value(); expect(value.length).toBeGreaterThan(0); expect(value.length).toBeLessThan('some keywords'.length); }); await inputPromise; await assertionPromise; expect(await input.value()).toBe('some keywords'); }); }); describe('enterValue', () => { // This is actually incorrect behaviour and the reason, why .enterValue() is deprecated it('should change input value when entering with single change', async () => { const onChange = vi.fn(); const {container} = render(); const driver = jsdomReactUniDriver(container); await driver.$('input').enterValue('some keywords'); const event: ChangeEvent = onChange.mock.calls.at(0)?.[0]; expect(event.target.value).toEqual('some keywords'); expect(onChange.mock.calls.length).toBe(1); }); 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'); }); it('should type with a delay if delay prop provided', async () => { const {container} = render(); const driver = jsdomReactUniDriver(container); const input = driver.$('input'); const inputPromise = input.enterValue('some keywords', {delay: 100}); const assertionPromise = delay(300).then(async () => { const value = await driver.$('input').value(); expect(value.length).toBeGreaterThan(0); expect(value.length).toBeLessThan('some keywords'.length); }); await inputPromise; await assertionPromise; expect(await input.value()).toBe('some keywords'); }); }); 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 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(
, ); const driver = jsdomReactUniDriver(container); const clickTaskError = await driver .$('div') .$('section') .$('span') .$('div') .$('button') .awaited(10) .click() .catch((e: unknown) => { return (e as Error).message; }); expect(clickTaskError).toContain('selector: button\r\n'); expect(clickTaskError).toContain('Failed path: div > section > span\r\n'); expect(clickTaskError).toContain('Full path: div > section > span > div > button'); }); }); it('should not throw immediately', async () => { const {container} = render(
); const driver = jsdomReactUniDriver(container); const completedTask = await Promise.race([ driver .$('button') .click() .catch(() => { return 'selector'; }), new Promise((resolve) => { setTimeout(() => { resolve('timeout'); }, 100); }), ]); expect(completedTask).toBe('timeout'); }); it('should show full selectors path if element not present', async () => { const {container} = render(
, ); const driver = jsdomReactUniDriver(container); const clickTaskError = await driver .$('div') .$('section') .$('span') .$('div') .$('button') .awaited(10) .click() .catch((e: unknown) => { return (e as Error).message; }); expect(clickTaskError).toContain('selector: button\r\n'); expect(clickTaskError).toContain('Failed path: div > section > span\r\n'); expect(clickTaskError).toContain('Full path: div > section > span > div > button'); }); it('should be able to get an element when app renders element with a delay', async () => { const {container} = render(
, ); const driver = jsdomReactUniDriver(container); await waitFor(async () => { expect(await driver.$('[data-testid="delayed-element"]').exists()).toBe(true); }); }); it('should return an object which syncs with the DOM', async () => { const {container, rerender} = render(
); const driver = jsdomReactUniDriver(container); const element = driver.$('[data-testid="test-element"]'); expect(await element.exists()).toBe(true); rerender(
); expect(await element.exists()).toBe(false); }); }); describe('$$', () => { it('should return unidriver list', async () => { const {container} = render( <>
, ); const driver = jsdomReactUniDriver(container); const elementList = driver.$$('[data-testid="test-element"]'); expect(await elementList.get(0).exists()).toBe(true); expect(await elementList.get(1).exists()).toBe(true); expect(await elementList.get(2).exists()).toBe(false); }); it('should show full selectors path if element not present', async () => { const {container} = render( <>
, ); const driver = jsdomReactUniDriver(container); const clickTaskError = await driver .$('div') .$$('section') .at(0) .$('span') .$('div') .$('button') .awaited(10) .click() .catch((e: unknown) => { return (e as Error).message; }); expect(clickTaskError).toContain('selector: button\r\n'); expect(clickTaskError).toContain('Failed path: div > section:nth-child(1) > span\r\n'); expect(clickTaskError).toContain('Full path: div > section:nth-child(1) > span > div > button'); }); it('should be able to get an element(s) when app renders element with a delay', async () => { const {container} = render(
, ); const driver = jsdomReactUniDriver(container); expect(await driver.$('[data-testid="delayed-element"]').exists()).toBe(false); const elementList = driver.$$('[data-testid="delayed-element"]'); await waitFor(async () => { expect(await elementList.get(0).exists()).toBe(true); expect(await elementList.get(1).exists()).toBe(true); }); }); it(`should sync with the DOM when it's updated`, async () => { const {container, rerender} = render(
, ); const driver = jsdomReactUniDriver(container); const elementList = driver.$$('[data-testid="test-element"]'); expect(await elementList.get(0).exists()).toBe(true); expect(await elementList.get(1).exists()).toBe(true); rerender(
); expect(await elementList.get(0).exists()).toBe(false); expect(await elementList.get(1).exists()).toBe(false); }); }); describe('get', () => { it('should resolve a promise with a unidriver element', async () => { const {container} = render(
); const driver = jsdomReactUniDriver(container); const element = await driver.get('[data-testid="test-element"]'); expect(await element.exists()).toBe(true); }); it('should be able to get an element when app renders element with a delay', async () => { const {container} = render(
, ); const driver = jsdomReactUniDriver(container); expect(await driver.$('[data-testid="delayed-element"]').exists()).toBe(false); const element = await driver.get('[data-testid="delayed-element"]'); expect(await element.exists()).toBe(true); }); it('should throw error when element is not found', async () => { const {container} = render(
); const driver = jsdomReactUniDriver(container); let error: unknown; try { await driver.get('[data-testid="test-element"]', { timeout: 10, }); } catch (e) { error = e; } expect(error).toBeInstanceOf(Error); }); it('should return an object which syncs with the DOM', async () => { const {container, rerender} = render(
); const driver = jsdomReactUniDriver(container); const element = await driver.get('[data-testid="test-element"]'); expect(await element.exists()).toBe(true); rerender(
); expect(await element.exists()).toBe(false); }); }); describe('getAll', () => { it('should resolve a promise with a unidriver element list', async () => { const {container} = render(
, ); const driver = jsdomReactUniDriver(container); const elementList = await driver.getAll('[data-testid="test-element"]'); expect(await elementList.at(0).exists()).toBe(true); expect(await elementList.at(1).exists()).toBe(true); expect(await elementList.at(2).exists()).toBe(false); }); it('should be able to get an element(s) when app renders element with a delay', async () => { const {container} = render(
, ); const driver = jsdomReactUniDriver(container); expect(await driver.$('[data-testid="delayed-element"]').exists()).toBe(false); const elementList = await driver.getAll('[data-testid="delayed-element"]'); expect(await elementList.at(0).exists()).toBe(true); expect(await elementList.at(1).exists()).toBe(true); }); it('should throw error when element is not found', async () => { vi.useFakeTimers(); const {container} = render(
); const driver = jsdomReactUniDriver(container); const getterPromise = driver.getAll('[data-testid="test-element"]'); await expect(getterPromise).rejects.toThrowError(); }); it('should return an object which syncs with the DOM', async () => { const {container, rerender} = render(
, ); const driver = jsdomReactUniDriver(container); const elementList = await driver.getAll('[data-testid="test-element"]'); expect(await elementList.at(0).exists()).toBe(true); expect(await elementList.at(1).exists()).toBe(true); rerender(
); expect(await elementList.at(0).exists()).toBe(false); expect(await elementList.at(1).exists()).toBe(false); }); }); it('getNative', async () => { const {container} = render(
test
, ); const driver = jsdomReactUniDriver(container); const span = driver.$('span'); const native: HTMLSpanElement = await span.getNative(); expect(native.textContent).toBe('test'); }); });