Created e2e tests for node interactions
This commit is contained in:
@@ -41,6 +41,7 @@ const initialNodes: Node[] = [
|
|||||||
data: { label: 'Node 4' },
|
data: { label: 'Node 4' },
|
||||||
position: { x: 400, y: 200 },
|
position: { x: 400, y: 200 },
|
||||||
className: 'light',
|
className: 'light',
|
||||||
|
connectable: false,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { NodeProps } from '@xyflow/svelte';
|
||||||
|
|
||||||
|
type $$Props = NodeProps;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="drag-handle custom-drag-handle" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.container {
|
||||||
|
width: 100px;
|
||||||
|
height: 50px;
|
||||||
|
background: red;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.drag-handle {
|
||||||
|
display: inline-block;
|
||||||
|
width: 25px;
|
||||||
|
height: 25px;
|
||||||
|
background-color: green;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -3,10 +3,16 @@
|
|||||||
import { initialNodes, initialEdges } from './nodesAndEdges';
|
import { initialNodes, initialEdges } from './nodesAndEdges';
|
||||||
import { SvelteFlow } from '@xyflow/svelte';
|
import { SvelteFlow } from '@xyflow/svelte';
|
||||||
|
|
||||||
|
import DragHandleNode from './DragHandleNode.svelte';
|
||||||
|
|
||||||
import '@xyflow/svelte/dist/style.css';
|
import '@xyflow/svelte/dist/style.css';
|
||||||
|
|
||||||
const nodes = writable(initialNodes);
|
const nodes = writable(initialNodes);
|
||||||
const edges = writable(initialEdges);
|
const edges = writable(initialEdges);
|
||||||
|
|
||||||
|
const nodeTypes = {
|
||||||
|
DragHandleNode
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<SvelteFlow {nodes} {edges} fitView />
|
<SvelteFlow {nodes} {nodeTypes} {edges} fitView />
|
||||||
|
|||||||
@@ -2,50 +2,61 @@ import type { Edge, Node } from '@xyflow/svelte';
|
|||||||
|
|
||||||
export const initialNodes: Node[] = [
|
export const initialNodes: Node[] = [
|
||||||
{
|
{
|
||||||
id: '1',
|
id: 'Node-1',
|
||||||
data: { label: '1' },
|
data: { label: 'Node-1' },
|
||||||
position: { x: 0, y: 0 },
|
position: { x: 0, y: 0 },
|
||||||
class: 'test-class',
|
type: 'input',
|
||||||
|
class: 'playwright-test-class-123',
|
||||||
style: 'background-color: red;'
|
style: 'background-color: red;'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '2',
|
id: 'Node-2',
|
||||||
data: { label: '2' },
|
type: 'output',
|
||||||
|
data: { label: 'Node-2' },
|
||||||
position: { x: -100, y: 100 }
|
position: { x: -100, y: 100 }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '3',
|
id: 'Node-3',
|
||||||
data: { label: '3' },
|
data: { label: 'Node-3' },
|
||||||
position: { x: 100, y: 100 }
|
position: { x: 100, y: 100 }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '4',
|
id: 'Node-4',
|
||||||
data: { label: '4' },
|
data: { label: 'Node-4' },
|
||||||
position: { x: 0, y: 200 }
|
position: { x: 0, y: 200 },
|
||||||
|
type: 'output'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'drag-handle',
|
||||||
|
data: { label: 'Drag Handle' },
|
||||||
|
position: { x: 200, y: 0 },
|
||||||
|
type: 'DragHandleNode',
|
||||||
|
dragHandle: '.custom-drag-handle'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'notConnectable',
|
||||||
|
type: 'output',
|
||||||
|
data: { label: 'notConnectable' },
|
||||||
|
position: { x: 0, y: 300 },
|
||||||
|
connectable: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'notDraggable',
|
id: 'notDraggable',
|
||||||
data: { label: 'notDraggable' },
|
data: { label: 'notDraggable' },
|
||||||
position: { x: 0, y: 300 },
|
position: { x: 0, y: 400 },
|
||||||
draggable: false
|
draggable: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'notSelectable',
|
id: 'notSelectable',
|
||||||
data: { label: 'notSelectable' },
|
data: { label: 'notSelectable' },
|
||||||
position: { x: 0, y: 400 },
|
|
||||||
selectable: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'notConnectable',
|
|
||||||
data: { label: 'notConnectable' },
|
|
||||||
position: { x: 0, y: 500 },
|
position: { x: 0, y: 500 },
|
||||||
connectable: false
|
selectable: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'notDeletable',
|
id: 'notDeletable',
|
||||||
data: { label: 'notDeletable' },
|
data: { label: 'notDeletable' },
|
||||||
position: { x: 0, y: 600 },
|
position: { x: 0, y: 600 },
|
||||||
connectable: false
|
deletable: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'hidden',
|
id: 'hidden',
|
||||||
@@ -59,15 +70,15 @@ export const initialEdges: Edge[] = [
|
|||||||
{
|
{
|
||||||
id: '1-2',
|
id: '1-2',
|
||||||
type: 'default',
|
type: 'default',
|
||||||
source: '1',
|
source: 'Node-1',
|
||||||
target: '2',
|
target: 'Node-2',
|
||||||
label: 'edge'
|
label: 'edge'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '1-3',
|
id: '1-3',
|
||||||
type: 'default',
|
type: 'default',
|
||||||
source: '1',
|
source: 'Node-1',
|
||||||
target: '3',
|
target: 'Node-3',
|
||||||
label: 'edge'
|
label: 'edge'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
import { test, expect } from '@playwright/test';
|
|
||||||
|
|
||||||
import { FRAMEWORK } from './constants';
|
|
||||||
|
|
||||||
test('Delete Node with backspace', async ({ page }) => {
|
|
||||||
await page.goto('/');
|
|
||||||
await page.locator(`.${FRAMEWORK}-flow__node`).nth(0).click();
|
|
||||||
// await page.getByTestId('rf__node-1').click();
|
|
||||||
await page.keyboard.press('Backspace');
|
|
||||||
|
|
||||||
const nodes = await page.locator(`.${FRAMEWORK}-flow__node`).all();
|
|
||||||
expect(nodes).toHaveLength(3);
|
|
||||||
|
|
||||||
const edges = await page.locator(`.${FRAMEWORK}-flow__edge`).all();
|
|
||||||
expect(edges).toHaveLength(0);
|
|
||||||
});
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import { test, expect } from '@playwright/test';
|
|
||||||
|
|
||||||
test('Move Node', async ({ page }) => {
|
|
||||||
await page.goto('/');
|
|
||||||
|
|
||||||
const locator = page.getByTestId('rf__node-1');
|
|
||||||
|
|
||||||
const transformBeforeMove = await locator.evaluate((element) => {
|
|
||||||
return element.style.transform;
|
|
||||||
});
|
|
||||||
|
|
||||||
await locator.hover();
|
|
||||||
await page.mouse.down();
|
|
||||||
await page.mouse.move(500, 500);
|
|
||||||
await page.mouse.up();
|
|
||||||
|
|
||||||
const transformAfterMove = await locator.evaluate((element) => {
|
|
||||||
return element.style.transform;
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(transformBeforeMove).not.toMatch(transformAfterMove);
|
|
||||||
});
|
|
||||||
@@ -9,6 +9,240 @@ test.describe('NODES', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test.describe('selection', () => {
|
test.describe('selection', () => {
|
||||||
test('selecting a node', async ({ page }) => {});
|
test('selecting a node by click', async ({ page }) => {
|
||||||
|
const locator = page.locator(`.${FRAMEWORK}-flow__node`).first();
|
||||||
|
await locator.click();
|
||||||
|
|
||||||
|
await expect(locator).toHaveClass(/selected/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('selecting multiple nodes with shift drag', async ({ page }) => {
|
||||||
|
const nodes = page.locator(`.${FRAMEWORK}-flow__node`);
|
||||||
|
const firstNode = nodes.first();
|
||||||
|
const secondNode = nodes.nth(1);
|
||||||
|
const thirdNode = nodes.nth(2);
|
||||||
|
|
||||||
|
await expect(firstNode).toBeInViewport();
|
||||||
|
await expect(secondNode).toBeInViewport();
|
||||||
|
await expect(thirdNode).toBeInViewport();
|
||||||
|
|
||||||
|
const box = await firstNode.boundingBox();
|
||||||
|
|
||||||
|
await page.mouse.move(box!.x - 100, box!.y - 10);
|
||||||
|
await page.keyboard.down('Shift');
|
||||||
|
await page.mouse.down();
|
||||||
|
await page.mouse.move(box!.x + 200, box!.y + 200);
|
||||||
|
await page.mouse.up();
|
||||||
|
await page.keyboard.up('Shift');
|
||||||
|
|
||||||
|
await expect(firstNode).toHaveClass(/selected/);
|
||||||
|
await expect(secondNode).toHaveClass(/selected/);
|
||||||
|
await expect(thirdNode).toHaveClass(/selected/);
|
||||||
|
|
||||||
|
const selection = page.locator(`.${FRAMEWORK}-flow__selection`);
|
||||||
|
await expect(selection).toBeInViewport();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('selectable=false prevents selection', async ({ page }) => {
|
||||||
|
const locator = page.locator(`.${FRAMEWORK}-flow__node`).and(page.locator('[data-id="notSelectable"]'));
|
||||||
|
await locator.click();
|
||||||
|
|
||||||
|
await expect(locator).not.toHaveClass(/selected/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe('dragging', () => {
|
||||||
|
test('dragging a node', async ({ page }) => {
|
||||||
|
const node = page.locator(`.${FRAMEWORK}-flow__node`).first();
|
||||||
|
|
||||||
|
const transformBeforeMove = await node.evaluate((element) => {
|
||||||
|
return element.style.transform;
|
||||||
|
});
|
||||||
|
|
||||||
|
await node.hover();
|
||||||
|
await page.mouse.down();
|
||||||
|
await page.mouse.move(500, 500);
|
||||||
|
await page.mouse.up();
|
||||||
|
|
||||||
|
const transformAfterMove = await node.evaluate((element) => {
|
||||||
|
return element.style.transform;
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(transformBeforeMove).not.toMatch(transformAfterMove);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('draggable=false prevents dragging', async ({ page }) => {
|
||||||
|
const node = page.locator(`.${FRAMEWORK}-flow__node`).and(page.locator('[data-id="notDraggable"]'));
|
||||||
|
|
||||||
|
const transformBeforeMove = await node.evaluate((element) => {
|
||||||
|
return element.style.transform;
|
||||||
|
});
|
||||||
|
|
||||||
|
await node.hover();
|
||||||
|
await page.mouse.down();
|
||||||
|
await page.mouse.move(500, 500);
|
||||||
|
await page.mouse.up();
|
||||||
|
|
||||||
|
const transformAfterMove = await node.evaluate((element) => {
|
||||||
|
return element.style.transform;
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(transformBeforeMove).toMatch(transformAfterMove);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('custom drag handle works', async ({ page }) => {
|
||||||
|
const node = page.locator(`.${FRAMEWORK}-flow__node`).and(page.locator('[data-id="drag-handle"]'));
|
||||||
|
const dragHandle = page.locator('.custom-drag-handle');
|
||||||
|
|
||||||
|
const transformBeforeMove = await node.evaluate((element) => {
|
||||||
|
return element.style.transform;
|
||||||
|
});
|
||||||
|
|
||||||
|
const nodeBox = await node.boundingBox();
|
||||||
|
|
||||||
|
await page.mouse.move(nodeBox!.x + 10, nodeBox!.y + 10);
|
||||||
|
await page.mouse.down();
|
||||||
|
await page.mouse.move(500, 500);
|
||||||
|
await page.mouse.up();
|
||||||
|
|
||||||
|
const transformAfterMove = await node.evaluate((element) => {
|
||||||
|
return element.style.transform;
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(transformBeforeMove).toMatch(transformAfterMove);
|
||||||
|
|
||||||
|
await dragHandle.hover();
|
||||||
|
await page.mouse.down();
|
||||||
|
await page.mouse.move(500, 500);
|
||||||
|
await page.mouse.up();
|
||||||
|
|
||||||
|
const transformAfterDragHandleMove = await node.evaluate((element) => {
|
||||||
|
return element.style.transform;
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(transformBeforeMove).not.toMatch(transformAfterDragHandleMove);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe('deleting', () => {
|
||||||
|
test('deleting a node and its edges', async ({ page }) => {
|
||||||
|
const node = page.locator(`.${FRAMEWORK}-flow__node`).and(page.locator('[data-id="Node-1"]'));
|
||||||
|
|
||||||
|
await node.click();
|
||||||
|
await page.keyboard.press('Backspace');
|
||||||
|
|
||||||
|
await expect(node).not.toBeAttached();
|
||||||
|
|
||||||
|
const edges = await page.locator(`.${FRAMEWORK}-flow__edge`).all();
|
||||||
|
expect(edges).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('deletable=false prevents deletion', async ({ page }) => {
|
||||||
|
const node = page.locator(`.${FRAMEWORK}-flow__node`).and(page.locator('[data-id="notDeletable"]'));
|
||||||
|
|
||||||
|
await node.click();
|
||||||
|
await page.keyboard.press('Backspace');
|
||||||
|
|
||||||
|
await expect(node).toBeAttached();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe('connecting', () => {
|
||||||
|
test('connecting two nodes', async ({ page }) => {
|
||||||
|
const outputSourceHandle = page.locator(`.${FRAMEWORK}-flow__handle`).and(page.locator('[data-nodeid="Node-1"]'));
|
||||||
|
const inputSourceHandle = page.locator(`.${FRAMEWORK}-flow__handle`).and(page.locator('[data-nodeid="Node-4"]'));
|
||||||
|
|
||||||
|
await expect(outputSourceHandle).toBeInViewport();
|
||||||
|
await expect(inputSourceHandle).toBeInViewport();
|
||||||
|
|
||||||
|
const edgesBefore = await page.locator(`.${FRAMEWORK}-flow__edge`).all();
|
||||||
|
|
||||||
|
await outputSourceHandle.hover();
|
||||||
|
await page.mouse.down();
|
||||||
|
await inputSourceHandle.hover();
|
||||||
|
await page.mouse.up();
|
||||||
|
|
||||||
|
const edgesAfter = await page.locator(`.${FRAMEWORK}-flow__edge`).all();
|
||||||
|
expect(edgesAfter).toHaveLength(edgesBefore.length + 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('connecting two output handles does not work', async ({ page }) => {
|
||||||
|
const firstOutputHandle = page.locator(`.${FRAMEWORK}-flow__handle`).and(page.locator('[data-nodeid="Node-2"]'));
|
||||||
|
const secondOutputHandle = page.locator(`.${FRAMEWORK}-flow__handle`).and(page.locator('[data-nodeid="Node-4"]'));
|
||||||
|
|
||||||
|
await expect(firstOutputHandle).toBeInViewport();
|
||||||
|
await expect(secondOutputHandle).toBeInViewport();
|
||||||
|
|
||||||
|
const edgesBefore = await page.locator(`.${FRAMEWORK}-flow__edge`).all();
|
||||||
|
|
||||||
|
await firstOutputHandle.hover();
|
||||||
|
await page.mouse.down();
|
||||||
|
await secondOutputHandle.hover();
|
||||||
|
await page.mouse.up();
|
||||||
|
|
||||||
|
const edgesAfter = await page.locator(`.${FRAMEWORK}-flow__edge`).all();
|
||||||
|
expect(edgesAfter).toHaveLength(edgesBefore.length);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('connecting two input handles does not work', async ({ page }) => {
|
||||||
|
const firstInputHandle = page.locator(`.${FRAMEWORK}-flow__handle`).and(page.locator('[data-nodeid="Node-1"]'));
|
||||||
|
const secondInputHandle = page
|
||||||
|
.locator(`.${FRAMEWORK}-flow__handle`)
|
||||||
|
.and(page.locator('[data-nodeid="Node-3"]'))
|
||||||
|
.and(page.locator('.source'));
|
||||||
|
|
||||||
|
await expect(firstInputHandle).toBeInViewport();
|
||||||
|
await expect(secondInputHandle).toBeInViewport();
|
||||||
|
|
||||||
|
const edgesBefore = await page.locator(`.${FRAMEWORK}-flow__edge`).all();
|
||||||
|
|
||||||
|
await firstInputHandle.hover();
|
||||||
|
await page.mouse.down();
|
||||||
|
await secondInputHandle.hover();
|
||||||
|
await page.mouse.up();
|
||||||
|
|
||||||
|
const edgesAfter = await page.locator(`.${FRAMEWORK}-flow__edge`).all();
|
||||||
|
expect(edgesAfter).toHaveLength(edgesBefore.length);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('connectable=false prevents connections', async ({ page }) => {
|
||||||
|
const outputHandle = page.locator(`.${FRAMEWORK}-flow__handle`).and(page.locator('[data-nodeid="Node-1"]'));
|
||||||
|
const notConnectableHandle = page
|
||||||
|
.locator(`.${FRAMEWORK}-flow__handle`)
|
||||||
|
.and(page.locator('[data-nodeid="notConnectable"]'));
|
||||||
|
|
||||||
|
const notConnectableBox = await notConnectableHandle.boundingBox();
|
||||||
|
|
||||||
|
await expect(outputHandle).toBeInViewport();
|
||||||
|
await expect(notConnectableHandle).toBeInViewport();
|
||||||
|
|
||||||
|
const edgesBefore = await page.locator(`.${FRAMEWORK}-flow__edge`).all();
|
||||||
|
|
||||||
|
await outputHandle.hover();
|
||||||
|
await page.mouse.down();
|
||||||
|
await page.mouse.move(notConnectableBox!.x + 2, notConnectableBox!.y + 2);
|
||||||
|
await page.mouse.up();
|
||||||
|
|
||||||
|
const edgesAfter = await page.locator(`.${FRAMEWORK}-flow__edge`).all();
|
||||||
|
expect(edgesAfter).toHaveLength(edgesBefore.length);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('hidden=true hides the node', async ({ page }) => {
|
||||||
|
const node = page.locator(`.${FRAMEWORK}-flow__node`).and(page.locator('[data-id="hidden"]'));
|
||||||
|
|
||||||
|
await expect(node).not.toBeInViewport();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('classes get applied', async ({ page }) => {
|
||||||
|
const node = page.locator(`.${FRAMEWORK}-flow__node`).and(page.locator('[data-id="Node-1"]'));
|
||||||
|
|
||||||
|
await expect(node).toHaveClass(/playwright-test-class-123/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('styles get applied', async ({ page }) => {
|
||||||
|
const node = page.locator(`.${FRAMEWORK}-flow__node`).and(page.locator('[data-id="Node-1"]'));
|
||||||
|
|
||||||
|
await expect(node).toHaveCSS('background-color', 'rgb(255, 0, 0)');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user