Files
app/frontend/tests/clipboard.spec.ts
T

100 lines
3.6 KiB
TypeScript

import { expect, test } from "@playwright/test"
import { deleteAll, openedFlowId } from "./utils/api"
const flowName = `test_clip_${Date.now().toString(36)}`
/** Issued by the server when the first test creates it in the app. */
let flowId = ""
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/${flowId}`])
})
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()
flowId = await openedFlowId(page)
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)
})