| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678 |
- import { test, expect } from '@playwright/test';
- const copy = obj => JSON.parse(JSON.stringify(obj));
- const delay = async ms => new Promise(resolve => setTimeout(resolve, ms));
- const expectUniquePoints = pts => {
- const toStringPt = pt => `(${pt.x},${pt.y})`;
- const toStringPts = pts => pts.map(toStringPt).join(' ');
- const str = toStringPts(pts);
- const strUnique = Array.from(new Set(pts.map(toStringPt))).join(' ');
- expect(str).toBe(strUnique);
- };
- const dist = (pt1, pt2) => Math.sqrt((pt1.x - pt2.x) ** 2 + (pt1.y - pt2.y) ** 2);
- test.describe('Renderer', () => {
- test.beforeEach(async ({ page }) => {
- page.on('console', (msg) => console.log(`[browser] ${msg.text()}`));
- await page.setViewportSize({ width: 1280, height: 720 });
- await page.goto('http://127.0.0.1:3333/playwright-page/index.html');
- });
- test('starts with no nodes', async ({ page }) => {
- const numNodes = await page.evaluate(() => {
- const cy = window.cy;
- return cy.nodes().length;
- });
- expect(numNodes).toBe(0);
- });
- test('adds a node', async ({ page }) => {
- const numNodes = await page.evaluate(() => {
- const cy = window.cy;
- cy.add({
- data: { id: 'foo' }
- });
- return cy.nodes().length;
- });
- expect(numNodes).toBe(1);
- });
- test.describe('node style', () => {
- test.beforeEach(async ({ page }) => {
- await page.evaluate(() => {
- const cy = window.cy;
- cy.add({ data: { id: 'a' } });
- });
- }); // beforeEach
- test('node bounding box extends beyond width and height for bordered triangle', async ({ page }) => {
- const bb = await page.evaluate(() => {
- const cy = window.cy;
- cy.style().fromJson([
- {
- selector: 'node',
- style: {
- 'shape': 'triangle',
- 'width': 100,
- 'height': 100,
- 'border-width': 10,
- 'border-color': 'black',
- }
- }
- ]).update();
- return cy.$('#a').boundingBox();
- });
- expect(bb.w).toBeGreaterThan(105);
- expect(bb.h).toBeGreaterThan(105);
- } ); // node bounding box extends beyond width and height for triangle
- test('boundingBox() does not throw for polygon node with border and edge', async ({ page }) => {
- const bb = await page.evaluate(() => {
- const cy = window.cy;
- cy.add([
- {
- data: { id: 'p' },
- position: { x: 100, y: 100 }
- },
- {
- data: { id: 'q' },
- position: { x: 200, y: 200 }
- },
- {
- data: { id: 'pq', source: 'p', target: 'q' }
- }
- ]);
- cy.style().fromJson([
- {
- selector: '#p',
- style: {
- 'shape': 'polygon',
- 'shape-polygon-points': '-1 -1 1 -1 1 1 -1 1',
- 'width': 50,
- 'height': 50,
- 'border-width': 5,
- 'border-color': 'black',
- }
- }
- ]).update();
- return cy.$('#p').boundingBox();
- });
- expect(bb.w).toBeGreaterThan(0);
- expect(bb.h).toBeGreaterThan(0);
- expect(bb.x1).toBeDefined();
- expect(bb.x2).toBeDefined();
- expect(bb.y1).toBeDefined();
- expect(bb.y2).toBeDefined();
- }); // boundingBox() does not throw for polygon node with border and edge
- test('polygon node bounding box extends beyond width and height for border', async ({ page }) => {
- const bb = await page.evaluate(() => {
- const cy = window.cy;
- cy.style().fromJson([
- {
- selector: 'node',
- style: {
- 'shape': 'polygon',
- 'shape-polygon-points': '-1 -1 1 -1 1 1 -1 1',
- 'width': 100,
- 'height': 100,
- 'border-width': 10,
- 'border-color': 'black',
- }
- }
- ]).update();
- return cy.$('#a').boundingBox();
- });
- expect(bb.w).toBeGreaterThan(105);
- expect(bb.h).toBeGreaterThan(105);
- }); // polygon node bounding box extends beyond width and height for border
- });
- test.describe('straight edges', () => {
- test.beforeEach(async ({ page }) => {
- await page.evaluate(() => {
- const cy = window.cy;
-
- cy.style().fromJson([
- {
- selector: 'edge',
- style: {
- 'curve-style': 'straight'
- }
- }
- ]).update();
-
- cy.add([
- {
- data: { id: 'a' },
- position: { x: 0, y: 0 }
- },
- {
- data: { id: 'b' },
- position: { x: 0, y: 0 }
- },
- {
- data: { id: 'ab1', source: 'a', target: 'b' }
- },
- {
- data: { id: 'ab2', source: 'a', target: 'b' }
- }
- ]);
- });
- }); // beforeEach
- test('initial bounding box is zero', async ({ page }) => {
- let bb1 = await page.evaluate(() => cy.$('#ab1').boundingBox());
- let bb2 = await page.evaluate(() => cy.$('#ab2').boundingBox());
- expect(bb1.w).toEqual(0);
- expect(bb1.h).toEqual(0);
- expect(bb2.w).toEqual(0);
- expect(bb2.h).toEqual(0);
- }); // initial bounding box is zero
- test('moved bounding box is nonzero', async ({ page }) => {
- await delay(500);
- await page.evaluate(() => {
- cy.layout({ name: 'grid', rows: 1, cols: 2 }).run();
- });
- await delay(500);
- let bb1 = await page.evaluate(() => cy.$('#ab1').boundingBox());
- let bb2 = await page.evaluate(() => cy.$('#ab2').boundingBox());
- expect(bb1.w).not.toEqual(0);
- expect(bb1.h).not.toEqual(0);
- expect(bb2.w).not.toEqual(0);
- expect(bb2.h).not.toEqual(0);
- }); // initial bounding box is nonzero
- test('manual endpoints correct', async ({ page }) => {
- // Set the endpoints manually
- const {
- ab1Source, ab1Target, ab2Source, ab2Target,
- ab1Midpoint, ab2Midpoint
- } = await page.evaluate(() => {
- const cy = window.cy;
- cy.$('#1').position({ x: 0, y: 0 });
- cy.$('#b').position({ x: 100, y: 0 });
- // Set the endpoints for the fist edge via style
- cy.$('#ab1').style('source-endpoint', '10px 10px');
- cy.$('#ab1').style('target-endpoint', '20px 10px');
- // Set the endpoints for the second edge via style
- cy.$('#ab2').style('source-endpoint', '30px 20px');
- cy.$('#ab2').style('target-endpoint', '40px 20px');
- // Return the endpoints for verification
- return {
- ab1Source: cy.$('#ab1').sourceEndpoint(),
- ab1Target: cy.$('#ab1').targetEndpoint(),
- ab2Source: cy.$('#ab2').sourceEndpoint(),
- ab2Target: cy.$('#ab2').targetEndpoint(),
- ab1Midpoint: cy.$('#ab1').midpoint(),
- ab2Midpoint: cy.$('#ab2').midpoint()
- };
- });
- // Verify the endpoints
- expect(ab1Source.x).toBe(10);
- expect(ab1Source.y).toBe(10);
- expect(ab1Target.x).toBe(20 + 100);
- expect(ab1Target.y).toBe(10);
- expect(ab2Source.x).toBe(30);
- expect(ab2Source.y).toBe(20);
- expect(ab2Target.x).toBe(40 + 100);
- expect(ab2Target.y).toBe(20);
- // Verify the midpoint y values only
- expect(ab1Midpoint.y).toEqual(10);
- expect(ab2Midpoint.y).toEqual(20);
- }); // manual endpoints correct
- });
- test.describe('bundled beziers', () => {
- const stepSize = 40;
- let ctrlpts1;
- let pt_ab1_1, pt_ab2_1;
- test.beforeEach(async ({ page }) => {
- ctrlpts1 = await page.evaluate(() => {
- const cy = window.cy;
-
- cy.style().fromJson([
- {
- selector: 'edge',
- style: {
- 'curve-style': 'bezier',
- 'control-point-step-size': 40
- }
- }
- ]).update();
-
- cy.add([
- {
- data: { id: 'a' }
- },
- {
- data: { id: 'b' }
- },
- {
- data: { id: 'ab1', source: 'a', target: 'b' }
- },
- {
- data: { id: 'ab2', source: 'a', target: 'b' }
- }
- ]);
-
- cy.layout({ name: 'grid', rows: 1, cols: 2 }).run();
-
- return cy.edges().map(edge => edge.controlPoints()[0]);
- });
- pt_ab1_1 = await page.evaluate(() => {
- return window.cy.$('#ab1').controlPoints()[0];
- });
-
- pt_ab2_1 = await page.evaluate(() => {
- return window.cy.$('#ab2').controlPoints()[0];
- });
- }); // beforeEach
- test('move when adding to the bundle', async ({ page }) => {
- expect(ctrlpts1.length).toBe(2);
- expectUniquePoints(ctrlpts1);
-
- // distance between the two control points
- let d_1_01 = dist(ctrlpts1[0], ctrlpts1[1]);
-
- expect(d_1_01).toBe(stepSize);
-
- let ctrlpts2 = await page.evaluate(() => {
- const cy = window.cy;
-
- cy.add([
- {
- data: { id: 'ab3', source: 'a', target: 'b' }
- },
- {
- data: { id: 'ab4', source: 'a', target: 'b' }
- }
- ]);
-
- return cy.edges().map(edge => edge.controlPoints()[0]);
- });
-
- // console.log(ctrlpts2);
-
- expect(ctrlpts2.length).toBe(4);
- expectUniquePoints(ctrlpts2);
-
- let d_2_01 = dist(ctrlpts2[0], ctrlpts2[1]);
- let d_2_12 = dist(ctrlpts2[1], ctrlpts2[2]);
- let d_2_23 = dist(ctrlpts2[2], ctrlpts2[3]);
-
- expect(d_2_01).toBeCloseTo(stepSize);
- expect(d_2_12).toBeCloseTo(stepSize);
- expect(d_2_23).toBeCloseTo(stepSize);
-
- let pt_ab1_2 = await page.evaluate(() => {
- return window.cy.$('#ab1').controlPoints()[0];
- });
-
- let pt_ab2_2 = await page.evaluate(() => {
- return window.cy.$('#ab2').controlPoints()[0];
- });
-
- // ctrl pts for ab1 and ab2 should have changed
- expect(pt_ab1_1).not.toEqual(pt_ab1_2);
- expect(pt_ab2_1).not.toEqual(pt_ab2_2);
- }); // move when adding to the bundle
- test('move when removing from the bundle', async ({ page }) => {
- await page.evaluate(() => {
- const cy = window.cy;
-
- cy.add([
- {
- data: { id: 'ab3', source: 'a', target: 'b' }
- },
- {
- data: { id: 'ab4', source: 'a', target: 'b' }
- },
- {
- data: { id: 'ab5', source: 'a', target: 'b' }
- }
- ]);
- });
- let pt_ab1_2 = await page.evaluate(() => cy.$('#ab1').controlPoints()[0]);
- let pt_ab2_2 = await page.evaluate(() => cy.$('#ab2').controlPoints()[0]);
- let ctrlpts3 = await page.evaluate(() => {
- const cy = window.cy;
- cy.$('#ab3').remove(); // only ab1,2,4,5 left
- return cy.edges().map(edge => edge.controlPoints()[0]);
- });
- let pt_ab1_3 = await page.evaluate(() => cy.$('#ab1').controlPoints()[0]);
- let pt_ab2_3 = await page.evaluate(() => cy.$('#ab2').controlPoints()[0]);
- expectUniquePoints(ctrlpts3);
- expect(pt_ab1_2).not.toEqual(pt_ab1_3);
- expect(pt_ab2_2).not.toEqual(pt_ab2_3);
- let d_3_01 = dist(ctrlpts3[0], ctrlpts3[1]);
- let d_3_12 = dist(ctrlpts3[1], ctrlpts3[2]);
- let d_3_23 = dist(ctrlpts3[2], ctrlpts3[3]);
-
- expect(d_3_01).toBeCloseTo(stepSize);
- expect(d_3_12).toBeCloseTo(stepSize);
- expect(d_3_23).toBeCloseTo(stepSize);
- }); // move when removing from the bundle
- test('do not move when setting one edge to visibility:hidden', async ({ page }) => {
- await page.evaluate(() => {
- window.cy.$('#ab1').style('visibility', 'hidden');
- });
- let pt_ab1_2 = await page.evaluate(() => cy.$('#ab1').controlPoints()[0]);
- let pt_ab2_2 = await page.evaluate(() => cy.$('#ab2').controlPoints()[0]);
- expect(pt_ab1_1).toEqual(pt_ab1_2);
- expect(pt_ab2_1).toEqual(pt_ab2_2);
- }); // do not move when setting one edge to visibility:hidden
- test('move when setting one edge to display:none', async ({ page }) => {
- await page.evaluate(() => {
- window.cy.$('#ab1').style('display', 'none');
- });
- let pt_ab1_2 = await page.evaluate(() => cy.$('#ab1').controlPoints());
- let pt_ab2_2 = await page.evaluate(() => cy.$('#ab2').controlPoints());
- expect(pt_ab1_2).toBeUndefined(); // because display: none
- expect(pt_ab2_2).toBeUndefined(); // because only one edge left => straight
- }); // move when setting one edge to display:none
- test('move when setting one edge to display:none (bigger bundle)', async ({ page }) => {
- await page.evaluate(() => {
- window.cy.add([
- {
- data: { id: 'ab3', source: 'a', target: 'b' }
- },
- {
- data: { id: 'ab4', source: 'a', target: 'b' }
- },
- {
- data: { id: 'ab5', source: 'a', target: 'b' }
- }
- ]);
- cy.$('#ab1').style('display', 'none');
- });
- let pt_ab1_2 = await page.evaluate(() => cy.$('#ab1').controlPoints());
- let pt_ab2_2 = await page.evaluate(() => cy.$('#ab2').controlPoints()[0]);
- expect(pt_ab1_2).toBeUndefined(); // because display: none
- expect(pt_ab2_2).toBeDefined();
-
- }); // move when setting one edge to display:none
- test('move when setting one edge to curve-style:straight', async ({ page }) => {
- await page.evaluate(() => {
- window.cy.add([
- {
- data: { id: 'ab3', source: 'a', target: 'b' }
- },
- {
- data: { id: 'ab4', source: 'a', target: 'b' }
- },
- {
- data: { id: 'ab5', source: 'a', target: 'b' }
- }
- ]);
- cy.$('#ab1').style('curve-style', 'straight');
- });
- let pt_ab1_2 = await page.evaluate(() => window.cy.$('#ab1').controlPoints());
- let pt_ab2_2 = await page.evaluate(() => window.cy.$('#ab2').controlPoints()[0]);
- expect(pt_ab1_2).toBeUndefined(); // because curve-style:straight
- expect(pt_ab2_2).toBeDefined();
-
- }); // move when setting one edge to curve-style:straight
- test('diagonal bottom left to top right', async ({page}) => {
- let controlPoints = await page.evaluate(() => {
- const cy = window.cy;
- cy.style().fromJson([{
- selector: 'edge',
- style: {
- 'curve-style': 'bezier',
- 'control-point-step-size': Math.sqrt(200)
- }
- }]).update();
- cy.add([{
- data: {id: 'a'}
- }, {
- data: {id: 'b'}
- }, {
- data: {id: 'ab1', source: 'a', target: 'b'}
- }, {
- data: {id: 'ab2', source: 'a', target: 'b'}
- }]);
- var presetOptions = {
- name: 'preset',
- positions: {
- a: {x: 0, y: 100},
- b: {x: 100, y: 0}
- }
- };
- cy.layout(presetOptions).run();
- let pt_ab1_1 = cy.$('#ab1').controlPoints()[0];
- let pt_ab2_1 = cy.$('#ab2').controlPoints()[0];
- return [pt_ab1_1, pt_ab2_1]
- });
- // Step size 14.14, sqrt(10*10+10*10).
- // Gives a 5 pixel translation away from the mid-point,
- // if the vector between the nodes are at a 45 deg angle.
- // Mid-point is at (50,50)
- expect(controlPoints[0].x).toBeCloseTo(45, 5);
- expect(controlPoints[0].y).toBeCloseTo(45, 5);
- expect(controlPoints[1].x).toBeCloseTo(55, 5);
- expect(controlPoints[1].y).toBeCloseTo(55, 5);
- });
- // test('do not move when straight edge added', async ({ page }) => {
- // await page.evaluate(() => {
- // window.cy.add([
- // {
- // data: { id: 'ab3', source: 'a', target: 'b' },
- // style: {
- // 'curve-style': 'straight'
- // }
- // }
- // ]);
- // });
- // // TODO...
-
- // }); // move when setting one edge to curve-style:straight
- }); // bundled beziers
- test.describe('rounded-edges', () => {
- let singleBentEdge;
- let midCollinearEdge;
- test.beforeEach(async ({ page }) => {
- await page.evaluate(() => {
- const cy = window.cy;
- cy.style().fromJson([
- {
- selector: 'edge',
- style: {
- 'curve-style': 'round-segments',
- 'segment-radii': 50,
- 'radius-type': 'influence-radius'
- }
- },
- {
- selector: '#ab1',
- style: {
- 'segment-weights': [0.5],
- 'segment-distances': [50]
- }
- },
- {
- selector: '#ab2',
- style: {
- 'segment-weights': [0.25 , 0.5, 0.75],
- 'segment-distances': [50, 50, 50]
- }
- }
- ]).update();
- cy.add([
- {
- data: { id: 'a' }
- },
- {
- data: { id: 'b' }
- },
- {
- data: { id: 'ab1', source: 'a', target: 'b' }
- },
- {
- data: { id: 'ab2', source: 'a', target: 'b' }
- },
- ]);
- cy.layout({ name: 'grid', rows: 1, cols: 2 }).run();
- singleBentEdge = cy.$('#ab1');
- midCollinearEdge = cy.$('#ab2');
- });
- }) // Before Each
- test('collinear mid point correctly defined', async ({page}) => {
- const midpoint = await page.evaluate(() => midCollinearEdge.midpoint());
- const points = await page.evaluate(() => midCollinearEdge.segmentPoints());
- expect(midpoint.x).not.toBeNaN();
- expect(midpoint.y).not.toBeNaN();
- expect(midpoint).toMatchObject(points[1])
- });
- test('mid point correctly defined', async ({page}) => {
- const {midpoint, points, a,b} = await page.evaluate(() => ({
- midpoint: singleBentEdge.midpoint(),
- points: singleBentEdge.segmentPoints(),
- a: singleBentEdge.source().position(),
- b: singleBentEdge.target().position(),
- }));
- expect(midpoint.x).not.toBeNaN();
- expect(midpoint.y).not.toBeNaN();
- const controlPoint = points[0];
- expect(midpoint).not.toMatchObject(controlPoint); // The mid point is supposed to be on the curve and not on the control point
- const isInsideTriangle = (point, {a,b,c}) => {
- const dX = point.x - a.x, dY = point.y - a.y;
- const dX21 = b.x - a.x, dY21 = b.y - a.y;
- const dX31 = c.x - a.x, dY31 = c.y - a.y;
- const denom = dY21 * dX31 - dX21 * dY31;
- const alpha = (dY21 * dX - dX21 * dY) / denom;
- const beta = (dX31 * dY - dY31 * dX) / denom;
- const gamma = 1 - alpha - beta;
- return alpha >= 0 && beta >= 0 && gamma >= 0;
- };
- // The actual midpoint should be within a triangle between the source, the target and the defined control point
- expect(isInsideTriangle(midpoint, {a,b,c: controlPoint})).toBeTruthy()
- })
- }); // Rounded edges
- test.describe('with layout', () => {
- test('single node cose layout with bounding box', async ({ page }) => {
- const pos = await page.evaluate(async () => {
- const cy = window.cy;
- // remove all eles
- cy.elements().remove();
- // add one node
- let node = cy.add({ data: { id: 'a' } });
- // run layout
- let layout = cy.layout({
- name: 'cose',
- boundingBox: {
- x1: 0,
- y1: 0,
- x2: 100,
- y2: 100
- },
- });
- let layoutstop = layout.promiseOn('layoutstop');
- layout.run();
- await layoutstop;
- return node.position();
- });
-
- expect(pos.x).not.toBeNaN();
- expect(pos.y).not.toBeNaN();
- }); // single node cose layout
- }); // with layout
- }); // renderer
|