// wxDragDrop Tests - HTML5 file drop support for KiCad // Tests external file drops via HTML5 drag and drop API import { test, expect, getCanvasBox, waitForCanvasApp } from './utils/fixtures'; import * as path from 'path'; import * as fs from 'fs'; import { stableShot } from './utils/element-tracker'; test.describe('wxDragDrop Tests', () => { test('DnD test app loads successfully', async ({ page, testLogger }) => { await page.goto('/standalone/dnd/dnd_test.html'); await waitForCanvasApp(page); await stableShot(page, 'dnd-01-loaded.png', { fullPage: true }); const hasStartup = testLogger.consoleLogs.some(l => l.includes('DND_TEST')); expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0); }); test('DnD handlers are registered', async ({ page, testLogger }) => { await page.goto('/standalone/dnd/dnd_test.html'); await waitForCanvasApp(page); await expect.poll( () => testLogger.consoleLogs.some(l => l.includes('[DND] Drag and drop handlers registered')), { message: 'DnD handlers should be registered' } ).toBe(true); await stableShot(page, 'dnd-02-handlers.png', { fullPage: true }); }); test('DragEnter event is detected', async ({ page, testLogger }) => { await page.goto('/standalone/dnd/dnd_test.html'); await waitForCanvasApp(page); const canvas = page.locator('#canvas'); const box = await canvas.boundingBox(); expect(box, 'Canvas should have bounding box').not.toBeNull(); // Simulate dragenter event await page.evaluate(({ x, y }) => { const canvas = document.getElementById('canvas'); if (canvas) { const event = new DragEvent('dragenter', { bubbles: true, cancelable: true, clientX: x, clientY: y, dataTransfer: new DataTransfer() }); canvas.dispatchEvent(event); } }, { x: box.x + 400, y: box.y + 200 }); await expect.poll( () => testLogger.consoleLogs.some(l => l.includes('[DND] dragenter')), { message: 'DragEnter event should be logged' } ).toBe(true); await stableShot(page, 'dnd-03-dragenter.png', { fullPage: true }); }); test('DragLeave event is detected', async ({ page, testLogger }) => { await page.goto('/standalone/dnd/dnd_test.html'); await waitForCanvasApp(page); const canvas = page.locator('#canvas'); const box = await canvas.boundingBox(); expect(box, 'Canvas should have bounding box').not.toBeNull(); // Simulate dragenter then dragleave await page.evaluate(({ x, y }) => { const canvas = document.getElementById('canvas'); if (canvas) { const enterEvent = new DragEvent('dragenter', { bubbles: true, cancelable: true, clientX: x, clientY: y, dataTransfer: new DataTransfer() }); canvas.dispatchEvent(enterEvent); const leaveEvent = new DragEvent('dragleave', { bubbles: true, cancelable: true, dataTransfer: new DataTransfer() }); canvas.dispatchEvent(leaveEvent); } }, { x: box.x + 400, y: box.y + 200 }); await expect.poll( () => testLogger.consoleLogs.some(l => l.includes('[DND] dragleave')), { message: 'DragLeave event should be logged' } ).toBe(true); await stableShot(page, 'dnd-04-dragleave.png', { fullPage: true }); }); test('Drop event triggers file processing', async ({ page, testLogger }) => { await page.goto('/standalone/dnd/dnd_test.html'); await waitForCanvasApp(page); const canvas = page.locator('#canvas'); const box = await canvas.boundingBox(); expect(box, 'Canvas should have bounding box').not.toBeNull(); // Create a test file and simulate drop const testContent = 'Test file content for DnD'; const testFileName = 'test-drop-file.txt'; await page.evaluate(({ x, y, fileName, content }) => { const canvas = document.getElementById('canvas'); if (canvas) { const dataTransfer = new DataTransfer(); const file = new File([content], fileName, { type: 'text/plain' }); dataTransfer.items.add(file); const event = new DragEvent('drop', { bubbles: true, cancelable: true, clientX: x, clientY: y, dataTransfer: dataTransfer }); canvas.dispatchEvent(event); } }, { x: box.x + 400, y: box.y + 200, fileName: testFileName, content: testContent }); // Wait for async file processing (deterministic: poll for the drop log) await expect.poll( () => testLogger.consoleLogs.some(l => l.includes('[DND] drop')), { message: 'Drop event should be logged' } ).toBe(true); await stableShot(page, 'dnd-05-drop.png', { fullPage: true }); }); test('Dropped file is written to WASM filesystem', async ({ page, testLogger }) => { await page.goto('/standalone/dnd/dnd_test.html'); await waitForCanvasApp(page); const canvas = page.locator('#canvas'); const box = await canvas.boundingBox(); expect(box, 'Canvas should have bounding box').not.toBeNull(); const testFileName = 'wasm-test-file.txt'; const testContent = 'Content written via DnD'; await page.evaluate(({ x, y, fileName, content }) => { const canvas = document.getElementById('canvas'); if (canvas) { const dataTransfer = new DataTransfer(); const file = new File([content], fileName, { type: 'text/plain' }); dataTransfer.items.add(file); const event = new DragEvent('drop', { bubbles: true, cancelable: true, clientX: x, clientY: y, dataTransfer: dataTransfer }); canvas.dispatchEvent(event); } }, { x: box.x + 400, y: box.y + 200, fileName: testFileName, content: testContent }); // Wait for file to be written (deterministic: poll for the write log) await expect.poll( () => testLogger.consoleLogs.some(l => l.includes('[DND] Wrote file:') && l.includes(testFileName)), { message: 'File should be written to WASM filesystem' } ).toBe(true); await stableShot(page, 'dnd-06-file-written.png', { fullPage: true }); }); test('wxDropFilesEvent is fired after drop', async ({ page, testLogger }) => { await page.goto('/standalone/dnd/dnd_test.html'); await waitForCanvasApp(page); const canvas = page.locator('#canvas'); const box = await canvas.boundingBox(); expect(box, 'Canvas should have bounding box').not.toBeNull(); const testFileName = 'event-test.kicad_pcb'; const testContent = '(kicad_pcb (version 20230121))'; await page.evaluate(({ x, y, fileName, content }) => { const canvas = document.getElementById('canvas'); if (canvas) { const dataTransfer = new DataTransfer(); const file = new File([content], fileName, { type: 'application/octet-stream' }); dataTransfer.items.add(file); const event = new DragEvent('drop', { bubbles: true, cancelable: true, clientX: x, clientY: y, dataTransfer: dataTransfer }); canvas.dispatchEvent(event); } }, { x: box.x + 400, y: box.y + 200, fileName: testFileName, content: testContent }); // Wait for wxDropFilesEvent processing (deterministic: poll for the [DND_EVENT] log). // The app logs "=== wxDropFilesEvent received! ===" which includes DND_EVENT prefix. await expect.poll( () => testLogger.consoleLogs.some(l => l.includes('[DND_EVENT]')), { message: 'wxDropFilesEvent should be fired' } ).toBe(true); await stableShot(page, 'dnd-07-event-fired.png', { fullPage: true }); }); test('Multiple files can be dropped', async ({ page, testLogger }) => { await page.goto('/standalone/dnd/dnd_test.html'); await waitForCanvasApp(page); const canvas = page.locator('#canvas'); const box = await canvas.boundingBox(); expect(box, 'Canvas should have bounding box').not.toBeNull(); await page.evaluate(({ x, y }) => { const canvas = document.getElementById('canvas'); if (canvas) { const dataTransfer = new DataTransfer(); const file1 = new File(['content1'], 'file1.txt', { type: 'text/plain' }); const file2 = new File(['content2'], 'file2.txt', { type: 'text/plain' }); const file3 = new File(['content3'], 'file3.txt', { type: 'text/plain' }); dataTransfer.items.add(file1); dataTransfer.items.add(file2); dataTransfer.items.add(file3); const event = new DragEvent('drop', { bubbles: true, cancelable: true, clientX: x, clientY: y, dataTransfer: dataTransfer }); canvas.dispatchEvent(event); } }, { x: box.x + 400, y: box.y + 200 }); await expect.poll( () => testLogger.consoleLogs.some(l => l.includes('[DND] drop: 3 files')), { message: 'Multiple files should be detected' } ).toBe(true); await stableShot(page, 'dnd-08-multiple-files.png', { fullPage: true }); }); test('Clear files button exists in UI', async ({ page, testLogger }) => { await page.goto('/standalone/dnd/dnd_test.html'); await waitForCanvasApp(page); // First drop a file to verify drop works const canvas = page.locator('#canvas'); const box = await canvas.boundingBox(); expect(box, 'Canvas should have bounding box').not.toBeNull(); await page.evaluate(({ x, y }) => { const canvas = document.getElementById('canvas'); if (canvas) { const dataTransfer = new DataTransfer(); const file = new File(['test'], 'test.txt', { type: 'text/plain' }); dataTransfer.items.add(file); const event = new DragEvent('drop', { bubbles: true, cancelable: true, clientX: x, clientY: y, dataTransfer: dataTransfer }); canvas.dispatchEvent(event); } }, { x: box.x + 400, y: box.y + 200 }); // Verify file was dropped (from JS side) - deterministic poll for the write log await expect.poll( () => testLogger.consoleLogs.some(l => l.includes('[DND] Wrote file:')), { message: 'File should be dropped and logged' } ).toBe(true); await stableShot(page, 'dnd-09-with-file.png', { fullPage: true }); }); });