test(components): add component tests, use ts for e2e tests

This commit is contained in:
moklick
2022-08-22 18:23:38 +02:00
parent 7b8b368572
commit 438a318d8f
21 changed files with 243 additions and 112 deletions

View File

@@ -7,4 +7,11 @@ module.exports = defineConfig({
viewportHeight: 720,
video: false,
},
component: {
devServer: {
framework: 'next',
bundler: 'webpack',
},
},
});

View File

@@ -29,9 +29,7 @@ describe('Basic Flow Rendering', () => {
});
it('selects a node by click', () => {
cy.get('.react-flow__node:first')
.click({ force: true })
.should('have.class', 'selected');
cy.get('.react-flow__node:first').click({ force: true }).should('have.class', 'selected');
});
it('deselects node', () => {
@@ -40,9 +38,7 @@ describe('Basic Flow Rendering', () => {
});
it('selects an edge by click', () => {
cy.get('.react-flow__edge:first')
.click({ force: true })
.should('have.class', 'selected');
cy.get('.react-flow__edge:first').click({ force: true }).should('have.class', 'selected');
});
it('deselects edge', () => {
@@ -93,17 +89,13 @@ describe('Basic Flow Rendering', () => {
});
it('selects an edge', () => {
cy.get('.react-flow__edge:first')
.click({ force: true })
.should('have.class', 'selected');
cy.get('.react-flow__edge:first').click({ force: true }).should('have.class', 'selected');
});
it('drags a node', () => {
const styleBeforeDrag = Cypress.$('.react-flow__node:first').css(
'transform'
);
const styleBeforeDrag = Cypress.$('.react-flow__node:first').css('transform');
cy.drag('.react-flow__node:first', { x: 500, y: 25 }).then(($el) => {
cy.drag('.react-flow__node:first', { x: 500, y: 25 }).then(($el: any) => {
const styleAfterDrag = $el.css('transform');
expect(styleBeforeDrag).to.not.equal(styleAfterDrag);
});
@@ -153,9 +145,7 @@ describe('Basic Flow Rendering', () => {
.wait(50)
.trigger('mouseup', { force: true, view: win })
.then(() => {
const styleAfterDrag = Cypress.$('.react-flow__viewport').css(
'transform'
);
const styleAfterDrag = Cypress.$('.react-flow__viewport').css('transform');
expect(styleBeforeDrag).to.not.equal(styleAfterDrag);
});
});
@@ -168,10 +158,10 @@ describe('Basic Flow Rendering', () => {
.trigger('wheel', 'topLeft', { deltaY: -200 })
.wait(50)
.then(() => {
const styleAfterZoom = Cypress.$('.react-flow__viewport').css(
'transform'
);
const styleAfterZoom = Cypress.$('.react-flow__viewport').css('transform');
expect(styleBeforeZoom).to.not.equal(styleAfterZoom);
});
});
});
export {};

View File

@@ -79,3 +79,5 @@ describe('Controls Testing', () => {
});
});
});
export {};

View File

@@ -28,3 +28,5 @@ describe('DragHandle Flow Rendering', () => {
});
});
});
export {};

View File

@@ -10,12 +10,15 @@ describe('Empty Flow Rendering', () => {
});
it('renders empty selection', () => {
cy.get('.react-flow__renderer').click();
cy.get('body')
.type('{shift}', { release: false })
.wait(50)
.get('.react-flow__selectionpane')
.trigger('mousedown', 'topLeft', { which: 1, force: true })
.trigger('mousemove', 'bottomLeft', { which: 1 })
.trigger('mouseup', 'bottomLeft', { force: true });
.trigger('mousedown', 400, 50, { which: 1, force: true })
.trigger('mousemove', 200, 200, { which: 1 })
.wait(50)
.trigger('mouseup', 200, 200, { force: true });
cy.get('body').type('{shift}', { release: true });
});
@@ -26,10 +29,7 @@ describe('Empty Flow Rendering', () => {
});
it('connects nodes', () => {
cy.get('.react-flow__node')
.first()
.find('.react-flow__handle.source')
.trigger('mousedown', { button: 0 });
cy.get('.react-flow__node').first().find('.react-flow__handle.source').trigger('mousedown', { button: 0 });
cy.get('.react-flow__node')
.last()
@@ -40,3 +40,5 @@ describe('Empty Flow Rendering', () => {
cy.get('.react-flow__edge').should('have.length', 1);
});
});
export {};

