import { UniDriver } from '@wix/wix-ui-test-utils/unidriver'; import { baseUniDriverFactory, isSled2, isSled3 } from '../../unidriver'; import { SledPage } from '../../sledPage'; // Playwright Locator shape we depend on for sled-3 dispatch. Typed locally to // avoid pulling Playwright types into cairo's runtime surface. type PlaywrightLocatorLike = { boundingBox: () => Promise<{ x: number; y: number; width: number; height: number; } | null>; page: () => PlaywrightPageLike; }; type PlaywrightPageLike = { mouse: { move: ( x: number, y: number, options?: { steps?: number }, ) => Promise; down: () => Promise; up: () => Promise; }; }; // Both sled-2 (Puppeteer) and sled-3 (Playwright) Page objects expose `.mouse` // with `.move/down/up`. The cairo DnD driver only needs that shape. type AnyDriverPage = SledPage['page'] | PlaywrightPageLike; const { fireEvent = null } = process.env.NODE_ENV === 'test' ? (() => { try { return require('@testing-library/react'); } catch {} return null; })() || (() => { try { // eslint-disable-next-line import/no-extraneous-dependencies return require('@testing-library/dom'); } catch {} return null; })() : {}; export function DragAndDropUniDriver(base: UniDriver) { return { ...baseUniDriverFactory(base), escapeDrag: async ( source: UniDriver, { key: code = 'Escape' }: { key?: string } = {}, ) => { const sourceNative = await source.getNative(); const { page } = sourceNative as { page?: SledPage['page'] }; if (page?.evaluate) { } else if (process.env.NODE_ENV === 'test') { fireEvent.keyDown(sourceNative, { code, }); } }, startKeyboardDrag: async (source: UniDriver) => { const sourceNative = await source.getNative(); const { page } = sourceNative as { page?: SledPage['page'] }; if (page?.evaluate) { } else if (fireEvent) { fireEvent.keyDown(sourceNative, { code: 'Enter', }); } }, dragOverKeyboard: async (target: UniDriver) => { const targetNative = await target.getNative(); const { page } = targetNative as { page?: SledPage['page'] }; if (page?.evaluate) { } else if (fireEvent) { fireEvent.keyDown(targetNative, { code: 'ArrowDown', }); } }, endKeyboardDrag: async (target: UniDriver) => { const targetNative = await target.getNative(); const { page } = targetNative as { page?: SledPage['page'] }; if (page?.evaluate) { } else if (fireEvent) { return fireEvent.keyDown(targetNative, { code: 'Enter', }); } }, dragStart: async ( source: UniDriver, options: { page?: AnyDriverPage; x?: number; y?: number } = {}, ) => { const sourceNative = await source.getNative(); if (isSled2(sourceNative)) { const page: SledPage['page'] = (options.page as SledPage['page']) ?? sourceNative.page; const sourceBox = await sourceNative.element.boundingBox(); await page.mouse.move( options.x ?? sourceBox.x + sourceBox.width / 2, options.y ?? sourceBox.y + sourceBox.height / 2, ); await page.mouse.down(); } else if (isSled3(sourceNative)) { const locator = sourceNative as PlaywrightLocatorLike; const page: PlaywrightPageLike = (options.page as PlaywrightPageLike) ?? locator.page(); const sourceBox = await locator.boundingBox(); if (!sourceBox) { throw new Error( 'DragAndDropUniDriver.dragStart: source.boundingBox() returned null (element detached, zero-size, or off-screen). Drag cannot start.', ); } await page.mouse.move( options.x ?? sourceBox.x + sourceBox.width / 2, options.y ?? sourceBox.y + sourceBox.height / 2, ); await page.mouse.down(); } else if (fireEvent) { fireEvent.pointerDown(sourceNative); } }, dragMove: async () => { await document.dispatchEvent(new Event('mousemove')); }, dragEnd: async ( target: UniDriver, options: { page?: AnyDriverPage } = {}, ) => { const targetNative = await target.getNative(); if (isSled2(targetNative)) { const page: SledPage['page'] = (options.page as SledPage['page']) ?? targetNative.page; await page.mouse.up(); } else if (isSled3(targetNative)) { const locator = targetNative as PlaywrightLocatorLike; const page: PlaywrightPageLike = (options.page as PlaywrightPageLike) ?? locator.page(); await page.mouse.up(); } else if (fireEvent) { fireEvent.pointerUp(targetNative); await document.dispatchEvent(new Event('mouseup')); } }, dragOver: async ( source: UniDriver, target: UniDriver, { dragPointRatio = { x: 2, y: 2 }, dropPointRatio = { x: 2, y: 2 }, steps = 3, }: { dragPointRatio?: { x: number; y: number }; dropPointRatio?: { x: number; y: number }; steps?: number; } = {}, ) => { const sourceRow = await source.getNative(); const targetRow = await target.getNative(); if (isSled2(sourceRow)) { const { page } = sourceRow as { page: SledPage['page'] }; await source.hover(); const sourceBox = await sourceRow.element.boundingBox(); const targetBox = await targetRow.element.boundingBox(); await page.mouse.move( sourceBox.x + sourceBox.width / dragPointRatio.x, sourceBox.y + sourceBox.height / dragPointRatio.y, ); await page.mouse.down(); await new Promise((resolve) => setTimeout(resolve, 100)); await page.mouse.move( targetBox.x + targetBox.width / dropPointRatio.x, targetBox.y + targetBox.height / dropPointRatio.y, { steps }, ); await new Promise((resolve) => setTimeout(resolve, 100)); await page.mouse.up(); await new Promise((resolve) => setTimeout(resolve, 100)); // wait for drop animation to complete } else if (isSled3(sourceRow)) { const sourceLocator = sourceRow as PlaywrightLocatorLike; const targetLocator = targetRow as PlaywrightLocatorLike; const page = sourceLocator.page(); await source.hover(); const sourceBox = await sourceLocator.boundingBox(); const targetBox = await targetLocator.boundingBox(); if (!sourceBox || !targetBox) { throw new Error( `DragAndDropUniDriver.dragOver: ${!sourceBox ? 'source' : 'target'}.boundingBox() returned null (element detached, zero-size, or off-screen). Drag cannot proceed.`, ); } await page.mouse.move( sourceBox.x + sourceBox.width / dragPointRatio.x, sourceBox.y + sourceBox.height / dragPointRatio.y, ); await page.mouse.down(); await new Promise((resolve) => setTimeout(resolve, 100)); await page.mouse.move( targetBox.x + targetBox.width / dropPointRatio.x, targetBox.y + targetBox.height / dropPointRatio.y, { steps }, ); await new Promise((resolve) => setTimeout(resolve, 100)); await page.mouse.up(); await new Promise((resolve) => setTimeout(resolve, 100)); } else if (fireEvent) { fireEvent.pointerDown(sourceRow); document.dispatchEvent(new Event('mousemove')); fireEvent.pointerUp(targetRow); } }, moveOver: async ( target: UniDriver, options: { page?: AnyDriverPage; pixels?: number; delta?: { x?: number }; } = {}, ) => { const targetRow = await target.getNative(); const pixels = options.pixels ?? 0; if (isSled2(targetRow)) { const page: SledPage['page'] = (options.page as SledPage['page']) ?? targetRow.page; const targetBox = await targetRow.element.boundingBox(); for (let i = 0; i < 2; i++) { await page.mouse.move( Math.max(options.delta?.x ?? targetBox.x - pixels, 0), Math.max(targetBox.y + targetBox.height - pixels, 0), ); } } else if (isSled3(targetRow)) { const locator = targetRow as PlaywrightLocatorLike; const page: PlaywrightPageLike = (options.page as PlaywrightPageLike) ?? locator.page(); const targetBox = await locator.boundingBox(); if (!targetBox) { throw new Error( 'DragAndDropUniDriver.moveOver: target.boundingBox() returned null (element detached, zero-size, or off-screen).', ); } for (let i = 0; i < 2; i++) { await page.mouse.move( Math.max(options.delta?.x ?? targetBox.x - pixels, 0), Math.max(targetBox.y + targetBox.height - pixels, 0), ); } } else if (fireEvent) { fireEvent.mouseMove(targetRow, { clientX: options.delta?.x ?? 0, }); } }, moveAny: async ( source: UniDriver, options?: { page?: AnyDriverPage; x?: number; y?: number; steps?: number; delay?: number; vertical?: boolean; }, ) => { const sourceNative = await source.getNative(); if (isSled2(sourceNative)) { const page: SledPage['page'] = (options?.page as SledPage['page']) ?? sourceNative.page; const sourceBox = await sourceNative.element.boundingBox(); const steps = options?.steps ?? 3; for (let i = steps; i >= 0; i--) { await page.mouse.move( (options?.x ?? sourceBox.x + sourceBox.width / 2) - i * 10, (options?.y ?? sourceBox.y + sourceBox.height / 2) - (options?.vertical ? i * 10 : 0), ); await new Promise((resolve) => setTimeout(resolve, options?.delay ?? 2000), ); } } else if (isSled3(sourceNative)) { const locator = sourceNative as PlaywrightLocatorLike; const page: PlaywrightPageLike = (options?.page as PlaywrightPageLike) ?? locator.page(); const sourceBox = await locator.boundingBox(); if (!sourceBox) { throw new Error( 'DragAndDropUniDriver.moveAny: source.boundingBox() returned null (element detached, zero-size, or off-screen).', ); } const steps = options?.steps ?? 3; for (let i = steps; i >= 0; i--) { await page.mouse.move( (options?.x ?? sourceBox.x + sourceBox.width / 2) - i * 10, (options?.y ?? sourceBox.y + sourceBox.height / 2) - (options?.vertical ? i * 10 : 0), ); await new Promise((resolve) => setTimeout(resolve, options?.delay ?? 2000), ); } } }, }; }