The clipboard spec selected text in the previous node's panel while it was still sliding out after the paste, so the selection collapsed when it unmounted and the canvas took the copy. The values spec read the panel's box mid-slide-in, where the translate leaves the width a float off 400.
98 lines
3.5 KiB
TypeScript
98 lines
3.5 KiB
TypeScript
import { expect, test } from "@playwright/test"
|
|
import { deleteAll } from "./utils/api"
|
|
|
|
const flowName = `test_clip_${Date.now().toString(36)}`
|
|
const KEY = "fluksio.nodeClipboard"
|
|
|
|
declare global {
|
|
interface Window {
|
|
/** Whether the app took the last ⌘C/⌘V away from the browser. */
|
|
prevented: boolean | null
|
|
}
|
|
}
|
|
|
|
test.use({
|
|
storageState: "playwright/.auth/user.json",
|
|
permissions: ["clipboard-read", "clipboard-write"],
|
|
})
|
|
|
|
test.afterAll(async ({ browser }) => {
|
|
await deleteAll(browser, [`/flows/${flowName}`])
|
|
})
|
|
|
|
test("the canvas clipboard yields to the native one", async ({ page }) => {
|
|
await page.goto("/flows")
|
|
await page.getByTestId("new-flow").click()
|
|
await page.getByTestId("new-flow-name").fill(flowName)
|
|
await page.getByTestId("create-flow").click()
|
|
await page.waitForURL(`/flows/${flowName}`)
|
|
|
|
await page.getByTestId("add-node").click()
|
|
await page
|
|
.getByRole("option", { name: /function/i })
|
|
.first()
|
|
.click()
|
|
await expect(page.locator(".react-flow__node")).toHaveCount(1)
|
|
await page.keyboard.press("Escape")
|
|
|
|
// Records whether the app took the chord away from the browser.
|
|
await page.evaluate((key) => {
|
|
localStorage.removeItem(key)
|
|
window.prevented = null
|
|
window.addEventListener("keydown", (event) => {
|
|
if (["c", "v"].includes(event.key.toLowerCase())) {
|
|
window.prevented = event.defaultPrevented
|
|
}
|
|
})
|
|
}, KEY)
|
|
const prevented = () => page.evaluate(() => window.prevented)
|
|
|
|
// A node selected and no text selection: the canvas clipboard acts.
|
|
await page.locator(".react-flow__node").first().click()
|
|
await page.keyboard.press("ControlOrMeta+c")
|
|
await expect
|
|
.poll(() => page.evaluate((key) => localStorage.getItem(key), KEY))
|
|
.not.toBeNull()
|
|
expect(await prevented()).toBe(true)
|
|
|
|
// And it still pastes.
|
|
await page.keyboard.press("ControlOrMeta+v")
|
|
await expect(page.locator(".react-flow__node")).toHaveCount(2)
|
|
expect(await prevented()).toBe(true)
|
|
|
|
// Seeded before the selection is made: writing the clipboard drops it.
|
|
await page.evaluate(() => navigator.clipboard.writeText("nothing copied"))
|
|
|
|
// A text selection wins, node selection or not. React Flow's own CSS puts
|
|
// `user-select: none` on every node, so the selectable text on this route is
|
|
// the floating chrome over the canvas — here the open node panel. The paste
|
|
// opened the copy's panel, and the original's is still sliding out in front
|
|
// of it: a selection made in that one collapses when it unmounts.
|
|
await expect(page.getByTestId("node-panel")).toHaveCount(1)
|
|
const selected = await page.evaluate(() => {
|
|
const node = document.querySelector(
|
|
'[data-testid="node-panel"]',
|
|
) as HTMLElement
|
|
const range = document.createRange()
|
|
range.selectNodeContents(node)
|
|
const selection = document.getSelection() as Selection
|
|
selection.removeAllRanges()
|
|
selection.addRange(range)
|
|
return selection.toString().trim()
|
|
})
|
|
expect(selected).not.toBe("")
|
|
await page.keyboard.press("ControlOrMeta+c")
|
|
expect(await prevented()).toBe(false)
|
|
const copied = await page.evaluate(() => navigator.clipboard.readText())
|
|
expect(copied.trim()).not.toBe("nothing copied")
|
|
expect(selected).toContain(copied.trim().split("\n")[0])
|
|
|
|
// Nothing stored: paste falls through to the browser too.
|
|
await page.evaluate((key) => {
|
|
localStorage.removeItem(key)
|
|
document.getSelection()?.removeAllRanges()
|
|
}, KEY)
|
|
await page.keyboard.press("ControlOrMeta+v")
|
|
expect(await prevented()).toBe(false)
|
|
})
|