From e0c3f1e1153e0e31245c1e17e8298e5ac7a62a2e Mon Sep 17 00:00:00 2001 From: nstclair-cc <20171905+nstclair-cc@users.noreply.github.com> Date: Wed, 11 Dec 2024 16:04:56 -0800 Subject: [PATCH 01/25] Added Scott's code from pairing today --- v3/README.md | 1 + v3/cypress/e2e/graph.spec.ts | 17 +++++++++++++++-- .../graph/components/graph-component.tsx | 7 +++++++ v3/src/lib/debug.ts | 1 + 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/v3/README.md b/v3/README.md index 051470ed19..2b210c953a 100644 --- a/v3/README.md +++ b/v3/README.md @@ -139,6 +139,7 @@ Various developer features can be enabled by adding a `debug` local storage key - `history` this will: print some info to the console as the history system records changes, print the full history as JSON each time it is loaded from Firestore, and provide a `window.historyDocument` so you can inspect the document while navigating the history. - `logger` console log all messages sent to the logging service - `map` print info about interactions with the map component +- `pixiPoints` this adds a map of tileId keys to PixiPoint instances as `window.pixiPointsMap`. This is also always available in Cypress. - `plugins` enable some extra plugins in the plugin menu and print information about interactions with plugins. - `undo` this will print information about each action that is added to the undo stack. diff --git a/v3/cypress/e2e/graph.spec.ts b/v3/cypress/e2e/graph.spec.ts index 1c22cb9759..ab4ada534e 100644 --- a/v3/cypress/e2e/graph.spec.ts +++ b/v3/cypress/e2e/graph.spec.ts @@ -46,7 +46,7 @@ context("Graph UI", () => { cy.wait(2500) }) describe("graph view", () => { - it.skip("should highlight a selected graph point", () => { + it("should highlight a selected graph point", () => { // This test is the outcome of a SPIKE to explore testing graph interactions. // It partially validates interactions but requires further PIXIJS-level support. // https://github.com/concord-consortium/codap/pull/1637 @@ -213,13 +213,25 @@ context("Graph UI", () => { // in the graph axis it might be possible to check the axis labels or scale cy.get("[data-testid=graph]").find("[data-testid=axis-bottom]").find(".tick").should("have.length", 29) }) - it("hides and shows selected/unselected cases", () => { + it.only("hides and shows selected/unselected cases", () => { ah.openAxisAttributeMenu("bottom") ah.selectMenuAttribute("Sleep", "bottom") // Sleep => x-axis cy.wait(500) // TODO: Add more thorough checks to make sure the cases are actually hidden and shown once Cypress is // configured to interact with the PixiJS canvas. For now, we just check that the buttons are disabled // and enabled as expected. + + // this could live in a skipped test for now + // TODO: add a folder that has the specific tests + + cy.window().then((win: Window) => { // this gets the window globally within the graph element and exposes the PixiJS points + const tileId: any = cy.get("[data-tile=graph-1]").invoke("id") // find the graph element and get the tileID from the graph element + const pixiPoints = (win as any).pixiPointsMap[tileId] // within the PixiPoints map look up the PixiPoints for the graph element + // here is where we want to get the number of points, iterate through the points, etc. + // for every point, find its (x, y) position + // color + // the points will be pixi Sprites with their Sprite properties, e.g. texture, or image (OK to experiment here) + }) graph.getHideShowButton().click() cy.get("[data-testid=hide-selected-cases]").should("be.disabled") cy.get("[data-testid=hide-unselected-cases]").should("not.be.disabled") @@ -566,6 +578,7 @@ context("Graph UI", () => { cy.get("[data-testid=graph-bin-alignment-setting]").find("input").should("exist").should("have.value", "2") // focus on the plot area cy.get("[data-testid=bin-ticks-graph-1]").click() + cy.window().then((win: Window) => { cy.get("[data-testid=bin-ticks-graph-1]").find("path.draggable-bin-boundary-cover").eq(2) .trigger("mousedown", { which: 1, force: true, view: win }) diff --git a/v3/src/components/graph/components/graph-component.tsx b/v3/src/components/graph/components/graph-component.tsx index 281f395131..bfafdbe16f 100644 --- a/v3/src/components/graph/components/graph-component.tsx +++ b/v3/src/components/graph/components/graph-component.tsx @@ -13,6 +13,7 @@ import {GraphLayoutContext} from '../hooks/use-graph-layout-context' import {useInitGraphLayout} from '../hooks/use-init-graph-layout' import {InstanceIdContext, useNextInstanceId} from "../../../hooks/use-instance-id-context" import { registerTileCollisionDetection } from "../../../lib/dnd-kit/dnd-detect-collision" +import {DEBUG_PIXI_POINTS} from '../../../lib/debug' import {AxisProviderContext} from '../../axis/hooks/use-axis-provider-context' import {AxisLayoutContext} from "../../axis/models/axis-layout-context" import {usePixiPointsArray} from '../../data-display/hooks/use-pixi-points-array' @@ -40,6 +41,12 @@ export const GraphComponent = observer(function GraphComponent({tile}: ITileBase [layout, instanceId] ) + if (((window as any).Cypress || DEBUG_PIXI_POINTS) && tile?.id) { + const pixiPointsMap: any = (window as any).pixiPointsMap || ({} as Record) + ;(window as any).pixiPointsMap = pixiPointsMap + pixiPointsMap[tile.id] = pixiPointsArray + } + useGraphController({graphController, graphModel, pixiPointsArray}) useEffect(() => { diff --git a/v3/src/lib/debug.ts b/v3/src/lib/debug.ts index 09d2f43d74..2e54d696e3 100644 --- a/v3/src/lib/debug.ts +++ b/v3/src/lib/debug.ts @@ -30,6 +30,7 @@ export const DEBUG_FORMULAS = debugContains("formulas") export const DEBUG_HISTORY = debugContains("history") export const DEBUG_LOGGER = debugContains("logger") export const DEBUG_MAP = debugContains("map") +export const DEBUG_PIXI_POINTS = debugContains("pixiPoints") export const DEBUG_PLUGINS = debugContains("plugins") export const DEBUG_SAVE_AS_V2 = debugContains("saveAsV2") export const DEBUG_UNDO = debugContains("undo") From 688ef057d0b3e4b8f308339dbb7d11dd4ac876d9 Mon Sep 17 00:00:00 2001 From: nstclair-cc <20171905+nstclair-cc@users.noreply.github.com> Date: Wed, 11 Dec 2024 16:27:26 -0800 Subject: [PATCH 02/25] added some comments to the code --- v3/cypress/e2e/graph.spec.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/v3/cypress/e2e/graph.spec.ts b/v3/cypress/e2e/graph.spec.ts index ab4ada534e..27a37d163f 100644 --- a/v3/cypress/e2e/graph.spec.ts +++ b/v3/cypress/e2e/graph.spec.ts @@ -226,6 +226,8 @@ context("Graph UI", () => { cy.window().then((win: Window) => { // this gets the window globally within the graph element and exposes the PixiJS points const tileId: any = cy.get("[data-tile=graph-1]").invoke("id") // find the graph element and get the tileID from the graph element + // NOTE: the correct element to target is
(the code above is wrong and we'd have to change the get + // argument to be something that picks up the right element) const pixiPoints = (win as any).pixiPointsMap[tileId] // within the PixiPoints map look up the PixiPoints for the graph element // here is where we want to get the number of points, iterate through the points, etc. // for every point, find its (x, y) position From d5ab8100cc7555cc7bcc9e4bc3e4e8af371cad8a Mon Sep 17 00:00:00 2001 From: nstclair-cc <20171905+nstclair-cc@users.noreply.github.com> Date: Thu, 12 Dec 2024 17:23:58 -0800 Subject: [PATCH 03/25] get the graph ids to dynamically update --- v3/cypress/e2e/graph.spec.ts | 81 +++++++++++++++++++++++++++--------- 1 file changed, 61 insertions(+), 20 deletions(-) diff --git a/v3/cypress/e2e/graph.spec.ts b/v3/cypress/e2e/graph.spec.ts index 27a37d163f..655323b218 100644 --- a/v3/cypress/e2e/graph.spec.ts +++ b/v3/cypress/e2e/graph.spec.ts @@ -224,32 +224,73 @@ context("Graph UI", () => { // this could live in a skipped test for now // TODO: add a folder that has the specific tests - cy.window().then((win: Window) => { // this gets the window globally within the graph element and exposes the PixiJS points - const tileId: any = cy.get("[data-tile=graph-1]").invoke("id") // find the graph element and get the tileID from the graph element + + graph.getGraphTile() + // cy.window().then((win: Window) => { // this gets the window globally within the graph element and exposes the PixiJS points + // Scotts original line of code - const tileId: any = cy.get("[data-tile=graph-1]").invoke("id") // find the graph element and get the tileID from the graph element // NOTE: the correct element to target is
(the code above is wrong and we'd have to change the get // argument to be something that picks up the right element) - const pixiPoints = (win as any).pixiPointsMap[tileId] // within the PixiPoints map look up the PixiPoints for the graph element + // const pixiPoints = (win as any).pixiPointsMap[tileId] // within the PixiPoints map look up the PixiPoints for the graph element // here is where we want to get the number of points, iterate through the points, etc. // for every point, find its (x, y) position // color // the points will be pixi Sprites with their Sprite properties, e.g. texture, or image (OK to experiment here) - }) - graph.getHideShowButton().click() - cy.get("[data-testid=hide-selected-cases]").should("be.disabled") - cy.get("[data-testid=hide-unselected-cases]").should("not.be.disabled") - cy.get("[data-testid=show-all-cases]").should("be.disabled") - cy.get("[data-testid=hide-unselected-cases]").click() - cy.wait(500) - graph.getHideShowButton().click() - cy.get("[data-testid=hide-selected-cases]").should("be.disabled") - cy.get("[data-testid=hide-unselected-cases]").should("be.disabled") - cy.get("[data-testid=show-all-cases]").should("not.be.disabled") - cy.get("[data-testid=show-all-cases]").click() - cy.wait(500) - graph.getHideShowButton().click() - cy.get("[data-testid=hide-selected-cases]").should("be.disabled") - cy.get("[data-testid=hide-unselected-cases]").should("not.be.disabled") - cy.get("[data-testid=show-all-cases]").should("be.disabled") + + // Interacting with the PixiJS canvas points + + // test and log the container + cy.get('[data-testid=codap-graph]') + .parents('.free-tile-component') // Traverse to the parent container + .invoke('attr', 'id') // Get the dynamic ID + .then((tileId) => { + console.log("Tile ID Retrieved from Parent:", tileId) + + cy.window().then((win: any) => { + const pixiPoints = win.pixiPointsMap?.[tileId]; // Use the parent ID + console.log(`PixiPoints for tileId ${tileId}:`, pixiPoints) + expect(pixiPoints).to.exist + }) + }) + + cy.get('[data-testid=codap-graph]') + .parents('.free-tile-component') + .invoke('attr', 'id') // Retrieve the dynamic graph ID + .then((tileId) => { + cy.log(`Tile ID Retrieved: ${tileId}`); // Log the graph ID for debugging + + cy.window().then((win: any) => { + const pixiPoints = win.pixiPointsMap[tileId]; // Access pixiPoints using the graph ID + console.log("PixiPoints Object:", pixiPoints); // Log the pixiPoints object for verification + + // Assert that pixiPoints exist + expect(pixiPoints).to.exist; + + // Access pointsCount + const pointsCount = pixiPoints.pointsCount; // Use the getter to determine the number of points + console.log(`Number of Points (pointsCount): ${pointsCount}`); + + // Assert the number of points + const expectedPointCount = 27; // Expected number of points + expect(pointsCount).to.equal(expectedPointCount); + }); + }); + + // graph.getHideShowButton().click() + // cy.get("[data-testid=hide-selected-cases]").should("be.disabled") + // cy.get("[data-testid=hide-unselected-cases]").should("not.be.disabled") + // cy.get("[data-testid=show-all-cases]").should("be.disabled") + // cy.get("[data-testid=hide-unselected-cases]").click() + // cy.wait(500) + // graph.getHideShowButton().click() + // cy.get("[data-testid=hide-selected-cases]").should("be.disabled") + // cy.get("[data-testid=hide-unselected-cases]").should("be.disabled") + // cy.get("[data-testid=show-all-cases]").should("not.be.disabled") + // cy.get("[data-testid=show-all-cases]").click() + // cy.wait(500) + // graph.getHideShowButton().click() + // cy.get("[data-testid=hide-selected-cases]").should("be.disabled") + // cy.get("[data-testid=hide-unselected-cases]").should("not.be.disabled") + // cy.get("[data-testid=show-all-cases]").should("be.disabled") }) it("displays only selected cases and adjusts axes when 'Display Only Selected Cases' is selected", () => { // TODO: Add more thorough checks to make sure cases are actually hidden and shown, and the axes adjust From 6fe229b6b9a63ced15deda2494dd639763530d48 Mon Sep 17 00:00:00 2001 From: nstclair-cc <20171905+nstclair-cc@users.noreply.github.com> Date: Fri, 13 Dec 2024 09:43:59 -0800 Subject: [PATCH 04/25] enable pixijs --- v3/cypress/e2e/graph.spec.ts | 211 +++++++++++++++++++++++++++++++---- 1 file changed, 192 insertions(+), 19 deletions(-) diff --git a/v3/cypress/e2e/graph.spec.ts b/v3/cypress/e2e/graph.spec.ts index 655323b218..9ac3848baa 100644 --- a/v3/cypress/e2e/graph.spec.ts +++ b/v3/cypress/e2e/graph.spec.ts @@ -1,3 +1,4 @@ +import * as PIXI from "pixi.js" import { GraphTileElements as graph } from "../support/elements/graph-tile" import { TableTileElements as table } from "../support/elements/table-tile" import { ComponentElements as c } from "../support/elements/component-elements" @@ -6,6 +7,60 @@ import { CfmElements as cfm } from "../support/elements/cfm" import { ColorPickerPaletteElements as cpp} from "../support/elements/color-picker-palette" import { AxisHelper as ah } from "../support/helpers/axis-helper" import graphRules from '../fixtures/graph-rules.json' +interface PixiObjectData { + type: string + [key: string]: unknown +} + +type PixiObject = (PIXI.Container | PIXI.DisplayObject) + & { 'pixi-data'?: PixiObjectData } + +function getPixiApplicationStageRoot(): Cypress.Chainable { + return cy.window().then((win) => { + if (!win.pixiApp) { + throw new Error('Pixi Application not attached to window.') + } + return win.pixiApp.stage as PixiObject + }) +} + +function pixiObjectMatches( + pixiObject: PixiObject, requiredPixiObjectData: PixiObjectData +) { + const pixiObjectData = pixiObject['pixi-data'] + if (!pixiObjectData) { + return false + } + return Object.entries(requiredPixiObjectData).every(([key, value]) => { + return pixiObjectData[key] === value || (pixiObject as any)[key] === value + }) +} + +function findPixiObject( + currentPixiObject: PixiObject | undefined, + requiredPixiObjectData: PixiObjectData +): PixiObject | null { + if (!currentPixiObject) { + return null + } + if (pixiObjectMatches(currentPixiObject, requiredPixiObjectData)) { + return currentPixiObject + } + if ('children' in currentPixiObject) { + // currentPixiObject is of type PIXI.Container + for (let i = 0; i < currentPixiObject.children.length; i++) { + // recursion + const childPixiObject = findPixiObject( + currentPixiObject.children[i], + requiredPixiObjectData + ) + if (childPixiObject) { + return childPixiObject + } + } + } + return null +} const collectionName = "Mammals" const newCollectionName = "Animals" @@ -46,6 +101,48 @@ context("Graph UI", () => { cy.wait(2500) }) describe("graph view", () => { + it.only('Validates PixiJS Points and Metadata', () => { + // Step 1: Locate the graph element and retrieve its dynamic ID + cy.get('[data-testid=codap-graph]') + .parents('.free-tile-component') + .invoke('attr', 'id') // Retrieve the graph tile ID + .then((tileId) => { + if (!tileId) { + throw new Error("tileId is undefined or null."); + } + cy.log(`Tile ID Retrieved: ${tileId}`); + + // Step 2: Access the PixiJS application stage root + getPixiApplicationStageRoot().then((stageRoot) => { + // Step 3: Define the required data for matching the PixiPoints object + const requiredPixiObjectData = { type: 'PixiPoints', id: tileId }; + + // Step 4: Find the PixiPoints object in the PixiJS rendering tree + const pixiPointsObject = findPixiObject(stageRoot, requiredPixiObjectData); + + if (!pixiPointsObject) { + throw new Error("PixiPoints object not found in the PixiJS rendering tree."); + } + + cy.log("PixiPoints Object Found:", pixiPointsObject); + + // Step 5: Validate the pointMetadata map + if (pixiPointsObject.pointMetadata && pixiPointsObject.pointMetadata instanceof Map) { + const metadataCount = pixiPointsObject.pointMetadata.size; + cy.log(`pointMetadata Map Size: ${metadataCount}`); + expect(metadataCount).to.be.greaterThan(0); + + // Iterate through pointMetadata entries and log the details + pixiPointsObject.pointMetadata.forEach((metadata, sprite) => { + cy.log(`Sprite: ${sprite}`); + cy.log(`Metadata: ${JSON.stringify(metadata)}`); + }); + } else { + throw new Error("pointMetadata is not a Map or is unavailable."); + } + }); + }); + }); it("should highlight a selected graph point", () => { // This test is the outcome of a SPIKE to explore testing graph interactions. // It partially validates interactions but requires further PIXIJS-level support. @@ -239,41 +336,117 @@ context("Graph UI", () => { // Interacting with the PixiJS canvas points // test and log the container + cy.log('pixiJS test 1: test and log the container') cy.get('[data-testid=codap-graph]') .parents('.free-tile-component') // Traverse to the parent container .invoke('attr', 'id') // Get the dynamic ID .then((tileId) => { - console.log("Tile ID Retrieved from Parent:", tileId) + cy.log("Tile ID Retrieved from Parent:", tileId) cy.window().then((win: any) => { const pixiPoints = win.pixiPointsMap?.[tileId]; // Use the parent ID - console.log(`PixiPoints for tileId ${tileId}:`, pixiPoints) + cy.log(`PixiPoints for tileId ${tileId}:`, pixiPoints) expect(pixiPoints).to.exist }) }) - cy.get('[data-testid=codap-graph]') - .parents('.free-tile-component') - .invoke('attr', 'id') // Retrieve the dynamic graph ID - .then((tileId) => { - cy.log(`Tile ID Retrieved: ${tileId}`); // Log the graph ID for debugging +cy.log('test 2: pixiJS test: Validate pointMetadata size'); - cy.window().then((win: any) => { - const pixiPoints = win.pixiPointsMap[tileId]; // Access pixiPoints using the graph ID - console.log("PixiPoints Object:", pixiPoints); // Log the pixiPoints object for verification +cy.get('[data-testid=codap-graph]') + .parents('.free-tile-component') + .invoke('attr', 'id') // Retrieve the dynamic graph ID + .then((tileId) => { + if (!tileId) { + throw new Error("tileId is undefined or null."); + } + cy.log(`Tile ID Retrieved: ${tileId}`); - // Assert that pixiPoints exist - expect(pixiPoints).to.exist; + cy.window().then((win: any) => { + const pixiPoints = win.pixiPointsMap[tileId]; - // Access pointsCount - const pointsCount = pixiPoints.pointsCount; // Use the getter to determine the number of points - console.log(`Number of Points (pointsCount): ${pointsCount}`); + if (!pixiPoints) { + throw new Error(`PixiPoints not found for tileId: ${tileId}`); + } + + // Log the full pixiPoints object for inspection + cy.log("Full pixiPoints Object:"); + console.log(pixiPoints); + + // Log available keys on pixiPoints + const pixiPointsKeys = Object.keys(pixiPoints); + cy.log("Available keys in pixiPoints:", pixiPointsKeys); + + // Check for pointMetadata + if (pixiPoints.pointMetadata) { + if (pixiPoints.pointMetadata instanceof Map) { + const metadataCount = pixiPoints.pointMetadata.size; + cy.log("pointMetadata Map Size:", metadataCount); + expect(metadataCount).to.be.greaterThan(0); + } else { + cy.log("pointMetadata is not a Map. Logging type and value:"); + cy.log(`Type: ${typeof pixiPoints.pointMetadata}`); + cy.log("Value:", pixiPoints.pointMetadata); + } + } else { + cy.log("pointMetadata is not present in pixiPoints."); + console.error("pointMetadata is not present in pixiPoints."); + } - // Assert the number of points - const expectedPointCount = 27; // Expected number of points - expect(pointsCount).to.equal(expectedPointCount); + // Validate and log caseDataToPoint + if (pixiPoints.caseDataToPoint) { + if (pixiPoints.caseDataToPoint instanceof Map) { + const caseDataCount = pixiPoints.caseDataToPoint.size; + cy.log("caseDataToPoint Map Size:", caseDataCount); + expect(caseDataCount).to.be.greaterThan(0); + + cy.log("caseDataToPoint Entries:"); + pixiPoints.caseDataToPoint.forEach((value: any, key: any) => { + cy.log(`Key: ${key}, Value: ${JSON.stringify(value)}`); }); - }); + } else { + cy.log("caseDataToPoint is not a Map. Logging type and value:"); + cy.log(`Type: ${typeof pixiPoints.caseDataToPoint}`); + cy.log("Value:", pixiPoints.caseDataToPoint); + } + } else { + cy.log("caseDataToPoint is not present in pixiPoints."); + console.error("caseDataToPoint is not present in pixiPoints."); + } + }); + }); + // cy.log('test 3: get the pixijs free tile component interaction') + // cy.get('[data-testid=codap-graph]') + // .parents('.free-tile-component') + // .invoke('attr', 'id') // Retrieve the dynamic graph ID + // .then((tileId) => { + // cy.window().then((win) => { + // if (!win.pixiApp) { + // throw new Error("Pixi Application not attached to window.") + // } + + // const stage = win.pixiApp.stage; // Root of the PixiJS rendering tree + // console.log("PixiJS Stage Object:", stage) + + // // Traverse the stage to count objects in pointMetadata + // let pointCount = 0 + + // function traversePixiTree(node) { + // if (node['pixi-data']?.type === 'point') { + // pointCount += 1 // Increment count if the node matches + // } + // if (node.children) { + // node.children.forEach(traversePixiTree) // Recursively check children + // } + // } + + // traversePixiTree(stage) + + // // Assert the total count matches the expected value + // const expectedPointCount = 27 // Adjust this value as needed + // expect(pointCount).to.equal(expectedPointCount) + // console.log(`Number of Points in Pixi Tree: ${pointCount}`) + // }) + // }) // graph.getHideShowButton().click() // cy.get("[data-testid=hide-selected-cases]").should("be.disabled") From c38326dc0c25f62aa707d2b34866f14223e9ab55 Mon Sep 17 00:00:00 2001 From: nstclair-cc <20171905+nstclair-cc@users.noreply.github.com> Date: Fri, 13 Dec 2024 16:13:18 -0800 Subject: [PATCH 05/25] added some point count scripts --- v3/cypress/e2e/graph.spec.ts | 291 +++-------------------------------- 1 file changed, 19 insertions(+), 272 deletions(-) diff --git a/v3/cypress/e2e/graph.spec.ts b/v3/cypress/e2e/graph.spec.ts index 9ac3848baa..2f44450e6e 100644 --- a/v3/cypress/e2e/graph.spec.ts +++ b/v3/cypress/e2e/graph.spec.ts @@ -1,4 +1,3 @@ -import * as PIXI from "pixi.js" import { GraphTileElements as graph } from "../support/elements/graph-tile" import { TableTileElements as table } from "../support/elements/table-tile" import { ComponentElements as c } from "../support/elements/component-elements" @@ -7,60 +6,6 @@ import { CfmElements as cfm } from "../support/elements/cfm" import { ColorPickerPaletteElements as cpp} from "../support/elements/color-picker-palette" import { AxisHelper as ah } from "../support/helpers/axis-helper" import graphRules from '../fixtures/graph-rules.json' -interface PixiObjectData { - type: string - [key: string]: unknown -} - -type PixiObject = (PIXI.Container | PIXI.DisplayObject) - & { 'pixi-data'?: PixiObjectData } - -function getPixiApplicationStageRoot(): Cypress.Chainable { - return cy.window().then((win) => { - if (!win.pixiApp) { - throw new Error('Pixi Application not attached to window.') - } - return win.pixiApp.stage as PixiObject - }) -} - -function pixiObjectMatches( - pixiObject: PixiObject, requiredPixiObjectData: PixiObjectData -) { - const pixiObjectData = pixiObject['pixi-data'] - if (!pixiObjectData) { - return false - } - return Object.entries(requiredPixiObjectData).every(([key, value]) => { - return pixiObjectData[key] === value || (pixiObject as any)[key] === value - }) -} - -function findPixiObject( - currentPixiObject: PixiObject | undefined, - requiredPixiObjectData: PixiObjectData -): PixiObject | null { - if (!currentPixiObject) { - return null - } - if (pixiObjectMatches(currentPixiObject, requiredPixiObjectData)) { - return currentPixiObject - } - if ('children' in currentPixiObject) { - // currentPixiObject is of type PIXI.Container - for (let i = 0; i < currentPixiObject.children.length; i++) { - // recursion - const childPixiObject = findPixiObject( - currentPixiObject.children[i], - requiredPixiObjectData - ) - if (childPixiObject) { - return childPixiObject - } - } - } - return null -} const collectionName = "Mammals" const newCollectionName = "Animals" @@ -101,74 +46,6 @@ context("Graph UI", () => { cy.wait(2500) }) describe("graph view", () => { - it.only('Validates PixiJS Points and Metadata', () => { - // Step 1: Locate the graph element and retrieve its dynamic ID - cy.get('[data-testid=codap-graph]') - .parents('.free-tile-component') - .invoke('attr', 'id') // Retrieve the graph tile ID - .then((tileId) => { - if (!tileId) { - throw new Error("tileId is undefined or null."); - } - cy.log(`Tile ID Retrieved: ${tileId}`); - - // Step 2: Access the PixiJS application stage root - getPixiApplicationStageRoot().then((stageRoot) => { - // Step 3: Define the required data for matching the PixiPoints object - const requiredPixiObjectData = { type: 'PixiPoints', id: tileId }; - - // Step 4: Find the PixiPoints object in the PixiJS rendering tree - const pixiPointsObject = findPixiObject(stageRoot, requiredPixiObjectData); - - if (!pixiPointsObject) { - throw new Error("PixiPoints object not found in the PixiJS rendering tree."); - } - - cy.log("PixiPoints Object Found:", pixiPointsObject); - - // Step 5: Validate the pointMetadata map - if (pixiPointsObject.pointMetadata && pixiPointsObject.pointMetadata instanceof Map) { - const metadataCount = pixiPointsObject.pointMetadata.size; - cy.log(`pointMetadata Map Size: ${metadataCount}`); - expect(metadataCount).to.be.greaterThan(0); - - // Iterate through pointMetadata entries and log the details - pixiPointsObject.pointMetadata.forEach((metadata, sprite) => { - cy.log(`Sprite: ${sprite}`); - cy.log(`Metadata: ${JSON.stringify(metadata)}`); - }); - } else { - throw new Error("pointMetadata is not a Map or is unavailable."); - } - }); - }); - }); - it("should highlight a selected graph point", () => { - // This test is the outcome of a SPIKE to explore testing graph interactions. - // It partially validates interactions but requires further PIXIJS-level support. - // https://github.com/concord-consortium/codap/pull/1637 - - // Select the target table cell - table.getGridCell(2, 2).should("contain", "African Elephant").click({ force: true }) - - // Verify the graph's component title matches the collection name - c.getComponentTitle("graph").should("contain", collectionName) - - // Re-click the table cell to ensure interaction consistency - table.getGridCell(2, 2).click({ force: true }) - - // Future goal: Validate the highlighted graph point - // ChatGPT suggests this approach could work if PIXIJS exposes DOM elements - // or provides API/event hooks that allow direct verification of point states. - // cy.get('[data-testid="graph"] canvas') - // .should('be.visible') // Ensure the canvas is rendered - // cy.get('[data-testid="graph"]') - // .find('svg .below-points-group circle') // Intended to locate graph points - // .then((elements) => { - // Debugging information (e.g. to find out point color or position of points) - // cy.log('Highlighted point details:', elements) - // }) - }) it("updates graph title", () => { c.getComponentTitle("graph").should("have.text", collectionName) c.changeComponentTitle("graph", newCollectionName) @@ -272,6 +149,8 @@ context("Graph UI", () => { }) }) describe("graph inspector panel", () => { + // This test is broken because of PT-#188601882 + // Skipping for now it.skip("change points in table and check for autoscale", () => { // create a graph with Lifespan (x-axis) and Height (y-axis) c.getComponentTitle("graph").should("have.text", collectionName) @@ -310,160 +189,28 @@ context("Graph UI", () => { // in the graph axis it might be possible to check the axis labels or scale cy.get("[data-testid=graph]").find("[data-testid=axis-bottom]").find(".tick").should("have.length", 29) }) - it.only("hides and shows selected/unselected cases", () => { + it("hides and shows selected/unselected cases", () => { ah.openAxisAttributeMenu("bottom") ah.selectMenuAttribute("Sleep", "bottom") // Sleep => x-axis cy.wait(500) - // TODO: Add more thorough checks to make sure the cases are actually hidden and shown once Cypress is - // configured to interact with the PixiJS canvas. For now, we just check that the buttons are disabled - // and enabled as expected. - - // this could live in a skipped test for now - // TODO: add a folder that has the specific tests - graph.getGraphTile() - // cy.window().then((win: Window) => { // this gets the window globally within the graph element and exposes the PixiJS points - // Scotts original line of code - const tileId: any = cy.get("[data-tile=graph-1]").invoke("id") // find the graph element and get the tileID from the graph element - // NOTE: the correct element to target is
(the code above is wrong and we'd have to change the get - // argument to be something that picks up the right element) - // const pixiPoints = (win as any).pixiPointsMap[tileId] // within the PixiPoints map look up the PixiPoints for the graph element - // here is where we want to get the number of points, iterate through the points, etc. - // for every point, find its (x, y) position - // color - // the points will be pixi Sprites with their Sprite properties, e.g. texture, or image (OK to experiment here) - - // Interacting with the PixiJS canvas points - - // test and log the container - cy.log('pixiJS test 1: test and log the container') - cy.get('[data-testid=codap-graph]') - .parents('.free-tile-component') // Traverse to the parent container - .invoke('attr', 'id') // Get the dynamic ID - .then((tileId) => { - cy.log("Tile ID Retrieved from Parent:", tileId) - - cy.window().then((win: any) => { - const pixiPoints = win.pixiPointsMap?.[tileId]; // Use the parent ID - cy.log(`PixiPoints for tileId ${tileId}:`, pixiPoints) - expect(pixiPoints).to.exist - }) - }) - -cy.log('test 2: pixiJS test: Validate pointMetadata size'); - -cy.get('[data-testid=codap-graph]') - .parents('.free-tile-component') - .invoke('attr', 'id') // Retrieve the dynamic graph ID - .then((tileId) => { - if (!tileId) { - throw new Error("tileId is undefined or null."); - } - cy.log(`Tile ID Retrieved: ${tileId}`); - - cy.window().then((win: any) => { - const pixiPoints = win.pixiPointsMap[tileId]; - - if (!pixiPoints) { - throw new Error(`PixiPoints not found for tileId: ${tileId}`); - } - - // Log the full pixiPoints object for inspection - cy.log("Full pixiPoints Object:"); - console.log(pixiPoints); - - // Log available keys on pixiPoints - const pixiPointsKeys = Object.keys(pixiPoints); - cy.log("Available keys in pixiPoints:", pixiPointsKeys); - - // Check for pointMetadata - if (pixiPoints.pointMetadata) { - if (pixiPoints.pointMetadata instanceof Map) { - const metadataCount = pixiPoints.pointMetadata.size; - cy.log("pointMetadata Map Size:", metadataCount); - expect(metadataCount).to.be.greaterThan(0); - } else { - cy.log("pointMetadata is not a Map. Logging type and value:"); - cy.log(`Type: ${typeof pixiPoints.pointMetadata}`); - cy.log("Value:", pixiPoints.pointMetadata); - } - } else { - cy.log("pointMetadata is not present in pixiPoints."); - console.error("pointMetadata is not present in pixiPoints."); - } - - // Validate and log caseDataToPoint - if (pixiPoints.caseDataToPoint) { - if (pixiPoints.caseDataToPoint instanceof Map) { - const caseDataCount = pixiPoints.caseDataToPoint.size; - cy.log("caseDataToPoint Map Size:", caseDataCount); - expect(caseDataCount).to.be.greaterThan(0); - - cy.log("caseDataToPoint Entries:"); - pixiPoints.caseDataToPoint.forEach((value: any, key: any) => { - cy.log(`Key: ${key}, Value: ${JSON.stringify(value)}`); - }); - } else { - cy.log("caseDataToPoint is not a Map. Logging type and value:"); - cy.log(`Type: ${typeof pixiPoints.caseDataToPoint}`); - cy.log("Value:", pixiPoints.caseDataToPoint); - } - } else { - cy.log("caseDataToPoint is not present in pixiPoints."); - console.error("caseDataToPoint is not present in pixiPoints."); - } - }); - }); - // cy.log('test 3: get the pixijs free tile component interaction') - // cy.get('[data-testid=codap-graph]') - // .parents('.free-tile-component') - // .invoke('attr', 'id') // Retrieve the dynamic graph ID - // .then((tileId) => { - // cy.window().then((win) => { - // if (!win.pixiApp) { - // throw new Error("Pixi Application not attached to window.") - // } - - // const stage = win.pixiApp.stage; // Root of the PixiJS rendering tree - // console.log("PixiJS Stage Object:", stage) - - // // Traverse the stage to count objects in pointMetadata - // let pointCount = 0 - - // function traversePixiTree(node) { - // if (node['pixi-data']?.type === 'point') { - // pointCount += 1 // Increment count if the node matches - // } - // if (node.children) { - // node.children.forEach(traversePixiTree) // Recursively check children - // } - // } - - // traversePixiTree(stage) - - // // Assert the total count matches the expected value - // const expectedPointCount = 27 // Adjust this value as needed - // expect(pointCount).to.equal(expectedPointCount) - // console.log(`Number of Points in Pixi Tree: ${pointCount}`) - // }) - // }) - - // graph.getHideShowButton().click() - // cy.get("[data-testid=hide-selected-cases]").should("be.disabled") - // cy.get("[data-testid=hide-unselected-cases]").should("not.be.disabled") - // cy.get("[data-testid=show-all-cases]").should("be.disabled") - // cy.get("[data-testid=hide-unselected-cases]").click() - // cy.wait(500) - // graph.getHideShowButton().click() - // cy.get("[data-testid=hide-selected-cases]").should("be.disabled") - // cy.get("[data-testid=hide-unselected-cases]").should("be.disabled") - // cy.get("[data-testid=show-all-cases]").should("not.be.disabled") - // cy.get("[data-testid=show-all-cases]").click() - // cy.wait(500) - // graph.getHideShowButton().click() - // cy.get("[data-testid=hide-selected-cases]").should("be.disabled") - // cy.get("[data-testid=hide-unselected-cases]").should("not.be.disabled") - // cy.get("[data-testid=show-all-cases]").should("be.disabled") + graph.getHideShowButton().click() + cy.get("[data-testid=hide-selected-cases]").should("be.disabled") + cy.get("[data-testid=hide-unselected-cases]").should("not.be.disabled") + cy.get("[data-testid=show-all-cases]").should("be.disabled") + cy.get("[data-testid=hide-unselected-cases]").click() + cy.wait(500) + graph.getHideShowButton().click() + cy.get("[data-testid=hide-selected-cases]").should("be.disabled") + cy.get("[data-testid=hide-unselected-cases]").should("be.disabled") + cy.get("[data-testid=show-all-cases]").should("not.be.disabled") + cy.get("[data-testid=show-all-cases]").click() + cy.wait(500) + graph.getHideShowButton().click() + cy.get("[data-testid=hide-selected-cases]").should("be.disabled") + cy.get("[data-testid=hide-unselected-cases]").should("not.be.disabled") + cy.get("[data-testid=show-all-cases]").should("be.disabled") }) it("displays only selected cases and adjusts axes when 'Display Only Selected Cases' is selected", () => { // TODO: Add more thorough checks to make sure cases are actually hidden and shown, and the axes adjust From 57568dfcbe383a377cd2cdae90cea8d9be4686c5 Mon Sep 17 00:00:00 2001 From: nstclair-cc <20171905+nstclair-cc@users.noreply.github.com> Date: Mon, 16 Dec 2024 15:20:16 -0800 Subject: [PATCH 06/25] added checks for count of graph points --- .../graph-pixi-interaction.spec.ts | 357 ++++++++++++++++++ .../support/helpers/graph-canvas-helper.ts | 55 +++ 2 files changed, 412 insertions(+) create mode 100644 v3/cypress/e2e/pixi-interaction/graph-pixi-interaction.spec.ts create mode 100644 v3/cypress/support/helpers/graph-canvas-helper.ts diff --git a/v3/cypress/e2e/pixi-interaction/graph-pixi-interaction.spec.ts b/v3/cypress/e2e/pixi-interaction/graph-pixi-interaction.spec.ts new file mode 100644 index 0000000000..2175e87f31 --- /dev/null +++ b/v3/cypress/e2e/pixi-interaction/graph-pixi-interaction.spec.ts @@ -0,0 +1,357 @@ +// import * as PIXI from "pixi.js" +import { GraphTileElements as graph } from "../../support/elements/graph-tile" +import { TableTileElements as table } from "../../support/elements/table-tile" +import { ComponentElements as c } from "../../support/elements/component-elements" +//import { ToolbarElements as toolbar } from "../../support/elements/toolbar-elements" +import { CfmElements as cfm } from "../../support/elements/cfm" +//import { ColorPickerPaletteElements as cpp} from "../../support/elements/color-picker-palette" +import { GraphCanvasHelper as gch } from "../../support/helpers/graph-canvas-helper" +import { AxisHelper as ah } from "../../support/helpers/axis-helper" +import graphRules from '../../fixtures/graph-rules.json' + +const plots = graphRules.plots + +//these tests may be run locally if desired. they can take awhile for on the cloud +context.skip("Test graph plot transitions", () => { + beforeEach(function () { + const queryParams = "?mouseSensor" + const url = `${Cypress.config("index")}${queryParams}` + cy.visit(url) + cfm.openLocalDoc("cypress/fixtures/3TableGroups.codap") + cy.wait(2500) + }) + + plots.forEach(test => { + it(`${test.testName}`, () => { + c.getIconFromToolShelf("graph").click() + c.moveComponent("graph", 1000) + test.axes.forEach(hash => { + hash.checks.forEach(check => { + cy.checkDragAttributeHighlights("table", hash.attribute, check.axis, check.active) + }) + cy.dragAttributeToTarget("table", hash.attribute, hash.target) + cy.wait(2000) + }) + }) + }) +}) + +context("Graph UI with Pixi interaction", () => { + beforeEach(function () { + const queryParams = "?sample=mammals&dashboard&mouseSensor" + const url = `${Cypress.config("index")}${queryParams}` + cy.visit(url) + cy.wait(2500) + }) + describe("graph view", () => { + it("validates point count for univariate graphs with different hierarchies with pixi interaction", () => { + cy.log('Correct number of points in univariate graphs with missing data in cases') + + cy.log('Test for "Mass" univariate (27 points)') + gch.setAxisAndRetrieveTileId("Mass", "bottom").then((tileId) => { + gch.validateGraphPointCount(tileId, 27) + }) + + cy.log('Test for "Sleep" univariate (24 points)') + gch.setAxisAndRetrieveTileId("Sleep", "bottom").then((tileId) => { + gch.validateGraphPointCount(tileId, 24) + }) + + cy.log('Test for "Diet" univariate with hierarchy (3 points)') + // Drag Diet to parent in case table + table.moveAttributeToParent("Diet", "newCollection") + table.getNumOfRows(1).should("contain", 5) // five rows: top, plants, meat, both, bottom + gch.setAxisAndRetrieveTileId("Diet", "bottom").then((tileId) => { + gch.validateGraphPointCount(tileId, 3) + }) + + cy.log('Test for "Diet", "Habitat" univariate with hierarchy (5 points)') + table.moveAttributeToParent("Habitat", "newCollection") + table.getNumOfRows(1).should("contain", 5) // five rows: top, land, water, both, bottom + gch.setAxisAndRetrieveTileId("Diet", "bottom").then((tileId) => { + gch.validateGraphPointCount(tileId, 5) + }) + }) + it("Checks count for hides and shows selected/unselected cases with pixijs interaction", () => { + ah.openAxisAttributeMenu("bottom") + ah.selectMenuAttribute("Sleep", "bottom") // Sleep => x-axis + cy.wait(500) + graph.getGraphTile() + + // Hide all cases + cy.log('Hide all cases in graph') + graph.getHideShowButton().click() + cy.get("[data-testid=hide-selected-cases]").should("be.disabled") + cy.get("[data-testid=hide-unselected-cases]").click() + gch.getGraphTileId().then((tileId) => { + gch.validateGraphPointCount(tileId, 0) // 0 points in graph + }) + + // Show all cases + cy.log('Show all cases in graph') + graph.getGraphTile() + graph.getHideShowButton().click() + cy.get("[data-testid=hide-selected-cases]").should("be.disabled") + cy.get("[data-testid=show-all-cases]").click() + cy.wait(500) + gch.getGraphTileId().then((tileId) => { + gch.validateGraphPointCount(tileId, 24) // 24 points in graph + }) + + cy.log('Hide selected cases in graph') + table.moveAttributeToParent("Diet", "newCollection") + table.getNumOfRows(1).should("contain", 5) // five rows: top, plants, meat, both, bottom + table.getGridCell(2, 2).should("contain", "plants").click() + graph.getGraphTile().click() + graph.getHideShowButton().click() + cy.get("[data-testid=hide-selected-cases]").should("not.be.disabled").click() + cy.wait(500) + gch.getGraphTileId().then((tileId) => { + gch.validateGraphPointCount(tileId, 18) // 18 points in graph + }) + }) + // it.skip("should highlight a selected graph point with pixi interaction", () => { + // // this is a spike to get interaction with point selection and texture + // // it makes use of the texture call in the pixiPoint + // // However, I'm pretty sure I've got the structure of the array wrong + // // this code tries to log and verify the selected case ID and see how it + // // interacts with the pixiPoint array + + // /** + // * Helper function to check if a graph point is selected. + // * @param pointMetadata - The `pointMetadata` map from `pixiPoints`. + // * @param selectedCaseID - The case ID of the selected point. + // * @param expectedColor - The expected color of the selected point. + // * @returns True if the point matches the expected color, false otherwise. + // */ + // const isPointSelected = (pointMetadata: Map, + // selectedCaseID: string, expectedColor: string): boolean => { + // // Find the entry in `pointMetadata` matching the `selectedCaseID` + // const selectedEntry = Array.from(pointMetadata.values()).find( + // (entry: any) => entry.caseID === selectedCaseID + // ) + // if (!selectedEntry) { + // console.log('No entry found for selected case ID:', selectedCaseID) + // return false + // } + // console.log('Selected Entry:', selectedEntry) + + // // Check if the `style.fill` matches the expected color + // const fillColor = selectedEntry.style?.fill + // console.log('Extracted Fill Color:', fillColor) + // return fillColor === expectedColor + // } + + // // Select the target table cell and dynamically retrieve the caseID + // table.getGridCell(2, 2) + // .should("contain", "African Elephant") + // .invoke('attr', 'data-row-id') + // .then((selectedCaseID) => { + // cy.log('Selected Case ID:', selectedCaseID) + + // // Verify the graph's component title matches the collection name + // c.getComponentTitle("graph").should("contain", collectionName) + + // // Re-click the table cell to ensure interaction consistency + // table.getGridCell(2, 2).click({ force: true }) + + // // Locate the graph and retrieve its dynamic tile ID + // cy.get('[data-testid=codap-graph]') + // .parents('.free-tile-component') + // .invoke('attr', 'id') + // .then((tileId) => { + // if (!tileId) { + // throw new Error("tileId is undefined or null.") + // } + // cy.log(`Graph Tile ID Retrieved: ${tileId}`) + + // // Access PIXI metadata + // cy.window().then((win: any) => { + // const pixiPoints = win.pixiPointsMap[tileId] + // cy.log('Full Pixi Points Object:', pixiPoints) + + // // Verify pixiPoints exists + // expect(pixiPoints[0], 'Pixi points should exist').to.exist + + // // Access `pointMetadata` + // const pointMetadata = pixiPoints[0].pointMetadata + // cy.log('Point Metadata Map:', pointMetadata) + + // // Verify the point is highlighted + // const selectedColor = "#4682B4" // Expected color for a selected point + // const isSelected = isPointSelected(pointMetadata, selectedCaseID, selectedColor) + + // expect(isSelected, `Point with case ID ${selectedCaseID} should be highlighted`).to.be.true + // }) + // }) + // }) + // }) + }) + describe("case card graph interaction", () => { + it("can drag attributes from the case card to the graph with pixijs interaction", () => { + const tableHeaderLeftSelector = ".codap-component.codap-case-table .component-title-bar .header-left" + cy.get(tableHeaderLeftSelector).click() + cy.get(`${tableHeaderLeftSelector} .card-table-toggle-message`).click() + cy.wait(500) + cy.dragAttributeToTarget("card", "Speed", "left") + cy.wait(500) + cy.get('[data-testid="axis-legend-attribute-button-left"]').should("have.text", "Speed") + gch.getGraphTileId().then((tileId) => { + gch.validateGraphPointCount(tileId, 24) // 24 points in graph + }) + }) + }) + describe("graph inspector panel with pixijs interaction", () => { + it("shows warning if 'Display Only Selected Cases' is selected and no cases have been selected with pixijs", () => { + ah.openAxisAttributeMenu("bottom") + ah.selectMenuAttribute("Sleep", "bottom") // Sleep => x-axis + cy.wait(500) + cy.get("[data-testid=display-only-selected-warning]").should("not.exist") + graph.getHideShowButton().click() + cy.get("[data-testid=display-selected-cases]").click() + gch.getGraphTileId().then((tileId) => { + gch.validateGraphPointCount(tileId, 0) // 0 points in graph + }) + // The warning is made up of six individual strings rendered in their own separate text elements + cy.get("[data-testid=display-only-selected-warning]").should("exist").and("have.length", 6) + graph.getHideShowButton().click() + // Resorting to using force: true because the option's parent is reported as hidden in CI but not locally. + cy.get("[data-testid=show-all-cases]").click({force: true}) + cy.get("[data-testid=display-only-selected-warning]").should("not.exist") + gch.getGraphTileId().then((tileId) => { + gch.validateGraphPointCount(tileId, 24) // 24 points in graph + }) + }) + it("shows parent visibility toggles when Show Parent Visibility Toggles option is selected with pixijs", () => { + ah.openAxisAttributeMenu("bottom") + ah.selectMenuAttribute("Sleep", "bottom") // Sleep => x-axis + cy.wait(500) + cy.get("[data-testid=parent-toggles-container]").should("not.exist") + graph.getHideShowButton().click() + cy.wait(500) + cy.get("[data-testid=show-parent-toggles]").should("exist").and("have.text", "Show Parent Visibility Toggles") + cy.get("[data-testid=show-parent-toggles]").click() + cy.wait(500) + gch.getGraphTileId().then((tileId) => { + gch.validateGraphPointCount(tileId, 24) // 24 points in graph + }) + cy.get("[data-testid=parent-toggles-container]").should("exist") + cy.get("[data-testid=parent-toggles-all]").should("exist").and("have.text", "Hide All –") + cy.get("[data-testid=parent-toggles-case-buttons-list]").find("button").should("exist").and("have.length", 27) + cy.get("[data-testid=parent-toggles-last]").should("exist").and("have.text", "☐ Last") + cy.get("[data-testid=graph]").click() + cy.get("[data-testid=parent-toggles-case-buttons-list]") + .find("button").contains("Spotted Hyena").should("exist").and("not.have.class", "case-hidden") + cy.get("[data-testid=parent-toggles-case-buttons-list]") + .find("button").contains("Spotted Hyena").click() + cy.get("[data-testid=parent-toggles-case-buttons-list]") + .find("button").contains("Spotted Hyena").should("have.class", "case-hidden") + gch.getGraphTileId().then((tileId) => { + gch.validateGraphPointCount(tileId, 23) // 23 points in graph + }) + cy.get("[data-testid=parent-toggles-case-buttons-list]") + .find("button").contains("Spotted Hyena").click() + cy.get("[data-testid=parent-toggles-case-buttons-list]") + .find("button").contains("Spotted Hyena").should("not.have.class", "case-hidden") + gch.getGraphTileId().then((tileId) => { + gch.validateGraphPointCount(tileId, 24) // 24 points in graph + }) + cy.get("[data-testid=parent-toggles-case-buttons-list]") + .find("button").contains("Red Fox").should("exist").and("be.visible") + cy.get("[data-testid=parent-toggles-case-buttons-list]") + .find("button").contains("Owl Monkey").should("exist").and("not.be.visible") + cy.get("[data-testid=parent-toggles-case-buttons-left]").should("exist") + cy.get("[data-testid=parent-toggles-case-buttons-right]").should("not.exist") + cy.get("[data-testid=parent-toggles-case-buttons-left]").click() + cy.wait(250) + cy.get("[data-testid=parent-toggles-case-buttons-right]").should("exist") + cy.get("[data-testid=parent-toggles-case-buttons-list]").find("button") + .contains("Red Fox").should("exist").and("not.be.visible") + cy.get("[data-testid=parent-toggles-case-buttons-list]") + .find("button").contains("Owl Monkey").should("exist").and("be.visible") + cy.get("[data-testid=parent-toggles-case-buttons-right]").click() + cy.wait(250) + cy.get("[data-testid=parent-toggles-case-buttons-list]") + .find("button").contains("Red Fox").should("exist").and("be.visible") + cy.get("[data-testid=parent-toggles-case-buttons-list]") + .find("button").contains("Gray Wolf").should("exist").and("not.be.visible") + cy.get("[data-testid=parent-toggles-last]").click() + gch.getGraphTileId().then((tileId) => { + gch.validateGraphPointCount(tileId, 1) // 1 point in graph + }) + cy.wait(250) + cy.get("[data-testid=parent-toggles-last]").should("have.text", "☒ Last") + cy.get("[data-testid=parent-toggles-case-buttons-list]").find("button").then((buttons) => { + const lastButtonIndex = buttons.length - 1 + buttons.each((i: number, button: HTMLButtonElement) => { + if (i !== lastButtonIndex) { + cy.wrap(button).should("have.class", "case-hidden") + } else { + cy.wrap(button).should("not.have.class", "case-hidden") + } + }) + }) + cy.get("[data-testid=parent-toggles-all]").should("have.text", "Show All –") + cy.get("[data-testid=parent-toggles-all]").click() + gch.getGraphTileId().then((tileId) => { + gch.validateGraphPointCount(tileId, 24) // 24 points in graph + }) + cy.wait(500) + cy.get("[data-testid=parent-toggles-case-buttons-list]").find("button").each((button: HTMLButtonElement) => { + cy.wrap(button).should("not.have.class", "case-hidden") + }) + cy.get("[data-testid=parent-toggles-last]").should("have.text", "☐ Last") + cy.get("[data-testid=parent-toggles-all]").should("have.text", "Hide All –") + cy.get("[data-testid=parent-toggles-last]").click() + cy.wait(250) + cy.get("[data-testid=parent-toggles-last]").should("have.text", "☒ Last") + cy.get("[data-testid=parent-toggles-case-buttons-list]").find("button").contains("Red Fox").click() + cy.get("[data-testid=parent-toggles-last]").should("have.text", "☐ Last") + // TODO: Figure out why the below doesn't work in Cypress -- some buttons aren't being set to 'case-hidden' when + // Hide All is clicked. It seems to work fine in a web browser, though. + // cy.get("[data-testid=parent-toggles-all]").should("have.text", "Show All –").click() + // cy.get("[data-testid=parent-toggles-all]").should("have.text", "Hide All –").click() + // cy.get("[data-testid=parent-toggles-case-buttons-list]").find("button").each((button: HTMLButtonElement) => { + // cy.wrap(button).should("have.class", "case-hidden") + // }) + cy.get("[data-testid=parent-toggles-all]").should("have.text", "Show All –") + cy.get("[data-testid=parent-toggles-all]").click() + cy.get("[data-testid=parent-toggles-case-buttons-list]").find("button").each((button: HTMLButtonElement) => { + cy.wrap(button).should("not.have.class", "case-hidden") + }) + graph.getHideShowButton().click() + cy.wait(500) + cy.get("[data-testid=show-parent-toggles]").should("exist").and("have.text", "Hide Parent Visibility Toggles") + cy.get("[data-testid=show-parent-toggles]").click() + cy.get("[data-testid=parent-toggles]").should("not.exist") + graph.getHideShowButton().click() + cy.wait(500) + cy.get("[data-testid=show-parent-toggles]").should("exist").and("have.text", "Show Parent Visibility Toggles") + }) + + it("adds a banner to the graph when Show Measures for Selection is activated with pixijs interaction", () => { + ah.openAxisAttributeMenu("bottom") + ah.selectMenuAttribute("Sleep", "bottom") // Sleep => x-axis + cy.get("[data-testid=measures-for-selection-banner]").should("not.exist") + graph.getHideShowButton().click() + cy.wait(500) + cy.get("[data-testid=show-selection-measures]").click() + cy.wait(500) + gch.getGraphTileId().then((tileId) => { + gch.validateGraphPointCount(tileId, 24) // 24 points in graph + }) + cy.get("[data-testid=measures-for-selection-banner]") + .should("exist").and("have.text", "Showing measures for 0 selected cases") + graph.getHideShowButton().click() + cy.wait(500) + gch.getGraphTileId().then((tileId) => { + gch.validateGraphPointCount(tileId, 24) // 24 points in graph + }) + cy.get("[data-testid=show-selection-measures]").click() + cy.wait(500) + cy.get("[data-testid=measures-for-selection-banner]").should("not.exist") + }) + + // NOTE: Adornments are covered in graph-adornments.spec.ts (including Show Measures) + }) +}) diff --git a/v3/cypress/support/helpers/graph-canvas-helper.ts b/v3/cypress/support/helpers/graph-canvas-helper.ts new file mode 100644 index 0000000000..8d55ee0d09 --- /dev/null +++ b/v3/cypress/support/helpers/graph-canvas-helper.ts @@ -0,0 +1,55 @@ +import { AxisHelper as ah } from "./axis-helper" + +export const GraphCanvasHelper = { + // Helper function to locate the graph element and retrieve its dynamic ID. + getGraphTileId(): Cypress.Chainable { + cy.log('Locate the graph element and retrieve its dynamic ID') + return cy.get('[data-testid=codap-graph]') + .parents('.free-tile-component') + .invoke('attr', 'id') + .then((tileId) => { + if (!tileId) { + throw new Error('Error: tileId is undefined or null.') + } + cy.log(`Graph Tile ID Retrieved: ${tileId}`) + return Cypress.Promise.resolve(tileId) // Ensure Cypress compatibility + }) + }, + // Helper function to set an attribute for the axis and retrieve the tile ID. + setAxisAndRetrieveTileId (attribute: string, axis: "bottom" | "left") { + cy.log(`Set ${attribute} on ${axis} axis`) + ah.openAxisAttributeMenu(axis) + ah.selectMenuAttribute(attribute, axis) + cy.wait(500) + + cy.log('Locate the graph element and retrieve its dynamic ID') + return cy.get('[data-testid=codap-graph]') + .parents('.free-tile-component') + .invoke('attr', 'id') + }, + // Helper function to validate pixi metadata and point count. + validateGraphPointCount(tileId: string | undefined, expectedPointCount: number) { + if (!tileId) { + throw new Error("Error: tileId is undefined or null. Cannot validate graph point count.") + } + + cy.log('Get the pixi metadata') + cy.window().then((win: any) => { + const pixiPoints = win.pixiPointsMap[tileId] // Access pixiPoints using the graph ID + cy.log('PixiPoints Object:', pixiPoints) // Log the pixiPoints object for verification + + // Assert that pixiPoints exist + chai.assert.exists(pixiPoints, "PixiPoints object exists") + + // Assert that pixiPoints[0] exists + chai.assert.exists(pixiPoints[0], "PixiPoints[0] exists") + + // Access pointsCount + const pointsCount = pixiPoints[0].pointsCount // Use the getter to determine the number of points + cy.log(`Number of Points (pointsCount): ${pointsCount}`) + + // Assert the number of points + expect(pointsCount).to.equal(expectedPointCount, "Point count matches expected value") + }) + } +} From 019a256b799ecb4384f73baee88b491b925b9815 Mon Sep 17 00:00:00 2001 From: nstclair-cc <20171905+nstclair-cc@users.noreply.github.com> Date: Mon, 16 Dec 2024 15:41:35 -0800 Subject: [PATCH 07/25] added github labels to run pixi tests --- .github/workflows/graph-pixi-tests.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .github/workflows/graph-pixi-tests.yml diff --git a/.github/workflows/graph-pixi-tests.yml b/.github/workflows/graph-pixi-tests.yml new file mode 100644 index 0000000000..d34b8672d6 --- /dev/null +++ b/.github/workflows/graph-pixi-tests.yml @@ -0,0 +1,19 @@ +name: Regression Label + +on: + pull_request: + types: [ labeled ] +jobs: + re_run: + # only continue if a push or PR labeled with 'run regression' + if: github.event.label.name == 'graph-pixi-tests' + runs-on: ubuntu-latest + permissions: + actions: write + steps: + - name: Run Cypress tests for graph-pixi + run: npm run cypress:run -- --spec "cypress/e2e/pixi-interaction/*" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # set repository so we don't have to check out all of the code + GH_REPO: ${{github.repository}} From 10f5179eb63f37e71453c22f15ebf394b52e2773 Mon Sep 17 00:00:00 2001 From: nstclair-cc <20171905+nstclair-cc@users.noreply.github.com> Date: Tue, 17 Dec 2024 16:01:27 -0800 Subject: [PATCH 08/25] Adds checks for map with pixi js interaction and spike for (x, y) coordinates with pixi js --- .../graph-pixi-interaction.spec.ts | 142 ++++++++++-------- .../components/map/components/codap-map.tsx | 10 ++ 2 files changed, 86 insertions(+), 66 deletions(-) diff --git a/v3/cypress/e2e/pixi-interaction/graph-pixi-interaction.spec.ts b/v3/cypress/e2e/pixi-interaction/graph-pixi-interaction.spec.ts index 2175e87f31..a443f5844f 100644 --- a/v3/cypress/e2e/pixi-interaction/graph-pixi-interaction.spec.ts +++ b/v3/cypress/e2e/pixi-interaction/graph-pixi-interaction.spec.ts @@ -2,7 +2,7 @@ import { GraphTileElements as graph } from "../../support/elements/graph-tile" import { TableTileElements as table } from "../../support/elements/table-tile" import { ComponentElements as c } from "../../support/elements/component-elements" -//import { ToolbarElements as toolbar } from "../../support/elements/toolbar-elements" +import { ToolbarElements as toolbar } from "../../support/elements/toolbar-elements" import { CfmElements as cfm } from "../../support/elements/cfm" //import { ColorPickerPaletteElements as cpp} from "../../support/elements/color-picker-palette" import { GraphCanvasHelper as gch } from "../../support/helpers/graph-canvas-helper" @@ -45,6 +45,9 @@ context("Graph UI with Pixi interaction", () => { }) describe("graph view", () => { it("validates point count for univariate graphs with different hierarchies with pixi interaction", () => { + + cy.log('opening the map will appear with the correct number of points') + cy.log('Correct number of points in univariate graphs with missing data in cases') cy.log('Test for "Mass" univariate (27 points)') @@ -110,82 +113,89 @@ context("Graph UI with Pixi interaction", () => { gch.validateGraphPointCount(tileId, 18) // 18 points in graph }) }) - // it.skip("should highlight a selected graph point with pixi interaction", () => { - // // this is a spike to get interaction with point selection and texture - // // it makes use of the texture call in the pixiPoint - // // However, I'm pretty sure I've got the structure of the array wrong - // // this code tries to log and verify the selected case ID and see how it - // // interacts with the pixiPoint array + it.skip("should highlight a selected graph point with pixi interaction", () => { + // this is a spike to get interaction with point selection and texture + // it makes use of the texture call in the pixiPoint + // However, I'm pretty sure I've got the structure of the array wrong + // this code tries to log and verify the selected case ID and see how it + // interacts with the pixiPoint array + + // /** + // * Helper function to check if a graph point is selected. + // * @param pointMetadata - The `pointMetadata` map from `pixiPoints`. + // * @param selectedCaseID - The case ID of the selected point. + // * @param expectedColor - The expected color of the selected point. + // * @returns True if the point matches the expected color, false otherwise. + // */ + const collectionName = "newCollectionName" - // /** - // * Helper function to check if a graph point is selected. - // * @param pointMetadata - The `pointMetadata` map from `pixiPoints`. - // * @param selectedCaseID - The case ID of the selected point. - // * @param expectedColor - The expected color of the selected point. - // * @returns True if the point matches the expected color, false otherwise. - // */ - // const isPointSelected = (pointMetadata: Map, - // selectedCaseID: string, expectedColor: string): boolean => { - // // Find the entry in `pointMetadata` matching the `selectedCaseID` - // const selectedEntry = Array.from(pointMetadata.values()).find( - // (entry: any) => entry.caseID === selectedCaseID - // ) - // if (!selectedEntry) { - // console.log('No entry found for selected case ID:', selectedCaseID) - // return false - // } - // console.log('Selected Entry:', selectedEntry) + const isPointSelected = (pointMetadata: Map, + selectedCaseID: string, expectedColor: string): boolean => { + // Find the entry in `pointMetadata` matching the `selectedCaseID` + const selectedEntry = Array.from(pointMetadata.values()).find( + (entry: any) => entry.caseID === selectedCaseID + ) + if (!selectedEntry) { + cy.log('No entry found for selected case ID:', selectedCaseID) + return false + } + cy.log('Selected Entry:', selectedEntry) - // // Check if the `style.fill` matches the expected color - // const fillColor = selectedEntry.style?.fill - // console.log('Extracted Fill Color:', fillColor) - // return fillColor === expectedColor - // } + // Check if the `style.fill` matches the expected color + const fillColor = selectedEntry.style?.fill + cy.log('Extracted Fill Color:', fillColor) + return fillColor === expectedColor + } - // // Select the target table cell and dynamically retrieve the caseID - // table.getGridCell(2, 2) - // .should("contain", "African Elephant") - // .invoke('attr', 'data-row-id') - // .then((selectedCaseID) => { - // cy.log('Selected Case ID:', selectedCaseID) + // Select the target table cell and dynamically retrieve the caseID + table.getGridCell(2, 2) + .should("contain", "African Elephant") + .invoke('attr', 'data-row-id') + .then((selectedCaseID) => { + cy.log(`Selected Case ID: ${selectedCaseID}`) + // extract the caseID from the data cell, which is in the div:
African Elephant
) - // // Verify the graph's component title matches the collection name - // c.getComponentTitle("graph").should("contain", collectionName) + // Verify the graph's component title matches the collection name + c.getComponentTitle("graph").should("contain", collectionName) - // // Re-click the table cell to ensure interaction consistency - // table.getGridCell(2, 2).click({ force: true }) + // Re-click the table cell to ensure interaction consistency + table.getGridCell(2, 2).click({ force: true }) - // // Locate the graph and retrieve its dynamic tile ID - // cy.get('[data-testid=codap-graph]') - // .parents('.free-tile-component') - // .invoke('attr', 'id') - // .then((tileId) => { - // if (!tileId) { - // throw new Error("tileId is undefined or null.") - // } - // cy.log(`Graph Tile ID Retrieved: ${tileId}`) + // for getting position + // pixiPointsMap[tileID][0].points[0].position.x + // pixiPointsMap[tileID][0].points[0].position.y - // // Access PIXI metadata - // cy.window().then((win: any) => { - // const pixiPoints = win.pixiPointsMap[tileId] - // cy.log('Full Pixi Points Object:', pixiPoints) + // Locate the graph and retrieve its dynamic tile ID + cy.get('[data-testid=codap-graph]') + .parents('.free-tile-component') + .invoke('attr', 'id') + .then((tileId) => { + if (!tileId) { + throw new Error("tileId is undefined or null.") + } + cy.log(`Graph Tile ID Retrieved: ${tileId}`) - // // Verify pixiPoints exists - // expect(pixiPoints[0], 'Pixi points should exist').to.exist + // Access PIXI metadata + cy.window().then((win: any) => { + const pixiPoints = win.pixiPointsMap[tileId] + cy.log('Full Pixi Points Object:', pixiPoints) - // // Access `pointMetadata` - // const pointMetadata = pixiPoints[0].pointMetadata - // cy.log('Point Metadata Map:', pointMetadata) + // Verify pixiPoints exists + expect(pixiPoints[0], 'Pixi points should exist').to.exist - // // Verify the point is highlighted - // const selectedColor = "#4682B4" // Expected color for a selected point - // const isSelected = isPointSelected(pointMetadata, selectedCaseID, selectedColor) + // Access `pointMetadata` + const pointMetadata = pixiPoints[0].pointMetadata + cy.log('Point Metadata Map:', pointMetadata) - // expect(isSelected, `Point with case ID ${selectedCaseID} should be highlighted`).to.be.true - // }) - // }) - // }) - // }) + // Verify the point is highlighted + const selectedColor = "#4682B4" // Expected color for a selected point + const isSelected = isPointSelected(pointMetadata, selectedCaseID, selectedColor) + + expect(isSelected, `Point with case ID ${selectedCaseID} should be highlighted`).to.be.true + }) + }) + }) + }) }) describe("case card graph interaction", () => { it("can drag attributes from the case card to the graph with pixijs interaction", () => { diff --git a/v3/src/components/map/components/codap-map.tsx b/v3/src/components/map/components/codap-map.tsx index da9003d4f9..e942c6988d 100644 --- a/v3/src/components/map/components/codap-map.tsx +++ b/v3/src/components/map/components/codap-map.tsx @@ -11,6 +11,7 @@ import {useDataDisplayLayout} from "../../data-display/hooks/use-data-display-la import {usePixiPointerDownDeselect} from "../../data-display/hooks/use-pixi-pointer-down-deselect" import {MultiLegend} from "../../data-display/components/legend/multi-legend" import {usePixiPointsArray} from "../../data-display/hooks/use-pixi-points-array" +import { DEBUG_PIXI_POINTS } from "../../../lib/debug" import {logStringifiedObjectMessage} from "../../../lib/log-message" import {DroppableMapArea} from "./droppable-map-area" import {MapBackground} from "./map-background" @@ -22,6 +23,7 @@ import {MapGridSlider} from "./map-grid-slider" import "leaflet/dist/leaflet.css" import "./map.scss" +import { useTileModelContext } from "../../../hooks/use-tile-model-context" interface IProps { mapRef: MutableRefObject @@ -38,6 +40,14 @@ export const CodapMap = observer(function CodapMap({mapRef}: IProps) { // trigger an additional render once references have been fulfilled useEffect(() => forceUpdate(), [forceUpdate]) + const { tile } = useTileModelContext() + + // weak map? + if (((window as any).Cypress || DEBUG_PIXI_POINTS) && tile?.id) { + const pixiPointsMap: any = (window as any).pixiPointsMap || ({} as Record) + ;(window as any).pixiPointsMap = pixiPointsMap + pixiPointsMap[tile.id] = pixiPointsArray + } usePixiPointerDownDeselect(pixiPointsArray, mapModel) From aa1dd09c9fe2e12707ca229cf1b9b95a0ffec191 Mon Sep 17 00:00:00 2001 From: nstclair-cc <20171905+nstclair-cc@users.noreply.github.com> Date: Thu, 19 Dec 2024 16:20:32 -0800 Subject: [PATCH 09/25] added map point count tests for Cypress --- .../map-pixi-interaction.spec.ts | 77 +++++++++++++++++++ .../support/helpers/map-canvas-helper.ts | 55 +++++++++++++ 2 files changed, 132 insertions(+) create mode 100644 v3/cypress/e2e/pixi-interaction/map-pixi-interaction.spec.ts create mode 100644 v3/cypress/support/helpers/map-canvas-helper.ts diff --git a/v3/cypress/e2e/pixi-interaction/map-pixi-interaction.spec.ts b/v3/cypress/e2e/pixi-interaction/map-pixi-interaction.spec.ts new file mode 100644 index 0000000000..0c807aaf52 --- /dev/null +++ b/v3/cypress/e2e/pixi-interaction/map-pixi-interaction.spec.ts @@ -0,0 +1,77 @@ +import { TableTileElements as table } from "../../support/elements/table-tile" +import { ComponentElements as c } from "../../support/elements/component-elements" +import { MapTileElements as map } from "../../support/elements/map-tile" +import { MapCanvasHelper as mch } from "../../support/helpers/map-canvas-helper" +//import { FormulaHelper as fh } from "../../support/helpers/formula-helper" +import graphRules from '../../fixtures/graph-rules.json' + +const plots = graphRules.plots + +context("Graph UI with Pixi interaction", () => { + beforeEach(function () { + const queryParams = "/#file=examples:Four%20Seals&dashboard&mouseSensor" + const url = `${Cypress.config("index")}${queryParams}` + cy.visit(url) + cy.wait(2500) + }) + describe("map view", () => { + it("validates point count for map data on map component with pixi interaction", () => { + cy.log('Correct number of points in maps') + map.getMapTile().click() + mch.getMapTileId().then((tileId) => { + mch.validateMapPointCount(tileId, 858) + }) + }) + it("checks show/hide selected/unselected/all map/filter formula point count with pixi interaction", () => { + c.selectTile("map", 0) + + cy.log('Hide unselected cases') + map.selectHideShowButton() + map.getHideSelectedCases().should("have.text", "Hide Selected Cases") + map.getHideUnselectedCases().should("have.text", "Hide Unselected Cases").click() + mch.getMapTileId().then((tileId) => { + mch.validateMapPointCount(tileId, 0) + }) + + cy.log('Show all cases') + map.selectHideShowButton() + map.getShowAllCases().should("have.text", "Show All Cases").click() + mch.getMapTileId().then((tileId) => { + mch.validateMapPointCount(tileId, 858) + }) + + cy.log('Hide selected cases from a case table') + table.getNumOfRows(1).should("contain", 6) // six rows: top, 546, 541, 536, 528, bottom + table.getGridCell(2, 2).should("contain", "546").click() + c.selectTile("map", 0) + map.selectHideShowButton() + map.getHideSelectedCases().should("have.text", "Hide Selected Cases").click() + mch.getMapTileId().then((tileId) => { + mch.validateMapPointCount(tileId, 628) + }) + map.selectHideShowButton() + map.getShowAllCases().should("have.text", "Show All Cases").click() + mch.getMapTileId().then((tileId) => { + mch.validateMapPointCount(tileId, 858) + }) + // This is a simple test case for maps with filter formula + // This bit can be uncommented with the implementation of + // PT-#188411722 + // cy.log('Case count with filter formula') + // c.selectTile("map", 0) + // map.selectHideShowButton() + // map.getFilterFormulaButton().should("have.text", "Add Filter Formula...").click() + // fh.addFilterFormula("animal_id = '546'") + // cy.get(".codap-modal-content [data-testid=Apply-button]").should("be.visible").click() + // cy.get("[data-testid=Apply-button]").click() + // mch.getMapTileId().then((tileId) => { + // mch.validateMapPointCount(tileId, 628) + // }) + // map.selectHideShowButton() + // map.getShowAllCases().should("have.text", "Show All Cases").click() + // mch.getMapTileId().then((tileId) => { + // mch.validateMapPointCount(tileId, 858) + // }) + }) + }) +}) diff --git a/v3/cypress/support/helpers/map-canvas-helper.ts b/v3/cypress/support/helpers/map-canvas-helper.ts new file mode 100644 index 0000000000..c9319f4659 --- /dev/null +++ b/v3/cypress/support/helpers/map-canvas-helper.ts @@ -0,0 +1,55 @@ +// import { AxisHelper as ah } from "./axis-helper" + +export const MapCanvasHelper = { + // Helper function to locate the graph element and retrieve its dynamic ID. + getMapTileId(): Cypress.Chainable { + cy.log('Locate the map element and retrieve its dynamic ID') + return cy.get('[data-testid=codap-map]') + .parents('.free-tile-component') + .invoke('attr', 'id') + .then((tileId) => { + if (!tileId) { + throw new Error('Error: tileId is undefined or null.') + } + cy.log(`Map Component Tile ID Retrieved: ${tileId}`) + return Cypress.Promise.resolve(tileId) // Ensure Cypress compatibility + }) + }, + // // Helper function to set an attribute for the axis and retrieve the tile ID. + // setAxisAndRetrieveTileId (attribute: string, axis: "bottom" | "left") { + // cy.log(`Set ${attribute} on ${axis} axis`) + // ah.openAxisAttributeMenu(axis) + // ah.selectMenuAttribute(attribute, axis) + // cy.wait(500) + + // cy.log('Locate the graph element and retrieve its dynamic ID') + // return cy.get('[data-testid=codap-graph]') + // .parents('.free-tile-component') + // .invoke('attr', 'id') + // }, + // Helper function to validate pixi metadata and point count. + validateMapPointCount(tileId: string | undefined, expectedPointCount: number) { + if (!tileId) { + throw new Error("Error: tileId is undefined or null. Cannot validate map point count.") + } + + cy.log('Get the pixi metadata') + cy.window().then((win: any) => { + const pixiPoints = win.pixiPointsMap[tileId] // Access pixiPoints using the graph ID + cy.log('PixiPoints Object:', pixiPoints) // Log the pixiPoints object for verification + + // Assert that pixiPoints exist + chai.assert.exists(pixiPoints, "PixiPoints object exists") + + // Assert that pixiPoints[0] exists + chai.assert.exists(pixiPoints[0], "PixiPoints[0] exists") + + // Access pointsCount + const pointsCount = pixiPoints[0].pointsCount // Use the getter to determine the number of points + cy.log(`Number of Points (pointsCount): ${pointsCount}`) + + // Assert the number of points + expect(pointsCount).to.equal(expectedPointCount, "Point count matches expected value") + }) + } +} From a863e6c43f586d3ebf8cb8c39846e7b6606d8022 Mon Sep 17 00:00:00 2001 From: nstclair-cc <20171905+nstclair-cc@users.noreply.github.com> Date: Thu, 19 Dec 2024 17:38:32 -0800 Subject: [PATCH 10/25] spike for position and finalize map with pixijs interaction --- .../graph-pixi-interaction.spec.ts | 128 +++++++----------- .../map-pixi-interaction.spec.ts | 3 - .../support/helpers/graph-canvas-helper.ts | 22 +++ 3 files changed, 72 insertions(+), 81 deletions(-) diff --git a/v3/cypress/e2e/pixi-interaction/graph-pixi-interaction.spec.ts b/v3/cypress/e2e/pixi-interaction/graph-pixi-interaction.spec.ts index a443f5844f..5c53cb0ed6 100644 --- a/v3/cypress/e2e/pixi-interaction/graph-pixi-interaction.spec.ts +++ b/v3/cypress/e2e/pixi-interaction/graph-pixi-interaction.spec.ts @@ -2,7 +2,6 @@ import { GraphTileElements as graph } from "../../support/elements/graph-tile" import { TableTileElements as table } from "../../support/elements/table-tile" import { ComponentElements as c } from "../../support/elements/component-elements" -import { ToolbarElements as toolbar } from "../../support/elements/toolbar-elements" import { CfmElements as cfm } from "../../support/elements/cfm" //import { ColorPickerPaletteElements as cpp} from "../../support/elements/color-picker-palette" import { GraphCanvasHelper as gch } from "../../support/helpers/graph-canvas-helper" @@ -113,88 +112,61 @@ context("Graph UI with Pixi interaction", () => { gch.validateGraphPointCount(tileId, 18) // 18 points in graph }) }) - it.skip("should highlight a selected graph point with pixi interaction", () => { - // this is a spike to get interaction with point selection and texture - // it makes use of the texture call in the pixiPoint - // However, I'm pretty sure I've got the structure of the array wrong - // this code tries to log and verify the selected case ID and see how it - // interacts with the pixiPoint array + it.skip("should check position of a point with pixi interaction", () => { + ah.openAxisAttributeMenu("bottom") + ah.selectMenuAttribute("Sleep", "bottom") // Sleep => x-axis + cy.wait(500) // Wait for the graph to update - // /** - // * Helper function to check if a graph point is selected. - // * @param pointMetadata - The `pointMetadata` map from `pixiPoints`. - // * @param selectedCaseID - The case ID of the selected point. - // * @param expectedColor - The expected color of the selected point. - // * @returns True if the point matches the expected color, false otherwise. - // */ - const collectionName = "newCollectionName" + graph.getGraphTile() // Ensure graph tile is loaded - const isPointSelected = (pointMetadata: Map, - selectedCaseID: string, expectedColor: string): boolean => { - // Find the entry in `pointMetadata` matching the `selectedCaseID` - const selectedEntry = Array.from(pointMetadata.values()).find( - (entry: any) => entry.caseID === selectedCaseID - ) - if (!selectedEntry) { - cy.log('No entry found for selected case ID:', selectedCaseID) - return false + gch.getGraphTileId().then((tileId: string) => { + cy.log(`Retrieved Tile ID: ${tileId}`) + if (!tileId) { + throw new Error("Tile ID is undefined or null.") } - cy.log('Selected Entry:', selectedEntry) - - // Check if the `style.fill` matches the expected color - const fillColor = selectedEntry.style?.fill - cy.log('Extracted Fill Color:', fillColor) - return fillColor === expectedColor - } - - // Select the target table cell and dynamically retrieve the caseID - table.getGridCell(2, 2) - .should("contain", "African Elephant") - .invoke('attr', 'data-row-id') - .then((selectedCaseID) => { - cy.log(`Selected Case ID: ${selectedCaseID}`) - // extract the caseID from the data cell, which is in the div:
African Elephant
) - - // Verify the graph's component title matches the collection name - c.getComponentTitle("graph").should("contain", collectionName) - - // Re-click the table cell to ensure interaction consistency - table.getGridCell(2, 2).click({ force: true }) - - // for getting position - // pixiPointsMap[tileID][0].points[0].position.x - // pixiPointsMap[tileID][0].points[0].position.y - - // Locate the graph and retrieve its dynamic tile ID - cy.get('[data-testid=codap-graph]') - .parents('.free-tile-component') - .invoke('attr', 'id') - .then((tileId) => { - if (!tileId) { - throw new Error("tileId is undefined or null.") - } - cy.log(`Graph Tile ID Retrieved: ${tileId}`) - // Access PIXI metadata - cy.window().then((win: any) => { - const pixiPoints = win.pixiPointsMap[tileId] - cy.log('Full Pixi Points Object:', pixiPoints) - - // Verify pixiPoints exists - expect(pixiPoints[0], 'Pixi points should exist').to.exist - - // Access `pointMetadata` - const pointMetadata = pixiPoints[0].pointMetadata - cy.log('Point Metadata Map:', pointMetadata) - - // Verify the point is highlighted - const selectedColor = "#4682B4" // Expected color for a selected point - const isSelected = isPointSelected(pointMetadata, selectedCaseID, selectedColor) - - expect(isSelected, `Point with case ID ${selectedCaseID} should be highlighted`).to.be.true - }) - }) + gch.getPixiPointPosition(tileId, 0).then((position: { x: number; y: number }) => { + cy.log(`Point 0 Position: x=${position.x}, y=${position.y}`) }) + }) + }) + it.skip("spike for point compression interaction", () => { + // next steps are to debug what the compression does to the points + // Open Four Seals + cy.log("Open Four Seals from Hamburger menu") + // hamburger menu is hidden initially + cfm.getHamburgerMenuButton().should("exist") + cfm.getHamburgerMenu().should("not.exist") + // hamburger menu is shows when button is clicked + cfm.getHamburgerMenuButton().click() + cfm.getHamburgerMenu().should("exist") + // clicking Open... item closes menu and shows Open dialog + cfm.getHamburgerMenu().contains("li", "Open...").click() + cfm.getHamburgerMenu().should("not.exist") + cfm.getModalDialog().contains(".modal-dialog-title", "Open") + // Example Documents should be selected by default + cfm.getModalDialog().contains(".tab-selected", "Example Documents") + cfm.getModalDialog().contains(".filelist div.selectable", "Mammals").should("exist") + cfm.getModalDialog().contains(".filelist div.selectable", "Four Seals").should("exist") + // Selecting Four Seals document should load the Four Seals example document + cfm.getModalDialog().contains(".filelist div.selectable", "Four Seals").click() + cfm.getModalDialog().contains(".buttons button", "Open").click() + cy.wait(1000) + // once loaded, Open dialog should be hidden and document content should be shown + cfm.getModalDialog().should("not.exist") + cy.get(".codap-component.codap-case-table").contains(".title-bar", "Tracks/Measurements").should("exist") + + // Create a graph + graph.getGraphTile().click() + ah.openAxisAttributeMenu("bottom") + ah.selectMenuAttribute("date", "bottom") // Date => x-axis + cy.get('[data-testid="axis-legend-attribute-button-bottom"]').eq(0).should("have.text", "date") + ah.openAxisAttributeMenu("left") + ah.selectMenuAttribute("animal_id", "left") // animal_id => y-axis + ah.openAxisAttributeMenu("left") + ah.treatAttributeAsNumeric("left") + ah.openAxisAttributeMenu("left") + ah.treatAttributeAsCategorical("left") }) }) describe("case card graph interaction", () => { diff --git a/v3/cypress/e2e/pixi-interaction/map-pixi-interaction.spec.ts b/v3/cypress/e2e/pixi-interaction/map-pixi-interaction.spec.ts index 0c807aaf52..67790f84da 100644 --- a/v3/cypress/e2e/pixi-interaction/map-pixi-interaction.spec.ts +++ b/v3/cypress/e2e/pixi-interaction/map-pixi-interaction.spec.ts @@ -3,9 +3,6 @@ import { ComponentElements as c } from "../../support/elements/component-element import { MapTileElements as map } from "../../support/elements/map-tile" import { MapCanvasHelper as mch } from "../../support/helpers/map-canvas-helper" //import { FormulaHelper as fh } from "../../support/helpers/formula-helper" -import graphRules from '../../fixtures/graph-rules.json' - -const plots = graphRules.plots context("Graph UI with Pixi interaction", () => { beforeEach(function () { diff --git a/v3/cypress/support/helpers/graph-canvas-helper.ts b/v3/cypress/support/helpers/graph-canvas-helper.ts index 8d55ee0d09..944894e42b 100644 --- a/v3/cypress/support/helpers/graph-canvas-helper.ts +++ b/v3/cypress/support/helpers/graph-canvas-helper.ts @@ -51,5 +51,27 @@ export const GraphCanvasHelper = { // Assert the number of points expect(pointsCount).to.equal(expectedPointCount, "Point count matches expected value") }) + }, + getPixiPointPosition(tileId: string, pointIndex: number): Cypress.Chainable<{ x: number; y: number }> { + cy.log("Get the PixiPoint position") + return cy.window().then((win: any) => { + const pixiPoints = win.pixiPointsMap[tileId] + cy.log(`PixiPoints for Tile ID ${tileId}:`, pixiPoints) + + // Use optional chaining to check if pixiPoints and pixiPoints[0] exist + if (!pixiPoints?.[0]) { + throw new Error(`PixiPoints for tile ID ${tileId} is undefined or empty.`) + } + + // Retrieve the point position + const pointPosition = pixiPoints[0].points[pointIndex]?.position + if (!pointPosition) { + throw new Error(`Point at index ${pointIndex} does not exist for tile ID ${tileId}.`) + } + cy.log(`Point Position (Index ${pointIndex}): x=${pointPosition.x}, y=${pointPosition.y}`) + + // Use cy.wrap to make the position Cypress-compatible + return cy.wrap({ x: pointPosition.x, y: pointPosition.y }) + }) } } From 540dde16bbcd9b9f19cabf8addcc78bd2b6d488c Mon Sep 17 00:00:00 2001 From: nstclair-cc <20171905+nstclair-cc@users.noreply.github.com> Date: Fri, 20 Dec 2024 11:52:15 -0800 Subject: [PATCH 11/25] - replaced the trigger: push to run the workflow on every push - Specified the cypress/e2e/pixi-interaction/**/*.ts pattern for the Cypress step. - Removed: Save Webpack Cache step. - Removed: Upload coverage to Codecov step. - Updated: spec: property to accept the pattern cypress/e2e/smoke/**/*.ts, allowing multiple tests in the smoke folder. --- .github/workflows/graph-pixi-tests.yml | 51 ++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/.github/workflows/graph-pixi-tests.yml b/.github/workflows/graph-pixi-tests.yml index d34b8672d6..b26258f62e 100644 --- a/.github/workflows/graph-pixi-tests.yml +++ b/.github/workflows/graph-pixi-tests.yml @@ -17,3 +17,54 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # set repository so we don't have to check out all of the code GH_REPO: ${{github.repository}} +cypress: + runs-on: ubuntu-latest + strategy: + # when one test fails, DO NOT cancel the other + # containers, because this will kill Cypress processes + # leaving the Dashboard hanging ... + # https://github.com/cypress-io/github-action/issues/48 + fail-fast: false + steps: + - uses: browser-actions/setup-chrome@v1 + with: + chrome-version: stable + - uses: snow-actions/sparse-checkout@v1.2.0 + with: + patterns: v3 + - name: Restore Webpack Cache + id: restore-webpack-cache + uses: actions/cache/restore@v4 + with: + path: v3/.cache/webpack/ + # this key is different from the build job above because this is a dev build so its + # folder in the cache will be different and cached data is also likely different + key: webpack-dev-build-${{ hashFiles('v3/webpack.config.js') }} + - uses: cypress-io/github-action@v6 + with: + working-directory: v3 + start: npm start + wait-on: 'http://localhost:8080' + # add timeout of 5 minutes to start + wait-on-timeout: 300 + # only record the results to dashboard.cypress.io if CYPRESS_RECORD_KEY is set + record: ${{ !!secrets.CYPRESS_RECORD_KEY }} + browser: chrome + # specify the Pixi pattern for tests + spec: cypress/e2e/pixi-interaction/**/*.ts + group: 'Pixi Interaction Tests' + env: + # pass the Dashboard record key as an environment variable + CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} + # pass GitHub token to allow accurately detecting a build vs a re-run build + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # turn on code coverage when running npm start + # so far we've been using a webpack coverage-istanbul-loader for this + # but there has been work on using the code coverage support in the browser directly, + # which should be much faster + CODE_COVERAGE: true + # Also turn on the code coverage tasks in cypress itself, these are disabled + # by default. + CYPRESS_coverage: true + # Have webpack skip eslint checking, that was already done by the build step + SKIP_ESLINT: true From 180063b6f6e0bd529f1cc7533a939f3e2b20d27e Mon Sep 17 00:00:00 2001 From: nstclair-cc <20171905+nstclair-cc@users.noreply.github.com> Date: Fri, 20 Dec 2024 13:08:41 -0800 Subject: [PATCH 12/25] regression label updates --- .github/workflows/graph-pixi-tests.yml | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/.github/workflows/graph-pixi-tests.yml b/.github/workflows/graph-pixi-tests.yml index b26258f62e..5cb7c180cf 100644 --- a/.github/workflows/graph-pixi-tests.yml +++ b/.github/workflows/graph-pixi-tests.yml @@ -1,12 +1,10 @@ name: Regression Label on: - pull_request: - types: [ labeled ] + push: + jobs: re_run: - # only continue if a push or PR labeled with 'run regression' - if: github.event.label.name == 'graph-pixi-tests' runs-on: ubuntu-latest permissions: actions: write @@ -17,6 +15,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # set repository so we don't have to check out all of the code GH_REPO: ${{github.repository}} + cypress: runs-on: ubuntu-latest strategy: @@ -37,34 +36,20 @@ cypress: uses: actions/cache/restore@v4 with: path: v3/.cache/webpack/ - # this key is different from the build job above because this is a dev build so its - # folder in the cache will be different and cached data is also likely different key: webpack-dev-build-${{ hashFiles('v3/webpack.config.js') }} - uses: cypress-io/github-action@v6 with: working-directory: v3 start: npm start wait-on: 'http://localhost:8080' - # add timeout of 5 minutes to start wait-on-timeout: 300 - # only record the results to dashboard.cypress.io if CYPRESS_RECORD_KEY is set record: ${{ !!secrets.CYPRESS_RECORD_KEY }} browser: chrome - # specify the Pixi pattern for tests spec: cypress/e2e/pixi-interaction/**/*.ts group: 'Pixi Interaction Tests' env: - # pass the Dashboard record key as an environment variable CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} - # pass GitHub token to allow accurately detecting a build vs a re-run build GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # turn on code coverage when running npm start - # so far we've been using a webpack coverage-istanbul-loader for this - # but there has been work on using the code coverage support in the browser directly, - # which should be much faster CODE_COVERAGE: true - # Also turn on the code coverage tasks in cypress itself, these are disabled - # by default. CYPRESS_coverage: true - # Have webpack skip eslint checking, that was already done by the build step SKIP_ESLINT: true From 312221b769f48fca6d8696506dce4f8aa1997520 Mon Sep 17 00:00:00 2001 From: nstclair-cc <20171905+nstclair-cc@users.noreply.github.com> Date: Fri, 20 Dec 2024 13:58:19 -0800 Subject: [PATCH 13/25] added helper function to check for point compression --- .../graph-pixi-interaction.spec.ts | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/v3/cypress/e2e/pixi-interaction/graph-pixi-interaction.spec.ts b/v3/cypress/e2e/pixi-interaction/graph-pixi-interaction.spec.ts index 5c53cb0ed6..d80b4ca61d 100644 --- a/v3/cypress/e2e/pixi-interaction/graph-pixi-interaction.spec.ts +++ b/v3/cypress/e2e/pixi-interaction/graph-pixi-interaction.spec.ts @@ -130,7 +130,7 @@ context("Graph UI with Pixi interaction", () => { }) }) }) - it.skip("spike for point compression interaction", () => { + it.only("spike for point compression interaction", () => { // next steps are to debug what the compression does to the points // Open Four Seals cy.log("Open Four Seals from Hamburger menu") @@ -167,6 +167,42 @@ context("Graph UI with Pixi interaction", () => { ah.treatAttributeAsNumeric("left") ah.openAxisAttributeMenu("left") ah.treatAttributeAsCategorical("left") + cy.wait(2000) + + // Check that point positions are not the same + gch.getGraphTileId().then((tileId: string) => { + if (!tileId) { + throw new Error("Tile ID is undefined or null.") + } + + cy.log(`Checking point positions for Tile ID: ${tileId}`) + cy.window().then((win: any) => { + const pixiPoints = win.pixiPointsMap[tileId] + + if (!pixiPoints?.length) { + throw new Error(`PixiPoints for Tile ID ${tileId} are undefined or empty.`) + } + + // Collect all point positions + const positions = pixiPoints[0].points.map((point: any) => point.position) + + // Log the positions for debugging + positions.forEach((pos: { x: number; y: number }, index: number) => { + cy.log(`Point ${index}: x=${pos.x}, y=${pos.y}`) + }); + + // Verify positions are not the same + const uniquePositions = new Set(positions.map((pos: { x: number; y: number }) => `${pos.x}-${pos.y}`)) + cy.log(`Total points: ${positions.length}, Unique positions: ${uniquePositions.size}`) + + // Debug log unique positions + uniquePositions.forEach((position) => { + cy.log(`Unique position: ${position}`) + }); + + expect(uniquePositions.size).to.equal(positions.length, "All points should have unique positions") + }) + }) }) }) describe("case card graph interaction", () => { From 38308e42bf15de2c769f0432e18a3d1032215dd5 Mon Sep 17 00:00:00 2001 From: nstclair-cc <20171905+nstclair-cc@users.noreply.github.com> Date: Fri, 20 Dec 2024 15:21:13 -0800 Subject: [PATCH 14/25] added checks for no point compression --- .../graph-pixi-interaction.spec.ts | 384 +++++++++++++----- .../support/helpers/graph-canvas-helper.ts | 29 ++ 2 files changed, 311 insertions(+), 102 deletions(-) diff --git a/v3/cypress/e2e/pixi-interaction/graph-pixi-interaction.spec.ts b/v3/cypress/e2e/pixi-interaction/graph-pixi-interaction.spec.ts index d80b4ca61d..bdccdc37c4 100644 --- a/v3/cypress/e2e/pixi-interaction/graph-pixi-interaction.spec.ts +++ b/v3/cypress/e2e/pixi-interaction/graph-pixi-interaction.spec.ts @@ -1,8 +1,10 @@ // import * as PIXI from "pixi.js" -import { GraphTileElements as graph } from "../../support/elements/graph-tile" -import { TableTileElements as table } from "../../support/elements/table-tile" import { ComponentElements as c } from "../../support/elements/component-elements" import { CfmElements as cfm } from "../../support/elements/cfm" +import { GraphLegendHelper as glh } from "../../support/helpers/graph-legend-helper" +import { GraphTileElements as graph } from "../../support/elements/graph-tile" +import { TableTileElements as table } from "../../support/elements/table-tile" +import { ToolbarElements as toolbar } from "../../support/elements/toolbar-elements" //import { ColorPickerPaletteElements as cpp} from "../../support/elements/color-picker-palette" import { GraphCanvasHelper as gch } from "../../support/helpers/graph-canvas-helper" import { AxisHelper as ah } from "../../support/helpers/axis-helper" @@ -42,8 +44,8 @@ context("Graph UI with Pixi interaction", () => { cy.visit(url) cy.wait(2500) }) - describe("graph view", () => { - it("validates point count for univariate graphs with different hierarchies with pixi interaction", () => { + describe("graph point count with point count pixi interaction", () => { + it("validates point count for univariate graphs with different hierarchies", () => { cy.log('opening the map will appear with the correct number of points') @@ -74,7 +76,7 @@ context("Graph UI with Pixi interaction", () => { gch.validateGraphPointCount(tileId, 5) }) }) - it("Checks count for hides and shows selected/unselected cases with pixijs interaction", () => { + it("checks graph point count for hides and shows selected/unselected cases", () => { ah.openAxisAttributeMenu("bottom") ah.selectMenuAttribute("Sleep", "bottom") // Sleep => x-axis cy.wait(500) @@ -112,100 +114,8 @@ context("Graph UI with Pixi interaction", () => { gch.validateGraphPointCount(tileId, 18) // 18 points in graph }) }) - it.skip("should check position of a point with pixi interaction", () => { - ah.openAxisAttributeMenu("bottom") - ah.selectMenuAttribute("Sleep", "bottom") // Sleep => x-axis - cy.wait(500) // Wait for the graph to update - - graph.getGraphTile() // Ensure graph tile is loaded - - gch.getGraphTileId().then((tileId: string) => { - cy.log(`Retrieved Tile ID: ${tileId}`) - if (!tileId) { - throw new Error("Tile ID is undefined or null.") - } - - gch.getPixiPointPosition(tileId, 0).then((position: { x: number; y: number }) => { - cy.log(`Point 0 Position: x=${position.x}, y=${position.y}`) - }) - }) - }) - it.only("spike for point compression interaction", () => { - // next steps are to debug what the compression does to the points - // Open Four Seals - cy.log("Open Four Seals from Hamburger menu") - // hamburger menu is hidden initially - cfm.getHamburgerMenuButton().should("exist") - cfm.getHamburgerMenu().should("not.exist") - // hamburger menu is shows when button is clicked - cfm.getHamburgerMenuButton().click() - cfm.getHamburgerMenu().should("exist") - // clicking Open... item closes menu and shows Open dialog - cfm.getHamburgerMenu().contains("li", "Open...").click() - cfm.getHamburgerMenu().should("not.exist") - cfm.getModalDialog().contains(".modal-dialog-title", "Open") - // Example Documents should be selected by default - cfm.getModalDialog().contains(".tab-selected", "Example Documents") - cfm.getModalDialog().contains(".filelist div.selectable", "Mammals").should("exist") - cfm.getModalDialog().contains(".filelist div.selectable", "Four Seals").should("exist") - // Selecting Four Seals document should load the Four Seals example document - cfm.getModalDialog().contains(".filelist div.selectable", "Four Seals").click() - cfm.getModalDialog().contains(".buttons button", "Open").click() - cy.wait(1000) - // once loaded, Open dialog should be hidden and document content should be shown - cfm.getModalDialog().should("not.exist") - cy.get(".codap-component.codap-case-table").contains(".title-bar", "Tracks/Measurements").should("exist") - - // Create a graph - graph.getGraphTile().click() - ah.openAxisAttributeMenu("bottom") - ah.selectMenuAttribute("date", "bottom") // Date => x-axis - cy.get('[data-testid="axis-legend-attribute-button-bottom"]').eq(0).should("have.text", "date") - ah.openAxisAttributeMenu("left") - ah.selectMenuAttribute("animal_id", "left") // animal_id => y-axis - ah.openAxisAttributeMenu("left") - ah.treatAttributeAsNumeric("left") - ah.openAxisAttributeMenu("left") - ah.treatAttributeAsCategorical("left") - cy.wait(2000) - - // Check that point positions are not the same - gch.getGraphTileId().then((tileId: string) => { - if (!tileId) { - throw new Error("Tile ID is undefined or null.") - } - - cy.log(`Checking point positions for Tile ID: ${tileId}`) - cy.window().then((win: any) => { - const pixiPoints = win.pixiPointsMap[tileId] - - if (!pixiPoints?.length) { - throw new Error(`PixiPoints for Tile ID ${tileId} are undefined or empty.`) - } - - // Collect all point positions - const positions = pixiPoints[0].points.map((point: any) => point.position) - - // Log the positions for debugging - positions.forEach((pos: { x: number; y: number }, index: number) => { - cy.log(`Point ${index}: x=${pos.x}, y=${pos.y}`) - }); - - // Verify positions are not the same - const uniquePositions = new Set(positions.map((pos: { x: number; y: number }) => `${pos.x}-${pos.y}`)) - cy.log(`Total points: ${positions.length}, Unique positions: ${uniquePositions.size}`) - - // Debug log unique positions - uniquePositions.forEach((position) => { - cy.log(`Unique position: ${position}`) - }); - - expect(uniquePositions.size).to.equal(positions.length, "All points should have unique positions") - }) - }) - }) }) - describe("case card graph interaction", () => { + describe("case card graph interaction with point count pixi interaction", () => { it("can drag attributes from the case card to the graph with pixijs interaction", () => { const tableHeaderLeftSelector = ".codap-component.codap-case-table .component-title-bar .header-left" cy.get(tableHeaderLeftSelector).click() @@ -219,8 +129,8 @@ context("Graph UI with Pixi interaction", () => { }) }) }) - describe("graph inspector panel with pixijs interaction", () => { - it("shows warning if 'Display Only Selected Cases' is selected and no cases have been selected with pixijs", () => { + describe("graph inspector panel with point count pixi interaction", () => { + it("shows warning if 'Display Only Selected Cases' is selected and checks point count", () => { ah.openAxisAttributeMenu("bottom") ah.selectMenuAttribute("Sleep", "bottom") // Sleep => x-axis cy.wait(500) @@ -240,7 +150,7 @@ context("Graph UI with Pixi interaction", () => { gch.validateGraphPointCount(tileId, 24) // 24 points in graph }) }) - it("shows parent visibility toggles when Show Parent Visibility Toggles option is selected with pixijs", () => { + it("shows parent visibility toggles when Show Parent Visibility Toggles option is selected and checks point count", () => { ah.openAxisAttributeMenu("bottom") ah.selectMenuAttribute("Sleep", "bottom") // Sleep => x-axis cy.wait(500) @@ -346,7 +256,6 @@ context("Graph UI with Pixi interaction", () => { cy.wait(500) cy.get("[data-testid=show-parent-toggles]").should("exist").and("have.text", "Show Parent Visibility Toggles") }) - it("adds a banner to the graph when Show Measures for Selection is activated with pixijs interaction", () => { ah.openAxisAttributeMenu("bottom") ah.selectMenuAttribute("Sleep", "bottom") // Sleep => x-axis @@ -372,4 +281,275 @@ context("Graph UI with Pixi interaction", () => { // NOTE: Adornments are covered in graph-adornments.spec.ts (including Show Measures) }) + describe("checks for graph point position with pixi interaction", () => { + // use this test to debug point positions when running locally + it.skip("should check position of a point", () => { + ah.openAxisAttributeMenu("bottom") + ah.selectMenuAttribute("Sleep", "bottom") // Sleep => x-axis + cy.wait(500) // Wait for the graph to update + + graph.getGraphTile() // Ensure graph tile is loaded + + gch.getGraphTileId().then((tileId: string) => { + cy.log(`Retrieved Tile ID: ${tileId}`) + if (!tileId) { + throw new Error("Tile ID is undefined or null.") + } + + gch.getPixiPointPosition(tileId, 0).then((position: { x: number + ; y: number }) => { + cy.log(`Point 0 Position: x=${position.x}, y=${position.y}`) + }) + }) + }) + // this test will work when PT-#188601933 is delivered + it.skip("check for point compression interaction", () => { + // Open Four Seals + cy.log("Open Four Seals from Hamburger menu") + cfm.getHamburgerMenuButton().should("exist") + cfm.getHamburgerMenu().should("not.exist") + cfm.getHamburgerMenuButton().click() + cfm.getHamburgerMenu().should("exist") + cfm.getHamburgerMenu().contains("li", "Open...").click() + cfm.getHamburgerMenu().should("not.exist") + cfm.getModalDialog().contains(".modal-dialog-title", "Open") + cfm.getModalDialog().contains(".tab-selected", "Example Documents") + cfm.getModalDialog().contains(".filelist div.selectable", "Mammals").should("exist") + cfm.getModalDialog().contains(".filelist div.selectable", "Four Seals").should("exist") + cfm.getModalDialog().contains(".filelist div.selectable", "Four Seals").click() + cfm.getModalDialog().contains(".buttons button", "Open").click() + cy.wait(1000) // Add wait time only when necessary + cfm.getModalDialog().should("not.exist") + cy.get(".codap-component.codap-case-table").contains(".title-bar", "Tracks/Measurements").should("exist") + + // Create a graph + graph.getGraphTile().click() + ah.openAxisAttributeMenu("bottom") + ah.selectMenuAttribute("date", "bottom") // Date => x-axis + cy.get('[data-testid="axis-legend-attribute-button-bottom"]').eq(0).should("have.text", "date") + ah.openAxisAttributeMenu("left") + ah.selectMenuAttribute("animal_id", "left") // animal_id => y-axis + ah.openAxisAttributeMenu("left") + ah.treatAttributeAsNumeric("left") + ah.openAxisAttributeMenu("left") + ah.treatAttributeAsCategorical("left") + + // Check point positions are unique + gch.getGraphTileId().then((tileId: string) => { + gch.checkPointsHaveUniquePositions(tileId) // Call the helper function + }) + }) + it("no point compression after drawing categorical legend with categorical attribute on x axis and undo/redo", () => { + // Initial setup: Drag attributes to the x-axis and plot area, respectively + ah.openAxisAttributeMenu("bottom") + ah.selectMenuAttribute("Diet", "bottom") // Diet => x-axis + glh.dragAttributeToPlot("Habitat") // Habitat => plot area + gch.getGraphTileId().then((tileId) => { + gch.validateGraphPointCount(tileId, 27) // 27 points in graph + }) + + cy.log("test undo/redo for categorical legend with categorical attribute on x axis") + // Undo add legend to graph and verify removal + toolbar.getUndoTool().click() + cy.wait(2500) + // Check point positions are unique + gch.getGraphTileId().then((tileId: string) => { + gch.checkPointsHaveUniquePositions(tileId) // Call the helper function + }) + + // Redo add legend to graph and verify legend returns + toolbar.getRedoTool().click() + // Check point positions are unique + gch.getGraphTileId().then((tileId: string) => { + gch.checkPointsHaveUniquePositions(tileId) // Call the helper function + }) + }) + it("no point compression after drawing categorical legend with categorical attribute on y axis and undo/redo", () => { + // Drag attribute to the y-axis and drag another attribute to the plot area + ah.openAxisAttributeMenu("left") + ah.selectMenuAttribute("Diet", "left") // Diet => y-axis + glh.dragAttributeToPlot("Habitat") // Habitat => plot area + + // Verify axis label and legend + ah.verifyAxisLabel("left", "Diet") + // Verify point count + gch.getGraphTileId().then((tileId) => { + gch.validateGraphPointCount(tileId, 27) // 27 points in graph + }) + + cy.log("test undo/redo for categorical legend with categorical attribute on y axis") + // Undo the removal of attributes from axis and legend + toolbar.getUndoTool().click() // Undo remove from legend + toolbar.getUndoTool().click() // Undo remove from axis + cy.wait(2500) + // Check point positions are unique + gch.getGraphTileId().then((tileId: string) => { + gch.checkPointsHaveUniquePositions(tileId) // Call the helper function + }) + + // Note: Redo button disables in Cypress at this step. + // The disable doesn't happen in CODAP though. + // Used force:true so that test can happen. + toolbar.getRedoTool().click({force: true}) + toolbar.getRedoTool().click({force: true}) + cy.wait(2500) + // Check point positions are unique + gch.getGraphTileId().then((tileId: string) => { + gch.checkPointsHaveUniquePositions(tileId) // Call the helper function + }) + }) + it("no point compression after drawing categorical legend with numerical attribute on x axis and test undo", () => { + // Initial setup: Drag attributes to the x-axis and plot area, respectively + ah.openAxisAttributeMenu("bottom") + ah.selectMenuAttribute("LifeSpan", "bottom") // LifeSpan => x-axis + glh.dragAttributeToPlot("Habitat") // Habitat => plot area + gch.getGraphTileId().then((tileId) => { + gch.validateGraphPointCount(tileId, 27) // 27 points in graph + }) + glh.verifyLegendLabel("Habitat") + + cy.log("test undo/redo for categorical legend with numerical attribute on x axis") + // Undo the removal of attributes + toolbar.getUndoTool().click() // Undo remove from legend + toolbar.getUndoTool().click() // Undo remove from axis + // Check point positions are unique + gch.getGraphTileId().then((tileId: string) => { + gch.checkPointsHaveUniquePositions(tileId) // Call the helper function + }) + + // Redo the removal of attributes + toolbar.getRedoTool().click({force: true}) // Redo remove from legend + toolbar.getRedoTool().click({force: true}) // Redo remove from axis + // Check point positions are unique + gch.getGraphTileId().then((tileId: string) => { + gch.checkPointsHaveUniquePositions(tileId) // Call the helper function + }) + }) + it("no point compression after drawing numeric legend with categorical attribute on x axis and test undo", () => { + // Initial setup: Drag attributes to the x-axis and plot area, respectively + ah.openAxisAttributeMenu("bottom") + ah.selectMenuAttribute("Habitat", "bottom") // Habitat => x-axis + glh.dragAttributeToPlot("LifeSpan") // LifeSpan => plot area + gch.getGraphTileId().then((tileId) => { + gch.validateGraphPointCount(tileId, 27) // 27 points in graph + }) + glh.verifyLegendLabel("LifeSpan") + + cy.log("test undo/redo for numeric legend with categorical attribute on x axis") + // Undo the removal of attributes + toolbar.getUndoTool().click() // Undo remove from legend + toolbar.getUndoTool().click() // Undo remove from axis + // Check point positions are unique + gch.getGraphTileId().then((tileId: string) => { + gch.checkPointsHaveUniquePositions(tileId) // Call the helper function + }) + + // Redo the removal of attributes + toolbar.getRedoTool().click() // Redo remove from legend + toolbar.getRedoTool().click() // Redo remove from axis + // Check point positions are unique + gch.getGraphTileId().then((tileId: string) => { + gch.checkPointsHaveUniquePositions(tileId) // Call the helper function + }) + }) + it("no point compression after drawing numeric legend with categorical attribute on y axis and test undo", () => { + // Drag attribute to the y-axis and drag another attribute to the plot area + ah.openAxisAttributeMenu("left") + ah.selectMenuAttribute("Diet", "left") // Diet => y-axis + glh.dragAttributeToPlot("LifeSpan") // LifeSpan => plot area + + // Verify axis label and legend + ah.verifyAxisLabel("left", "Diet") + // Verify point count + gch.getGraphTileId().then((tileId) => { + gch.validateGraphPointCount(tileId, 27) // 27 points in graph + }) + + cy.log("test undo/redo for numeric legend with categorical attribute on y axis") + // Undo the removal of attributes + toolbar.getUndoTool().click() // Undo remove from legend + toolbar.getUndoTool().click() // Undo remove from axis + // Check point positions are unique + gch.getGraphTileId().then((tileId: string) => { + gch.checkPointsHaveUniquePositions(tileId) // Call the helper function + }) + + // Redo the removal of attributes + toolbar.getRedoTool().click({force: true}) // Redo remove from legend + toolbar.getRedoTool().click({force: true}) // Redo remove from axis + // Check point positions are unique + gch.getGraphTileId().then((tileId: string) => { + gch.checkPointsHaveUniquePositions(tileId) // Call the helper function + }) + }) + it("no point compression after drawing numeric legend with numerical attribute on x axis and test undo", () => { + // Drag attribute to the y-axis and drag another attribute to the plot area + ah.openAxisAttributeMenu("bottom") + ah.selectMenuAttribute("LifeSpan", "bottom") // LifeSpan => x-axis + glh.dragAttributeToPlot("Height") // Height => plot area + + // Verify axis label and legend + ah.verifyAxisLabel("bottom", "LifeSpan") + // Verify point count + gch.getGraphTileId().then((tileId) => { + gch.validateGraphPointCount(tileId, 27) // 27 points in graph + }) + + cy.log("test undo/redo for numeric legend with numerical attributes on x axis") + // Undo the removal of attributes + toolbar.getUndoTool().click() // Undo remove from legend + toolbar.getUndoTool().click() // Undo remove from axis + // Check point positions are unique + gch.getGraphTileId().then((tileId: string) => { + gch.checkPointsHaveUniquePositions(tileId) // Call the helper function + }) + + cy.log("test undo/redo for numeric legend with numerical attribute on x axis") + // Redo the removal of attributes + // Note: Redo button disables in Cypress at this step. + // The disable doesn't happen in CODAP though. + // Used force:true so that test can happen. + toolbar.getRedoTool().click() // Redo remove from legend + toolbar.getRedoTool().click() // Redo remove from axis + + // Check point positions are unique + gch.getGraphTileId().then((tileId: string) => { + gch.checkPointsHaveUniquePositions(tileId) // Call the helper function + }) + }) + it("will draw numeric legend with numerical attribute on y axis and test undo", () => { + // Drag attribute to the y-axis and drag another attribute to the plot area + ah.openAxisAttributeMenu("left") + ah.selectMenuAttribute("LifeSpan", "left") // LifeSpan => y-axis + glh.dragAttributeToPlot("Height") // Height => plot area + + // Verify axis label and legend + ah.verifyAxisLabel("left", "LifeSpan") + // Verify point count + gch.getGraphTileId().then((tileId) => { + gch.validateGraphPointCount(tileId, 27) // 27 points in graph + }) + + cy.log("test undo/redo for draw numeric legend with numerical attribute on y axis") + // Undo the removal of attributes + toolbar.getUndoTool().click() // Undo remove from legend + toolbar.getUndoTool().click() // Undo remove from axis + // Check point positions are unique + gch.getGraphTileId().then((tileId: string) => { + gch.checkPointsHaveUniquePositions(tileId) // Call the helper function + }) + + // Note: Redo button disables in Cypress at this step. + // The disable doesn't happen in CODAP though. + // Used force:true so that test can happen. + + // Redo the removal of attributes + toolbar.getRedoTool().click() // Redo remove from legend + toolbar.getRedoTool().click() // Redo remove from axis + // Check point positions are unique + gch.getGraphTileId().then((tileId: string) => { + gch.checkPointsHaveUniquePositions(tileId) // Call the helper function + }) + }) + }) }) diff --git a/v3/cypress/support/helpers/graph-canvas-helper.ts b/v3/cypress/support/helpers/graph-canvas-helper.ts index 944894e42b..968566dba6 100644 --- a/v3/cypress/support/helpers/graph-canvas-helper.ts +++ b/v3/cypress/support/helpers/graph-canvas-helper.ts @@ -52,6 +52,8 @@ export const GraphCanvasHelper = { expect(pointsCount).to.equal(expectedPointCount, "Point count matches expected value") }) }, + // Helper function to retrieve the position of a PixiPoint + // Useful for debugging and validating point positions getPixiPointPosition(tileId: string, pointIndex: number): Cypress.Chainable<{ x: number; y: number }> { cy.log("Get the PixiPoint position") return cy.window().then((win: any) => { @@ -73,5 +75,32 @@ export const GraphCanvasHelper = { // Use cy.wrap to make the position Cypress-compatible return cy.wrap({ x: pointPosition.x, y: pointPosition.y }) }) + }, + // Checks if the points in a graph have unique positions. + // @param {string} tileId - The ID of the graph tile to check. + checkPointsHaveUniquePositions(tileId: string) { + cy.window().then((win: any) => { + const pixiPoints = win.pixiPointsMap[tileId] + + if (!pixiPoints?.length) { + throw new Error(`PixiPoints for Tile ID ${tileId} are undefined or empty.`) + } + + // Collect all point positions + const positions = pixiPoints[0].points.map((point: any) => point.position) + + // Log the positions for debugging + positions.forEach((pos: { x: number + ; y: number }, index: number) => { + cy.log(`Point ${index}: x=${pos.x}, y=${pos.y}`) + }) + + // Verify positions are not the same + const uniquePositions = new Set(positions.map((pos: { x: number; y: number }) => `${pos.x}-${pos.y}`)) + cy.log(`Total points: ${positions.length}, Unique positions: ${uniquePositions.size}`) + + // Assert all points have unique positions + expect(uniquePositions.size).to.equal(positions.length, "All points should have unique positions") + }) } } From 2ec3e07a9f6d499697a941302f5a30f8eb07a373 Mon Sep 17 00:00:00 2001 From: nstclair-cc <20171905+nstclair-cc@users.noreply.github.com> Date: Fri, 20 Dec 2024 16:56:14 -0800 Subject: [PATCH 15/25] Spike into graph fill color --- .../graph-pixi-interaction.spec.ts | 27 ++++++- .../support/helpers/graph-canvas-helper.ts | 79 ++++++++++++++++++- 2 files changed, 102 insertions(+), 4 deletions(-) diff --git a/v3/cypress/e2e/pixi-interaction/graph-pixi-interaction.spec.ts b/v3/cypress/e2e/pixi-interaction/graph-pixi-interaction.spec.ts index bdccdc37c4..7d9171ad9d 100644 --- a/v3/cypress/e2e/pixi-interaction/graph-pixi-interaction.spec.ts +++ b/v3/cypress/e2e/pixi-interaction/graph-pixi-interaction.spec.ts @@ -1,11 +1,9 @@ -// import * as PIXI from "pixi.js" import { ComponentElements as c } from "../../support/elements/component-elements" import { CfmElements as cfm } from "../../support/elements/cfm" import { GraphLegendHelper as glh } from "../../support/helpers/graph-legend-helper" import { GraphTileElements as graph } from "../../support/elements/graph-tile" import { TableTileElements as table } from "../../support/elements/table-tile" import { ToolbarElements as toolbar } from "../../support/elements/toolbar-elements" -//import { ColorPickerPaletteElements as cpp} from "../../support/elements/color-picker-palette" import { GraphCanvasHelper as gch } from "../../support/helpers/graph-canvas-helper" import { AxisHelper as ah } from "../../support/helpers/axis-helper" import graphRules from '../../fixtures/graph-rules.json' @@ -116,7 +114,7 @@ context("Graph UI with Pixi interaction", () => { }) }) describe("case card graph interaction with point count pixi interaction", () => { - it("can drag attributes from the case card to the graph with pixijs interaction", () => { + it("can drag attributes from the case card to the graph and check point count", () => { const tableHeaderLeftSelector = ".codap-component.codap-case-table .component-title-bar .header-left" cy.get(tableHeaderLeftSelector).click() cy.get(`${tableHeaderLeftSelector} .card-table-toggle-message`).click() @@ -302,6 +300,29 @@ context("Graph UI with Pixi interaction", () => { }) }) }) + it.skip("SPIKE: should check color of a point", () => { + ah.openAxisAttributeMenu("bottom") + ah.selectMenuAttribute("Diet", "bottom") // Diet => x-axis + glh.dragAttributeToPlot("Habitat") // Habitat => plot area + cy.wait(500) // Wait for the graph to update + + ah.verifyAxisLabel("bottom", "Diet") + //glh.verifyCategoricalLegend("LifeSpan") + + gch.getGraphTileId().then((tileId) => { + gch.validateGraphPointCount(tileId, 27) // 27 points in graph + }) + + graph.getGraphTile() // Ensure graph tile is loaded + + gch.getGraphTileId().then((tileId: string) => { + gch.getPixiPointFillColorHardcoded(tileId, 0).then(({ fill }) => { + cy.log(`Retrieved Fill Color for Point 0: ${fill}`); + expect(fill).to.exist; + expect(fill).to.match(/^#[0-9a-fA-F]{6}$/, "Fill color should be a valid hex code"); + }); + }); + }) // this test will work when PT-#188601933 is delivered it.skip("check for point compression interaction", () => { // Open Four Seals diff --git a/v3/cypress/support/helpers/graph-canvas-helper.ts b/v3/cypress/support/helpers/graph-canvas-helper.ts index 968566dba6..673168e104 100644 --- a/v3/cypress/support/helpers/graph-canvas-helper.ts +++ b/v3/cypress/support/helpers/graph-canvas-helper.ts @@ -102,5 +102,82 @@ export const GraphCanvasHelper = { // Assert all points have unique positions expect(uniquePositions.size).to.equal(positions.length, "All points should have unique positions") }) - } + }, + // getPixiPointFillColorHardcoded(tileId: string, pointIndex: number): Cypress.Chainable<{ fill?: string }> { + // cy.log("Get the PixiPoint fill color (manual inspection)"); + // return cy.window().then((win: any) => { + // const pixiPoints = win.pixiPointsMap[tileId]; + // const textures = pixiPoints[0].textures; // Access textures directly from PixiPoints + + // // Log basic info for debugging + // cy.log("PixiPoints:", pixiPoints); + // cy.log("Textures Object (Type):", typeof textures); + + // if (!textures) { + // throw new Error("Textures object is undefined."); + // } + + // // Check if textures is a Map-like structure + // if (typeof textures.entries === "function") { + // const textureEntries = Array.from(textures.entries()); + // cy.log("Texture Entries (from Map):", textureEntries); + + // // Manually log all entries to inspect them + // textureEntries.forEach(([key, entry], index) => { + // cy.log(`Texture Entry ${index} Key:`, key); + // cy.log(`Texture Entry ${index} Properties:`, { + // fill: entry.fill, + // style: entry.style, + // }); + // }); + + // // Attempt to retrieve the first texture's fill property + // const [textureKey, textureEntry] = textureEntries[0] || []; + // if (!textureEntry) { + // throw new Error(`Texture entry for key ${textureKey} is not found.`); + // } + + // const fillColor = textureEntry.fill || textureEntry.style?.fill; + // cy.log("Fill Color (From Map):", fillColor); + + // if (!fillColor) { + // throw new Error("Fill color is undefined in the texture entry."); + // } + + // return cy.wrap({ fill: fillColor }); + // } + + // // Handle case where textures is an object + // const textureKeys = Object.keys(textures); + // cy.log("Texture Keys (from Object):", textureKeys); + + // textureKeys.forEach((key, index) => { + // const entry = textures[key]; + // cy.log(`Texture Entry ${index} Key:`, key); + // cy.log(`Texture Entry ${index} Properties:`, { + // fill: entry.fill, + // style: entry.style, + // }); + // }); + + // const textureKey = textureKeys[0]; + // if (!textureKey) { + // throw new Error("No texture keys found in the textures object."); + // } + + // const textureEntry = textures[textureKey]; + // if (!textureEntry) { + // throw new Error(`Texture entry for key ${textureKey} is not found.`); + // } + + // const fillColor = textureEntry.fill || textureEntry.style?.fill; + // cy.log("Fill Color (From Object):", fillColor); + + // if (!fillColor) { + // throw new Error("Fill color is undefined in the texture entry."); + // } + + // return cy.wrap({ fill: fillColor }); + // }); + // } } From c057e59152c00a41aa4f9c310ec6b550e1a6a14f Mon Sep 17 00:00:00 2001 From: nstclair-cc <20171905+nstclair-cc@users.noreply.github.com> Date: Mon, 23 Dec 2024 15:50:47 -0800 Subject: [PATCH 16/25] added checks for graph and map point color --- .../graph-pixi-interaction.spec.ts | 80 +++++++---- .../map-pixi-interaction.spec.ts | 42 +++++- .../support/helpers/graph-canvas-helper.ts | 127 +++++++----------- .../support/helpers/map-canvas-helper.ts | 50 +++++++ 4 files changed, 198 insertions(+), 101 deletions(-) diff --git a/v3/cypress/e2e/pixi-interaction/graph-pixi-interaction.spec.ts b/v3/cypress/e2e/pixi-interaction/graph-pixi-interaction.spec.ts index 7d9171ad9d..ddab82a37d 100644 --- a/v3/cypress/e2e/pixi-interaction/graph-pixi-interaction.spec.ts +++ b/v3/cypress/e2e/pixi-interaction/graph-pixi-interaction.spec.ts @@ -148,7 +148,7 @@ context("Graph UI with Pixi interaction", () => { gch.validateGraphPointCount(tileId, 24) // 24 points in graph }) }) - it("shows parent visibility toggles when Show Parent Visibility Toggles option is selected and checks point count", () => { + it("shows parent visibility toggles and verifies point count with Show Parent Visibility Toggles selected", () => { ah.openAxisAttributeMenu("bottom") ah.selectMenuAttribute("Sleep", "bottom") // Sleep => x-axis cy.wait(500) @@ -279,31 +279,35 @@ context("Graph UI with Pixi interaction", () => { // NOTE: Adornments are covered in graph-adornments.spec.ts (including Show Measures) }) - describe("checks for graph point position with pixi interaction", () => { - // use this test to debug point positions when running locally - it.skip("should check position of a point", () => { + describe("graph colors and selection with point count pixi interaction", () => { + it("checks color of a point with Legend colors", () => { ah.openAxisAttributeMenu("bottom") - ah.selectMenuAttribute("Sleep", "bottom") // Sleep => x-axis + ah.selectMenuAttribute("Diet", "bottom") // Diet => x-axis + glh.dragAttributeToPlot("Habitat") // Habitat => plot area cy.wait(500) // Wait for the graph to update + ah.verifyAxisLabel("bottom", "Diet") + //glh.verifyCategoricalLegend("LifeSpan") + + gch.getGraphTileId().then((tileId) => { + gch.validateGraphPointCount(tileId, 27) // 27 points in graph + }) + graph.getGraphTile() // Ensure graph tile is loaded gch.getGraphTileId().then((tileId: string) => { - cy.log(`Retrieved Tile ID: ${tileId}`) - if (!tileId) { - throw new Error("Tile ID is undefined or null.") - } - - gch.getPixiPointPosition(tileId, 0).then((position: { x: number - ; y: number }) => { - cy.log(`Point 0 Position: x=${position.x}, y=${position.y}`) + gch.getPixiPointFillColors(tileId).then((colors) => { + cy.log(`Extracted Fill Colors: ${colors}`) + expect(colors).to.have.length.greaterThan(0) + colors.forEach((color) => { + expect(color).to.match(/^#[0-9a-fA-F]{6}$/, "Each color should be a valid hex code") + }) }) }) }) - it.skip("SPIKE: should check color of a point", () => { + it("checks point selection using color of a point", () => { ah.openAxisAttributeMenu("bottom") ah.selectMenuAttribute("Diet", "bottom") // Diet => x-axis - glh.dragAttributeToPlot("Habitat") // Habitat => plot area cy.wait(500) // Wait for the graph to update ah.verifyAxisLabel("bottom", "Diet") @@ -313,15 +317,45 @@ context("Graph UI with Pixi interaction", () => { gch.validateGraphPointCount(tileId, 27) // 27 points in graph }) + table.moveAttributeToParent("Diet", "newCollection") + table.getNumOfRows(1).should("contain", 5) // five rows: top, plants, meat, both, bottom + gch.setAxisAndRetrieveTileId("Diet", "bottom").then((tileId) => { + gch.validateGraphPointCount(tileId, 3) + }) + + table.getGridCell(2, 2).should("contain", "plants").click() graph.getGraphTile() // Ensure graph tile is loaded gch.getGraphTileId().then((tileId: string) => { - gch.getPixiPointFillColorHardcoded(tileId, 0).then(({ fill }) => { - cy.log(`Retrieved Fill Color for Point 0: ${fill}`); - expect(fill).to.exist; - expect(fill).to.match(/^#[0-9a-fA-F]{6}$/, "Fill color should be a valid hex code"); - }); - }); + gch.getPixiPointFillColors(tileId).then((colors) => { + cy.log(`Extracted Fill Colors: ${colors}`) + expect(colors).to.have.length(2) // Verify there are exactly 2 colors + expect(colors).to.deep.equal(["#4682B4", "#E6805B"]) // Verify the colors are as expected + }) + }) + }) + }) + describe("checks for graph point position and color with pixi interaction", () => { + // use this test to debug point positions when running locally + // skip this on the cloud because it's not necessary + it.skip("should check position of a point", () => { + ah.openAxisAttributeMenu("bottom") + ah.selectMenuAttribute("Sleep", "bottom") // Sleep => x-axis + cy.wait(500) // Wait for the graph to update + + graph.getGraphTile() // Ensure graph tile is loaded + + gch.getGraphTileId().then((tileId: string) => { + cy.log(`Retrieved Tile ID: ${tileId}`) + if (!tileId) { + throw new Error("Tile ID is undefined or null.") + } + + gch.getPixiPointPosition(tileId, 0).then((position: { x: number + ; y: number }) => { + cy.log(`Point 0 Position: x=${position.x}, y=${position.y}`) + }) + }) }) // this test will work when PT-#188601933 is delivered it.skip("check for point compression interaction", () => { @@ -360,7 +394,7 @@ context("Graph UI with Pixi interaction", () => { gch.checkPointsHaveUniquePositions(tileId) // Call the helper function }) }) - it("no point compression after drawing categorical legend with categorical attribute on x axis and undo/redo", () => { + it("verifies no point compression after drawing categorical legend on x-axis with undo/redo actions", () => { // Initial setup: Drag attributes to the x-axis and plot area, respectively ah.openAxisAttributeMenu("bottom") ah.selectMenuAttribute("Diet", "bottom") // Diet => x-axis @@ -385,7 +419,7 @@ context("Graph UI with Pixi interaction", () => { gch.checkPointsHaveUniquePositions(tileId) // Call the helper function }) }) - it("no point compression after drawing categorical legend with categorical attribute on y axis and undo/redo", () => { + it("verifies no point compression after drawing categorical legend on y-axis with undo/redo actions", () => { // Drag attribute to the y-axis and drag another attribute to the plot area ah.openAxisAttributeMenu("left") ah.selectMenuAttribute("Diet", "left") // Diet => y-axis diff --git a/v3/cypress/e2e/pixi-interaction/map-pixi-interaction.spec.ts b/v3/cypress/e2e/pixi-interaction/map-pixi-interaction.spec.ts index 67790f84da..d0fa585fd2 100644 --- a/v3/cypress/e2e/pixi-interaction/map-pixi-interaction.spec.ts +++ b/v3/cypress/e2e/pixi-interaction/map-pixi-interaction.spec.ts @@ -11,7 +11,7 @@ context("Graph UI with Pixi interaction", () => { cy.visit(url) cy.wait(2500) }) - describe("map view", () => { + describe("map point count with point count pixi interaction", () => { it("validates point count for map data on map component with pixi interaction", () => { cy.log('Correct number of points in maps') map.getMapTile().click() @@ -71,4 +71,44 @@ context("Graph UI with Pixi interaction", () => { // }) }) }) + describe("map colors and selection with pixi interaction", () => { + it("validates point selection for map data on map component with pixi interaction", () => { + table.getGridCell(2, 2).should("contain", "546").click() + + cy.log('Correct highlighting when points are selected') + map.getMapTile().click() + mch.getMapTileId().then((tileId) => { + mch.validateMapPointCount(tileId, 858) + }) + + mch.getMapTileId().then((tileId: string) => { + mch.getPixiPointFillColors(tileId).then((colors) => { + const upperCaseColors = colors.map(color => color.toUpperCase()) // Convert all colors to uppercase + cy.log(`Extracted Fill Colors (Uppercase): ${upperCaseColors}`) + expect(upperCaseColors).to.have.length(2) // Verify there are exactly 2 colors + expect(upperCaseColors).to.deep.equal(["#E6805B", "#4682B4"]) // Verify the colors are as expected + }) + }) + }) + // This is a simple test case for maps with legends + // we'd need to get the target drag working for this to work + // it("validates map color data on map legend plot with pixi interaction", () => { + // cy.dragAttributeToTarget("animal_id", "color", "map") + + // cy.log('Correct highlighting when points are selected') + // map.getMapTile().click() + // mch.getMapTileId().then((tileId) => { + // mch.validateMapPointCount(tileId, 858) + // }) + + // mch.getMapTileId().then((tileId: string) => { + // mch.getPixiPointFillColors(tileId).then((colors) => { + // const upperCaseColors = colors.map(color => color.toUpperCase()) // Convert all colors to uppercase + // cy.log(`Extracted Fill Colors (Uppercase): ${upperCaseColors}`) + // expect(upperCaseColors).to.have.length(2) // Verify there are exactly 2 colors + // expect(upperCaseColors).to.deep.equal(["#E6805B", "#4682B4"]) // Verify the colors are as expected + // }) + // }) + // }) + }) }) diff --git a/v3/cypress/support/helpers/graph-canvas-helper.ts b/v3/cypress/support/helpers/graph-canvas-helper.ts index 673168e104..ab13845b80 100644 --- a/v3/cypress/support/helpers/graph-canvas-helper.ts +++ b/v3/cypress/support/helpers/graph-canvas-helper.ts @@ -103,81 +103,54 @@ export const GraphCanvasHelper = { expect(uniquePositions.size).to.equal(positions.length, "All points should have unique positions") }) }, - // getPixiPointFillColorHardcoded(tileId: string, pointIndex: number): Cypress.Chainable<{ fill?: string }> { - // cy.log("Get the PixiPoint fill color (manual inspection)"); - // return cy.window().then((win: any) => { - // const pixiPoints = win.pixiPointsMap[tileId]; - // const textures = pixiPoints[0].textures; // Access textures directly from PixiPoints - - // // Log basic info for debugging - // cy.log("PixiPoints:", pixiPoints); - // cy.log("Textures Object (Type):", typeof textures); - - // if (!textures) { - // throw new Error("Textures object is undefined."); - // } - - // // Check if textures is a Map-like structure - // if (typeof textures.entries === "function") { - // const textureEntries = Array.from(textures.entries()); - // cy.log("Texture Entries (from Map):", textureEntries); - - // // Manually log all entries to inspect them - // textureEntries.forEach(([key, entry], index) => { - // cy.log(`Texture Entry ${index} Key:`, key); - // cy.log(`Texture Entry ${index} Properties:`, { - // fill: entry.fill, - // style: entry.style, - // }); - // }); - - // // Attempt to retrieve the first texture's fill property - // const [textureKey, textureEntry] = textureEntries[0] || []; - // if (!textureEntry) { - // throw new Error(`Texture entry for key ${textureKey} is not found.`); - // } - - // const fillColor = textureEntry.fill || textureEntry.style?.fill; - // cy.log("Fill Color (From Map):", fillColor); - - // if (!fillColor) { - // throw new Error("Fill color is undefined in the texture entry."); - // } - - // return cy.wrap({ fill: fillColor }); - // } - - // // Handle case where textures is an object - // const textureKeys = Object.keys(textures); - // cy.log("Texture Keys (from Object):", textureKeys); - - // textureKeys.forEach((key, index) => { - // const entry = textures[key]; - // cy.log(`Texture Entry ${index} Key:`, key); - // cy.log(`Texture Entry ${index} Properties:`, { - // fill: entry.fill, - // style: entry.style, - // }); - // }); - - // const textureKey = textureKeys[0]; - // if (!textureKey) { - // throw new Error("No texture keys found in the textures object."); - // } - - // const textureEntry = textures[textureKey]; - // if (!textureEntry) { - // throw new Error(`Texture entry for key ${textureKey} is not found.`); - // } - - // const fillColor = textureEntry.fill || textureEntry.style?.fill; - // cy.log("Fill Color (From Object):", fillColor); - - // if (!fillColor) { - // throw new Error("Fill color is undefined in the texture entry."); - // } - - // return cy.wrap({ fill: fillColor }); - // }); - // } + // Checks if the points in a graph have unique fill colors. + getPixiPointFillColors(tileId: string): Cypress.Chainable { + cy.log("Get all PixiPoint fill colors from textures") + return cy.window().then((win: any) => { + const pixiPoints = win.pixiPointsMap[tileId] + const textures = pixiPoints[0].textures // Access textures directly from PixiPoints + + if (!textures) { + throw new Error("Textures object is undefined.") + } + + // Array to store extracted fill colors + const fillColors: string[] = [] + + if (typeof textures.entries === "function") { + for (const [key] of textures.entries()) { + try { + // Parse the key to extract the `fill` color + const parsedKey = JSON.parse(key) + if (parsedKey.fill) { + fillColors.push(parsedKey.fill) + } + } catch (error) { + cy.log(`Error parsing texture key: ${key}`, error) + } + } + } else { + // If textures is an object + for (const key of Object.keys(textures)) { + try { + // Parse the key to extract the `fill` color + const parsedKey = JSON.parse(key) + if (parsedKey.fill) { + fillColors.push(parsedKey.fill) + } + } catch (error) { + cy.log(`Error parsing texture key: ${key}`, error) + } + } + } + + cy.log("Extracted Fill Colors:", fillColors) + + if (fillColors.length === 0) { + throw new Error("No fill colors found in the textures map.") + } + + return cy.wrap(fillColors) + }) + } } diff --git a/v3/cypress/support/helpers/map-canvas-helper.ts b/v3/cypress/support/helpers/map-canvas-helper.ts index c9319f4659..52b8539624 100644 --- a/v3/cypress/support/helpers/map-canvas-helper.ts +++ b/v3/cypress/support/helpers/map-canvas-helper.ts @@ -51,5 +51,55 @@ export const MapCanvasHelper = { // Assert the number of points expect(pointsCount).to.equal(expectedPointCount, "Point count matches expected value") }) + }, + // Checks if the points in a graph have unique fill colors. + getPixiPointFillColors(tileId: string): Cypress.Chainable { + cy.log("Get all PixiPoint fill colors from textures") + return cy.window().then((win: any) => { + const pixiPoints = win.pixiPointsMap[tileId] + const textures = pixiPoints[0].textures // Access textures directly from PixiPoints + + if (!textures) { + throw new Error("Textures object is undefined.") + } + + // Array to store extracted fill colors + const fillColors: string[] = [] + + if (typeof textures.entries === "function") { + for (const [key] of textures.entries()) { + try { + // Parse the key to extract the `fill` color + const parsedKey = JSON.parse(key) + if (parsedKey.fill) { + fillColors.push(parsedKey.fill) + } + } catch (error) { + cy.log(`Error parsing texture key: ${key}`, error) + } + } + } else { + // If textures is an object + for (const key of Object.keys(textures)) { + try { + // Parse the key to extract the `fill` color + const parsedKey = JSON.parse(key) + if (parsedKey.fill) { + fillColors.push(parsedKey.fill) + } + } catch (error) { + cy.log(`Error parsing texture key: ${key}`, error) + } + } + } + + cy.log("Extracted Fill Colors:", fillColors) + + if (fillColors.length === 0) { + throw new Error("No fill colors found in the textures map.") + } + + return cy.wrap(fillColors) + }) } } From 8c4a7b81370443bf74e0065204fe41b01387dfe5 Mon Sep 17 00:00:00 2001 From: nstclair-cc <20171905+nstclair-cc@users.noreply.github.com> Date: Mon, 23 Dec 2024 16:21:34 -0800 Subject: [PATCH 17/25] updated graph.spect --- v3/cypress/e2e/graph.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/v3/cypress/e2e/graph.spec.ts b/v3/cypress/e2e/graph.spec.ts index 2f44450e6e..8c9bfc61ba 100644 --- a/v3/cypress/e2e/graph.spec.ts +++ b/v3/cypress/e2e/graph.spec.ts @@ -60,7 +60,7 @@ context("Graph UI", () => { toolbar.getRedoTool().click({force: true}) c.getComponentTitle("graph").should("have.text", newCollectionName) }) - it("tests creating graphs with undo/redo", () => { + it("tests creating multiple graphs with undo/redo", () => { // Function to count CODAP graphs and return the count const countCodapGraphs = () => { return cy.get('.codap-graph').its('length') From 53bd8d5a8d613fc0259e3044c36fe95a4e695935 Mon Sep 17 00:00:00 2001 From: nstclair-cc <20171905+nstclair-cc@users.noreply.github.com> Date: Mon, 23 Dec 2024 16:23:25 -0800 Subject: [PATCH 18/25] clean up code --- v3/cypress/support/helpers/map-canvas-helper.ts | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/v3/cypress/support/helpers/map-canvas-helper.ts b/v3/cypress/support/helpers/map-canvas-helper.ts index 52b8539624..ee56089d46 100644 --- a/v3/cypress/support/helpers/map-canvas-helper.ts +++ b/v3/cypress/support/helpers/map-canvas-helper.ts @@ -1,5 +1,3 @@ -// import { AxisHelper as ah } from "./axis-helper" - export const MapCanvasHelper = { // Helper function to locate the graph element and retrieve its dynamic ID. getMapTileId(): Cypress.Chainable { @@ -15,18 +13,6 @@ export const MapCanvasHelper = { return Cypress.Promise.resolve(tileId) // Ensure Cypress compatibility }) }, - // // Helper function to set an attribute for the axis and retrieve the tile ID. - // setAxisAndRetrieveTileId (attribute: string, axis: "bottom" | "left") { - // cy.log(`Set ${attribute} on ${axis} axis`) - // ah.openAxisAttributeMenu(axis) - // ah.selectMenuAttribute(attribute, axis) - // cy.wait(500) - - // cy.log('Locate the graph element and retrieve its dynamic ID') - // return cy.get('[data-testid=codap-graph]') - // .parents('.free-tile-component') - // .invoke('attr', 'id') - // }, // Helper function to validate pixi metadata and point count. validateMapPointCount(tileId: string | undefined, expectedPointCount: number) { if (!tileId) { From 559aff4c00e562f02c7a901a27050859363327ff Mon Sep 17 00:00:00 2001 From: nstclair-cc <20171905+nstclair-cc@users.noreply.github.com> Date: Mon, 23 Dec 2024 16:25:01 -0800 Subject: [PATCH 19/25] removed commented line --- v3/cypress/e2e/pixi-interaction/map-pixi-interaction.spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/v3/cypress/e2e/pixi-interaction/map-pixi-interaction.spec.ts b/v3/cypress/e2e/pixi-interaction/map-pixi-interaction.spec.ts index d0fa585fd2..a599b3fd26 100644 --- a/v3/cypress/e2e/pixi-interaction/map-pixi-interaction.spec.ts +++ b/v3/cypress/e2e/pixi-interaction/map-pixi-interaction.spec.ts @@ -2,7 +2,6 @@ import { TableTileElements as table } from "../../support/elements/table-tile" import { ComponentElements as c } from "../../support/elements/component-elements" import { MapTileElements as map } from "../../support/elements/map-tile" import { MapCanvasHelper as mch } from "../../support/helpers/map-canvas-helper" -//import { FormulaHelper as fh } from "../../support/helpers/formula-helper" context("Graph UI with Pixi interaction", () => { beforeEach(function () { From 39ec10ef735403762fd3546d6b62f22a7d6638d4 Mon Sep 17 00:00:00 2001 From: nstclair-cc <20171905+nstclair-cc@users.noreply.github.com> Date: Mon, 6 Jan 2025 15:33:18 -0800 Subject: [PATCH 20/25] incorporated Scott's feedback to PR --- .github/workflows/graph-pixi-tests.yml | 74 +++++++++++++------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/.github/workflows/graph-pixi-tests.yml b/.github/workflows/graph-pixi-tests.yml index 5cb7c180cf..df428ca106 100644 --- a/.github/workflows/graph-pixi-tests.yml +++ b/.github/workflows/graph-pixi-tests.yml @@ -1,4 +1,4 @@ -name: Regression Label +name: Graph Pixi Tests on: push: @@ -17,39 +17,39 @@ jobs: GH_REPO: ${{github.repository}} cypress: - runs-on: ubuntu-latest - strategy: - # when one test fails, DO NOT cancel the other - # containers, because this will kill Cypress processes - # leaving the Dashboard hanging ... - # https://github.com/cypress-io/github-action/issues/48 - fail-fast: false - steps: - - uses: browser-actions/setup-chrome@v1 - with: - chrome-version: stable - - uses: snow-actions/sparse-checkout@v1.2.0 - with: - patterns: v3 - - name: Restore Webpack Cache - id: restore-webpack-cache - uses: actions/cache/restore@v4 - with: - path: v3/.cache/webpack/ - key: webpack-dev-build-${{ hashFiles('v3/webpack.config.js') }} - - uses: cypress-io/github-action@v6 - with: - working-directory: v3 - start: npm start - wait-on: 'http://localhost:8080' - wait-on-timeout: 300 - record: ${{ !!secrets.CYPRESS_RECORD_KEY }} - browser: chrome - spec: cypress/e2e/pixi-interaction/**/*.ts - group: 'Pixi Interaction Tests' - env: - CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - CODE_COVERAGE: true - CYPRESS_coverage: true - SKIP_ESLINT: true + runs-on: ubuntu-latest + strategy: + # when one test fails, DO NOT cancel the other + # containers, because this will kill Cypress processes + # leaving the Dashboard hanging ... + # https://github.com/cypress-io/github-action/issues/48 + fail-fast: false + steps: + - uses: browser-actions/setup-chrome@v1 + with: + chrome-version: stable + - uses: snow-actions/sparse-checkout@v1.2.0 + with: + patterns: v3 + - name: Restore Webpack Cache + id: restore-webpack-cache + uses: actions/cache/restore@v4 + with: + path: v3/.cache/webpack/ + key: webpack-dev-build-${{ hashFiles('v3/webpack.config.js') }} + - uses: cypress-io/github-action@v6 + with: + working-directory: v3 + start: npm start + wait-on: 'http://localhost:8080' + wait-on-timeout: 300 + record: ${{ !!secrets.CYPRESS_RECORD_KEY }} + browser: chrome + spec: cypress/e2e/pixi-interaction/**/*.ts + group: 'Pixi Interaction Tests' + env: + CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CODE_COVERAGE: true + CYPRESS_coverage: true + SKIP_ESLINT: true From aa9a11703758f1fb8f8530903f17deef0e18218f Mon Sep 17 00:00:00 2001 From: nstclair-cc <20171905+nstclair-cc@users.noreply.github.com> Date: Mon, 6 Jan 2025 15:37:31 -0800 Subject: [PATCH 21/25] correct the YAML file --- .github/workflows/graph-pixi-tests.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/graph-pixi-tests.yml b/.github/workflows/graph-pixi-tests.yml index df428ca106..bf4467f7a7 100644 --- a/.github/workflows/graph-pixi-tests.yml +++ b/.github/workflows/graph-pixi-tests.yml @@ -16,7 +16,7 @@ jobs: # set repository so we don't have to check out all of the code GH_REPO: ${{github.repository}} -cypress: + cypress: runs-on: ubuntu-latest strategy: # when one test fails, DO NOT cancel the other @@ -33,7 +33,7 @@ cypress: patterns: v3 - name: Restore Webpack Cache id: restore-webpack-cache - uses: actions/cache/restore@v4 + uses: actions/cache@v4 with: path: v3/.cache/webpack/ key: webpack-dev-build-${{ hashFiles('v3/webpack.config.js') }} @@ -52,4 +52,4 @@ cypress: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} CODE_COVERAGE: true CYPRESS_coverage: true - SKIP_ESLINT: true + SKIP_ESLINT: true \ No newline at end of file From bf626af57fda561c316110e40ca0b1d29dc0b8d3 Mon Sep 17 00:00:00 2001 From: nstclair-cc <20171905+nstclair-cc@users.noreply.github.com> Date: Mon, 6 Jan 2025 15:58:01 -0800 Subject: [PATCH 22/25] run a checkout step before running npm commands. --- .github/workflows/graph-pixi-tests.yml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/graph-pixi-tests.yml b/.github/workflows/graph-pixi-tests.yml index bf4467f7a7..c8e6ffc014 100644 --- a/.github/workflows/graph-pixi-tests.yml +++ b/.github/workflows/graph-pixi-tests.yml @@ -9,22 +9,24 @@ jobs: permissions: actions: write steps: + - name: Check Out Repository + uses: actions/checkout@v3 + - name: Run Cypress tests for graph-pixi run: npm run cypress:run -- --spec "cypress/e2e/pixi-interaction/*" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # set repository so we don't have to check out all of the code - GH_REPO: ${{github.repository}} + GH_REPO: ${{ github.repository }} cypress: runs-on: ubuntu-latest strategy: - # when one test fails, DO NOT cancel the other - # containers, because this will kill Cypress processes - # leaving the Dashboard hanging ... - # https://github.com/cypress-io/github-action/issues/48 fail-fast: false steps: + - name: Check Out Repository + uses: actions/checkout@v3 + - uses: browser-actions/setup-chrome@v1 with: chrome-version: stable From c969ec2d931130b447b9f441473c3ec08d09a187 Mon Sep 17 00:00:00 2001 From: nstclair-cc <20171905+nstclair-cc@users.noreply.github.com> Date: Mon, 6 Jan 2025 16:01:56 -0800 Subject: [PATCH 23/25] keep the pixi tests isolated in their own cypress job --- .github/workflows/graph-pixi-tests.yml | 52 ++++++++++++++------------ 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/.github/workflows/graph-pixi-tests.yml b/.github/workflows/graph-pixi-tests.yml index c8e6ffc014..9a99619ad8 100644 --- a/.github/workflows/graph-pixi-tests.yml +++ b/.github/workflows/graph-pixi-tests.yml @@ -2,40 +2,35 @@ name: Graph Pixi Tests on: push: + paths: + - 'cypress/e2e/pixi-interaction/**' + - '.github/workflows/graph-pixi-tests.yml' jobs: - re_run: - runs-on: ubuntu-latest - permissions: - actions: write - steps: - - name: Check Out Repository - uses: actions/checkout@v3 - - - name: Run Cypress tests for graph-pixi - run: npm run cypress:run -- --spec "cypress/e2e/pixi-interaction/*" - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # set repository so we don't have to check out all of the code - GH_REPO: ${{ github.repository }} - cypress: runs-on: ubuntu-latest strategy: fail-fast: false steps: - - name: Check Out Repository - uses: actions/checkout@v3 - + - uses: snow-actions/sparse-checkout@v1.2.0 + with: + patterns: | + v3 + cypress/e2e/pixi-interaction/** + - uses: actions/setup-node@v4 + with: + node-version: 18 + cache: 'npm' + cache-dependency-path: package-lock.json + - name: Install Dependencies + working-directory: v3 + run: npm ci - uses: browser-actions/setup-chrome@v1 with: chrome-version: stable - - uses: snow-actions/sparse-checkout@v1.2.0 - with: - patterns: v3 - name: Restore Webpack Cache id: restore-webpack-cache - uses: actions/cache@v4 + uses: actions/cache/restore@v4 with: path: v3/.cache/webpack/ key: webpack-dev-build-${{ hashFiles('v3/webpack.config.js') }} @@ -54,4 +49,15 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} CODE_COVERAGE: true CYPRESS_coverage: true - SKIP_ESLINT: true \ No newline at end of file + SKIP_ESLINT: true + - name: Save Webpack Cache + uses: actions/cache/save@v4 + if: github.ref_name == 'main' || steps.restore-webpack-cache.outputs.cache-hit != 'true' + with: + path: v3/.cache/webpack/ + key: ${{ steps.restore-webpack-cache.outputs.cache-primary-key }} + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + flags: pixi \ No newline at end of file From 57b4b74702543e0cfe66f09a81f4ab186d53c33f Mon Sep 17 00:00:00 2001 From: nstclair-cc <20171905+nstclair-cc@users.noreply.github.com> Date: Mon, 6 Jan 2025 16:05:30 -0800 Subject: [PATCH 24/25] list the directory paths explicitly --- .github/workflows/graph-pixi-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/graph-pixi-tests.yml b/.github/workflows/graph-pixi-tests.yml index 9a99619ad8..78f0ccdd14 100644 --- a/.github/workflows/graph-pixi-tests.yml +++ b/.github/workflows/graph-pixi-tests.yml @@ -16,7 +16,7 @@ jobs: with: patterns: | v3 - cypress/e2e/pixi-interaction/** + cypress/e2e/pixi-interaction - uses: actions/setup-node@v4 with: node-version: 18 From 7b3ca2e2f124b00f697c22d1190e32d4708dc5a9 Mon Sep 17 00:00:00 2001 From: nstclair-cc <20171905+nstclair-cc@users.noreply.github.com> Date: Mon, 6 Jan 2025 16:22:06 -0800 Subject: [PATCH 25/25] include linting and jest tests alongside cypress pixi tests --- .github/workflows/graph-pixi-tests.yml | 70 ++++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 3 deletions(-) diff --git a/.github/workflows/graph-pixi-tests.yml b/.github/workflows/graph-pixi-tests.yml index 78f0ccdd14..8cb307875a 100644 --- a/.github/workflows/graph-pixi-tests.yml +++ b/.github/workflows/graph-pixi-tests.yml @@ -7,7 +7,71 @@ on: - '.github/workflows/graph-pixi-tests.yml' jobs: + lint: + name: Run Lint + runs-on: ubuntu-latest + steps: + - uses: snow-actions/sparse-checkout@v1.2.0 + with: + patterns: v3 + - uses: actions/setup-node@v4 + with: + node-version: 18 + cache: 'npm' + cache-dependency-path: v3/package-lock.json + - name: Install Dependencies + working-directory: v3 + run: npm ci + - name: Restore ESLint Cache + id: restore-eslint-cache + uses: actions/cache/restore@v4 + with: + path: v3/.cache/eslint/ + key: eslint-${{ hashFiles('v3/package-lock.json', 'v3/.eslintrc.build.cjs', 'v3/.eslintrc.cjs') }} + - name: Run Lint Check + working-directory: v3 + run: npm run lint:build + - name: Save ESLint Cache + uses: actions/cache/save@v4 + if: github.ref_name == 'main' || steps.restore-eslint-cache.outputs.cache-hit != 'true' + with: + path: v3/.cache/eslint/ + key: ${{ steps.restore-eslint-cache.outputs.cache-primary-key }} + + jest: + name: Run Jest Tests + runs-on: ubuntu-latest + steps: + - uses: snow-actions/sparse-checkout@v1.2.0 + with: + patterns: v3 + - uses: actions/setup-node@v4 + with: + node-version: 18 + cache: 'npm' + cache-dependency-path: v3/package-lock.json + - name: Install Dependencies + working-directory: v3 + run: npm ci + - name: Jest Cache + uses: actions/cache@v4 + with: + path: /tmp/jest_rt + key: jest-${{ hashFiles('v3/package-lock.json') }} + - name: Get Number of CPU Cores + id: cpu-cores + uses: SimenB/github-actions-cpu-cores@v2 + - name: Run Jest Tests + working-directory: v3 + run: npm run test:coverage -- --maxWorkers=${{ steps.cpu-cores.outputs.count }} + - name: Upload Jest Coverage to Codecov + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + flags: jest + cypress: + name: Run Cypress Pixi Tests runs-on: ubuntu-latest strategy: fail-fast: false @@ -21,7 +85,7 @@ jobs: with: node-version: 18 cache: 'npm' - cache-dependency-path: package-lock.json + cache-dependency-path: v3/package-lock.json - name: Install Dependencies working-directory: v3 run: npm ci @@ -56,8 +120,8 @@ jobs: with: path: v3/.cache/webpack/ key: ${{ steps.restore-webpack-cache.outputs.cache-primary-key }} - - name: Upload coverage to Codecov + - name: Upload Cypress Coverage to Codecov uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} - flags: pixi \ No newline at end of file + flags: cypress \ No newline at end of file