diff --git a/vuu-ui/cypress/e2e/layout-management/screenshot.cy.js b/vuu-ui/cypress/e2e/layout-management/screenshot.cy.js index b7efeea0b6..b9caff13b8 100644 --- a/vuu-ui/cypress/e2e/layout-management/screenshot.cy.js +++ b/vuu-ui/cypress/e2e/layout-management/screenshot.cy.js @@ -1,5 +1,5 @@ import "cypress-iframe"; -import { SHELL_WITH_NEW_THEME_URL } from "../../support/constants"; +import { SHELL_WITH_NEW_THEME_URL } from "../../support/e2e/constants"; context("Screenshot", () => { beforeEach(() => { diff --git a/vuu-ui/cypress/support/component.ts b/vuu-ui/cypress/support/component.ts index 95b505cc73..84744a01a2 100644 --- a/vuu-ui/cypress/support/component.ts +++ b/vuu-ui/cypress/support/component.ts @@ -1,10 +1,10 @@ import "cypress-real-events"; // import "@cypress/code-coverage/support"; -import "./assertions"; -import "./commands"; +import "./component/assertions"; +import "./component/commands"; -import "./cypress.css"; -import "./index.css"; +import "./component/cypress.css"; +import "./component/index.css"; beforeEach(() => { cy.window({ log: false }).focus({ log: false }); diff --git a/vuu-ui/cypress/support/assertions.ts b/vuu-ui/cypress/support/component/assertions.ts similarity index 100% rename from vuu-ui/cypress/support/assertions.ts rename to vuu-ui/cypress/support/component/assertions.ts diff --git a/vuu-ui/cypress/support/component/commands.tsx b/vuu-ui/cypress/support/component/commands.tsx new file mode 100644 index 0000000000..419c6bb643 --- /dev/null +++ b/vuu-ui/cypress/support/component/commands.tsx @@ -0,0 +1,160 @@ +import "@testing-library/cypress/add-commands"; +import { mount as cypressMount } from "cypress/react18"; +import type { MountOptions, MountReturn } from "cypress/react"; +import "cypress-axe"; +import { Options } from "cypress-axe"; +// import { PerformanceResult, PerformanceTester } from "./PerformanceTester"; +import { ReactNode } from "react"; +import { ThemeProvider } from "@finos/vuu-shell"; + +const SupportedThemeModeValues = ["light", "dark"] as const; +type SupportedThemeMode = (typeof SupportedThemeModeValues)[number]; +const SupportedDensityValues = ["touch", "low", "medium", "high"]; +type SupportedDensity = (typeof SupportedDensityValues)[number]; + +// Must be declared global to be detected by typescript (allows import/export) +declare global { + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace Cypress { + // unsure why this Subject is unused, nor what to do with it... + // eslint-disable-next-line @typescript-eslint/no-unused-vars + interface Chainable { + /** + * Set Theme Mode + * @example + * cy.setMode('light') + */ + setMode(theme: SupportedThemeMode): Chainable; + + /** + * Set Density + * + * @example + * cy.setDensity('medium') + */ + setDensity(theme: SupportedDensity): Chainable; + + /** + * Set Density + * + * @example + * cy.checkAxeComponent() + */ + checkAxeComponent( + options?: Options, + enableFailures?: boolean + ): Chainable; + + mountPerformance: ( + jsx: ReactNode, + options?: MountOptions + ) => Chainable; + mount: (jsx: ReactNode, options?: MountOptions) => Chainable; + + getRenderCount(): Chainable; + + getRenderTime(): Chainable; + + paste(string: string): Chainable; + } + } +} + +Cypress.Commands.add("setMode", function (mode) { + if (SupportedThemeModeValues.includes(mode)) { + this.mode; + } else { + cy.log("Unsupported mode", mode); + } +}); + +Cypress.Commands.add("setDensity", function (density) { + if (SupportedDensityValues.includes(density)) { + this.density = density; + } else { + cy.log("Unsupported density", density); + } +}); + +Cypress.Commands.add( + "checkAxeComponent", + (options: Options = {}, enableFailures = false) => { + cy.injectAxe(); + cy.checkA11y( + //So the region rule does not have to be disabled globally + "[data-cy-root]", + options, + (a11yErrors) => { + // Don't output the violations twice + if (Cypress.browser.isHeadless) { + for (const a11yError of a11yErrors) { + cy.task("log", a11yError); + } + } + }, + !enableFailures + ); + } +); + +Cypress.Commands.add("mount", function (children, options) { + return cypressMount( + + {children}, + , + options + ); +}); + +// Cypress.Commands.add("mountPerformance", function (children, options) { +// const handleRender = (result: PerformanceResult) => { +// // @ts-ignore +// cy.state("performanceResult", result); +// }; + +// return cy.mount( +// {children}, +// options +// ); +// }); + +// Cypress.Commands.add("getRenderTime", function () { +// // @ts-ignore +// return cy.state("performanceResult").renderTime; +// }); + +// Cypress.Commands.add("getRenderCount", function () { +// // @ts-ignore +// return cy.state("performanceResult").renderCount; +// }); + +Cypress.Commands.add("paste", { prevSubject: "element" }, (input, value) => { + // taken from https://stackoverflow.com/a/69552958/11217233 + const nativeInputValueSetter = Object.getOwnPropertyDescriptor( + window.HTMLInputElement.prototype, + "value" + )?.set; + + if (nativeInputValueSetter) { + cy.wrap(input).then((input) => { + nativeInputValueSetter.call(input[0], value); + input[0].dispatchEvent( + new Event("input", { + bubbles: true, + composed: true, + }) + ); + }); + } +}); + +// Workaround for an issue in Cypress, where ResizeObserver fails with the message +// ResizeObserver loop limit exceeded +// Seems to occur for us in Cypress but never in browser in normal use +Cypress.on("uncaught:exception", (err) => { + if (err.message.includes("ResizeObserver")) { + return false; + } +}); + +export {}; diff --git a/vuu-ui/cypress/support/component-index.html b/vuu-ui/cypress/support/component/component-index.html similarity index 100% rename from vuu-ui/cypress/support/component-index.html rename to vuu-ui/cypress/support/component/component-index.html diff --git a/vuu-ui/cypress/support/cypress.css b/vuu-ui/cypress/support/component/cypress.css similarity index 100% rename from vuu-ui/cypress/support/cypress.css rename to vuu-ui/cypress/support/component/cypress.css diff --git a/vuu-ui/cypress/support/index.css b/vuu-ui/cypress/support/component/index.css similarity index 100% rename from vuu-ui/cypress/support/index.css rename to vuu-ui/cypress/support/component/index.css diff --git a/vuu-ui/cypress/support/constants.ts b/vuu-ui/cypress/support/e2e/constants.ts similarity index 100% rename from vuu-ui/cypress/support/constants.ts rename to vuu-ui/cypress/support/e2e/constants.ts diff --git a/vuu-ui/cypress/support/PerformanceTester.tsx b/vuu-ui/cypress/tests/PerformanceTester.tsx similarity index 100% rename from vuu-ui/cypress/support/PerformanceTester.tsx rename to vuu-ui/cypress/tests/PerformanceTester.tsx diff --git a/vuu-ui/cypress/tests/checkAccessibility.tsx b/vuu-ui/cypress/tests/checkAccessibility.tsx index 289960b15e..ec6161dfa0 100644 --- a/vuu-ui/cypress/tests/checkAccessibility.tsx +++ b/vuu-ui/cypress/tests/checkAccessibility.tsx @@ -1,24 +1,24 @@ // import { Options } from "cypress-axe"; - +// // export function checkAccessibility(stories: StoriesWithPartialProps) { // describe("Axe Testing", () => { // Object.entries(stories).forEach(([name, StoryComponent]) => { // const Component = StoryComponent as Story; - +// // const disabledRules: string[] = // Component.parameters?.axe?.disabledRules ?? []; // const shouldSkip: boolean = Component.parameters?.axe?.skip; - +// // const testFunction = shouldSkip ? it.skip : it; - +// // testFunction(`Story "${name}", should not have an axe violations`, () => { // cy.mount(); - +// // const rules = disabledRules.reduce((acc, rule) => { // acc[rule] = { enabled: false }; // return acc; // }, {} as Required["rules"]); - +// // cy.checkAxeComponent({ rules }, true); // }); // });