|
| 1 | +import fs from 'node:fs' |
| 2 | +import os from 'node:os' |
| 3 | +import path from 'node:path' |
1 | 4 | import { test, expect } from './fixtures/server' |
2 | 5 | import { dragResizeHandle, nonSeededBoardObjects, openCard } from './fixtures/atlasBoard' |
3 | 6 | import { deleteViaPageMenu } from './fixtures/atlasPage' |
@@ -124,6 +127,50 @@ test('pasting a clipboard image lands a board object -- selectable, draggable, d |
124 | 127 | await expect(object).toHaveCount(0) |
125 | 128 | }) |
126 | 129 |
|
| 130 | +// Regression: the paste zone answered only bitmap clipboard data -- a |
| 131 | +// pasted image-file PATH (plain text) was silently ignored, and the |
| 132 | +// board's window-level paste door acting on the same event could land |
| 133 | +// a second copy. The zone now routes image-shaped text through the |
| 134 | +// same server-side recognizer the board door uses (mirror-copy), and |
| 135 | +// marks the event handled so exactly ONE object lands. |
| 136 | +test('pasting an image file path as text lands a board object rendering that file', async ({ page }) => { |
| 137 | + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'mill-e2e-atlas-image-paste-path-')) |
| 138 | + const pngPath = path.join(dir, 'ZzE2ePastedPath.png') |
| 139 | + fs.writeFileSync(pngPath, Buffer.from(ONE_PIXEL_PNG_BASE64, 'base64')) |
| 140 | + try { |
| 141 | + await page.goto('/') |
| 142 | + await page.getByRole('link', { name: 'Atlas' }).click() |
| 143 | + await expect(page.getByTestId('atlas-board')).toBeVisible() |
| 144 | + |
| 145 | + await openImagePopover(page) |
| 146 | + // Same dispatched-ClipboardEvent escape hatch as the bitmap-paste |
| 147 | + // test above: no user primitive can place arbitrary text on the |
| 148 | + // real OS clipboard portably in this harness, and the dispatched |
| 149 | + // event carries the exact DataTransfer shape a real ⌘V delivers. |
| 150 | + await page.getByTestId('atlas-image-paste-zone').evaluate((el, p) => { |
| 151 | + const dt = new DataTransfer() |
| 152 | + dt.setData('text/plain', p) |
| 153 | + el.dispatchEvent(new ClipboardEvent('paste', { clipboardData: dt, bubbles: true, cancelable: true })) |
| 154 | + }, pngPath) |
| 155 | + |
| 156 | + const object = imageObjects(page) |
| 157 | + await expect(object).toHaveCount(1) |
| 158 | + // The object renders the file found at the pasted path -- a real |
| 159 | + // <img> confirms the bytes loaded, not just a titled box. |
| 160 | + await expect(object.locator('img')).toBeVisible() |
| 161 | + // The popover closes itself once the paste resolves. |
| 162 | + await expect(page.getByTestId('atlas-image-input')).not.toBeVisible() |
| 163 | + // The rule, absolute: never a card. |
| 164 | + await expect(page.getByTestId('atlas-note-card').filter({ hasText: 'ZzE2ePastedPath' })).toHaveCount(0) |
| 165 | + |
| 166 | + await object.click() |
| 167 | + await page.keyboard.press('Delete') |
| 168 | + await expect(object).toHaveCount(0) |
| 169 | + } finally { |
| 170 | + fs.rmSync(dir, { recursive: true, force: true }) |
| 171 | + } |
| 172 | +}) |
| 173 | + |
127 | 174 | // Note (goal 0206): the prior "a non-image path shows an inline error" |
128 | 175 | // case tested typing an arbitrary bad path into a free-text field -- |
129 | 176 | // that field no longer exists (replaced by the native picker, which |
|
0 commit comments