8 Commits
Author SHA1 Message Date
Ninosaurier 477f2bcc47 feat(a11y-example) Added my-component
my-component will remove in the future or moved in example. It is just a preview to see, if the tests run
2026-07-16 15:34:35 +02:00
Ninosaurier 64a6ff6a20 feat(a11y): add extensible accessibility test infrastructure with axe-core" -m "- Add A11yChecker interface (types.ts) as the shared contract for accessibility checkers 2026-07-16 15:33:28 +02:00
Ninosaurier 5564eaf31a build: add axe-core dependency 2026-07-16 15:23:14 +02:00
Ninosaurier a10b79b504 test(pnpm and playwright): Playwright run successful in docker container" -m "- Pin Dockerfile.test to mcr.microsoft.com/playwright:v1.52.0-noble to match the playwright npm package version (was mismatched at v1.61.1)
- Add supportedArchitectures (linux-x64-gnu) to pnpm-workspace.yaml so native bindings (rolldown, esbuild) are included in the lockfile for the container's platform
- Add onlyBuiltDependencies: [playwright] to pnpm-workspace.yaml so build scripts run non-interactively in CI/Docker
- Remove stale/redundant pnpm field from package.json (moved to pnpm-workspace.yaml per pnpm v10+ config location)
- Split stencil-test into test:unit (native, fast) and test:browser (Docker, cross-browser) scripts
- Set ENV CI=true in Dockerfile.test to avoid interactive TTY prompts during pnpm install
- Remove anonymous node_modules volumes from docker-compose.test.yaml to prevent stale volume state from surviving image rebuilds
- Add root-level test:unit / test:component / test scripts to orchestrate native unit tests and Dockerized cross-browser tests
2026-07-15 16:42:43 +02:00
Ninosaurier d8d4c7cb69 test(pnpm and playwright): Updated dependencies 2026-07-15 13:57:44 +02:00
Ninosaurier 9821ffe642 test(docker): Added docker container
Playwright tests execute in containers, allowing developers to test independently of the operating system.
2026-07-15 13:55:27 +02:00
Ninosaurier 1874c0ea9e Added .pnpm-store in .gitignore 2026-07-15 13:51:26 +02:00
Ninosaurier 2c2fbd160e ignore(test): Added new folder
After pnpm test, test-results will be created. Does need to push. Added the folder in .gitignore
2026-07-15 12:18:38 +02:00
15 changed files with 167 additions and 11 deletions
+2
View File
@@ -0,0 +1,2 @@
node_modules
packages/**/node_modules
+2 -1
View File
@@ -1 +1,2 @@
node_modules node_modules
.pnpm-store
+23
View File
@@ -0,0 +1,23 @@
FROM mcr.microsoft.com/playwright:v1.61.1-noble
RUN corepack enable && corepack prepare pnpm@10 --activate
ENV CI=true
WORKDIR /app
COPY . .
RUN rm -rf node_modules packages/**/node_modules
RUN rm -f pnpm-lock.yaml
ENV PNPM_CONFIG_SUPPORTED_ARCHITECTURES="current,linux-x64-gnu"
RUN pnpm install
WORKDIR /app/packages/luna-charts
RUN pnpm add -D @rolldown/binding-linux-x64-gnu
CMD ["pnpm", "test:browser"]
+6
View File
@@ -0,0 +1,6 @@
services:
test:
build:
context: .
dockerfile: Dockerfile.test
working_dir: /app/packages/luna-charts
+4 -2
View File
@@ -6,10 +6,12 @@
"scripts": { "scripts": {
"lint": "eslint .", "lint": "eslint .",
"format": "prettier --write .", "format": "prettier --write .",
"test": "echo \"Error: no test specified\" && exit 1",
"changeset": "changeset", "changeset": "changeset",
"version": "changeset version", "version": "changeset version",
"release": "changeset publish" "release": "changeset publish",
"test:unit": "pnpm --filter luna-charts test:unit",
"test:component": "docker compose -f docker-compose.test.yaml up --build --abort-on-container-exit --exit-code-from test",
"test": "pnpm test:unit && pnpm test:component"
}, },
"keywords": [], "keywords": [],
"author": "", "author": "",
+2
View File
@@ -24,3 +24,5 @@ $RECYCLE.BIN/
Thumbs.db Thumbs.db
UserInterfaceState.xcuserstate UserInterfaceState.xcuserstate
.env .env
test-results
+7 -4
View File
@@ -35,6 +35,8 @@
"build": "stencil build", "build": "stencil build",
"start": "stencil build --dev --watch --serve", "start": "stencil build --dev --watch --serve",
"test": "stencil-test --prod", "test": "stencil-test --prod",
"test:unit": "stencil-test --prod --project=unit",
"test:browser": "stencil-test --prod --project=browser",
"test:watch": "stencil-test --prod --watch", "test:watch": "stencil-test --prod --watch",
"generate": "stencil generate", "generate": "stencil generate",
"storybook": "storybook dev -p 6006 --no-open" "storybook": "storybook dev -p 6006 --no-open"
@@ -43,14 +45,15 @@
"@stencil/core": "^4.43.5", "@stencil/core": "^4.43.5",
"@stencil/storybook-plugin": "^0.7.0", "@stencil/storybook-plugin": "^0.7.0",
"@stencil/vitest": "^1.8.3", "@stencil/vitest": "^1.8.3",
"@storybook/addon-docs": "^10.5.0",
"@storybook/addon-links": "^10.5.0", "@storybook/addon-links": "^10.5.0",
"@types/node": "^22.13.5", "@types/node": "^22.13.5",
"@vitest/browser-playwright": "^4.0.0", "@vitest/browser-playwright": "^4.0.0",
"playwright": "^1.52.0", "axe-core": "^4.12.1",
"storybook": "^10.5.0",
"vitest": "^4.0.0",
"eslint-plugin-storybook": "10.5.0", "eslint-plugin-storybook": "10.5.0",
"@storybook/addon-docs": "^10.5.0" "playwright": "^1.61.1",
"storybook": "^10.5.0",
"vitest": "^4.0.0"
}, },
"license": "MIT" "license": "MIT"
} }
@@ -1,4 +1,5 @@
import { render, h, describe, it, expect } from '@stencil/vitest'; import { render, h, describe, it, expect } from '@stencil/vitest';
import { runA11yChecks } from '../../testing/a11y';
describe('my-component', () => { describe('my-component', () => {
it('renders', async () => { it('renders', async () => {
@@ -14,10 +15,15 @@ describe('my-component', () => {
`); `);
}); });
it('has no accessibility violations', async () => {
const { root } = await render(<my-component></my-component>);
const results = await runA11yChecks(root);
expect(results).toHaveNoA11yViolations();
});
it('renders with values', async () => { it('renders with values', async () => {
const { root } = await render( const { root } = await render(<my-component first="Stencil" middle="'Don't call me a framework'" last="JS"></my-component>);
<my-component first="Stencil" middle="'Don't call me a framework'" last="JS"></my-component>,
);
await expect(root).toEqualHtml(` await expect(root).toEqualHtml(`
<my-component class="hydrated"> <my-component class="hydrated">
<mock:shadow-root> <mock:shadow-root>
@@ -0,0 +1,21 @@
import axe from 'axe-core';
import type { A11yChecker, A11yCheckResult } from './types';
export const axeCoreChecker: A11yChecker = {
name: 'axe-core',
async run(rootElement: Element): Promise<A11yCheckResult> {
const results = await axe.run(rootElement);
return {
checkerName: 'axe-core',
violations: results.violations.map((violations) => ({
id: violations.id,
impact: violations.impact ?? null,
description: violations.help,
helpUrl: violations.helpUrl,
nodes: violations.nodes.map((node) => node.target.join(' ')),
})),
};
},
};
@@ -0,0 +1,17 @@
export interface A11yViolation {
id: string;
impact: 'minor' | 'moderate' | 'serious' | 'critical' | null;
description: string;
helpUrl?: string;
nodes: string[];
}
export interface A11yCheckResult {
checkerName: string;
violations: A11yViolation[];
}
export interface A11yChecker {
name: string;
run(root: Element): Promise<A11yCheckResult>;
}
@@ -0,0 +1,12 @@
import type { A11yChecker, A11yCheckResult } from './checkers/types';
import { axeCoreChecker } from './checkers/axe-core.checker';
const a11yCheckerRegistry: A11yChecker[] = [
axeCoreChecker,
];
export async function runA11yChecks(rootElement: Element): Promise<A11yCheckResult[]> {
return Promise.all(a11yCheckerRegistry.map((a11yChecker) => a11yChecker.run(rootElement)));
}
export type { A11yChecker, A11yCheckResult, A11yViolation } from './checkers/types';
@@ -0,0 +1,36 @@
import { expect } from 'vitest';
import type { A11yCheckResult } from './checkers/types';
expect.extend({
toHaveNoA11yViolations(results: A11yCheckResult[]) {
const failing = results.filter((results) => results.violations.length > 0);
if (failing.length === 0) {
return {
pass: true,
message: () => 'expected accessibility violations, but none were found',
};
}
const message = failing
.map(
(results) =>
`[${results.checkerName}] ${results.violations.length} violation(s):\n` +
results.violations
.map((validation) => ` - ${validation.id} (${validation.impact}): ${validation.description}\n affected: ${validation.nodes.join(', ')}`)
.join('\n')
)
.join('\n\n');
return {
pass: false,
message: () => `Accessibility violations found:\n\n${message}`,
};
},
});
declare module 'vitest' {
interface Assertion {
toHaveNoA11yViolations(): void;
}
}
+1
View File
@@ -1,3 +1,4 @@
import { defineCustomElements } from "./loader"; import { defineCustomElements } from "./loader";
import './src/testing/a11y/matchers';
defineCustomElements(); defineCustomElements();
+10 -1
View File
@@ -240,11 +240,14 @@ importers:
'@vitest/browser-playwright': '@vitest/browser-playwright':
specifier: ^4.0.0 specifier: ^4.0.0
version: 4.1.10(playwright@1.61.1)(vite@8.1.4(@types/node@22.20.1)(jiti@2.7.0))(vitest@4.1.10) version: 4.1.10(playwright@1.61.1)(vite@8.1.4(@types/node@22.20.1)(jiti@2.7.0))(vitest@4.1.10)
axe-core:
specifier: ^4.12.1
version: 4.12.1
eslint-plugin-storybook: eslint-plugin-storybook:
specifier: 10.5.0 specifier: 10.5.0
version: 10.5.0(eslint@10.6.0(jiti@2.7.0))(storybook@10.5.0(@types/react@19.2.17)(prettier@3.9.4)(react@19.2.7))(typescript@5.9.3) version: 10.5.0(eslint@10.6.0(jiti@2.7.0))(storybook@10.5.0(@types/react@19.2.17)(prettier@3.9.4)(react@19.2.7))(typescript@5.9.3)
playwright: playwright:
specifier: ^1.52.0 specifier: ^1.61.1
version: 1.61.1 version: 1.61.1
storybook: storybook:
specifier: ^10.5.0 specifier: ^10.5.0
@@ -1324,6 +1327,10 @@ packages:
resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==} resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==}
engines: {node: '>=4'} engines: {node: '>=4'}
axe-core@4.12.1:
resolution: {integrity: sha512-s7iGf5GaVMxEG0ENN9x+xTr7GFZCb1ZP/1uATUpCEK2X78nDB3RwbtFCo9pGAf9ru+VwoQ464DkaLEeRM08wJA==}
engines: {node: '>=4'}
balanced-match@4.0.4: balanced-match@4.0.4:
resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==}
engines: {node: 18 || 20 || >=22} engines: {node: 18 || 20 || >=22}
@@ -3266,6 +3273,8 @@ snapshots:
dependencies: dependencies:
tslib: 2.8.1 tslib: 2.8.1
axe-core@4.12.1: {}
balanced-match@4.0.4: {} balanced-match@4.0.4: {}
better-path-resolve@1.0.0: better-path-resolve@1.0.0:
+15
View File
@@ -1,5 +1,20 @@
packages: packages:
- "packages/*" - "packages/*"
- "apps/*" - "apps/*"
allowBuilds: allowBuilds:
esbuild: false esbuild: false
onlyBuiltDependencies:
- playwright
supportedArchitectures:
os:
- current
- linux
cpu:
- current
- x64
libc:
- current
- glibc