From d71607f902bb7ae6b9fcffe16b16c83d8147fbde Mon Sep 17 00:00:00 2001 From: Hedwig Doets Date: Fri, 11 Jul 2025 10:25:29 +0200 Subject: [PATCH 01/23] feat: initial setup of skiplink widget --- .../pluggableWidgets/skiplink-web/README.md | 27 ++++++++ .../skiplink-web/e2e/SkipLink.spec.ts | 23 +++++++ .../skiplink-web/package.json | 50 ++++++++++++++ .../skiplink-web/src/SkipLink.css | 20 ++++++ .../skiplink-web/src/SkipLink.tsx | 66 +++++++++++++++++++ .../skiplink-web/src/SkipLink.xml | 20 ++++++ .../skiplink-web/src/package.xml | 11 ++++ .../skiplink-web/tsconfig.json | 25 +++++++ .../skiplink-web/typings/SkipLinkProps.d.ts | 30 +++++++++ pnpm-lock.yaml | 24 +++++++ 10 files changed, 296 insertions(+) create mode 100644 packages/pluggableWidgets/skiplink-web/README.md create mode 100644 packages/pluggableWidgets/skiplink-web/e2e/SkipLink.spec.ts create mode 100644 packages/pluggableWidgets/skiplink-web/package.json create mode 100644 packages/pluggableWidgets/skiplink-web/src/SkipLink.css create mode 100644 packages/pluggableWidgets/skiplink-web/src/SkipLink.tsx create mode 100644 packages/pluggableWidgets/skiplink-web/src/SkipLink.xml create mode 100644 packages/pluggableWidgets/skiplink-web/src/package.xml create mode 100644 packages/pluggableWidgets/skiplink-web/tsconfig.json create mode 100644 packages/pluggableWidgets/skiplink-web/typings/SkipLinkProps.d.ts diff --git a/packages/pluggableWidgets/skiplink-web/README.md b/packages/pluggableWidgets/skiplink-web/README.md new file mode 100644 index 0000000000..02b3c70903 --- /dev/null +++ b/packages/pluggableWidgets/skiplink-web/README.md @@ -0,0 +1,27 @@ +# SkipLink Web Widget + +A simple accessibility widget that adds a skip link to the top of the page. The link is only visible when focused and allows users to jump directly to the main content. + +## Usage + +1. Place the `` component at the very top of your page or layout. +2. Ensure your main content container has `id="main-content"`. + + ```jsx + +
Main content here
+ ``` + +## Accessibility + +- The skip link is visually hidden except when focused, making it accessible for keyboard and screen reader users. + +## End-to-End Testing + +E2E tests are located in the `e2e/` folder and use Playwright. Run them with: + +``` +npm install +npx playwright install +npm test +``` diff --git a/packages/pluggableWidgets/skiplink-web/e2e/SkipLink.spec.ts b/packages/pluggableWidgets/skiplink-web/e2e/SkipLink.spec.ts new file mode 100644 index 0000000000..80c13e8155 --- /dev/null +++ b/packages/pluggableWidgets/skiplink-web/e2e/SkipLink.spec.ts @@ -0,0 +1,23 @@ +import { expect, test } from "@playwright/test"; + +// Assumes the test project renders and a
element + +test.describe("SkipLink", () => { + test("should be hidden by default and visible on focus, and should skip to main content", async ({ page }) => { + await page.goto("/"); + const skipLink = page.locator(".skip-link"); + // Should be hidden by default + await expect(skipLink).toHaveCSS("transform", "matrix(1, 0, 0, 1, 0, -120)"); + // Tab to focus the skip link + await page.keyboard.press("Tab"); + await expect(skipLink).toBeVisible(); + // Check if skipLink is the active element + const isFocused = await skipLink.evaluate(node => node === document.activeElement); + expect(isFocused).toBe(true); + // Press Enter to activate the link + await page.keyboard.press("Enter"); + // The main content should be focused or scrolled into view + const main = page.locator("#main-content"); + await expect(main).toBeVisible(); + }); +}); diff --git a/packages/pluggableWidgets/skiplink-web/package.json b/packages/pluggableWidgets/skiplink-web/package.json new file mode 100644 index 0000000000..5bc7730fc4 --- /dev/null +++ b/packages/pluggableWidgets/skiplink-web/package.json @@ -0,0 +1,50 @@ +{ + "name": "@mendix/pluggable-widget-skiplink-web", + "widgetName": "SkipLink", + "version": "1.0.0", + "description": "Adds a skip link to the top of the page for accessibility.", + "copyright": "© Mendix Technology BV 2025. All rights reserved.", + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "https://github.com/mendix/web-widgets.git" + }, + "config": {}, + "mxpackage": { + "name": "SkipLink", + "type": "widget", + "mpkName": "com.mendix.widget.web.SkipLink.mpk" + }, + "packagePath": "com.mendix.widget.web", + "marketplace": { + "minimumMXVersion": "9.6.0", + "appNumber": 119999, + "appName": "SkipLink", + "reactReady": true + }, + "scripts": { + "build": "pluggable-widgets-tools build:web", + "create-gh-release": "rui-create-gh-release", + "create-translation": "rui-create-translation", + "dev": "pluggable-widgets-tools start:web", + "e2e": "run-e2e ci", + "e2edev": "run-e2e dev --with-preps", + "format": "prettier --ignore-path ./node_modules/@mendix/prettier-config-web-widgets/global-prettierignore --write .", + "lint": "eslint src/ package.json", + "publish-marketplace": "rui-publish-marketplace", + "release": "pluggable-widgets-tools release:web", + "start": "pluggable-widgets-tools start:server", + "test": "jest --projects jest.config.js", + "update-changelog": "rui-update-changelog-widget", + "verify": "rui-verify-package-format" + }, + "devDependencies": { + "@mendix/automation-utils": "workspace:*", + "@mendix/eslint-config-web-widgets": "workspace:*", + "@mendix/pluggable-widgets-tools": "*", + "@mendix/prettier-config-web-widgets": "workspace:*", + "@mendix/run-e2e": "workspace:*", + "@mendix/widget-plugin-hooks": "workspace:*", + "@mendix/widget-plugin-platform": "workspace:*" + } +} diff --git a/packages/pluggableWidgets/skiplink-web/src/SkipLink.css b/packages/pluggableWidgets/skiplink-web/src/SkipLink.css new file mode 100644 index 0000000000..5a613ceb39 --- /dev/null +++ b/packages/pluggableWidgets/skiplink-web/src/SkipLink.css @@ -0,0 +1,20 @@ +.skip-link { + position: absolute; + top: 0; + left: 0; + background: #fff; + color: #0078d4; + padding: 8px 16px; + z-index: 1000; + transform: translateY(-120%); + transition: transform 0.2s; + text-decoration: none; + border: 2px solid #0078d4; + border-radius: 4px; + font-weight: bold; +} + +.skip-link:focus { + transform: translateY(0); + outline: none; +} diff --git a/packages/pluggableWidgets/skiplink-web/src/SkipLink.tsx b/packages/pluggableWidgets/skiplink-web/src/SkipLink.tsx new file mode 100644 index 0000000000..ff5569ad64 --- /dev/null +++ b/packages/pluggableWidgets/skiplink-web/src/SkipLink.tsx @@ -0,0 +1,66 @@ +import "./SkipLink.css"; +import { useEffect } from "react"; + +export interface SkipLinkProps { + /** + * The text displayed for the skip link. + */ + linkText: string; + /** + * The id of the main content element to jump to. + */ + mainContentId: string; +} + +/** + * Inserts a skip link as the first child of the element with ID 'root'. + * When activated, focus is programmatically set to the main content. + */ +export function SkipLink({ linkText, mainContentId }: SkipLinkProps): null { + useEffect(() => { + // Create the skip link element + const link = document.createElement("a"); + link.href = `#${mainContentId}`; + link.className = "skip-link"; + link.textContent = linkText; + link.tabIndex = 0; + + // Handler to move focus to the main content + function handleClick(event: MouseEvent) { + event.preventDefault(); + const main = document.getElementById(mainContentId); + if (main) { + // Store previous tabindex + const prevTabIndex = main.getAttribute("tabindex"); + // Ensure main is focusable + if (!main.hasAttribute("tabindex")) { + main.setAttribute("tabindex", "-1"); + } + main.focus(); + // Clean up tabindex if it was not present before + if (prevTabIndex === null) { + main.addEventListener("blur", () => main.removeAttribute("tabindex"), { once: true }); + } + } + } + + link.addEventListener("click", handleClick); + + // Insert as the first child of the element with ID 'root' + const root = document.getElementById("root"); + if (root) { + root.insertBefore(link, root.firstChild); + } + + // Cleanup on unmount + return () => { + link.removeEventListener("click", handleClick); + if (link.parentNode) { + link.parentNode.removeChild(link); + } + }; + }, [linkText, mainContentId]); + + // This component does not render anything in the React tree + return null; +} diff --git a/packages/pluggableWidgets/skiplink-web/src/SkipLink.xml b/packages/pluggableWidgets/skiplink-web/src/SkipLink.xml new file mode 100644 index 0000000000..9769fe7d01 --- /dev/null +++ b/packages/pluggableWidgets/skiplink-web/src/SkipLink.xml @@ -0,0 +1,20 @@ + + + SkipLink + A skip link for accessibility, allowing users to jump directly to the main content. + Accessibility + Accessibility + https://docs.mendix.com/appstore/widgets/skiplink + + + + Link text + The text displayed for the skip link. + + + Main content ID + The id of the main content element to jump to. + + + + diff --git a/packages/pluggableWidgets/skiplink-web/src/package.xml b/packages/pluggableWidgets/skiplink-web/src/package.xml new file mode 100644 index 0000000000..811a87e4ab --- /dev/null +++ b/packages/pluggableWidgets/skiplink-web/src/package.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/packages/pluggableWidgets/skiplink-web/tsconfig.json b/packages/pluggableWidgets/skiplink-web/tsconfig.json new file mode 100644 index 0000000000..743a60a240 --- /dev/null +++ b/packages/pluggableWidgets/skiplink-web/tsconfig.json @@ -0,0 +1,25 @@ +{ + "include": ["./src", "./typings"], + "compilerOptions": { + "baseUrl": "./", + "noEmitOnError": true, + "sourceMap": true, + "module": "esnext", + "target": "es6", + "lib": ["esnext", "dom"], + "types": ["jest", "node"], + "moduleResolution": "node", + "declaration": false, + "noLib": false, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "strict": true, + "strictFunctionTypes": false, + "skipLibCheck": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "jsx": "react-jsx", + "outDir": "dist", + "rootDir": "src" + } +} diff --git a/packages/pluggableWidgets/skiplink-web/typings/SkipLinkProps.d.ts b/packages/pluggableWidgets/skiplink-web/typings/SkipLinkProps.d.ts new file mode 100644 index 0000000000..dd4d4c8a82 --- /dev/null +++ b/packages/pluggableWidgets/skiplink-web/typings/SkipLinkProps.d.ts @@ -0,0 +1,30 @@ +/** + * This file was generated from SkipLink.xml + * WARNING: All changes made to this file will be overwritten + * @author Mendix Widgets Framework Team + */ +import { CSSProperties } from "react"; + +export interface SkipLinkContainerProps { + name: string; + class: string; + style?: CSSProperties; + tabIndex?: number; + linkText: string; + mainContentId: string; +} + +export interface SkipLinkPreviewProps { + /** + * @deprecated Deprecated since version 9.18.0. Please use class property instead. + */ + className: string; + class: string; + style: string; + styleObject?: CSSProperties; + readOnly: boolean; + renderMode: "design" | "xray" | "structure"; + translate: (text: string) => string; + linkText: string; + mainContentId: string; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 684f01279c..60d73aa28a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2268,6 +2268,30 @@ importers: specifier: ^7.0.3 version: 7.0.3 + packages/pluggableWidgets/skiplink-web: + devDependencies: + '@mendix/automation-utils': + specifier: workspace:* + version: link:../../../automation/utils + '@mendix/eslint-config-web-widgets': + specifier: workspace:* + version: link:../../shared/eslint-config-web-widgets + '@mendix/pluggable-widgets-tools': + specifier: 10.21.0 + version: 10.21.0(@jest/transform@29.7.0)(@jest/types@29.6.3)(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/babel__core@7.20.3)(@types/node@22.14.0)(picomatch@4.0.2)(react-dom@18.2.0(react@18.2.0))(react-native@0.75.3(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.2.36)(react@18.2.0)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1) + '@mendix/prettier-config-web-widgets': + specifier: workspace:* + version: link:../../shared/prettier-config-web-widgets + '@mendix/run-e2e': + specifier: workspace:* + version: link:../../../automation/run-e2e + '@mendix/widget-plugin-hooks': + specifier: workspace:* + version: link:../../shared/widget-plugin-hooks + '@mendix/widget-plugin-platform': + specifier: workspace:* + version: link:../../shared/widget-plugin-platform + packages/pluggableWidgets/slider-web: dependencies: '@mendix/widget-plugin-component-kit': From 93c74e8df78f53e18c99402b39ac035b41d913f9 Mon Sep 17 00:00:00 2001 From: Hedwig Doets Date: Fri, 11 Jul 2025 13:43:01 +0200 Subject: [PATCH 02/23] feat(skiplink-web): adding configuration --- .../pluggableWidgets/skiplink-web/.gitignore | 14 ++++ .../skiplink-web/.prettierrc.js | 1 + .../skiplink-web/e2e/package.json | 0 .../skiplink-web/eslint.config.mjs | 3 + .../skiplink-web/jest.config.js | 3 + .../skiplink-web/package.json | 4 +- .../skiplink-web/playwright.config.cjs | 1 + .../skiplink-web/src/SkipLink.editorConfig.ts | 80 +++++++++++++++++++ .../src/SkipLink.editorPreview.tsx | 35 ++++++++ .../skiplink-web/src/SkipLink.tsx | 2 +- .../skiplink-web/src/{ => ui}/SkipLink.css | 0 .../skiplink-web/tsconfig.json | 12 ++- 12 files changed, 149 insertions(+), 6 deletions(-) create mode 100644 packages/pluggableWidgets/skiplink-web/.gitignore create mode 100644 packages/pluggableWidgets/skiplink-web/.prettierrc.js create mode 100644 packages/pluggableWidgets/skiplink-web/e2e/package.json create mode 100644 packages/pluggableWidgets/skiplink-web/eslint.config.mjs create mode 100644 packages/pluggableWidgets/skiplink-web/jest.config.js create mode 100644 packages/pluggableWidgets/skiplink-web/playwright.config.cjs create mode 100644 packages/pluggableWidgets/skiplink-web/src/SkipLink.editorConfig.ts create mode 100644 packages/pluggableWidgets/skiplink-web/src/SkipLink.editorPreview.tsx rename packages/pluggableWidgets/skiplink-web/src/{ => ui}/SkipLink.css (100%) diff --git a/packages/pluggableWidgets/skiplink-web/.gitignore b/packages/pluggableWidgets/skiplink-web/.gitignore new file mode 100644 index 0000000000..a1bd0102fd --- /dev/null +++ b/packages/pluggableWidgets/skiplink-web/.gitignore @@ -0,0 +1,14 @@ +/tests/TestProjects/**/.classpath +/tests/TestProjects/**/.project +/tests/TestProjects/**/javascriptsource +/tests/TestProjects/**/javasource +/tests/TestProjects/**/resources +/tests/TestProjects/**/userlib + +/tests/TestProjects/Mendix8/theme/styles/native +/tests/TestProjects/Mendix8/theme/styles/web/sass +/tests/TestProjects/Mendix8/theme/*.* +!/tests/TestProjects/Mendix8/theme/components.json +!/tests/TestProjects/Mendix8/theme/favicon.ico +!/tests/TestProjects/Mendix8/theme/LICENSE +!/tests/TestProjects/Mendix8/theme/settings.json diff --git a/packages/pluggableWidgets/skiplink-web/.prettierrc.js b/packages/pluggableWidgets/skiplink-web/.prettierrc.js new file mode 100644 index 0000000000..0892704ab0 --- /dev/null +++ b/packages/pluggableWidgets/skiplink-web/.prettierrc.js @@ -0,0 +1 @@ +module.exports = require("@mendix/prettier-config-web-widgets"); diff --git a/packages/pluggableWidgets/skiplink-web/e2e/package.json b/packages/pluggableWidgets/skiplink-web/e2e/package.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/pluggableWidgets/skiplink-web/eslint.config.mjs b/packages/pluggableWidgets/skiplink-web/eslint.config.mjs new file mode 100644 index 0000000000..ed68ae9e78 --- /dev/null +++ b/packages/pluggableWidgets/skiplink-web/eslint.config.mjs @@ -0,0 +1,3 @@ +import config from "@mendix/eslint-config-web-widgets/widget-ts.mjs"; + +export default config; diff --git a/packages/pluggableWidgets/skiplink-web/jest.config.js b/packages/pluggableWidgets/skiplink-web/jest.config.js new file mode 100644 index 0000000000..88999d5568 --- /dev/null +++ b/packages/pluggableWidgets/skiplink-web/jest.config.js @@ -0,0 +1,3 @@ +module.exports = { + ...require("@mendix/pluggable-widgets-tools/test-config/jest.enzyme-free.config.js") +}; diff --git a/packages/pluggableWidgets/skiplink-web/package.json b/packages/pluggableWidgets/skiplink-web/package.json index 5bc7730fc4..1215278b7a 100644 --- a/packages/pluggableWidgets/skiplink-web/package.json +++ b/packages/pluggableWidgets/skiplink-web/package.json @@ -17,7 +17,7 @@ }, "packagePath": "com.mendix.widget.web", "marketplace": { - "minimumMXVersion": "9.6.0", + "minimumMXVersion": "11.1.0", "appNumber": 119999, "appName": "SkipLink", "reactReady": true @@ -30,7 +30,7 @@ "e2e": "run-e2e ci", "e2edev": "run-e2e dev --with-preps", "format": "prettier --ignore-path ./node_modules/@mendix/prettier-config-web-widgets/global-prettierignore --write .", - "lint": "eslint src/ package.json", + "lint": "eslint src package.json", "publish-marketplace": "rui-publish-marketplace", "release": "pluggable-widgets-tools release:web", "start": "pluggable-widgets-tools start:server", diff --git a/packages/pluggableWidgets/skiplink-web/playwright.config.cjs b/packages/pluggableWidgets/skiplink-web/playwright.config.cjs new file mode 100644 index 0000000000..29045fc372 --- /dev/null +++ b/packages/pluggableWidgets/skiplink-web/playwright.config.cjs @@ -0,0 +1 @@ +module.exports = require("@mendix/run-e2e/playwright.config.cjs"); diff --git a/packages/pluggableWidgets/skiplink-web/src/SkipLink.editorConfig.ts b/packages/pluggableWidgets/skiplink-web/src/SkipLink.editorConfig.ts new file mode 100644 index 0000000000..b4e9f6536a --- /dev/null +++ b/packages/pluggableWidgets/skiplink-web/src/SkipLink.editorConfig.ts @@ -0,0 +1,80 @@ +import { hidePropertiesIn, Problem, Properties } from "@mendix/pluggable-widgets-tools"; +import { + StructurePreviewProps, + RowLayoutProps, + ContainerProps, + TextProps, + structurePreviewPalette +} from "@mendix/widget-plugin-platform/preview/structure-preview-api"; + +export function getProperties(values: any, defaultValues: Properties): Properties { + // No conditional properties for skiplink, but function provided for consistency + return defaultValues; +} + +export function check(values: any): Problem[] { + const errors: Problem[] = []; + if (!values.linkText) { + errors.push({ + property: "linkText", + message: "Link text is required" + }); + } + if (!values.mainContentId) { + errors.push({ + property: "mainContentId", + message: "Main content ID is required" + }); + } + return errors; +} + +export function getPreview(values: any, isDarkMode: boolean): StructurePreviewProps | null { + const palette = structurePreviewPalette[isDarkMode ? "dark" : "light"]; + const titleHeader: RowLayoutProps = { + type: "RowLayout", + columnSize: "grow", + backgroundColor: palette.background.topbarStandard, + borders: true, + borderWidth: 1, + children: [ + { + type: "Container", + padding: 4, + children: [ + { + type: "Text", + content: "SkipLink", + fontColor: palette.text.secondary + } as TextProps + ] + } + ] + }; + const linkContent: RowLayoutProps = { + type: "RowLayout", + columnSize: "grow", + borders: true, + padding: 0, + children: [ + { + type: "Container", + padding: 6, + children: [ + { + type: "Text", + content: values.linkText || "Skip to main content", + fontSize: 14, + fontColor: palette.text.primary, + bold: true + } as TextProps + ] + } + ] + }; + return { + type: "Container", + borders: true, + children: [titleHeader, linkContent] + } as ContainerProps; +} diff --git a/packages/pluggableWidgets/skiplink-web/src/SkipLink.editorPreview.tsx b/packages/pluggableWidgets/skiplink-web/src/SkipLink.editorPreview.tsx new file mode 100644 index 0000000000..c0278a11fe --- /dev/null +++ b/packages/pluggableWidgets/skiplink-web/src/SkipLink.editorPreview.tsx @@ -0,0 +1,35 @@ +import { createElement, ReactElement } from "react"; + +export interface SkipLinkPreviewProps { + linkText: string; + mainContentId: string; +} + +export const preview = (props: SkipLinkPreviewProps): ReactElement => { + return ( + + ); +}; + +export function getPreviewCss(): string { + return require("./SkipLink.css"); +} diff --git a/packages/pluggableWidgets/skiplink-web/src/SkipLink.tsx b/packages/pluggableWidgets/skiplink-web/src/SkipLink.tsx index ff5569ad64..acee6f0363 100644 --- a/packages/pluggableWidgets/skiplink-web/src/SkipLink.tsx +++ b/packages/pluggableWidgets/skiplink-web/src/SkipLink.tsx @@ -1,4 +1,4 @@ -import "./SkipLink.css"; +import "./ui/SkipLink.css"; import { useEffect } from "react"; export interface SkipLinkProps { diff --git a/packages/pluggableWidgets/skiplink-web/src/SkipLink.css b/packages/pluggableWidgets/skiplink-web/src/ui/SkipLink.css similarity index 100% rename from packages/pluggableWidgets/skiplink-web/src/SkipLink.css rename to packages/pluggableWidgets/skiplink-web/src/ui/SkipLink.css diff --git a/packages/pluggableWidgets/skiplink-web/tsconfig.json b/packages/pluggableWidgets/skiplink-web/tsconfig.json index 743a60a240..a2a5b87e60 100644 --- a/packages/pluggableWidgets/skiplink-web/tsconfig.json +++ b/packages/pluggableWidgets/skiplink-web/tsconfig.json @@ -18,8 +18,14 @@ "skipLibCheck": true, "noUnusedLocals": true, "noUnusedParameters": true, - "jsx": "react-jsx", - "outDir": "dist", - "rootDir": "src" + "jsx": "react", + "jsxFactory": "createElement", + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "useUnknownInCatchVariables": false, + "exactOptionalPropertyTypes": false, + "paths": { + "react-hot-loader/root": ["./hot-typescript.ts"] + } } } From fa2060f13d5e821165d9cb5a960e4d46a9e745ec Mon Sep 17 00:00:00 2001 From: Hedwig Doets Date: Tue, 15 Jul 2025 14:09:08 +0200 Subject: [PATCH 03/23] chore: improve config files, ran linting --- .../skiplink-web/package.json | 9 ++- .../skiplink-web/src/SkipLink.editorConfig.ts | 4 +- .../src/SkipLink.editorPreview.tsx | 55 +++++++++---------- .../skiplink-web/src/SkipLink.tsx | 2 +- .../src/ui/{SkipLink.css => SkipLink.scss} | 0 pnpm-lock.yaml | 14 ++++- 6 files changed, 49 insertions(+), 35 deletions(-) rename packages/pluggableWidgets/skiplink-web/src/ui/{SkipLink.css => SkipLink.scss} (100%) diff --git a/packages/pluggableWidgets/skiplink-web/package.json b/packages/pluggableWidgets/skiplink-web/package.json index 1215278b7a..dd660ba034 100644 --- a/packages/pluggableWidgets/skiplink-web/package.json +++ b/packages/pluggableWidgets/skiplink-web/package.json @@ -1,5 +1,5 @@ { - "name": "@mendix/pluggable-widget-skiplink-web", + "name": "@mendix/skiplink-web", "widgetName": "SkipLink", "version": "1.0.0", "description": "Adds a skip link to the top of the page for accessibility.", @@ -30,7 +30,7 @@ "e2e": "run-e2e ci", "e2edev": "run-e2e dev --with-preps", "format": "prettier --ignore-path ./node_modules/@mendix/prettier-config-web-widgets/global-prettierignore --write .", - "lint": "eslint src package.json", + "lint": "eslint src/ package.json", "publish-marketplace": "rui-publish-marketplace", "release": "pluggable-widgets-tools release:web", "start": "pluggable-widgets-tools start:server", @@ -38,6 +38,11 @@ "update-changelog": "rui-update-changelog-widget", "verify": "rui-verify-package-format" }, + "dependencies": { + "@floating-ui/react": "^0.26.27", + "@mendix/widget-plugin-component-kit": "workspace:*", + "classnames": "^2.5.1" + }, "devDependencies": { "@mendix/automation-utils": "workspace:*", "@mendix/eslint-config-web-widgets": "workspace:*", diff --git a/packages/pluggableWidgets/skiplink-web/src/SkipLink.editorConfig.ts b/packages/pluggableWidgets/skiplink-web/src/SkipLink.editorConfig.ts index b4e9f6536a..6fa0fe3dbb 100644 --- a/packages/pluggableWidgets/skiplink-web/src/SkipLink.editorConfig.ts +++ b/packages/pluggableWidgets/skiplink-web/src/SkipLink.editorConfig.ts @@ -1,4 +1,4 @@ -import { hidePropertiesIn, Problem, Properties } from "@mendix/pluggable-widgets-tools"; +import { Problem, Properties } from "@mendix/pluggable-widgets-tools"; import { StructurePreviewProps, RowLayoutProps, @@ -7,7 +7,7 @@ import { structurePreviewPalette } from "@mendix/widget-plugin-platform/preview/structure-preview-api"; -export function getProperties(values: any, defaultValues: Properties): Properties { +export function getProperties(defaultValues: Properties): Properties { // No conditional properties for skiplink, but function provided for consistency return defaultValues; } diff --git a/packages/pluggableWidgets/skiplink-web/src/SkipLink.editorPreview.tsx b/packages/pluggableWidgets/skiplink-web/src/SkipLink.editorPreview.tsx index c0278a11fe..543e043658 100644 --- a/packages/pluggableWidgets/skiplink-web/src/SkipLink.editorPreview.tsx +++ b/packages/pluggableWidgets/skiplink-web/src/SkipLink.editorPreview.tsx @@ -1,35 +1,34 @@ import { createElement, ReactElement } from "react"; - -export interface SkipLinkPreviewProps { - linkText: string; - mainContentId: string; -} +import { SkipLinkPreviewProps } from "../typings/SkipLinkProps"; export const preview = (props: SkipLinkPreviewProps): ReactElement => { - return ( - - ); + if (props.renderMode === "xray") { + return ( + + ); + } + return
; }; export function getPreviewCss(): string { - return require("./SkipLink.css"); + return require("./ui/SkipLink.scss"); } diff --git a/packages/pluggableWidgets/skiplink-web/src/SkipLink.tsx b/packages/pluggableWidgets/skiplink-web/src/SkipLink.tsx index acee6f0363..2c748c0263 100644 --- a/packages/pluggableWidgets/skiplink-web/src/SkipLink.tsx +++ b/packages/pluggableWidgets/skiplink-web/src/SkipLink.tsx @@ -1,4 +1,4 @@ -import "./ui/SkipLink.css"; +import "./ui/SkipLink.scss"; import { useEffect } from "react"; export interface SkipLinkProps { diff --git a/packages/pluggableWidgets/skiplink-web/src/ui/SkipLink.css b/packages/pluggableWidgets/skiplink-web/src/ui/SkipLink.scss similarity index 100% rename from packages/pluggableWidgets/skiplink-web/src/ui/SkipLink.css rename to packages/pluggableWidgets/skiplink-web/src/ui/SkipLink.scss diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 60d73aa28a..7afa0e3551 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2269,6 +2269,16 @@ importers: version: 7.0.3 packages/pluggableWidgets/skiplink-web: + dependencies: + '@floating-ui/react': + specifier: ^0.26.27 + version: 0.26.27(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@mendix/widget-plugin-component-kit': + specifier: workspace:* + version: link:../../shared/widget-plugin-component-kit + classnames: + specifier: ^2.5.1 + version: 2.5.1 devDependencies: '@mendix/automation-utils': specifier: workspace:* @@ -2277,8 +2287,8 @@ importers: specifier: workspace:* version: link:../../shared/eslint-config-web-widgets '@mendix/pluggable-widgets-tools': - specifier: 10.21.0 - version: 10.21.0(@jest/transform@29.7.0)(@jest/types@29.6.3)(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/babel__core@7.20.3)(@types/node@22.14.0)(picomatch@4.0.2)(react-dom@18.2.0(react@18.2.0))(react-native@0.75.3(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.2.36)(react@18.2.0)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1) + specifier: 10.21.2 + version: 10.21.2(@jest/transform@29.7.0)(@jest/types@29.6.3)(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/babel__core@7.20.3)(@types/node@22.14.1)(picomatch@4.0.2)(react-dom@18.2.0(react@18.2.0))(react-native@0.75.3(@babel/core@7.27.4)(@babel/preset-env@7.26.9(@babel/core@7.27.4))(@types/react@18.2.36)(react@18.2.0))(react@18.2.0)(tslib@2.8.1) '@mendix/prettier-config-web-widgets': specifier: workspace:* version: link:../../shared/prettier-config-web-widgets From 6815e12bccc56162d53e00adf2b2d22c7c0985a3 Mon Sep 17 00:00:00 2001 From: "hedwig.doets" Date: Wed, 12 Nov 2025 12:56:05 +0100 Subject: [PATCH 04/23] fix: small improvements --- .../skiplink-web/src/SkipLink.editorConfig.ts | 6 ----- .../src/SkipLink.editorPreview.tsx | 22 +++++++++++++++++-- .../skiplink-web/src/SkipLink.tsx | 10 ++++++++- .../skiplink-web/src/SkipLink.xml | 8 +++---- .../skiplink-web/tsconfig.json | 3 +-- 5 files changed, 34 insertions(+), 15 deletions(-) diff --git a/packages/pluggableWidgets/skiplink-web/src/SkipLink.editorConfig.ts b/packages/pluggableWidgets/skiplink-web/src/SkipLink.editorConfig.ts index 6fa0fe3dbb..212808ef3a 100644 --- a/packages/pluggableWidgets/skiplink-web/src/SkipLink.editorConfig.ts +++ b/packages/pluggableWidgets/skiplink-web/src/SkipLink.editorConfig.ts @@ -20,12 +20,6 @@ export function check(values: any): Problem[] { message: "Link text is required" }); } - if (!values.mainContentId) { - errors.push({ - property: "mainContentId", - message: "Main content ID is required" - }); - } return errors; } diff --git a/packages/pluggableWidgets/skiplink-web/src/SkipLink.editorPreview.tsx b/packages/pluggableWidgets/skiplink-web/src/SkipLink.editorPreview.tsx index 543e043658..7d1c2809ab 100644 --- a/packages/pluggableWidgets/skiplink-web/src/SkipLink.editorPreview.tsx +++ b/packages/pluggableWidgets/skiplink-web/src/SkipLink.editorPreview.tsx @@ -1,4 +1,4 @@ -import { createElement, ReactElement } from "react"; +import { ReactElement } from "react"; import { SkipLinkPreviewProps } from "../typings/SkipLinkProps"; export const preview = (props: SkipLinkPreviewProps): ReactElement => { @@ -25,8 +25,26 @@ export const preview = (props: SkipLinkPreviewProps): ReactElement => {
); + } else { + return + {props.linkText} + } - return
; }; export function getPreviewCss(): string { diff --git a/packages/pluggableWidgets/skiplink-web/src/SkipLink.tsx b/packages/pluggableWidgets/skiplink-web/src/SkipLink.tsx index 2c748c0263..e6f6e17398 100644 --- a/packages/pluggableWidgets/skiplink-web/src/SkipLink.tsx +++ b/packages/pluggableWidgets/skiplink-web/src/SkipLink.tsx @@ -28,7 +28,15 @@ export function SkipLink({ linkText, mainContentId }: SkipLinkProps): null { // Handler to move focus to the main content function handleClick(event: MouseEvent) { event.preventDefault(); - const main = document.getElementById(mainContentId); + let main: HTMLElement; + const mainByID = document.getElementById(mainContentId); + if(mainContentId !== "" && mainByID !== null){ + main = mainByID; + } + else { + main = document.getElementsByTagName("main")[0]; + } + if (main) { // Store previous tabindex const prevTabIndex = main.getAttribute("tabindex"); diff --git a/packages/pluggableWidgets/skiplink-web/src/SkipLink.xml b/packages/pluggableWidgets/skiplink-web/src/SkipLink.xml index 9769fe7d01..5761834caf 100644 --- a/packages/pluggableWidgets/skiplink-web/src/SkipLink.xml +++ b/packages/pluggableWidgets/skiplink-web/src/SkipLink.xml @@ -4,16 +4,16 @@ A skip link for accessibility, allowing users to jump directly to the main content. Accessibility Accessibility - https://docs.mendix.com/appstore/widgets/skiplink + Link text - The text displayed for the skip link. + The text displayed in the skip link. - + Main content ID - The id of the main content element to jump to. + The id of the main content element to jump to, if left empty the skip link widget will search for a main tag on the page. diff --git a/packages/pluggableWidgets/skiplink-web/tsconfig.json b/packages/pluggableWidgets/skiplink-web/tsconfig.json index a2a5b87e60..7aa60df0c9 100644 --- a/packages/pluggableWidgets/skiplink-web/tsconfig.json +++ b/packages/pluggableWidgets/skiplink-web/tsconfig.json @@ -18,8 +18,7 @@ "skipLibCheck": true, "noUnusedLocals": true, "noUnusedParameters": true, - "jsx": "react", - "jsxFactory": "createElement", + "jsx": "react-jsx", "allowSyntheticDefaultImports": true, "esModuleInterop": true, "useUnknownInCatchVariables": false, From 52c763c427c84032646478bcc11a3f453e2dc71a Mon Sep 17 00:00:00 2001 From: "hedwig.doets" Date: Wed, 12 Nov 2025 14:43:05 +0100 Subject: [PATCH 05/23] fix: rewrite tests for current version of the widget --- .../skiplink-web/e2e/SkipLink.spec.ts | 23 ----- .../skiplink-web/e2e/package.json | 0 .../skiplink-web/package.json | 3 +- .../src/__tests__/SkipLink.spec.tsx | 84 +++++++++++++++++++ .../__snapshots__/SkipLink.spec.tsx.snap | 57 +++++++++++++ 5 files changed, 143 insertions(+), 24 deletions(-) delete mode 100644 packages/pluggableWidgets/skiplink-web/e2e/SkipLink.spec.ts delete mode 100644 packages/pluggableWidgets/skiplink-web/e2e/package.json create mode 100644 packages/pluggableWidgets/skiplink-web/src/__tests__/SkipLink.spec.tsx create mode 100644 packages/pluggableWidgets/skiplink-web/src/__tests__/__snapshots__/SkipLink.spec.tsx.snap diff --git a/packages/pluggableWidgets/skiplink-web/e2e/SkipLink.spec.ts b/packages/pluggableWidgets/skiplink-web/e2e/SkipLink.spec.ts deleted file mode 100644 index 80c13e8155..0000000000 --- a/packages/pluggableWidgets/skiplink-web/e2e/SkipLink.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { expect, test } from "@playwright/test"; - -// Assumes the test project renders and a
element - -test.describe("SkipLink", () => { - test("should be hidden by default and visible on focus, and should skip to main content", async ({ page }) => { - await page.goto("/"); - const skipLink = page.locator(".skip-link"); - // Should be hidden by default - await expect(skipLink).toHaveCSS("transform", "matrix(1, 0, 0, 1, 0, -120)"); - // Tab to focus the skip link - await page.keyboard.press("Tab"); - await expect(skipLink).toBeVisible(); - // Check if skipLink is the active element - const isFocused = await skipLink.evaluate(node => node === document.activeElement); - expect(isFocused).toBe(true); - // Press Enter to activate the link - await page.keyboard.press("Enter"); - // The main content should be focused or scrolled into view - const main = page.locator("#main-content"); - await expect(main).toBeVisible(); - }); -}); diff --git a/packages/pluggableWidgets/skiplink-web/e2e/package.json b/packages/pluggableWidgets/skiplink-web/e2e/package.json deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/pluggableWidgets/skiplink-web/package.json b/packages/pluggableWidgets/skiplink-web/package.json index dd660ba034..8b21f5ad6e 100644 --- a/packages/pluggableWidgets/skiplink-web/package.json +++ b/packages/pluggableWidgets/skiplink-web/package.json @@ -50,6 +50,7 @@ "@mendix/prettier-config-web-widgets": "workspace:*", "@mendix/run-e2e": "workspace:*", "@mendix/widget-plugin-hooks": "workspace:*", - "@mendix/widget-plugin-platform": "workspace:*" + "@mendix/widget-plugin-platform": "workspace:*", + "@mendix/widget-plugin-test-utils": "workspace:*" } } diff --git a/packages/pluggableWidgets/skiplink-web/src/__tests__/SkipLink.spec.tsx b/packages/pluggableWidgets/skiplink-web/src/__tests__/SkipLink.spec.tsx new file mode 100644 index 0000000000..93fe4c9075 --- /dev/null +++ b/packages/pluggableWidgets/skiplink-web/src/__tests__/SkipLink.spec.tsx @@ -0,0 +1,84 @@ +import "@testing-library/jest-dom"; +import { render } from "@testing-library/react"; +import { SkipLinkContainerProps } from "../../typings/SkipLinkProps"; +import { SkipLink } from "../SkipLink"; + +describe("SkipLink", () => { + let defaultProps: SkipLinkContainerProps; + let rootElement: HTMLElement; + + beforeEach(() => { + // Set up the DOM structure that SkipLink expects + document.body.innerHTML = ""; + rootElement = document.createElement("div"); + rootElement.id = "root"; + document.body.appendChild(rootElement); + + defaultProps = { + name: "SkipLink1", + class: "mx-skiplink", + style: {}, + linkText: "Skip to main content", + mainContentId: "main-content" + }; + }); + + afterEach(() => { + document.body.innerHTML = ""; + }); + + it("renders skiplink widget and adds skip link to DOM", () => { + render(); + + // Check that the skip link was added to the root element + const skipLink = rootElement.querySelector(".skip-link") as HTMLAnchorElement; + expect(skipLink).toBeInTheDocument(); + expect(skipLink.textContent).toBe("Skip to main content"); + expect(skipLink.href).toBe(`${window.location.origin}/#main-content`); + expect(skipLink.tabIndex).toBe(0); + + // Snapshot the actual root element that contains the skip link + expect(rootElement).toMatchSnapshot(); + }); + + it("renders with custom link text", () => { + render(); + + const skipLink = rootElement.querySelector(".skip-link") as HTMLAnchorElement; + expect(skipLink).toBeInTheDocument(); + expect(skipLink.textContent).toBe("Jump to content"); + + expect(rootElement).toMatchSnapshot(); + }); + + it("renders with custom main content id", () => { + render(); + + const skipLink = rootElement.querySelector(".skip-link") as HTMLAnchorElement; + expect(skipLink).toBeInTheDocument(); + expect(skipLink.href).toBe(`${window.location.origin}/#content-area`); + + expect(rootElement).toMatchSnapshot(); + }); + + it("renders with empty main content id", () => { + render(); + + const skipLink = rootElement.querySelector(".skip-link") as HTMLAnchorElement; + expect(skipLink).toBeInTheDocument(); + expect(skipLink.href).toBe(`${window.location.origin}/#`); + + expect(rootElement).toMatchSnapshot(); + }); + + it("cleans up skip link when component unmounts", () => { + const { unmount } = render(); + + // Verify skip link is present + expect(rootElement.querySelector(".skip-link")).toBeInTheDocument(); + + // Unmount and verify cleanup + unmount(); + expect(rootElement.querySelector(".skip-link")).not.toBeInTheDocument(); + }); +}); \ No newline at end of file diff --git a/packages/pluggableWidgets/skiplink-web/src/__tests__/__snapshots__/SkipLink.spec.tsx.snap b/packages/pluggableWidgets/skiplink-web/src/__tests__/__snapshots__/SkipLink.spec.tsx.snap new file mode 100644 index 0000000000..1dc3dc6f8c --- /dev/null +++ b/packages/pluggableWidgets/skiplink-web/src/__tests__/__snapshots__/SkipLink.spec.tsx.snap @@ -0,0 +1,57 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`SkipLink renders skiplink widget and adds skip link to DOM 1`] = ` + +`; + +exports[`SkipLink renders with custom link text 1`] = ` + +`; + +exports[`SkipLink renders with custom main content id 1`] = ` + +`; + +exports[`SkipLink renders with empty main content id 1`] = ` + +`; From 74d766d19a24dd16f57e4c69e27f926fdfebc4a2 Mon Sep 17 00:00:00 2001 From: "hedwig.doets" Date: Thu, 13 Nov 2025 12:36:16 +0100 Subject: [PATCH 06/23] fix: add changes in pnpm lock file --- pnpm-lock.yaml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7afa0e3551..6980f94c52 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2272,7 +2272,7 @@ importers: dependencies: '@floating-ui/react': specifier: ^0.26.27 - version: 0.26.27(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 0.26.28(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@mendix/widget-plugin-component-kit': specifier: workspace:* version: link:../../shared/widget-plugin-component-kit @@ -2288,7 +2288,7 @@ importers: version: link:../../shared/eslint-config-web-widgets '@mendix/pluggable-widgets-tools': specifier: 10.21.2 - version: 10.21.2(@jest/transform@29.7.0)(@jest/types@29.6.3)(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/babel__core@7.20.3)(@types/node@22.14.1)(picomatch@4.0.2)(react-dom@18.2.0(react@18.2.0))(react-native@0.75.3(@babel/core@7.27.4)(@babel/preset-env@7.26.9(@babel/core@7.27.4))(@types/react@18.2.36)(react@18.2.0))(react@18.2.0)(tslib@2.8.1) + version: 10.21.2(@jest/transform@29.7.0)(@jest/types@30.2.0)(@swc/core@1.13.5)(@types/babel__core@7.20.5)(@types/node@22.14.1)(jest-util@30.2.0)(picomatch@4.0.3)(react-dom@18.3.1(react@18.3.1))(react-native@0.82.0(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1)(tslib@2.8.1) '@mendix/prettier-config-web-widgets': specifier: workspace:* version: link:../../shared/prettier-config-web-widgets @@ -2301,6 +2301,9 @@ importers: '@mendix/widget-plugin-platform': specifier: workspace:* version: link:../../shared/widget-plugin-platform + '@mendix/widget-plugin-test-utils': + specifier: workspace:* + version: link:../../shared/widget-plugin-test-utils packages/pluggableWidgets/slider-web: dependencies: From bef09bdb30f6ad244c58ca15b6ee7a7053d4e9bf Mon Sep 17 00:00:00 2001 From: "hedwig.doets" Date: Mon, 24 Nov 2025 12:37:23 +0100 Subject: [PATCH 07/23] chore: update readme --- .../pluggableWidgets/skiplink-web/README.md | 29 ++++++++----------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/packages/pluggableWidgets/skiplink-web/README.md b/packages/pluggableWidgets/skiplink-web/README.md index 02b3c70903..3516757513 100644 --- a/packages/pluggableWidgets/skiplink-web/README.md +++ b/packages/pluggableWidgets/skiplink-web/README.md @@ -1,27 +1,22 @@ -# SkipLink Web Widget +# Skip Link -A simple accessibility widget that adds a skip link to the top of the page. The link is only visible when focused and allows users to jump directly to the main content. +Adds a skip navigation link for keyboard accessibility. The link is hidden until focused and allows users to jump directly to the main content. ## Usage -1. Place the `` component at the very top of your page or layout. -2. Ensure your main content container has `id="main-content"`. +1. Add the Skip Link widget anywhere on your page, preferrably at the top or in a layout. +2. Configure the **Link Text** and **Main Content ID** properties. +3. Ensure your main content element has the specified ID, or there's a main tag on the page. - ```jsx - -
Main content here
- ``` +The widget automatically inserts the skip link as the first child of the `#root` element. -## Accessibility +## Properties -- The skip link is visually hidden except when focused, making it accessible for keyboard and screen reader users. +- **Link Text**: Text displayed for the skip link (default: "Skip to main content"). +- **Main Content ID**: ID of the main content element to focus (optional). -## End-to-End Testing +If the target element is not found, the widget will focus the first `
` element instead. -E2E tests are located in the `e2e/` folder and use Playwright. Run them with: +## Accessibility -``` -npm install -npx playwright install -npm test -``` +The skip link is positioned absolutely at the top-left of the page, hidden by default with `transform: translateY(-120%)`, and becomes visible when focused via keyboard navigation. From 95005b6a9ef3065373cb01375e5369d160f676f3 Mon Sep 17 00:00:00 2001 From: "hedwig.doets" Date: Wed, 26 Nov 2025 11:06:36 +0100 Subject: [PATCH 08/23] fix: fix lint issues --- .../skiplink-web/src/SkipLink.editorConfig.ts | 8 ++-- .../src/SkipLink.editorPreview.tsx | 40 ++++++++++--------- .../skiplink-web/src/SkipLink.tsx | 7 ++-- .../skiplink-web/src/SkipLink.xml | 2 +- .../src/__tests__/SkipLink.spec.tsx | 22 +++++----- 5 files changed, 40 insertions(+), 39 deletions(-) diff --git a/packages/pluggableWidgets/skiplink-web/src/SkipLink.editorConfig.ts b/packages/pluggableWidgets/skiplink-web/src/SkipLink.editorConfig.ts index 212808ef3a..60cb6fb97f 100644 --- a/packages/pluggableWidgets/skiplink-web/src/SkipLink.editorConfig.ts +++ b/packages/pluggableWidgets/skiplink-web/src/SkipLink.editorConfig.ts @@ -1,10 +1,10 @@ import { Problem, Properties } from "@mendix/pluggable-widgets-tools"; import { - StructurePreviewProps, - RowLayoutProps, ContainerProps, - TextProps, - structurePreviewPalette + RowLayoutProps, + structurePreviewPalette, + StructurePreviewProps, + TextProps } from "@mendix/widget-plugin-platform/preview/structure-preview-api"; export function getProperties(defaultValues: Properties): Properties { diff --git a/packages/pluggableWidgets/skiplink-web/src/SkipLink.editorPreview.tsx b/packages/pluggableWidgets/skiplink-web/src/SkipLink.editorPreview.tsx index 7d1c2809ab..750f504f22 100644 --- a/packages/pluggableWidgets/skiplink-web/src/SkipLink.editorPreview.tsx +++ b/packages/pluggableWidgets/skiplink-web/src/SkipLink.editorPreview.tsx @@ -1,4 +1,4 @@ -import { ReactElement } from "react"; +import React, { ReactElement } from "react"; import { SkipLinkPreviewProps } from "../typings/SkipLinkProps"; export const preview = (props: SkipLinkPreviewProps): ReactElement => { @@ -26,24 +26,26 @@ export const preview = (props: SkipLinkPreviewProps): ReactElement => {
); } else { - return - {props.linkText} - + return ( + + {props.linkText} + + ); } }; diff --git a/packages/pluggableWidgets/skiplink-web/src/SkipLink.tsx b/packages/pluggableWidgets/skiplink-web/src/SkipLink.tsx index e6f6e17398..108a382cd2 100644 --- a/packages/pluggableWidgets/skiplink-web/src/SkipLink.tsx +++ b/packages/pluggableWidgets/skiplink-web/src/SkipLink.tsx @@ -26,14 +26,13 @@ export function SkipLink({ linkText, mainContentId }: SkipLinkProps): null { link.tabIndex = 0; // Handler to move focus to the main content - function handleClick(event: MouseEvent) { + function handleClick(event: MouseEvent): void { event.preventDefault(); let main: HTMLElement; const mainByID = document.getElementById(mainContentId); - if(mainContentId !== "" && mainByID !== null){ + if (mainContentId !== "" && mainByID !== null) { main = mainByID; - } - else { + } else { main = document.getElementsByTagName("main")[0]; } diff --git a/packages/pluggableWidgets/skiplink-web/src/SkipLink.xml b/packages/pluggableWidgets/skiplink-web/src/SkipLink.xml index 5761834caf..ed18f9be9c 100644 --- a/packages/pluggableWidgets/skiplink-web/src/SkipLink.xml +++ b/packages/pluggableWidgets/skiplink-web/src/SkipLink.xml @@ -4,7 +4,7 @@ A skip link for accessibility, allowing users to jump directly to the main content. Accessibility Accessibility - + diff --git a/packages/pluggableWidgets/skiplink-web/src/__tests__/SkipLink.spec.tsx b/packages/pluggableWidgets/skiplink-web/src/__tests__/SkipLink.spec.tsx index 93fe4c9075..d635ee1c17 100644 --- a/packages/pluggableWidgets/skiplink-web/src/__tests__/SkipLink.spec.tsx +++ b/packages/pluggableWidgets/skiplink-web/src/__tests__/SkipLink.spec.tsx @@ -29,56 +29,56 @@ describe("SkipLink", () => { it("renders skiplink widget and adds skip link to DOM", () => { render(); - + // Check that the skip link was added to the root element const skipLink = rootElement.querySelector(".skip-link") as HTMLAnchorElement; expect(skipLink).toBeInTheDocument(); expect(skipLink.textContent).toBe("Skip to main content"); expect(skipLink.href).toBe(`${window.location.origin}/#main-content`); expect(skipLink.tabIndex).toBe(0); - + // Snapshot the actual root element that contains the skip link expect(rootElement).toMatchSnapshot(); }); it("renders with custom link text", () => { render(); - + const skipLink = rootElement.querySelector(".skip-link") as HTMLAnchorElement; expect(skipLink).toBeInTheDocument(); expect(skipLink.textContent).toBe("Jump to content"); - + expect(rootElement).toMatchSnapshot(); }); it("renders with custom main content id", () => { render(); - + const skipLink = rootElement.querySelector(".skip-link") as HTMLAnchorElement; expect(skipLink).toBeInTheDocument(); expect(skipLink.href).toBe(`${window.location.origin}/#content-area`); - + expect(rootElement).toMatchSnapshot(); }); it("renders with empty main content id", () => { render(); - + const skipLink = rootElement.querySelector(".skip-link") as HTMLAnchorElement; expect(skipLink).toBeInTheDocument(); expect(skipLink.href).toBe(`${window.location.origin}/#`); - + expect(rootElement).toMatchSnapshot(); }); it("cleans up skip link when component unmounts", () => { const { unmount } = render(); - + // Verify skip link is present expect(rootElement.querySelector(".skip-link")).toBeInTheDocument(); - + // Unmount and verify cleanup unmount(); expect(rootElement.querySelector(".skip-link")).not.toBeInTheDocument(); }); -}); \ No newline at end of file +}); From ef5e03545f799213b4fe43035863027dcd3cc52a Mon Sep 17 00:00:00 2001 From: "hedwig.doets" Date: Wed, 26 Nov 2025 13:02:46 +0100 Subject: [PATCH 09/23] chore: add testproject info to package json --- packages/pluggableWidgets/skiplink-web/package.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/pluggableWidgets/skiplink-web/package.json b/packages/pluggableWidgets/skiplink-web/package.json index 8b21f5ad6e..211d8ce70b 100644 --- a/packages/pluggableWidgets/skiplink-web/package.json +++ b/packages/pluggableWidgets/skiplink-web/package.json @@ -15,6 +15,10 @@ "type": "widget", "mpkName": "com.mendix.widget.web.SkipLink.mpk" }, + "testProject": { + "githubUrl": "https://github.com/mendix/testProjects", + "branchName": "skiplink-web" + }, "packagePath": "com.mendix.widget.web", "marketplace": { "minimumMXVersion": "11.1.0", From 7ec99cd7a70ee6358f3206b1178d1cbb62dfc68a Mon Sep 17 00:00:00 2001 From: "hedwig.doets" Date: Wed, 26 Nov 2025 14:32:11 +0100 Subject: [PATCH 10/23] fix: lint error on new testproject --- packages/pluggableWidgets/skiplink-web/package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/pluggableWidgets/skiplink-web/package.json b/packages/pluggableWidgets/skiplink-web/package.json index 211d8ce70b..2429e8daab 100644 --- a/packages/pluggableWidgets/skiplink-web/package.json +++ b/packages/pluggableWidgets/skiplink-web/package.json @@ -15,10 +15,6 @@ "type": "widget", "mpkName": "com.mendix.widget.web.SkipLink.mpk" }, - "testProject": { - "githubUrl": "https://github.com/mendix/testProjects", - "branchName": "skiplink-web" - }, "packagePath": "com.mendix.widget.web", "marketplace": { "minimumMXVersion": "11.1.0", @@ -26,6 +22,10 @@ "appName": "SkipLink", "reactReady": true }, + "testProject": { + "githubUrl": "https://github.com/mendix/testProjects", + "branchName": "skiplink-web" + }, "scripts": { "build": "pluggable-widgets-tools build:web", "create-gh-release": "rui-create-gh-release", From fd33e2a884025b66b6cc64206f78ffaa417a5e6c Mon Sep 17 00:00:00 2001 From: "hedwig.doets" Date: Wed, 26 Nov 2025 15:48:09 +0100 Subject: [PATCH 11/23] chore: add changelog file --- packages/pluggableWidgets/skiplink-web/CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 packages/pluggableWidgets/skiplink-web/CHANGELOG.md diff --git a/packages/pluggableWidgets/skiplink-web/CHANGELOG.md b/packages/pluggableWidgets/skiplink-web/CHANGELOG.md new file mode 100644 index 0000000000..5c51039d19 --- /dev/null +++ b/packages/pluggableWidgets/skiplink-web/CHANGELOG.md @@ -0,0 +1,9 @@ + Changelog + +All notable changes to this widget will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +- Created skiplink widget. \ No newline at end of file From 8230160338acb0aed0098a4750fa1b25f144f61e Mon Sep 17 00:00:00 2001 From: "hedwig.doets" Date: Wed, 26 Nov 2025 16:00:00 +0100 Subject: [PATCH 12/23] fix: fix changelog --- packages/pluggableWidgets/skiplink-web/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pluggableWidgets/skiplink-web/CHANGELOG.md b/packages/pluggableWidgets/skiplink-web/CHANGELOG.md index 5c51039d19..3d2f31b3d4 100644 --- a/packages/pluggableWidgets/skiplink-web/CHANGELOG.md +++ b/packages/pluggableWidgets/skiplink-web/CHANGELOG.md @@ -1,4 +1,4 @@ - Changelog +# Changelog All notable changes to this widget will be documented in this file. From 574c5515678cdecc31dadb828e78d6aa41f45d29 Mon Sep 17 00:00:00 2001 From: "hedwig.doets" Date: Thu, 27 Nov 2025 12:50:14 +0100 Subject: [PATCH 13/23] fix: add whitespace to changelog --- packages/pluggableWidgets/skiplink-web/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pluggableWidgets/skiplink-web/CHANGELOG.md b/packages/pluggableWidgets/skiplink-web/CHANGELOG.md index 3d2f31b3d4..e16831216f 100644 --- a/packages/pluggableWidgets/skiplink-web/CHANGELOG.md +++ b/packages/pluggableWidgets/skiplink-web/CHANGELOG.md @@ -6,4 +6,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] -- Created skiplink widget. \ No newline at end of file +- Created skiplink widget. From 74ebca9dbbb3165f7b86cdf423453c58c1547168 Mon Sep 17 00:00:00 2001 From: "hedwig.doets" Date: Thu, 27 Nov 2025 13:06:00 +0100 Subject: [PATCH 14/23] fix: add added header to changelog --- packages/pluggableWidgets/skiplink-web/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/pluggableWidgets/skiplink-web/CHANGELOG.md b/packages/pluggableWidgets/skiplink-web/CHANGELOG.md index e16831216f..b1c9496c7b 100644 --- a/packages/pluggableWidgets/skiplink-web/CHANGELOG.md +++ b/packages/pluggableWidgets/skiplink-web/CHANGELOG.md @@ -6,4 +6,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +### Added + - Created skiplink widget. From 0ee3595cb82cfc4213665d0ae423f4120fb0bf68 Mon Sep 17 00:00:00 2001 From: "hedwig.doets" Date: Thu, 27 Nov 2025 13:23:45 +0100 Subject: [PATCH 15/23] fix: remove react import --- .../skiplink-web/src/SkipLink.editorPreview.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pluggableWidgets/skiplink-web/src/SkipLink.editorPreview.tsx b/packages/pluggableWidgets/skiplink-web/src/SkipLink.editorPreview.tsx index 750f504f22..27f1a351c0 100644 --- a/packages/pluggableWidgets/skiplink-web/src/SkipLink.editorPreview.tsx +++ b/packages/pluggableWidgets/skiplink-web/src/SkipLink.editorPreview.tsx @@ -1,4 +1,4 @@ -import React, { ReactElement } from "react"; +import { ReactElement } from "react"; import { SkipLinkPreviewProps } from "../typings/SkipLinkProps"; export const preview = (props: SkipLinkPreviewProps): ReactElement => { From 5958650030e675e0d14c3b89b211081ad71e26f7 Mon Sep 17 00:00:00 2001 From: "hedwig.doets" Date: Fri, 28 Nov 2025 10:05:03 +0100 Subject: [PATCH 16/23] chore: add specific mendix version to e2e, add not required to xml --- packages/pluggableWidgets/skiplink-web/package.json | 2 +- packages/pluggableWidgets/skiplink-web/src/SkipLink.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/pluggableWidgets/skiplink-web/package.json b/packages/pluggableWidgets/skiplink-web/package.json index 2429e8daab..975309069f 100644 --- a/packages/pluggableWidgets/skiplink-web/package.json +++ b/packages/pluggableWidgets/skiplink-web/package.json @@ -31,7 +31,7 @@ "create-gh-release": "rui-create-gh-release", "create-translation": "rui-create-translation", "dev": "pluggable-widgets-tools start:web", - "e2e": "run-e2e ci", + "e2e": "MENDIX_VERSION=11.1.0.75979 run-e2e ci", "e2edev": "run-e2e dev --with-preps", "format": "prettier --ignore-path ./node_modules/@mendix/prettier-config-web-widgets/global-prettierignore --write .", "lint": "eslint src/ package.json", diff --git a/packages/pluggableWidgets/skiplink-web/src/SkipLink.xml b/packages/pluggableWidgets/skiplink-web/src/SkipLink.xml index ed18f9be9c..d3de605963 100644 --- a/packages/pluggableWidgets/skiplink-web/src/SkipLink.xml +++ b/packages/pluggableWidgets/skiplink-web/src/SkipLink.xml @@ -11,7 +11,7 @@ Link text The text displayed in the skip link. - + Main content ID The id of the main content element to jump to, if left empty the skip link widget will search for a main tag on the page. From 01492c8bc41f61e9ec80c5d7915bc7f232558115 Mon Sep 17 00:00:00 2001 From: leonardomendix Date: Tue, 2 Dec 2025 15:18:21 +0100 Subject: [PATCH 17/23] test: fix Docker mxbuild for ARM arch and skip the atlas theme copy --- automation/run-e2e/docker/mxbuild.Dockerfile | 6 ++++-- packages/pluggableWidgets/skiplink-web/package.json | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/automation/run-e2e/docker/mxbuild.Dockerfile b/automation/run-e2e/docker/mxbuild.Dockerfile index 45cd2e9c9a..c6a3934271 100644 --- a/automation/run-e2e/docker/mxbuild.Dockerfile +++ b/automation/run-e2e/docker/mxbuild.Dockerfile @@ -24,10 +24,12 @@ echo "Downloading mxbuild ${MENDIX_VERSION} and docker building for ${BUILDPLATF && tar xfz /tmp/mxbuild.tar.gz --directory /tmp/mxbuild \ && rm /tmp/mxbuild.tar.gz && \ \ - apt-get update -qqy && \ - apt-get install -qqy libicu70 && \ + rm -rf /var/lib/apt/lists/* && \ + apt-get update --allow-insecure-repositories -qqy && \ + apt-get install -qqy --allow-unauthenticated libicu70 && \ apt-get -qqy remove --auto-remove wget && \ apt-get clean && \ + rm -rf /var/lib/apt/lists/* && \ \ echo "#!/bin/bash -x" >/bin/mxbuild && \ echo "/tmp/mxbuild/modeler/mxbuild --java-home=/opt/java/openjdk --java-exe-path=/opt/java/openjdk/bin/java \$@" >>/bin/mxbuild && \ diff --git a/packages/pluggableWidgets/skiplink-web/package.json b/packages/pluggableWidgets/skiplink-web/package.json index 975309069f..39b8155f29 100644 --- a/packages/pluggableWidgets/skiplink-web/package.json +++ b/packages/pluggableWidgets/skiplink-web/package.json @@ -31,7 +31,7 @@ "create-gh-release": "rui-create-gh-release", "create-translation": "rui-create-translation", "dev": "pluggable-widgets-tools start:web", - "e2e": "MENDIX_VERSION=11.1.0.75979 run-e2e ci", + "e2e": "MENDIX_VERSION=11.1.0.75979 run-e2e ci --no-update-project", "e2edev": "run-e2e dev --with-preps", "format": "prettier --ignore-path ./node_modules/@mendix/prettier-config-web-widgets/global-prettierignore --write .", "lint": "eslint src/ package.json", From 73b48d08ffb82656ba856d4360f527fffda5247e Mon Sep 17 00:00:00 2001 From: "hedwig.doets" Date: Thu, 4 Dec 2025 11:09:57 +0100 Subject: [PATCH 18/23] feat: add e2e test --- .../skiplink-web/e2e/SkipLink.spec.js | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 packages/pluggableWidgets/skiplink-web/e2e/SkipLink.spec.js diff --git a/packages/pluggableWidgets/skiplink-web/e2e/SkipLink.spec.js b/packages/pluggableWidgets/skiplink-web/e2e/SkipLink.spec.js new file mode 100644 index 0000000000..aa249b4c2d --- /dev/null +++ b/packages/pluggableWidgets/skiplink-web/e2e/SkipLink.spec.js @@ -0,0 +1,84 @@ +import { test, expect } from "@playwright/test"; + +test.afterEach("Cleanup session", async ({ page }) => { + // Because the test isolation that will open a new session for every test executed, and that exceeds Mendix's license limit of 5 sessions, so we need to force logout after each test. + await page.evaluate(() => window.mx.session.logout()); +}); + +test.beforeEach(async ({ page }) => { + await page.goto("/"); +}); + +test.describe("SkipLink:", function () { + test("skip link is present in DOM but initially hidden", async ({ page }) => { + // Skip link should be in the DOM but not visible + const skipLink = page.locator(".skip-link").first(); + await expect(skipLink).toBeAttached(); + + // Check initial styling (hidden) + const transform = await skipLink.evaluate(el => getComputedStyle(el).transform); + expect(transform).toContain("translateY(-120%)") || expect(transform).toContain("matrix"); + }); + + test("skip link becomes visible when focused via keyboard", async ({ page }) => { + // Tab to focus the skip link (should be first focusable element) + await page.keyboard.press("Tab"); + + const skipLink = page.locator(".skip-link").first(); + await expect(skipLink).toBeFocused(); + + // Check that it becomes visible when focused + const transform = await skipLink.evaluate(el => getComputedStyle(el).transform); + expect(transform).toContain("translateY(0px)") || expect(transform).toBe("none"); + }); + + test("skip link navigates to main content when activated", async ({ page }) => { + // Create a main content element to test focus behavior + await page.evaluate(() => { + const main = document.createElement("main"); + main.id = "main-content"; + main.textContent = "Main content area"; + document.body.appendChild(main); + }); + + // Tab to focus the skip link + await page.keyboard.press("Tab"); + + const skipLink = page.locator(".skip-link").first(); + await expect(skipLink).toBeFocused(); + + // Activate the skip link + await page.keyboard.press("Enter"); + + // Check that main content is now focused + const mainContent = page.locator("#main-content"); + await expect(mainContent).toBeFocused(); + }); + + test("skip link has correct attributes and text", async ({ page }) => { + const skipLink = page.locator(".skip-link").first(); + + // Check default text + await expect(skipLink).toHaveText("Skip to main content"); + + // Check href attribute + await expect(skipLink).toHaveAttribute("href", "#main-content"); + + // Check tabindex + await expect(skipLink).toHaveAttribute("tabindex", "0"); + + // Check CSS class + await expect(skipLink).toHaveClass("skip-link"); + }); + + test("visual comparison", async ({ page }) => { + // Tab to make skip link visible for screenshot + await page.keyboard.press("Tab"); + + const skipLink = page.locator(".skip-link").first(); + await expect(skipLink).toBeFocused(); + + // Visual comparison of focused skip link + await expect(skipLink).toHaveScreenshot("skiplink-focused.png"); + }); +}); \ No newline at end of file From de74789d96eb3b9d71cf91861f844b96f0dd0095 Mon Sep 17 00:00:00 2001 From: "hedwig.doets" Date: Mon, 8 Dec 2025 12:32:01 +0100 Subject: [PATCH 19/23] fix: e2e tests --- .../skiplink-web/e2e/SkipLink.spec.js | 21 +++++++------------ .../skiplink-web/package.json | 2 +- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/packages/pluggableWidgets/skiplink-web/e2e/SkipLink.spec.js b/packages/pluggableWidgets/skiplink-web/e2e/SkipLink.spec.js index aa249b4c2d..4b6ad94c80 100644 --- a/packages/pluggableWidgets/skiplink-web/e2e/SkipLink.spec.js +++ b/packages/pluggableWidgets/skiplink-web/e2e/SkipLink.spec.js @@ -7,6 +7,7 @@ test.afterEach("Cleanup session", async ({ page }) => { test.beforeEach(async ({ page }) => { await page.goto("/"); + await page.waitForLoadState("networkidle"); }); test.describe("SkipLink:", function () { @@ -17,30 +18,22 @@ test.describe("SkipLink:", function () { // Check initial styling (hidden) const transform = await skipLink.evaluate(el => getComputedStyle(el).transform); - expect(transform).toContain("translateY(-120%)") || expect(transform).toContain("matrix"); + expect(transform).toContain("matrix(1, 0, 0, 1, 0, -48)"); }); test("skip link becomes visible when focused via keyboard", async ({ page }) => { // Tab to focus the skip link (should be first focusable element) + const skipLink = page.locator(".skip-link").first(); await page.keyboard.press("Tab"); - const skipLink = page.locator(".skip-link").first(); await expect(skipLink).toBeFocused(); - + await page.waitForTimeout(1000); // Check that it becomes visible when focused const transform = await skipLink.evaluate(el => getComputedStyle(el).transform); - expect(transform).toContain("translateY(0px)") || expect(transform).toBe("none"); + expect(transform).toContain("matrix(1, 0, 0, 1, 0, 0)") }); test("skip link navigates to main content when activated", async ({ page }) => { - // Create a main content element to test focus behavior - await page.evaluate(() => { - const main = document.createElement("main"); - main.id = "main-content"; - main.textContent = "Main content area"; - document.body.appendChild(main); - }); - // Tab to focus the skip link await page.keyboard.press("Tab"); @@ -51,7 +44,7 @@ test.describe("SkipLink:", function () { await page.keyboard.press("Enter"); // Check that main content is now focused - const mainContent = page.locator("#main-content"); + const mainContent = page.locator("main"); await expect(mainContent).toBeFocused(); }); @@ -62,7 +55,7 @@ test.describe("SkipLink:", function () { await expect(skipLink).toHaveText("Skip to main content"); // Check href attribute - await expect(skipLink).toHaveAttribute("href", "#main-content"); + await expect(skipLink).toHaveAttribute("href", "#"); // Check tabindex await expect(skipLink).toHaveAttribute("tabindex", "0"); diff --git a/packages/pluggableWidgets/skiplink-web/package.json b/packages/pluggableWidgets/skiplink-web/package.json index 39b8155f29..934124d975 100644 --- a/packages/pluggableWidgets/skiplink-web/package.json +++ b/packages/pluggableWidgets/skiplink-web/package.json @@ -32,7 +32,7 @@ "create-translation": "rui-create-translation", "dev": "pluggable-widgets-tools start:web", "e2e": "MENDIX_VERSION=11.1.0.75979 run-e2e ci --no-update-project", - "e2edev": "run-e2e dev --with-preps", + "e2edev": "MENDIX_VERSION=11.1.0.75979 run-e2e dev --with-preps --no-update-project", "format": "prettier --ignore-path ./node_modules/@mendix/prettier-config-web-widgets/global-prettierignore --write .", "lint": "eslint src/ package.json", "publish-marketplace": "rui-publish-marketplace", From d476c9b6b58c7d5b557ac3419999215a3681f0c3 Mon Sep 17 00:00:00 2001 From: "hedwig.doets" Date: Mon, 8 Dec 2025 16:22:14 +0100 Subject: [PATCH 20/23] fix: add screenshot for screenshot test --- .../skiplink-focused-actual.png | Bin 0 -> 2569 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 packages/pluggableWidgets/skiplink-web/e2e/SkipLink-SkipLink-visual-comparison-chromium/skiplink-focused-actual.png diff --git a/packages/pluggableWidgets/skiplink-web/e2e/SkipLink-SkipLink-visual-comparison-chromium/skiplink-focused-actual.png b/packages/pluggableWidgets/skiplink-web/e2e/SkipLink-SkipLink-visual-comparison-chromium/skiplink-focused-actual.png new file mode 100644 index 0000000000000000000000000000000000000000..34157374d23516dbb48b3e012aafafd329876e4b GIT binary patch literal 2569 zcmV+k3ikDhP)>q^+_tya(p#WFt3XQzM4SLCiLk+>on$w5qsi2SjWrn~JQ&L+x;PICyV()v zVZeD2XCAEa0gRJ~Srck96KiJ7Kq3)LoCQ{D1&6;5AVqphOM6Q%?VfX6pd%nnCC<2W zJ|Xn9=ic8rzjMF$cYf#G0-0TL$Dbl`?~nzPgkhloU@$Qd!I01wlt1@K=KQ2Tg>vpr z0~pMD!0@ps7dz!*s<& zbW8<1!L9-6v0QvoaQXHp=IitH+=V@+fkbOi^B$f z)_*V&-MU9S0tsT7CM|EicYFKsIJ{DreX&s7_fECSo(b(Qyh>| zhSIbKoxDJTYM4p4rE|_Y?C)WT;;e0^(lm2v5)DG5J$x%bbhwBdg|%LglO=De%rvDF zy|&>$cJaZPng8(ffij)8!$7g)Oj8$Q8)O~adnHl11Y)pxRkqg9Ke%LlPecdZo(MXXe`{x!9LTrZ3| z=j)x364z;;>zoY1i=^Mslc@N{qGtp+Hec<(kJNu(v1Q=g^+jDSSQ@5JR1Z~!_zx=p7 z^?KQxozJ^np^i#LrfaV1>eTJ%1Q6~U@$`*&Z61wHzoN9ooBfNC(%1xZyYkdN^Whgm z`skVPtIuDDG z|AAcpxojiFw%$WR5i65hYTkcRoqkvfYZomYp3&f1%fe#^vY(vXaW)J27+^WNF#U(q zxp#C)s3F#mI_z{?29Z$2O1k~z`wxt=onRB>S&H_PxzQszwymFC$pF}y`R0DqkK!WL zf6i!c6e0PJ_0P6VEp~U!BB7Af-%Z+)lUa>g0g_$UY-|=%yhrumwD!hc5&8yz)7v_} zRAR_|P{H2=FwXsBceg><+vS2n5vyQt^Id1MFCsf!TbIkR>OOfm#_O>`rh|~ZI*bVg z6_9vtD@(SV%DrP$9L^%eC?vS5Tb}9LJ*#qYdajJX%OE^ax!tVZcoCtbn+=KrMUqjM z#)~i1I~>?(jmoz+UtGXr+GI7y>UHtVG*Yxc4>=Th`!QexK8<>^T# zeX1UHVQJbMdex_3uzL#G2~07Ez8X|~F@742FbFtV$I79~loqf$h9f0dMO>#rxF?H z;EQX#-bxaSE1}UxV>DdL)ju)b4^S$pLAl0`Qg#AG$l#S*KU*f3HnimQG}{9#SnBPY zwera_T2ZgmLeJ4v3{N|Q3|e)IO3e5*3i$DPG4is>?i-zSH+Ona$|aQ; zl&{~mA)?ZR;laNIBxJ;nd&=(sq7qfO`~FOwZ3a$!o2-O?IgAODm{tUu{1meax)Hh! zQ|nKk9pTf%vSLN~@?0n;#pZ*V$Ka)9eKl>k37y zm6WI)ZOMSXhZCyvV^(gPxq{C&7*F-(XS-Tv@3JT#YB0z(3GM7rYPxO@pHO0yl!D3`?43MJ;s z?Pv2u8Oyyb*q7b?aoUMIQ4R^jlu%cg(q5DOx_oF8gocAT?G@586s|yqN_s;329vT- zY%*!?>`&gWBIR(VpsB;U9o9+()G?i%VXaX&7DSWO`~Dv?##9W z$vSCtGmy^BupL*OEtC}VXNqZQ%kgaUrhCe-4r784pzbkzfms-Mt3>w$#HzI3nv8tr&jB%S5dpU(b_bM?Uq~4Ce*gdg|NnCQKdt}( f00v1!K~w_(;$#k3e))B200000NkvXXu0mjf)^GX- literal 0 HcmV?d00001 From 29de627c8d6f83611015405f42832504a030da3c Mon Sep 17 00:00:00 2001 From: "hedwig.doets" Date: Mon, 8 Dec 2025 16:33:52 +0100 Subject: [PATCH 21/23] fix: rename snaphot --- .../skiplink-focused-chromium-linux.png} | Bin 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/pluggableWidgets/skiplink-web/e2e/{SkipLink-SkipLink-visual-comparison-chromium/skiplink-focused-actual.png => Skiplink.spec.js-snapshots/skiplink-focused-chromium-linux.png} (100%) diff --git a/packages/pluggableWidgets/skiplink-web/e2e/SkipLink-SkipLink-visual-comparison-chromium/skiplink-focused-actual.png b/packages/pluggableWidgets/skiplink-web/e2e/Skiplink.spec.js-snapshots/skiplink-focused-chromium-linux.png similarity index 100% rename from packages/pluggableWidgets/skiplink-web/e2e/SkipLink-SkipLink-visual-comparison-chromium/skiplink-focused-actual.png rename to packages/pluggableWidgets/skiplink-web/e2e/Skiplink.spec.js-snapshots/skiplink-focused-chromium-linux.png From d72d9d3b2ec5605371d130ad42b2ec508802b9d3 Mon Sep 17 00:00:00 2001 From: "hedwig.doets" Date: Tue, 9 Dec 2025 10:09:24 +0100 Subject: [PATCH 22/23] fix: remove screenshot --- .../skiplink-focused-chromium-linux.png | Bin 2569 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 packages/pluggableWidgets/skiplink-web/e2e/Skiplink.spec.js-snapshots/skiplink-focused-chromium-linux.png diff --git a/packages/pluggableWidgets/skiplink-web/e2e/Skiplink.spec.js-snapshots/skiplink-focused-chromium-linux.png b/packages/pluggableWidgets/skiplink-web/e2e/Skiplink.spec.js-snapshots/skiplink-focused-chromium-linux.png deleted file mode 100644 index 34157374d23516dbb48b3e012aafafd329876e4b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2569 zcmV+k3ikDhP)>q^+_tya(p#WFt3XQzM4SLCiLk+>on$w5qsi2SjWrn~JQ&L+x;PICyV()v zVZeD2XCAEa0gRJ~Srck96KiJ7Kq3)LoCQ{D1&6;5AVqphOM6Q%?VfX6pd%nnCC<2W zJ|Xn9=ic8rzjMF$cYf#G0-0TL$Dbl`?~nzPgkhloU@$Qd!I01wlt1@K=KQ2Tg>vpr z0~pMD!0@ps7dz!*s<& zbW8<1!L9-6v0QvoaQXHp=IitH+=V@+fkbOi^B$f z)_*V&-MU9S0tsT7CM|EicYFKsIJ{DreX&s7_fECSo(b(Qyh>| zhSIbKoxDJTYM4p4rE|_Y?C)WT;;e0^(lm2v5)DG5J$x%bbhwBdg|%LglO=De%rvDF zy|&>$cJaZPng8(ffij)8!$7g)Oj8$Q8)O~adnHl11Y)pxRkqg9Ke%LlPecdZo(MXXe`{x!9LTrZ3| z=j)x364z;;>zoY1i=^Mslc@N{qGtp+Hec<(kJNu(v1Q=g^+jDSSQ@5JR1Z~!_zx=p7 z^?KQxozJ^np^i#LrfaV1>eTJ%1Q6~U@$`*&Z61wHzoN9ooBfNC(%1xZyYkdN^Whgm z`skVPtIuDDG z|AAcpxojiFw%$WR5i65hYTkcRoqkvfYZomYp3&f1%fe#^vY(vXaW)J27+^WNF#U(q zxp#C)s3F#mI_z{?29Z$2O1k~z`wxt=onRB>S&H_PxzQszwymFC$pF}y`R0DqkK!WL zf6i!c6e0PJ_0P6VEp~U!BB7Af-%Z+)lUa>g0g_$UY-|=%yhrumwD!hc5&8yz)7v_} zRAR_|P{H2=FwXsBceg><+vS2n5vyQt^Id1MFCsf!TbIkR>OOfm#_O>`rh|~ZI*bVg z6_9vtD@(SV%DrP$9L^%eC?vS5Tb}9LJ*#qYdajJX%OE^ax!tVZcoCtbn+=KrMUqjM z#)~i1I~>?(jmoz+UtGXr+GI7y>UHtVG*Yxc4>=Th`!QexK8<>^T# zeX1UHVQJbMdex_3uzL#G2~07Ez8X|~F@742FbFtV$I79~loqf$h9f0dMO>#rxF?H z;EQX#-bxaSE1}UxV>DdL)ju)b4^S$pLAl0`Qg#AG$l#S*KU*f3HnimQG}{9#SnBPY zwera_T2ZgmLeJ4v3{N|Q3|e)IO3e5*3i$DPG4is>?i-zSH+Ona$|aQ; zl&{~mA)?ZR;laNIBxJ;nd&=(sq7qfO`~FOwZ3a$!o2-O?IgAODm{tUu{1meax)Hh! zQ|nKk9pTf%vSLN~@?0n;#pZ*V$Ka)9eKl>k37y zm6WI)ZOMSXhZCyvV^(gPxq{C&7*F-(XS-Tv@3JT#YB0z(3GM7rYPxO@pHO0yl!D3`?43MJ;s z?Pv2u8Oyyb*q7b?aoUMIQ4R^jlu%cg(q5DOx_oF8gocAT?G@586s|yqN_s;329vT- zY%*!?>`&gWBIR(VpsB;U9o9+()G?i%VXaX&7DSWO`~Dv?##9W z$vSCtGmy^BupL*OEtC}VXNqZQ%kgaUrhCe-4r784pzbkzfms-Mt3>w$#HzI3nv8tr&jB%S5dpU(b_bM?Uq~4Ce*gdg|NnCQKdt}( f00v1!K~w_(;$#k3e))B200000NkvXXu0mjf)^GX- From 9f1fd25a1cdda6b7eae939d4162ad6b81c5f39a0 Mon Sep 17 00:00:00 2001 From: "hedwig.doets" Date: Tue, 9 Dec 2025 10:21:39 +0100 Subject: [PATCH 23/23] fix: readd screenshot --- .../skiplink-focused-chromium-linux.png | Bin 0 -> 2569 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 packages/pluggableWidgets/skiplink-web/e2e/SkipLink.spec.js-snapshots/skiplink-focused-chromium-linux.png diff --git a/packages/pluggableWidgets/skiplink-web/e2e/SkipLink.spec.js-snapshots/skiplink-focused-chromium-linux.png b/packages/pluggableWidgets/skiplink-web/e2e/SkipLink.spec.js-snapshots/skiplink-focused-chromium-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..34157374d23516dbb48b3e012aafafd329876e4b GIT binary patch literal 2569 zcmV+k3ikDhP)>q^+_tya(p#WFt3XQzM4SLCiLk+>on$w5qsi2SjWrn~JQ&L+x;PICyV()v zVZeD2XCAEa0gRJ~Srck96KiJ7Kq3)LoCQ{D1&6;5AVqphOM6Q%?VfX6pd%nnCC<2W zJ|Xn9=ic8rzjMF$cYf#G0-0TL$Dbl`?~nzPgkhloU@$Qd!I01wlt1@K=KQ2Tg>vpr z0~pMD!0@ps7dz!*s<& zbW8<1!L9-6v0QvoaQXHp=IitH+=V@+fkbOi^B$f z)_*V&-MU9S0tsT7CM|EicYFKsIJ{DreX&s7_fECSo(b(Qyh>| zhSIbKoxDJTYM4p4rE|_Y?C)WT;;e0^(lm2v5)DG5J$x%bbhwBdg|%LglO=De%rvDF zy|&>$cJaZPng8(ffij)8!$7g)Oj8$Q8)O~adnHl11Y)pxRkqg9Ke%LlPecdZo(MXXe`{x!9LTrZ3| z=j)x364z;;>zoY1i=^Mslc@N{qGtp+Hec<(kJNu(v1Q=g^+jDSSQ@5JR1Z~!_zx=p7 z^?KQxozJ^np^i#LrfaV1>eTJ%1Q6~U@$`*&Z61wHzoN9ooBfNC(%1xZyYkdN^Whgm z`skVPtIuDDG z|AAcpxojiFw%$WR5i65hYTkcRoqkvfYZomYp3&f1%fe#^vY(vXaW)J27+^WNF#U(q zxp#C)s3F#mI_z{?29Z$2O1k~z`wxt=onRB>S&H_PxzQszwymFC$pF}y`R0DqkK!WL zf6i!c6e0PJ_0P6VEp~U!BB7Af-%Z+)lUa>g0g_$UY-|=%yhrumwD!hc5&8yz)7v_} zRAR_|P{H2=FwXsBceg><+vS2n5vyQt^Id1MFCsf!TbIkR>OOfm#_O>`rh|~ZI*bVg z6_9vtD@(SV%DrP$9L^%eC?vS5Tb}9LJ*#qYdajJX%OE^ax!tVZcoCtbn+=KrMUqjM z#)~i1I~>?(jmoz+UtGXr+GI7y>UHtVG*Yxc4>=Th`!QexK8<>^T# zeX1UHVQJbMdex_3uzL#G2~07Ez8X|~F@742FbFtV$I79~loqf$h9f0dMO>#rxF?H z;EQX#-bxaSE1}UxV>DdL)ju)b4^S$pLAl0`Qg#AG$l#S*KU*f3HnimQG}{9#SnBPY zwera_T2ZgmLeJ4v3{N|Q3|e)IO3e5*3i$DPG4is>?i-zSH+Ona$|aQ; zl&{~mA)?ZR;laNIBxJ;nd&=(sq7qfO`~FOwZ3a$!o2-O?IgAODm{tUu{1meax)Hh! zQ|nKk9pTf%vSLN~@?0n;#pZ*V$Ka)9eKl>k37y zm6WI)ZOMSXhZCyvV^(gPxq{C&7*F-(XS-Tv@3JT#YB0z(3GM7rYPxO@pHO0yl!D3`?43MJ;s z?Pv2u8Oyyb*q7b?aoUMIQ4R^jlu%cg(q5DOx_oF8gocAT?G@586s|yqN_s;329vT- zY%*!?>`&gWBIR(VpsB;U9o9+()G?i%VXaX&7DSWO`~Dv?##9W z$vSCtGmy^BupL*OEtC}VXNqZQ%kgaUrhCe-4r784pzbkzfms-Mt3>w$#HzI3nv8tr&jB%S5dpU(b_bM?Uq~4Ce*gdg|NnCQKdt}( f00v1!K~w_(;$#k3e))B200000NkvXXu0mjf)^GX- literal 0 HcmV?d00001