View File

@@ -1,12 +1,6 @@
import {
isNode,
isEdge,
getOutgoers,
getIncomers,
addEdge,
} from '../../packages/core/dist/react-flow-core.esm.js';
import { Node, Edge, isNode, isEdge, getOutgoers, getIncomers, addEdge } from '@react-flow/bundle';
const nodes = [
const nodes: Node[] = [
{
id: '1',
type: 'input',
@@ -18,7 +12,7 @@ const nodes = [
{ id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 200 } },
];
const edges = [
const edges: Edge[] = [
{ id: 'e1-2', source: '1', target: '2', animated: true },
{ id: 'e1-3', source: '1', target: '3' },
{ id: 'e2-3', source: '2', target: '3' },
@@ -54,31 +48,32 @@ describe('Graph Utils Testing', () => {
describe('tests addEdge function', () => {
it('adds edge', () => {
const newEdge = { source: '1', target: '4' };
const newEdge: Edge = { source: '1', target: '4', id: 'new-edge-1-4' };
const nextEdges = addEdge(newEdge, edges);
expect(nextEdges.length).to.be.equal(edges.length + 1);
});
it('tries to add existing edge', () => {
const newEdge = { source: '2', target: '3' };
const newEdge: Edge = { source: '2', target: '3', id: 'new-edge-2-3' };
const nextEdges = addEdge(newEdge, edges);
expect(nextEdges.length).to.be.equal(edges.length);
});
it('tries to add invalid edge', () => {
const newEdge = { nosource: '1', notarget: '3' };
// @ts-ignore
const newEdge: Edge = { nosource: '1', notarget: '3' };
try {
addEdge(newEdge, edges);
} catch (e) {
} catch (e: any) {
console.log(e.message);
expect(e.message).to.be.equal(
"Can't create edge. An edge needs a source and a target."
);
expect(e.message).to.be.equal("Can't create edge. An edge needs a source and a target.");
}
});
});
});
export {};

View File

@@ -30,3 +30,5 @@ describe('Hidden Flow Rendering', () => {
cy.get('.react-flow__minimap-node').should('not.exist');
});
});
export {};

View File

@@ -11,16 +11,12 @@ describe('Interaction Flow Rendering', () => {
});
it('tries to select a node by click', () => {
const pointerEvents = Cypress.$('.react-flow__node:first').css(
'pointer-events'
);
const pointerEvents = Cypress.$('.react-flow__node:first').css('pointer-events');
expect(pointerEvents).to.equal('none');
});
it('tries to select an edge by click', () => {
const pointerEvents = Cypress.$('.react-flow__edge:first').css(
'pointer-events'
);
const pointerEvents = Cypress.$('.react-flow__edge:first').css('pointer-events');
expect(pointerEvents).to.equal('none');
});
@@ -29,24 +25,17 @@ describe('Interaction Flow Rendering', () => {
});
it('allows node clicks when enabled', () => {
const pointerEvents = Cypress.$('.react-flow__node:first').css(
'pointer-events'
);
const pointerEvents = Cypress.$('.react-flow__node:first').css('pointer-events');
expect(pointerEvents).to.equal('all');
});
it('allows edge clicks when enabled', () => {
const pointerEvents = Cypress.$('.react-flow__edge:first').css(
'pointer-events'
);
const pointerEvents = Cypress.$('.react-flow__edge:first').css('pointer-events');
expect(pointerEvents.toLowerCase()).to.equal('visiblestroke');
});
it('tries to do a selection', () => {
cy.get('body')
.type('{shift}', { release: false })
.get('.react-flow__selectionpane')
.should('not.exist');
cy.get('body').type('{shift}', { release: false }).get('.react-flow__selectionpane').should('not.exist');
cy.get('body').type('{shift}', { release: true });
});
@@ -87,9 +76,7 @@ describe('Interaction Flow Rendering', () => {
});
it('drags a node', () => {
const styleBeforeDrag = Cypress.$('.react-flow__node:first').css(
'transform'
);
const styleBeforeDrag = Cypress.$('.react-flow__node:first').css('transform');
cy.drag('.react-flow__node:first', { x: 325, y: 100 }).then(($el) => {
const styleAfterDrag = $el.css('transform');
@@ -140,9 +127,7 @@ describe('Interaction Flow Rendering', () => {
.trigger('wheel', 'topLeft', { deltaY: 200 })
.wait(50)
.then(() => {
const styleAfterZoom = Cypress.$('.react-flow__viewport').css(
'transform'
);
const styleAfterZoom = Cypress.$('.react-flow__viewport').css('transform');
expect(styleBeforeZoom).not.to.equal(styleAfterZoom);
});
});
@@ -159,10 +144,10 @@ describe('Interaction Flow Rendering', () => {
.dblclick()
.wait(50)
.then(() => {
const styleAfterZoom = Cypress.$('.react-flow__viewport').css(
'transform'
);
const styleAfterZoom = Cypress.$('.react-flow__viewport').css('transform');
expect(styleBeforeZoom).not.to.equal(styleAfterZoom);
});
});
});
export {};

View File

@@ -21,19 +21,15 @@ describe('Minimap Testing', () => {
});
it('changes zoom level', () => {
const viewBoxBeforeZoom = Cypress.$('.react-flow__minimap').attr('viewBox');
const viewBoxBeforeZoom = Cypress.$('.react-flow__minimap svg').attr('viewBox');
const maskPathBeforeZoom = Cypress.$('.react-flow__minimap-mask').attr('d');
cy.get('.react-flow__pane')
.trigger('wheel', 'topLeft', { deltaY: -200 })
.wait(50)
.then(() => {
const viewBoxAfterZoom = Cypress.$('.react-flow__minimap').attr(
'viewBox'
);
const maskPathAfterZoom = Cypress.$('.react-flow__minimap-mask').attr(
'd'
);
const viewBoxAfterZoom = Cypress.$('.react-flow__minimap svg').attr('viewBox');
const maskPathAfterZoom = Cypress.$('.react-flow__minimap-mask').attr('d');
expect(viewBoxBeforeZoom).to.not.equal(viewBoxAfterZoom);
expect(maskPathBeforeZoom).to.not.equal(maskPathAfterZoom);
@@ -41,22 +37,14 @@ describe('Minimap Testing', () => {
});
it('changes node position', () => {
const xPosBeforeDrag = Cypress.$('.react-flow__minimap-node:first').attr(
'x'
);
const yPosBeforeDrag = Cypress.$('.react-flow__minimap-node:first').attr(
'y'
);
const xPosBeforeDrag = Cypress.$('.react-flow__minimap-node:first').attr('x');
const yPosBeforeDrag = Cypress.$('.react-flow__minimap-node:first').attr('y');
cy.drag('.react-flow__node:first', { x: 500, y: 25 })
.wait(100)
.then(() => {
const xPosAfterDrag = Cypress.$('.react-flow__minimap-node:first').attr(
'x'
);
const yPosAfterDrag = Cypress.$('.react-flow__minimap-node:first').attr(
'y'
);
const xPosAfterDrag = Cypress.$('.react-flow__minimap-node:first').attr('x');
const yPosAfterDrag = Cypress.$('.react-flow__minimap-node:first').attr('y');
expect(xPosBeforeDrag).to.not.equal(xPosAfterDrag);
expect(yPosBeforeDrag).to.not.equal(yPosAfterDrag);
@@ -64,7 +52,7 @@ describe('Minimap Testing', () => {
});
it('changes node positions via pane drag', () => {
const viewBoxBeforeDrag = Cypress.$('.react-flow__minimap').attr('viewBox');
const viewBoxBeforeDrag = Cypress.$('.react-flow__minimap svg').attr('viewBox');
const maskPathBeforeDrag = Cypress.$('.react-flow__minimap-mask').attr('d');
// for d3 we have to pass the window to the event
@@ -76,12 +64,8 @@ describe('Minimap Testing', () => {
.wait(50)
.trigger('mouseup', { force: true, view: win })
.then(() => {
const viewBoxAfterDrag = Cypress.$('.react-flow__minimap').attr(
'viewBox'
);
const maskPathAfterDrag = Cypress.$('.react-flow__minimap-mask').attr(
'd'
);
const viewBoxAfterDrag = Cypress.$('.react-flow__minimap svg').attr('viewBox');
const maskPathAfterDrag = Cypress.$('.react-flow__minimap-mask').attr('d');
expect(viewBoxBeforeDrag).to.not.equal(viewBoxAfterDrag);
expect(maskPathBeforeDrag).to.not.equal(maskPathAfterDrag);
@@ -89,3 +73,5 @@ describe('Minimap Testing', () => {
});
});
});
export {};

View File

@@ -0,0 +1,5 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}

View File

@@ -1,3 +1,5 @@
/// <reference types="cypress" />
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
@@ -25,14 +27,14 @@
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
Cypress.Commands.add('drag', (selector, { x, y }) => {
return cy
.window()
.then((window) =>
cy
.get(selector)
.trigger('mousedown', { which: 1, view: window })
.trigger('mousemove', { clientX: x, clientY: y, force: true })
.wait(50)
.trigger('mouseup', { view: window, force: true })
);
return cy.window().then((window) =>
cy
.get(selector as string)
.trigger('mousedown', { which: 1, view: window })
.trigger('mousemove', { clientX: x, clientY: y, force: true })
.wait(50)
.trigger('mouseup', { view: window, force: true })
);
});
export {};

View File

@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Components App</title>
<!-- Used by Next.js to inject CSS. -->
<div id="__next_css__DO_NOT_USE__"></div>
</head>
<body>
<div data-cy-root id="root"></div>
</body>
</html>

View File

@@ -0,0 +1,39 @@
// ***********************************************************
// This example support/component.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import './commands';
// Alternatively you can use CommonJS syntax:
// require('./commands')
import { mount } from 'cypress/react18';
import '../../styles/globals.css';
import '../../styles/rf-style.css';
declare global {
namespace Cypress {
interface Chainable {
mount: typeof mount;
drag: (selector: string, { x, y }: { x: number; y: number }) => Cypress.Chainable<JQuery<HTMLElement>>;
}
}
}
Cypress.Commands.add('mount', mount);
// Example use:
// cy.mount(<MyComponent />)

View File

@@ -1,9 +1,16 @@
const withPreconstruct = require('@preconstruct/next');
const { patchWebpackConfig } = require('next-global-css');
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
swcMinify: true,
webpack: (config, options) => {
if (process.env.CYPRESS === 'true') {
//Allows importing the global.css file in cypress/support/component.ts
patchWebpackConfig(config, options);
}
return config;
},
};
module.exports = withPreconstruct({ ...nextConfig });

View File

@@ -8,7 +8,8 @@
"copystyles": "cp ../../node_modules/@react-flow/bundle/dist/style.css ./styles/rf-style.css && cp ../../node_modules/@react-flow/bundle/dist/base.css ./styles/rf-base.css",
"build": "next build",
"start": "next start",
"lint": "next lint"
"lint": "next lint",
"cypress": "cypress"
},
"dependencies": {
"@preconstruct/next": "^4.0.0",
@@ -21,8 +22,10 @@
},
"devDependencies": {
"@types/dagre": "^0.7.47",
"cypress": "^10.6.0",
"eslint": "^8.22.0",
"eslint-config-next": "12.2.2",
"next-global-css": "^1.3.1",
"postcss": "^8.4.16",
"postcss-flexbugs-fixes": "^5.0.2",
"postcss-import": "^14.1.0",

View File

@@ -5,7 +5,8 @@ body {
html,
body,
#__next {
#__next,
#root {
margin: 0;
height: 100%;
}

View File

@@ -0,0 +1,76 @@
import { ReactFlow, useOnViewportChange, Viewport } from '@react-flow/bundle';
describe('useOnViewportChange.cy.js', () => {
it('listen to viewport drag', () => {
let startViewport = {};
let changeViewport = {};
let endViewport = {};
const onStart = (viewport: Viewport) => {
startViewport = viewport;
};
const onChange = (viewport: Viewport) => {
changeViewport = viewport;
};
const onEnd = (viewport: Viewport) => {
endViewport = viewport;
};
cy.mount(
<ReactFlow>
<OnViewportChange onStart={onStart} onChange={onChange} onEnd={onEnd} />
</ReactFlow>
);
cy.window().then((win: any) => {
cy.get('.react-flow__pane')
.trigger('mousedown', 50, 50, { which: 1, view: win })
.trigger('mousemove', 50, 100)
.trigger('mouseup', { force: true, view: win })
.then(() => {
expect(startViewport).to.eql({ x: 0, y: 0, zoom: 1 });
expect(changeViewport).to.not.eql({ x: 0, y: 0, zoom: 1 });
expect(endViewport).to.eql({ x: 0, y: 50, zoom: 1 });
});
});
});
it('handles default viewport', () => {
const defaultViewport = { x: 100, y: 100, zoom: 0.5 };
let startViewport = {};
const onStart = (viewport: Viewport) => {
startViewport = viewport;
};
cy.mount(
<ReactFlow defaultViewport={defaultViewport}>
<OnViewportChange onStart={onStart} />
</ReactFlow>
);
cy.window().then((win: any) => {
cy.get('.react-flow__pane')
.trigger('mousedown', 50, 50, { which: 1, view: win })
.trigger('mousemove', 50, 100)
.trigger('mouseup', { force: true, view: win })
.then(() => {
expect(startViewport).to.eql(defaultViewport);
});
});
});
});
function OnViewportChange({
onStart,
onChange,
onEnd,
}: {
onStart?: (viewport: Viewport) => void;
onChange?: (viewport: Viewport) => void;
onEnd?: (viewport: Viewport) => void;
}) {
useOnViewportChange({ onStart, onChange, onEnd });
return null;
}

View File

@@ -4,16 +4,16 @@
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"incremental": true,
"esModuleInterop": true,
"module": "esnext",
"resolveJsonModule": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve"
"jsx": "preserve",
"incremental": true
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]

View File

@@ -15,8 +15,8 @@
"build": "preconstruct build && yarn run packages",
"packages": "yarn workspaces foreach --include '@react-flow/**' run build",
"test:all": "yarn test:chrome && yarn test:firefox",
"test:chrome": "cypress run --browser chrome",
"test:firefox": "cypress run --browser firefox",
"test:chrome": "cd examples/nextjs && cypress run --browser chrome",
"test:firefox": "cd examples/nextjs && cypress run --browser firefox",
"test": "yarn build && start-server-and-test dev:example http://localhost:3000 test:chrome",
"release": "changeset publish"
},

View File

@@ -2295,6 +2295,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "@react-flow/background@workspace:packages/background"
dependencies:
"@babel/runtime": ^7.18.9
"@react-flow/core": "workspace:*"
classcat: ^5.0.3
peerDependencies:
@@ -2307,6 +2308,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "@react-flow/bundle@workspace:packages/bundle"
dependencies:
"@babel/runtime": ^7.18.9
"@react-flow/background": "workspace:*"
"@react-flow/controls": "workspace:*"
"@react-flow/core": "workspace:*"
@@ -2327,6 +2329,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "@react-flow/controls@workspace:packages/controls"
dependencies:
"@babel/runtime": ^7.18.9
"@react-flow/core": "workspace:*"
autoprefixer: ^10.4.8
classcat: ^5.0.3
@@ -2369,6 +2372,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "@react-flow/minimap@workspace:packages/minimap"
dependencies:
"@babel/runtime": ^7.18.9
"@react-flow/core": "workspace:*"
autoprefixer: ^10.4.8
classcat: ^5.0.3
@@ -6923,6 +6927,13 @@ __metadata:
languageName: node
linkType: hard
"next-global-css@npm:^1.3.1":
version: 1.3.1
resolution: "next-global-css@npm:1.3.1"
checksum: fdb85e2ea614e17dc2d6f3f5064644346de9458774844f50a398c2b71d3d320717713d6f915fa8b91039956440900e6166a8b1bc19d3091c31aed582495f8a0f
languageName: node
linkType: hard
"next@npm:12.2.2":
version: 12.2.2
resolution: "next@npm:12.2.2"
@@ -8167,11 +8178,13 @@ __metadata:
"@preconstruct/next": ^4.0.0
"@react-flow/bundle": "workspace:*"
"@types/dagre": ^0.7.47
cypress: ^10.6.0
dagre: ^0.8.5
eslint: ^8.22.0
eslint-config-next: 12.2.2
localforage: ^1.10.0
next: 12.2.2
next-global-css: ^1.3.1
postcss: ^8.4.16
postcss-flexbugs-fixes: ^5.0.2
postcss-import: ^14.1.0