From bc285cfe62b1e3acdd088f5a9e4bd546217c86cd Mon Sep 17 00:00:00 2001 From: Robson Oliveira dos Santos Date: Tue, 10 Dec 2024 23:49:13 +0930 Subject: [PATCH] test(spie-ui-e2e): add plotter test --- apps/spie-ui-e2e/src/e2e/plotter.cy.ts | 257 ++++++++++++++++++ .../src/fixtures/plotOneVariable.json | 107 ++++++++ .../src/fixtures/plotThreeVariables.json | 107 ++++++++ apps/spie-ui-e2e/tsconfig.json | 4 +- 4 files changed, 474 insertions(+), 1 deletion(-) create mode 100644 apps/spie-ui-e2e/src/e2e/plotter.cy.ts create mode 100644 apps/spie-ui-e2e/src/fixtures/plotOneVariable.json create mode 100644 apps/spie-ui-e2e/src/fixtures/plotThreeVariables.json diff --git a/apps/spie-ui-e2e/src/e2e/plotter.cy.ts b/apps/spie-ui-e2e/src/e2e/plotter.cy.ts new file mode 100644 index 0000000..e2904db --- /dev/null +++ b/apps/spie-ui-e2e/src/e2e/plotter.cy.ts @@ -0,0 +1,257 @@ +import { + mockElectronAPI, + mockSerialPortList, +} from '../fixtures/mocks/electron-api.mock'; +import plotOneVariable from '../fixtures/plotOneVariable.json'; +import plotThreeVariables from '../fixtures/plotThreeVariables.json'; + +describe('Plotter routine', () => { + before(() => { + cy.fixture('plotOneVariable').then((data) => { + expect(plotOneVariable).to.deep.equal(data); + }); + cy.fixture('plotThreeVariables').then((data) => { + expect(plotThreeVariables).to.deep.equal(data); + }); + }); + + beforeEach(() => { + cy.visit('/'); + + cy.on('window:before:load', (win) => { + win.electron = mockElectronAPI(win); + }); + + // Ensure onSerialPortEventTrigger is loaded + cy.window().should((win) => { + expect(win.onSerialPortEventTrigger).to.be.a('function'); + }); + + // Navigate to plotter tab + cy.get('ion-tab-button').contains('Plotter').parent().click(); + cy.get('app-plotter-component').should('not.have.class', 'ion-page-hidden'); + + // Mock connection + cy.connect(mockSerialPortList[0].path, 9600); + cy.get('ion-tabs ion-accordion').contains('Connection').parent().click(); + }); + + it('should render the chart', () => { + cy.get('apx-chart').should('exist'); + cy.get('.apexcharts-canvas').should('be.visible'); + }); + + it('should render series', () => { + const numberOfPoints = 2; + const mockEventData = plotOneVariable.eventData.slice(0, numberOfPoints); + const numberOfSeries = plotOneVariable.numberOfSeries; + + cy.window() + .then((win) => { + return new Promise((resolve) => { + // Mock data event with numberOfPoints point 50ms apart + mockEventData.forEach((data, index) => { + setTimeout(() => { + win.onSerialPortEventTrigger({ + type: 'data', + data: data, + }); + + if (index === mockEventData.length - 1) { + resolve(); + } + }, index * 50); + }); + }); + }) + .then(() => { + cy.get('apx-chart svg path.apexcharts-line').should( + 'have.length', + numberOfSeries + ); + }); + }); + + it('should render tooltips', () => { + const numberOfPoints = 2; + const mockEventData = plotOneVariable.eventData.slice(0, numberOfPoints); + + cy.window() + .then((win) => { + return new Promise((resolve) => { + // Mock data event with numberOfPoints point 50ms apart + mockEventData.forEach((data, index) => { + setTimeout(() => { + win.onSerialPortEventTrigger({ + type: 'data', + data: data, + }); + + if (index === mockEventData.length - 1) { + resolve(); + } + }, index * 50); + }); + }); + }) + .then(() => { + // Pause data stream (to enable tooltips) + cy.get('app-plotter-component ion-button').contains('Pause').click(); + + // Trigger tooltip + cy.get('.apexcharts-canvas').trigger('mousemove', { + clientX: 100, + clientY: 150, + }); + + // Ensure the tooltip is visible and active + cy.get('.apexcharts-tooltip').and('have.class', 'apexcharts-active'); + }); + }); + + it('should render labels and values for multiple variables', () => { + const numberOfPoints = 2; + const mockEventData = plotThreeVariables.eventData.slice(0, numberOfPoints); + const mockSeries = plotThreeVariables.series.slice(0, numberOfPoints); + + cy.window() + .then((win) => { + return new Promise((resolve) => { + // Mock data event with numberOfPoints point 50ms apart + mockEventData.forEach((data, index) => { + setTimeout(() => { + win.onSerialPortEventTrigger({ + type: 'data', + data: data, + }); + + if (index === mockEventData.length - 1) { + resolve(); + } + }, index * 50); + }); + }); + }) + .then(() => { + // Pause data stream (to enable tooltips) + cy.get('app-plotter-component ion-button').contains('Pause').click(); + + // Test labels and values + mockSeries.forEach((points, pointsIndex) => { + // Move mouse to estimated tooltip position + cy.get('.apexcharts-canvas').trigger('mousemove', { + clientX: 100 + (pointsIndex * 1000) / numberOfPoints, + clientY: 150, + }); + + points.forEach((point, pointIndex) => { + // Ensure the label are correct + cy.get('.apexcharts-tooltip-text-y-label').should( + 'contain', + `Variable ${pointIndex + 1}` + ); + + // Ensure the values are correct + cy.get('.apexcharts-tooltip-text-y-value').should('contain', point); + }); + }); + }); + }); + + it('should render multiple variables and large series', () => { + const mockEventData = plotThreeVariables.eventData; + const mockSeries = plotThreeVariables.series; + + cy.window() + .then((win) => { + return new Promise((resolve) => { + // Mock data event with numberOfPoints point 50ms apart + mockEventData.forEach((data, index) => { + setTimeout(() => { + win.onSerialPortEventTrigger({ + type: 'data', + data: data, + }); + + if (index === mockEventData.length - 1) { + resolve(); + } + }, index * 50); + }); + }); + }) + .then(() => { + // Pause data stream (to enable tooltips) + cy.get('app-plotter-component ion-button').contains('Pause').click(); + + // INFO: hacky way to wait until the canvas has finished updating from stream pause + cy.get('.apexcharts-canvas').invoke('width').should('be.below', 940); + + // Select the first path element and extract X coordinates + cy.get('g.apexcharts-series path.apexcharts-line') + .first() + .then(($path) => { + const pageOffset = 85; + const pathData = $path.attr('d'); + + const xCoordinates = (pathData as string) + .split('L') + .map((segment, index, array) => { + const [x] = segment + .trim() + .replace('M', '') + .split(' ') + .map(Number); + + // If it's the last element in the array, adjust the X coordinate + if (index === array.length - 1) { + return x + pageOffset - 1; + } + + // Otherwise, add the pageOffset to the X value + return x + pageOffset; + }) + .filter((x) => !isNaN(x)); // Filter out NaN values + + // Test labels and values + mockSeries.forEach((points, pointsIndex) => { + // // Helper for debug + // cy.get('body').then(($body) => { + // const refCircle = document.createElement('div'); + // refCircle.style.position = 'absolute'; + // refCircle.style.left = `${xCoordinates[pointsIndex]}px`; + // refCircle.style.top = `${150}px`; + // refCircle.style.width = '5px'; + // refCircle.style.height = '5px'; + // refCircle.style.borderRadius = '50%'; + // refCircle.style.backgroundColor = 'red'; // Red for visibility + // refCircle.style.zIndex = '9999'; // High z-index to appear on top + + // // Append the circle to the body to mark the inferred points + // $body[0].appendChild(refCircle); + // }); + + // Move mouse to estimated tooltip position + cy.get('.apexcharts-canvas').trigger('mousemove', { + clientX: xCoordinates[pointsIndex], + clientY: 150, + }); + + points.forEach((point, pointIndex) => { + // Ensure the label is correct + cy.get('.apexcharts-tooltip-text-y-label').should( + 'contain', + `Variable ${pointIndex + 1}` + ); + + // Ensure the values are correct + cy.get('.apexcharts-tooltip-text-y-value').should( + 'contain', + point + ); + }); + }); + }); + }); + }); +}); diff --git a/apps/spie-ui-e2e/src/fixtures/plotOneVariable.json b/apps/spie-ui-e2e/src/fixtures/plotOneVariable.json new file mode 100644 index 0000000..2e6ef26 --- /dev/null +++ b/apps/spie-ui-e2e/src/fixtures/plotOneVariable.json @@ -0,0 +1,107 @@ +{ + "eventData": [ + "0.00\n", + "1.25\n", + "2.49\n", + "3.68\n", + "4.82\n", + "5.88\n", + "6.85\n", + "7.71\n", + "8.44\n", + "9.05\n", + "9.51\n", + "9.82\n", + "9.98\n", + "9.98\n", + "9.82\n", + "9.51\n", + "9.05\n", + "8.44\n", + "7.71\n", + "6.85\n", + "5.88\n", + "4.82\n", + "3.68\n", + "2.49\n", + "1.25\n", + "0.00\n", + "-1.25\n", + "-2.49\n", + "-3.68\n", + "-4.82\n", + "-5.88\n", + "-6.85\n", + "-7.71\n", + "-8.44\n", + "-9.05\n", + "-9.51\n", + "-9.82\n", + "-9.98\n", + "-9.98\n", + "-9.82\n", + "-9.51\n", + "-9.05\n", + "-8.44\n", + "-7.71\n", + "-6.85\n", + "-5.88\n", + "-4.82\n", + "-3.68\n", + "-2.49\n", + "-1.25\n" + ], + "series": [ + [0.0], + [1.25], + [2.49], + [3.68], + [4.82], + [5.88], + [6.85], + [7.71], + [8.44], + [9.05], + [9.51], + [9.82], + [9.98], + [9.98], + [9.82], + [9.51], + [9.05], + [8.44], + [7.71], + [6.85], + [5.88], + [4.82], + [3.68], + [2.49], + [1.25], + [0.0], + [-1.25], + [-2.49], + [-3.68], + [-4.82], + [-5.88], + [-6.85], + [-7.71], + [-8.44], + [-9.05], + [-9.51], + [-9.82], + [-9.98], + [-9.98], + [-9.82], + [-9.51], + [-9.05], + [-8.44], + [-7.71], + [-6.85], + [-5.88], + [-4.82], + [-3.68], + [-2.49], + [-1.25] + ], + "numberOfSeries": 1 +} diff --git a/apps/spie-ui-e2e/src/fixtures/plotThreeVariables.json b/apps/spie-ui-e2e/src/fixtures/plotThreeVariables.json new file mode 100644 index 0000000..ffa4a57 --- /dev/null +++ b/apps/spie-ui-e2e/src/fixtures/plotThreeVariables.json @@ -0,0 +1,107 @@ +{ + "eventData": [ + "0.00\t8.66\t-8.66\n", + "1.25\t7.97\t-9.22\n", + "2.49\t7.14\t-9.63\n", + "3.68\t6.21\t-9.89\n", + "4.82\t5.18\t-10.00\n", + "5.88\t4.07\t-9.95\n", + "6.85\t2.89\t-9.74\n", + "7.71\t1.67\t-9.37\n", + "8.44\t0.42\t-8.86\n", + "9.05\t-0.84\t-8.21\n", + "9.51\t-2.08\t-7.43\n", + "9.82\t-3.29\t-6.53\n", + "9.98\t-4.45\t-5.53\n", + "9.98\t-5.53\t-4.45\n", + "9.82\t-6.53\t-3.29\n", + "9.51\t-7.43\t-2.08\n", + "9.05\t-8.21\t-0.84\n", + "8.44\t-8.86\t0.42\n", + "7.71\t-9.37\t1.67\n", + "6.85\t-9.74\t2.89\n", + "5.88\t-9.95\t4.07\n", + "4.82\t-10.00\t5.18\n", + "3.68\t-9.89\t6.21\n", + "2.49\t-9.63\t7.14\n", + "1.25\t-9.22\t7.97\n", + "0.00\t-8.66\t8.66\n", + "-1.25\t-7.97\t9.22\n", + "-2.49\t-7.14\t9.63\n", + "-3.68\t-6.21\t9.89\n", + "-4.82\t-5.18\t10.00\n", + "-5.88\t-4.07\t9.95\n", + "-6.85\t-2.89\t9.74\n", + "-7.71\t-1.67\t9.37\n", + "-8.44\t-0.42\t8.86\n", + "-9.05\t0.84\t8.21\n", + "-9.51\t2.08\t7.43\n", + "-9.82\t3.29\t6.53\n", + "-9.98\t4.45\t5.53\n", + "-9.98\t5.53\t4.45\n", + "-9.82\t6.53\t3.29\n", + "-9.51\t7.43\t2.08\n", + "-9.05\t8.21\t0.84\n", + "-8.44\t8.86\t-0.42\n", + "-7.71\t9.37\t-1.67\n", + "-6.85\t9.74\t-2.89\n", + "-5.88\t9.95\t-4.07\n", + "-4.82\t10.00\t-5.18\n", + "-3.68\t9.89\t-6.21\n", + "-2.49\t9.63\t-7.14\n", + "-1.25\t9.22\t-7.97\n" + ], + "series": [ + [0.0, 8.66, -8.66], + [1.25, 7.97, -9.22], + [2.49, 7.14, -9.63], + [3.68, 6.21, -9.89], + [4.82, 5.18, -10.0], + [5.88, 4.07, -9.95], + [6.85, 2.89, -9.74], + [7.71, 1.67, -9.37], + [8.44, 0.42, -8.86], + [9.05, -0.84, -8.21], + [9.51, -2.08, -7.43], + [9.82, -3.29, -6.53], + [9.98, -4.45, -5.53], + [9.98, -5.53, -4.45], + [9.82, -6.53, -3.29], + [9.51, -7.43, -2.08], + [9.05, -8.21, -0.84], + [8.44, -8.86, 0.42], + [7.71, -9.37, 1.67], + [6.85, -9.74, 2.89], + [5.88, -9.95, 4.07], + [4.82, -10.0, 5.18], + [3.68, -9.89, 6.21], + [2.49, -9.63, 7.14], + [1.25, -9.22, 7.97], + [0.0, -8.66, 8.66], + [-1.25, -7.97, 9.22], + [-2.49, -7.14, 9.63], + [-3.68, -6.21, 9.89], + [-4.82, -5.18, 10.0], + [-5.88, -4.07, 9.95], + [-6.85, -2.89, 9.74], + [-7.71, -1.67, 9.37], + [-8.44, -0.42, 8.86], + [-9.05, 0.84, 8.21], + [-9.51, 2.08, 7.43], + [-9.82, 3.29, 6.53], + [-9.98, 4.45, 5.53], + [-9.98, 5.53, 4.45], + [-9.82, 6.53, 3.29], + [-9.51, 7.43, 2.08], + [-9.05, 8.21, 0.84], + [-8.44, 8.86, -0.42], + [-7.71, 9.37, -1.67], + [-6.85, 9.74, -2.89], + [-5.88, 9.95, -4.07], + [-4.82, 10.0, -5.18], + [-3.68, 9.89, -6.21], + [-2.49, 9.63, -7.14], + [-1.25, 9.22, -7.97] + ], + "numberOfSeries": 3 +} diff --git a/apps/spie-ui-e2e/tsconfig.json b/apps/spie-ui-e2e/tsconfig.json index 2a012dc..a62f8e8 100644 --- a/apps/spie-ui-e2e/tsconfig.json +++ b/apps/spie-ui-e2e/tsconfig.json @@ -11,7 +11,9 @@ "noImplicitOverride": true, "noPropertyAccessFromIndexSignature": true, "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + "resolveJsonModule": true, + "esModuleInterop": true }, "include": [ "**/*.ts",