test(components): add component tests, use ts for e2e tests
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
const { defineConfig } = require('cypress');
|
||||
|
||||
module.exports = defineConfig({
|
||||
e2e: {
|
||||
baseUrl: 'http://localhost:3000',
|
||||
viewportWidth: 1280,
|
||||
viewportHeight: 720,
|
||||
video: false,
|
||||
},
|
||||
|
||||
component: {
|
||||
devServer: {
|
||||
framework: 'next',
|
||||
bundler: 'webpack',
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,167 @@
|
||||
describe('Basic Flow Rendering', () => {
|
||||
before(() => {
|
||||
cy.visit('/');
|
||||
});
|
||||
|
||||
it('renders a flow with three nodes', () => {
|
||||
cy.get('.react-flow__renderer');
|
||||
cy.get('.react-flow-basic-example'); // check if className prop works
|
||||
cy.get('.react-flow__node').should('have.length', 4);
|
||||
cy.get('.react-flow__edge').should('have.length', 2);
|
||||
cy.get('.react-flow__node').children('.react-flow__handle');
|
||||
});
|
||||
|
||||
it('renders a grid', () => {
|
||||
cy.get('.react-flow__background');
|
||||
});
|
||||
|
||||
it('selects two nodes by clicks', () => {
|
||||
cy.get('body').type('{cmd}', { release: false });
|
||||
cy.get('.react-flow__node:first')
|
||||
.click()
|
||||
.should('have.class', 'selected')
|
||||
.get('.react-flow__node:last')
|
||||
.click()
|
||||
.should('have.class', 'selected')
|
||||
.get('.react-flow__node:first')
|
||||
.should('have.class', 'selected');
|
||||
cy.get('body').type('{cmd}', { release: true });
|
||||
});
|
||||
|
||||
it('selects a node by click', () => {
|
||||
cy.get('.react-flow__node:first').click({ force: true }).should('have.class', 'selected');
|
||||
});
|
||||
|
||||
it('deselects node', () => {
|
||||
cy.get('.react-flow__renderer').click('bottomLeft');
|
||||
cy.get('.react-flow__node:first').should('not.have.class', 'selected');
|
||||
});
|
||||
|
||||
it('selects an edge by click', () => {
|
||||
cy.get('.react-flow__edge:first').click({ force: true }).should('have.class', 'selected');
|
||||
});
|
||||
|
||||
it('deselects edge', () => {
|
||||
cy.get('.react-flow__renderer').click('bottomLeft');
|
||||
cy.get('.react-flow__edge:first').should('not.have.class', 'selected');
|
||||
});
|
||||
|
||||
it('selects one node with a selection', () => {
|
||||
cy.get('body')
|
||||
.type('{shift}', { release: false })
|
||||
.wait(50)
|
||||
.get('.react-flow__selectionpane')
|
||||
.trigger('mousedown', 1000, 50, { which: 1, force: true })
|
||||
.trigger('mousemove', 1, 400, { which: 1 })
|
||||
.wait(50)
|
||||
.trigger('mouseup', 1, 200, { force: true });
|
||||
|
||||
cy.wait(100);
|
||||
|
||||
cy.get('.react-flow__node').eq(1).should('have.class', 'selected');
|
||||
|
||||
cy.get('.react-flow__node').eq(0).should('have.not.class', 'selected');
|
||||
|
||||
cy.get('.react-flow__nodesselection-rect');
|
||||
|
||||
cy.get('body').type('{shift}', { release: true, force: true });
|
||||
});
|
||||
|
||||
it('selects all nodes', () => {
|
||||
cy.get('body')
|
||||
.type('{shift}', { release: false })
|
||||
.get('.react-flow__selectionpane')
|
||||
.trigger('mousedown', 'topRight', { which: 1, force: true })
|
||||
.trigger('mousemove', 'bottomLeft', { which: 1 })
|
||||
.wait(50)
|
||||
.trigger('mouseup', 'bottomLeft', { force: true })
|
||||
.wait(50)
|
||||
.get('.react-flow__node')
|
||||
.should('have.class', 'selected')
|
||||
.get('.react-flow__nodesselection-rect');
|
||||
|
||||
cy.get('body').type('{shift}', { release: true });
|
||||
});
|
||||
|
||||
it('removes selection', () => {
|
||||
cy.get('.react-flow__renderer').click('bottomLeft');
|
||||
cy.get('.react-flow__nodesselection-rect').should('not.exist');
|
||||
});
|
||||
|
||||
it('selects an edge', () => {
|
||||
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');
|
||||
|
||||
cy.drag('.react-flow__node:first', { x: 500, y: 25 }).then(($el: any) => {
|
||||
const styleAfterDrag = $el.css('transform');
|
||||
expect(styleBeforeDrag).to.not.equal(styleAfterDrag);
|
||||
});
|
||||
});
|
||||
|
||||
// @TODO: why does this fail since react18?
|
||||
// it('removes a node', () => {
|
||||
// cy.get('.react-flow__node').contains('Node 1').click().should('have.class', 'selected');
|
||||
// cy.get('html').type('{backspace}').wait(100);
|
||||
// cy.get('.react-flow__node').should('have.length', 3);
|
||||
// cy.get('.react-flow__edge').should('have.length', 1);
|
||||
// });
|
||||
|
||||
it('connects nodes', () => {
|
||||
cy.get('.react-flow__node')
|
||||
.contains('Node 3')
|
||||
.find('.react-flow__handle.source')
|
||||
.trigger('mousedown', { button: 0 });
|
||||
|
||||
cy.get('.react-flow__node')
|
||||
.contains('Node 4')
|
||||
.find('.react-flow__handle.target')
|
||||
.trigger('mousemove', { force: true })
|
||||
.wait(50)
|
||||
.trigger('mouseup', { force: true });
|
||||
|
||||
cy.get('.react-flow__edge').should('have.length', 3);
|
||||
});
|
||||
|
||||
// @TODO: why does this fail since react18?
|
||||
// it('removes an edge', () => {
|
||||
// cy.get('.react-flow__edge:first').click();
|
||||
// cy.get('body').type('{backspace}');
|
||||
|
||||
// cy.get('.react-flow__edge').should('have.length', 1);
|
||||
// });
|
||||
|
||||
it('drags the pane', () => {
|
||||
const styleBeforeDrag = Cypress.$('.react-flow__viewport').css('transform');
|
||||
|
||||
// for d3 we have to pass the window to the event
|
||||
// https://github.com/cypress-io/cypress/issues/3441
|
||||
cy.window().then((win) => {
|
||||
cy.get('.react-flow__pane')
|
||||
.trigger('mousedown', 'topLeft', { which: 1, view: win })
|
||||
.trigger('mousemove', 'bottomLeft')
|
||||
.wait(50)
|
||||
.trigger('mouseup', { force: true, view: win })
|
||||
.then(() => {
|
||||
const styleAfterDrag = Cypress.$('.react-flow__viewport').css('transform');
|
||||
expect(styleBeforeDrag).to.not.equal(styleAfterDrag);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('zooms the pane', () => {
|
||||
const styleBeforeZoom = Cypress.$('.react-flow__viewport').css('transform');
|
||||
|
||||
cy.get('.react-flow__pane')
|
||||
.trigger('wheel', 'topLeft', { deltaY: -200 })
|
||||
.wait(50)
|
||||
.then(() => {
|
||||
const styleAfterZoom = Cypress.$('.react-flow__viewport').css('transform');
|
||||
expect(styleBeforeZoom).to.not.equal(styleAfterZoom);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
export {};
|
||||
@@ -0,0 +1,83 @@
|
||||
describe('Controls Testing', () => {
|
||||
before(() => {
|
||||
cy.visit('/');
|
||||
});
|
||||
|
||||
it('renders the control panel', () => {
|
||||
cy.get('.react-flow__controls');
|
||||
});
|
||||
|
||||
it('zooms in', () => {
|
||||
const styleBeforeZoom = Cypress.$('.react-flow__viewport').css('transform');
|
||||
|
||||
cy.get('.react-flow__controls-zoomin')
|
||||
.click()
|
||||
.then(() => {
|
||||
const styleAfterZoom = Cypress.$('.react-flow__viewport').css('transform');
|
||||
expect(styleBeforeZoom).to.not.equal(styleAfterZoom);
|
||||
});
|
||||
});
|
||||
|
||||
it('zooms out', () => {
|
||||
const styleBeforeZoom = Cypress.$('.react-flow__viewport').css('transform');
|
||||
|
||||
cy.get('.react-flow__controls-zoomout')
|
||||
.click()
|
||||
.then(() => {
|
||||
const styleAfterZoom = Cypress.$('.react-flow__viewport').css('transform');
|
||||
expect(styleBeforeZoom).to.not.equal(styleAfterZoom);
|
||||
});
|
||||
});
|
||||
|
||||
// view is already fitted so we drag the pane to un-fit it
|
||||
it('drags the pane', () => {
|
||||
const styleBeforeDrag = Cypress.$('.react-flow__viewport').css('transform');
|
||||
|
||||
// for d3 we have to pass the window to the event
|
||||
// https://github.com/cypress-io/cypress/issues/3441
|
||||
cy.window().then((win) => {
|
||||
cy.get('.react-flow__renderer')
|
||||
.trigger('mousedown', 'topLeft', { which: 1, view: win })
|
||||
.trigger('mousemove', 10, 400)
|
||||
.wait(50)
|
||||
.trigger('mouseup', 10, 400, { force: true, view: win })
|
||||
.then(() => {
|
||||
const styleAfterDrag = Cypress.$('.react-flow__viewport').css('transform');
|
||||
expect(styleBeforeDrag).to.not.equal(styleAfterDrag);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('fits view', () => {
|
||||
const styleBeforeZoom = Cypress.$('.react-flow__viewport').css('transform');
|
||||
|
||||
cy.get('.react-flow__controls-fitview')
|
||||
.click()
|
||||
.then(() => {
|
||||
const styleAfterZoom = Cypress.$('.react-flow__viewport').css('transform');
|
||||
expect(styleBeforeZoom).to.not.equal(styleAfterZoom);
|
||||
});
|
||||
});
|
||||
|
||||
it('uses interactive control - not interactive', () => {
|
||||
cy.get('.react-flow__node:first').click().should('have.class', 'selected');
|
||||
cy.get('.react-flow__pane').click('topLeft');
|
||||
cy.get('.react-flow__node:first').should('not.have.class', 'selected');
|
||||
|
||||
cy.get('.react-flow__controls-interactive')
|
||||
.click()
|
||||
.then(() => {
|
||||
cy.get('.react-flow__node:first').should('not.have.class', 'selected');
|
||||
});
|
||||
});
|
||||
|
||||
it('uses interactive control - interactive', () => {
|
||||
cy.get('.react-flow__controls-interactive')
|
||||
.click()
|
||||
.then(() => {
|
||||
cy.get('.react-flow__node:first').click({ force: true }).should('have.class', 'selected');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
export {};
|
||||
@@ -0,0 +1,32 @@
|
||||
describe('DragHandle Flow Rendering', () => {
|
||||
before(() => {
|
||||
cy.visit('/DragHandle');
|
||||
});
|
||||
|
||||
it('renders a flow with a node', () => {
|
||||
cy.get('.react-flow__renderer');
|
||||
cy.get('.react-flow__node').should('have.length', 1);
|
||||
});
|
||||
|
||||
it('tries to drag a node', () => {
|
||||
const $nodeElement = Cypress.$('.react-flow__node:first');
|
||||
const styleBeforeDrag = $nodeElement.css('transform');
|
||||
|
||||
cy.drag('.react-flow__node:first', { x: 500, y: 500 }).then(() => {
|
||||
const styleAfterDrag = $nodeElement.css('transform');
|
||||
expect(styleBeforeDrag).to.be.equal(styleAfterDrag);
|
||||
});
|
||||
});
|
||||
|
||||
it('drags a node', () => {
|
||||
const $nodeElement = Cypress.$('.react-flow__node:first');
|
||||
const styleBeforeDrag = $nodeElement.css('transform');
|
||||
|
||||
cy.drag('.custom-drag-handle:first', { x: 500, y: 500 }).then(() => {
|
||||
const styleAfterDrag = $nodeElement.css('transform');
|
||||
expect(styleBeforeDrag).to.not.equal(styleAfterDrag);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
export {};
|
||||
@@ -0,0 +1,44 @@
|
||||
describe('Empty Flow Rendering', () => {
|
||||
before(() => {
|
||||
cy.visit('/Empty');
|
||||
});
|
||||
|
||||
it('renders an empty flow', () => {
|
||||
cy.get('.react-flow__renderer');
|
||||
cy.get('.react-flow__node').should('not.exist');
|
||||
cy.get('.react-flow__edge').should('not.exist');
|
||||
});
|
||||
|
||||
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', 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 });
|
||||
});
|
||||
|
||||
it('adds two nodes', () => {
|
||||
cy.contains('add node').click();
|
||||
cy.contains('add node').click();
|
||||
});
|
||||
|
||||
it('connects nodes', () => {
|
||||
cy.get('.react-flow__node').first().find('.react-flow__handle.source').trigger('mousedown', { button: 0 });
|
||||
|
||||
cy.get('.react-flow__node')
|
||||
.last()
|
||||
.find('.react-flow__handle.target')
|
||||
.trigger('mousemove')
|
||||
.trigger('mouseup', { force: true });
|
||||
|
||||
cy.get('.react-flow__edge').should('have.length', 1);
|
||||
});
|
||||
});
|
||||
|
||||
export {};
|
||||
@@ -0,0 +1,79 @@
|
||||
import { Node, Edge, isNode, isEdge, getOutgoers, getIncomers, addEdge } from '@react-flow/bundle';
|
||||
|
||||
const nodes: Node[] = [
|
||||
{
|
||||
id: '1',
|
||||
type: 'input',
|
||||
data: { label: 'Node 1' },
|
||||
position: { x: 250, y: 5 },
|
||||
},
|
||||
{ id: '2', data: { label: 'Node 2' }, position: { x: 100, y: 100 } },
|
||||
{ id: '3', data: { label: 'Node 3' }, position: { x: 400, y: 100 } },
|
||||
{ id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 200 } },
|
||||
];
|
||||
|
||||
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' },
|
||||
];
|
||||
|
||||
describe('Graph Utils Testing', () => {
|
||||
it('tests isNode function', () => {
|
||||
expect(isNode(nodes[0])).to.be.true;
|
||||
expect(isNode(edges[0])).to.be.false;
|
||||
});
|
||||
|
||||
it('tests isEdge function', () => {
|
||||
expect(isEdge(edges[0])).to.be.true;
|
||||
expect(isEdge(nodes[0])).to.be.false;
|
||||
});
|
||||
|
||||
it('tests getOutgoers function', () => {
|
||||
const outgoers = getOutgoers(nodes[0], nodes, edges);
|
||||
|
||||
expect(outgoers.length).to.be.equal(2);
|
||||
|
||||
const noOutgoers = getOutgoers(nodes[2], nodes, edges);
|
||||
expect(noOutgoers.length).to.be.equal(0);
|
||||
});
|
||||
|
||||
it('tests getIncomers function', () => {
|
||||
const incomers = getIncomers(nodes[2], nodes, edges);
|
||||
expect(incomers.length).to.be.equal(2);
|
||||
|
||||
const noIncomers = getIncomers(nodes[0], nodes, edges);
|
||||
expect(noIncomers.length).to.be.equal(0);
|
||||
});
|
||||
|
||||
describe('tests addEdge function', () => {
|
||||
it('adds edge', () => {
|
||||
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: 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', () => {
|
||||
// @ts-ignore
|
||||
const newEdge: Edge = { nosource: '1', notarget: '3' };
|
||||
|
||||
try {
|
||||
addEdge(newEdge, edges);
|
||||
} 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.");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
export {};
|
||||
@@ -0,0 +1,34 @@
|
||||
describe('Hidden Flow Rendering', () => {
|
||||
before(() => {
|
||||
cy.visit('/Hidden');
|
||||
});
|
||||
|
||||
it('renders empty flow', () => {
|
||||
cy.get('.react-flow__node').should('not.exist');
|
||||
cy.get('.react-flow__edge').should('not.exist');
|
||||
cy.get('.react-flow__minimap-node').should('not.exist');
|
||||
});
|
||||
|
||||
it('toggles isHidden mode', () => {
|
||||
cy.get('.react-flow__ishidden').click();
|
||||
});
|
||||
|
||||
it('renders initial flow', () => {
|
||||
cy.get('.react-flow__renderer');
|
||||
cy.get('.react-flow__node').should('have.length', 4);
|
||||
cy.get('.react-flow__edge').should('have.length', 3);
|
||||
cy.get('.react-flow__minimap-node').should('have.length', 4);
|
||||
});
|
||||
|
||||
it('toggles isHidden mode again', () => {
|
||||
cy.get('.react-flow__ishidden').click();
|
||||
});
|
||||
|
||||
it('renders empty flow', () => {
|
||||
cy.get('.react-flow__node').should('not.exist');
|
||||
cy.get('.react-flow__edge').should('not.exist');
|
||||
cy.get('.react-flow__minimap-node').should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
export {};
|
||||
@@ -0,0 +1,153 @@
|
||||
describe('Interaction Flow Rendering', () => {
|
||||
before(() => {
|
||||
cy.visit('/Interaction');
|
||||
});
|
||||
|
||||
it('renders initial flow', () => {
|
||||
cy.get('.react-flow__renderer');
|
||||
cy.get('.react-flow__node').should('have.length', 4);
|
||||
cy.get('.react-flow__edge').should('have.length', 2);
|
||||
cy.get('.react-flow__node').children('.react-flow__handle');
|
||||
});
|
||||
|
||||
it('tries to select a node by click', () => {
|
||||
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');
|
||||
expect(pointerEvents).to.equal('none');
|
||||
});
|
||||
|
||||
it('toggles on capture element click', () => {
|
||||
cy.get('.react-flow__captureelementclick').click();
|
||||
});
|
||||
|
||||
it('allows node clicks when enabled', () => {
|
||||
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');
|
||||
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: true });
|
||||
});
|
||||
|
||||
it('tries to connect to nodes', () => {
|
||||
cy.get('.react-flow__node')
|
||||
.contains('Node 3')
|
||||
.find('.react-flow__handle.source')
|
||||
.then(($el) => {
|
||||
const pointerEvents = $el.css('pointer-events');
|
||||
expect(pointerEvents).to.equal('none');
|
||||
});
|
||||
});
|
||||
|
||||
it('tries to zoom by double click', () => {
|
||||
const styleBeforeZoom = Cypress.$('.react-flow__nodes').css('transform');
|
||||
|
||||
cy.get('.react-flow__renderer')
|
||||
.dblclick()
|
||||
.then(() => {
|
||||
const styleAfterZoom = Cypress.$('.react-flow__nodes').css('transform');
|
||||
expect(styleBeforeZoom).to.equal(styleAfterZoom);
|
||||
});
|
||||
});
|
||||
|
||||
it('tries to zoom by scroll', () => {
|
||||
const styleBeforeZoom = Cypress.$('.react-flow__nodes').css('transform');
|
||||
|
||||
cy.get('.react-flow__renderer')
|
||||
.trigger('wheel', 'topLeft', { deltaY: -200 })
|
||||
.then(() => {
|
||||
const styleAfterZoom = Cypress.$('.react-flow__nodes').css('transform');
|
||||
expect(styleBeforeZoom).to.equal(styleAfterZoom);
|
||||
});
|
||||
});
|
||||
|
||||
it('toggles draggable mode', () => {
|
||||
cy.get('.react-flow__draggable').click();
|
||||
});
|
||||
|
||||
it('drags a node', () => {
|
||||
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');
|
||||
expect(styleBeforeDrag).to.not.equal(styleAfterDrag);
|
||||
});
|
||||
});
|
||||
|
||||
it('toggles selectable mode', () => {
|
||||
cy.get('.react-flow__selectable').click();
|
||||
});
|
||||
|
||||
it('selects a node by click', () => {
|
||||
cy.get('.react-flow__node:first').click().should('have.class', 'selected');
|
||||
});
|
||||
|
||||
it('selects an edge by click', () => {
|
||||
cy.get('.react-flow__edge:first').click({ force: true });
|
||||
cy.get('.react-flow__edge:first').should('have.class', 'selected');
|
||||
});
|
||||
|
||||
it('toggles connectable mode', () => {
|
||||
cy.get('.react-flow__connectable').click();
|
||||
});
|
||||
|
||||
it('connects two nodes', () => {
|
||||
cy.get('.react-flow__node')
|
||||
.contains('Node 3')
|
||||
.find('.react-flow__handle.source')
|
||||
.trigger('mousedown', { button: 0 });
|
||||
|
||||
cy.get('.react-flow__node')
|
||||
.contains('Node 4')
|
||||
.find('.react-flow__handle.target')
|
||||
.trigger('mousemove')
|
||||
.trigger('mouseup', { force: true });
|
||||
|
||||
cy.get('.react-flow__edge').should('have.length', 3);
|
||||
});
|
||||
|
||||
it('toggles zoom on scroll', () => {
|
||||
cy.get('.react-flow__zoomonscroll').click();
|
||||
});
|
||||
|
||||
it('zooms by scroll', () => {
|
||||
const styleBeforeZoom = Cypress.$('.react-flow__viewport').css('transform');
|
||||
|
||||
cy.get('.react-flow__pane')
|
||||
.trigger('wheel', 'topLeft', { deltaY: 200 })
|
||||
.wait(50)
|
||||
.then(() => {
|
||||
const styleAfterZoom = Cypress.$('.react-flow__viewport').css('transform');
|
||||
expect(styleBeforeZoom).not.to.equal(styleAfterZoom);
|
||||
});
|
||||
});
|
||||
|
||||
it('toggles zoom on double click', () => {
|
||||
cy.get('.react-flow__zoomondbl').click();
|
||||
});
|
||||
|
||||
it('zooms by double click', () => {
|
||||
cy.get('.react-flow__controls-zoomout').click();
|
||||
const styleBeforeZoom = Cypress.$('.react-flow__viewport').css('transform');
|
||||
|
||||
cy.get('.react-flow__pane')
|
||||
.dblclick()
|
||||
.wait(50)
|
||||
.then(() => {
|
||||
const styleAfterZoom = Cypress.$('.react-flow__viewport').css('transform');
|
||||
expect(styleBeforeZoom).not.to.equal(styleAfterZoom);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
export {};
|
||||
@@ -0,0 +1,77 @@
|
||||
describe('Minimap Testing', () => {
|
||||
before(() => {
|
||||
cy.visit('/');
|
||||
});
|
||||
|
||||
it('renders the mini map', () => {
|
||||
cy.get('.react-flow__minimap');
|
||||
cy.get('.react-flow__minimap-mask');
|
||||
});
|
||||
|
||||
it('has same number of nodes as the pane', () => {
|
||||
const paneNodes = Cypress.$('.react-flow__node').length;
|
||||
|
||||
cy.wait(100);
|
||||
|
||||
const minimapNodes = Cypress.$('.react-flow__minimap-node').length;
|
||||
|
||||
cy.wait(100);
|
||||
|
||||
expect(paneNodes).equal(minimapNodes);
|
||||
});
|
||||
|
||||
it('changes zoom level', () => {
|
||||
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 svg').attr('viewBox');
|
||||
const maskPathAfterZoom = Cypress.$('.react-flow__minimap-mask').attr('d');
|
||||
|
||||
expect(viewBoxBeforeZoom).to.not.equal(viewBoxAfterZoom);
|
||||
expect(maskPathBeforeZoom).to.not.equal(maskPathAfterZoom);
|
||||
});
|
||||
});
|
||||
|
||||
it('changes node position', () => {
|
||||
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');
|
||||
|
||||
expect(xPosBeforeDrag).to.not.equal(xPosAfterDrag);
|
||||
expect(yPosBeforeDrag).to.not.equal(yPosAfterDrag);
|
||||
});
|
||||
});
|
||||
|
||||
it('changes node positions via pane drag', () => {
|
||||
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
|
||||
// https://github.com/cypress-io/cypress/issues/3441
|
||||
cy.window().then((win) => {
|
||||
cy.get('.react-flow__pane')
|
||||
.trigger('mousedown', 'topLeft', { which: 1, view: win })
|
||||
.trigger('mousemove', 'bottomLeft')
|
||||
.wait(50)
|
||||
.trigger('mouseup', { force: true, view: win })
|
||||
.then(() => {
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
export {};
|
||||
@@ -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"
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/// <reference types="cypress" />
|
||||
|
||||
// ***********************************************
|
||||
// This example commands.js shows you how to
|
||||
// create various custom commands and overwrite
|
||||
// existing commands.
|
||||
//
|
||||
// For more comprehensive examples of custom
|
||||
// commands please read more here:
|
||||
// https://on.cypress.io/custom-commands
|
||||
// ***********************************************
|
||||
//
|
||||
//
|
||||
// -- This is a parent command --
|
||||
// Cypress.Commands.add("login", (email, password) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a child command --
|
||||
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a dual command --
|
||||
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
|
||||
|
||||
Cypress.Commands.add('drag', (selector, { x, y }) => {
|
||||
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 {};
|
||||
@@ -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>
|
||||
@@ -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 />)
|
||||
@@ -0,0 +1,25 @@
|
||||
// ***********************************************************
|
||||
// This example support/index.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';
|
||||
|
||||
const resizeObserverLoopErrRe = /^[^(ResizeObserver loop limit exceeded)]/;
|
||||
|
||||
Cypress.on('uncaught:exception', (err) => {
|
||||
if (resizeObserverLoopErrRe.test(err.message)) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
@@ -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 });
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -5,7 +5,8 @@ body {
|
||||
|
||||
html,
|
||||
body,
|
||||
#__next {
|
||||
#__next,
|
||||
#root {
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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"]
|
||||
|
||||
Reference in New Issue
Block a user