Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
477f2bcc47 | ||
|
|
64a6ff6a20 | ||
|
|
5564eaf31a | ||
|
|
a10b79b504 | ||
|
|
d8d4c7cb69 | ||
|
|
9821ffe642 | ||
|
|
1874c0ea9e | ||
|
|
2c2fbd160e |
@@ -0,0 +1,2 @@
|
|||||||
|
node_modules
|
||||||
|
packages/**/node_modules
|
||||||
@@ -1 +1,2 @@
|
|||||||
node_modules
|
node_modules
|
||||||
|
.pnpm-store
|
||||||
|
|||||||
@@ -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"]
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
services:
|
||||||
|
test:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.test
|
||||||
|
working_dir: /app/packages/luna-charts
|
||||||
+4
-2
@@ -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": "",
|
||||||
|
|||||||
@@ -24,3 +24,5 @@ $RECYCLE.BIN/
|
|||||||
Thumbs.db
|
Thumbs.db
|
||||||
UserInterfaceState.xcuserstate
|
UserInterfaceState.xcuserstate
|
||||||
.env
|
.env
|
||||||
|
|
||||||
|
test-results
|
||||||
@@ -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,3 +1,4 @@
|
|||||||
import { defineCustomElements } from "./loader";
|
import { defineCustomElements } from "./loader";
|
||||||
|
import './src/testing/a11y/matchers';
|
||||||
|
|
||||||
defineCustomElements();
|
defineCustomElements();
|
||||||
Generated
+10
-1
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user