From 36645f6d13adf7d2c5f9d811ed769433144b6d08 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli-ai-agent <268630448+NullVoxPopuli-ai-agent@users.noreply.github.com> Date: Mon, 20 Jul 2026 11:50:05 -0400 Subject: [PATCH 1/2] docs-app: restore the site styling layer; add page-walking a11y tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The deployed site rendered with browser-default (serif) typography and a theme toggle that changed nothing visible: docs-support's Shell brings the structural site CSS, but the app itself is responsible for the layer ember-primitives' docs-app defines in app.css — the reset, the --font-sans stack + body font-family, prose/typedoc overrides, and the .theme-light/.theme-dark variables the chrome reads. Ported that layer (verified in a browser: sans-serif typography and a working visible theme toggle). Also port ember-primitives' docs-app test setup: - an application test walks every page in the kolay manifest, runs an axe-core audit per page in default/dark/light themes (color-contrast off, matching primitives), and asserts no [data-page-error] — which is exactly the signal for a live demo failing to compile/render - testem with ember-a11y-testing's middleware reporter, vite build --mode development + testem ci - root `pnpm test` now runs all workspace test tasks; the try-scenario job stays scoped to test-app (scenarios only vary its deps) Co-Authored-By: Claude Fable 5 --- .github/workflows/ci.yml | 4 +- .../2026-07-20T15-47-33-999Z.json | 1 + .../2026-07-20T15-49-22-239Z.json | 1 + docs-app/package.json | 13 +- docs-app/src/config.ts | 11 +- docs-app/src/styles/app.css | 120 ++++++++++++++++++ docs-app/testem.cjs | 34 +++++ docs-app/tests/application/pages-test.ts | 108 ++++++++++++++++ docs-app/tests/index.html | 27 ++++ docs-app/tests/test-helper.ts | 24 ++++ docs-app/tsconfig.json | 8 +- package.json | 2 +- packages/form/LICENSE.md | 9 ++ packages/form/README.md | 42 ++++++ pnpm-lock.yaml | 24 ++++ 15 files changed, 422 insertions(+), 6 deletions(-) create mode 100644 docs-app/ember-a11y-report/2026-07-20T15-47-33-999Z.json create mode 100644 docs-app/ember-a11y-report/2026-07-20T15-49-22-239Z.json create mode 100644 docs-app/testem.cjs create mode 100644 docs-app/tests/application/pages-test.ts create mode 100644 docs-app/tests/index.html create mode 100644 docs-app/tests/test-helper.ts create mode 100644 packages/form/LICENSE.md create mode 100644 packages/form/README.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f26e706..2f29850 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -141,7 +141,9 @@ jobs: # but skips the actual `pnpm run build`, so pnpm's # syncInjectedDepsAfterScripts hook never refreshes the injected # copies created by the scenario install - run: pnpm turbo test --force + # scenarios only vary test-app's dependencies; docs-app tests run + # in the Default Tests job + run: pnpm turbo test --filter test-app --force env: ${{ matrix.env }} build_docs: diff --git a/docs-app/ember-a11y-report/2026-07-20T15-47-33-999Z.json b/docs-app/ember-a11y-report/2026-07-20T15-47-33-999Z.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/docs-app/ember-a11y-report/2026-07-20T15-47-33-999Z.json @@ -0,0 +1 @@ +[] diff --git a/docs-app/ember-a11y-report/2026-07-20T15-49-22-239Z.json b/docs-app/ember-a11y-report/2026-07-20T15-49-22-239Z.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/docs-app/ember-a11y-report/2026-07-20T15-49-22-239Z.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/docs-app/package.json b/docs-app/package.json index afe208e..ad750c5 100644 --- a/docs-app/package.json +++ b/docs-app/package.json @@ -33,7 +33,10 @@ "lint:prettier": "prettier -c .", "lint:prettier:fix": "prettier -w .", "lint:types": "ember-tsc --noEmit", - "start": "vite" + "start": "vite", + "build:test": "NODE_ENV=development vite build --mode development", + "test": "pnpm build:test && pnpm test:ci", + "test:ci": "testem ci --port 0" }, "dependencies": { "@ember/test-waiters": "^4.1.1", @@ -65,6 +68,7 @@ "@babel/plugin-transform-typescript": "^7.29.7", "@babel/runtime": "^7.29.7", "@ember/app-tsconfig": "^2.0.0", + "@ember/test-helpers": "^5.4.3", "@embroider/core": "^4.4.3", "@embroider/macros": "^1.19.7", "@embroider/vite": "^1.5.2", @@ -74,15 +78,22 @@ "@glint/tsserver-plugin": "^2.5.18", "@nullvoxpopuli/eslint-configs": "^6.0.0", "@rollup/plugin-babel": "^7.1.0", + "@types/qunit": "^2.19.14", "@warp-drive/build-config": "^5.8.2", + "axe-core": "^4.12.1", "babel-plugin-ember-template-compilation": "^4.0.0", "concurrently": "^9.2.1", "decorator-transforms": "^2.3.1", + "ember-a11y-testing": "^8.0.1", + "ember-qunit": "^9.1.0", "ember-source": "^7.1.0", "ember-template-lint": "^5.2.0", "eslint": "^9.22.0", "prettier": "^3.8.1", "prettier-plugin-ember-template-tag": "^2.1.3", + "qunit": "^2.26.0", + "qunit-dom": "^3.6.0", + "testem": "^3.20.1", "typescript": "~6.0.0", "vite": "^8.1.5" }, diff --git a/docs-app/src/config.ts b/docs-app/src/config.ts index 5933244..429a69a 100644 --- a/docs-app/src/config.ts +++ b/docs-app/src/config.ts @@ -4,7 +4,16 @@ const ENV = { rootURL: '/', locationType: 'history', EmberENV: {}, - APP: {} as Record, + APP: {} as Record & { + rootElement?: string; + autoboot?: boolean; + }, }; export default ENV; + +export function enterTestMode() { + ENV.locationType = 'none'; + ENV.APP.rootElement = '#ember-testing'; + ENV.APP.autoboot = false; +} diff --git a/docs-app/src/styles/app.css b/docs-app/src/styles/app.css index 1eabe50..dbc69b8 100644 --- a/docs-app/src/styles/app.css +++ b/docs-app/src/styles/app.css @@ -1,5 +1,125 @@ @import './docs-support.css'; +/* + * CSS Reset (modern minimal) + */ +*, +*::before, +*::after { + box-sizing: border-box; +} + +* { + margin: 0; +} + +body { + line-height: 1.5; + -webkit-font-smoothing: antialiased; +} + +img, +picture, +video, +canvas, +svg { + display: block; + max-width: 100%; +} + +input, +button, +textarea, +select { + font: inherit; +} + +p, +h1, +h2, +h3, +h4, +h5, +h6 { + overflow-wrap: break-word; +} + +ol, +ul { + list-style: none; + margin: 0; + padding: 0; +} + +/* + * Custom properties + */ +:root { + --font-sans: + 'InterVariable', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', + 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + --font-display: 'Helvetica', 'Arial', sans-serif; + --max-width-8xl: 88rem; +} + +body { + font-family: var(--font-sans); +} + +/* + * Theme variables + */ +.theme-light { + --color-page-background: white; + --color-foreground: black; +} + +.theme-dark { + --color-page-background: #252525; + --color-foreground: white; +} + +/* + * Prose overrides + */ +.prose + :where(code):not( + :where([class~='not-prose'], [class~='not-prose'] *) + )::before { + content: '' !important; +} + +.prose + :where(code):not( + :where([class~='not-prose'], [class~='not-prose'] *) + )::after { + content: '' !important; +} + +.prose .demo details { + border: 0; + margin: 0; + padding: 0; +} + +.prose .featured-demo input { + color: black; +} + +/* + * TypeDoc overrides + */ +.typedoc__declaration-children { + max-width: 800px; +} + +.typedoc__component-signature__block + > .typedoc__property + > .typedoc__declaration + .typedoc__declaration-children { + display: block; +} + /* * Small utility set used by the doc pages' demos * (kept intentionally tiny -- this app does not use tailwind) diff --git a/docs-app/testem.cjs b/docs-app/testem.cjs new file mode 100644 index 0000000..2d9d25a --- /dev/null +++ b/docs-app/testem.cjs @@ -0,0 +1,34 @@ +'use strict'; + +if (typeof module !== 'undefined') { + const { a11yMiddleware } = require('ember-a11y-testing/middleware'); + + module.exports = { + test_page: 'tests/index.html?hidepassed', + cwd: 'dist', + middleware: [a11yMiddleware], + disable_watching: true, + launch_in_ci: ['Chrome'], + launch_in_dev: ['Chrome'], + browser_start_timeout: 120, + // The Pages a11y test walks every docs page and runs axe-core + // three times per page (default/dark/light theme); give the + // disconnect timeout enough headroom that a slow audit isn't + // mistaken for a dead browser. + browser_disconnect_timeout: 240, + browser_args: { + Chrome: { + ci: [ + // --no-sandbox is needed when running Chrome inside a container + process.env.CI ? '--no-sandbox' : null, + '--headless=new', + '--disable-dev-shm-usage', + '--disable-software-rasterizer', + '--mute-audio', + '--remote-debugging-port=0', + '--window-size=1440,900', + ].filter(Boolean), + }, + }, + }; +} diff --git a/docs-app/tests/application/pages-test.ts b/docs-app/tests/application/pages-test.ts new file mode 100644 index 0000000..9c27741 --- /dev/null +++ b/docs-app/tests/application/pages-test.ts @@ -0,0 +1,108 @@ +import { findAll, settled, visit, waitUntil } from '@ember/test-helpers'; +import { module, test } from 'qunit'; +import { setupApplicationTest } from 'ember-qunit'; + +import { colorScheme } from 'ember-primitives/color-scheme'; +import { docsManager } from 'kolay'; + +import { a11yAudit } from 'ember-a11y-testing/test-support'; + +/** + * per-page a11y rule overrides + */ +const a11yChecks: { + [url: string]: { + [checkName: string]: Record; + }; +} = {}; + +/** + * a11yAudit halts tests, this gets around that + */ +async function checkA11y( + assert: Assert, + path: string, + theme: string, + settings: object, +) { + await settled(); + + try { + await a11yAudit({ + rules: { + // TODO: find a syntax highlighting theme + // with better contrast + 'color-contrast': { + enabled: false, + }, + ...settings, + }, + }); + assert.ok( + true, + `no a11y errors found for ${path} using the ${theme} theme`, + ); + } catch (e) { + let errorText = ''; + + if (typeof e === 'object') { + if (e && 'message' in e && typeof e.message === 'string') { + errorText = e.message; + } + } + + const message = + `${path}: no a11y errors found using the ${theme} theme` + + `\n\n` + + errorText; + + if (window.location.search.includes('debugA11yAudit')) { + console.error(errorText); + } + + assert.ok(false, message); + } +} + +module('Application | Pages', function (hooks) { + setupApplicationTest(hooks); + + test('Pages all render and fit a11y criteria', async function (assert) { + // every page is visited and audited three times + // (default / dark / light theme) + assert.timeout(600_000); + + await visit('/'); + + const pages: { path: string }[] = []; + + const docsService = docsManager(this); + const groups = docsService.manifest.groups; + + for (const group of groups) { + for (const page of group.list) { + pages.push(page); + } + } + + assert.ok(pages.length > 10, `There are at least a few pages`); + + for (const page of pages) { + const path = page.path.replace('.gjs.md', '').replace('.md', ''); + const settings: object = a11yChecks[page.path] ?? {}; + + await visit(path); + await waitUntil(() => findAll('nav a').length !== 0); + + for (const theme of ['default', 'dark', 'light'] as const) { + if (theme === 'dark') colorScheme.update('dark'); + if (theme === 'light') colorScheme.update('light'); + await checkA11y(assert, path, theme, settings); + } + + assert + .dom('[data-page-error]') + .doesNotExist(`${page.path}: does not contain [data-page-error]`); + } + }); +}); diff --git a/docs-app/tests/index.html b/docs-app/tests/index.html new file mode 100644 index 0000000..3332b0a --- /dev/null +++ b/docs-app/tests/index.html @@ -0,0 +1,27 @@ + + + + + docs-app Tests + + + +
+
+
+
+
+
+ + + + + + + diff --git a/docs-app/tests/test-helper.ts b/docs-app/tests/test-helper.ts new file mode 100644 index 0000000..d6d7d5f --- /dev/null +++ b/docs-app/tests/test-helper.ts @@ -0,0 +1,24 @@ +import { setApplication } from '@ember/test-helpers'; +import * as QUnit from 'qunit'; +import { setup } from 'qunit-dom'; +import { start as qunitStart } from 'ember-qunit'; + +import Application from 'docs-app/app'; +import config, { enterTestMode } from 'docs-app/config'; + +import { setupMiddlewareReporter } from 'ember-a11y-testing/test-support'; + +QUnit.config.urlConfig.push({ + id: 'debugA11yAudit', + label: 'Log a11y violations', +}); + +export function start() { + enterTestMode(); + setupMiddlewareReporter(); + setApplication(Application.create(config.APP)); + + setup(QUnit.assert); + + qunitStart(); +} diff --git a/docs-app/tsconfig.json b/docs-app/tsconfig.json index 15dbdb7..3369993 100644 --- a/docs-app/tsconfig.json +++ b/docs-app/tsconfig.json @@ -7,7 +7,11 @@ "kolay/virtual", "@embroider/core/virtual", "@glint/ember-tsc/types" - ] + ], + "paths": { + "docs-app/tests/*": ["./tests/*"], + "docs-app/*": ["./src/*"] + } }, - "include": ["src/**/*"] + "include": ["src/**/*", "tests/**/*"] } diff --git a/package.json b/package.json index 1126dd7..d54f538 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "start": "pnpm --filter './packages/*' build; concurrently 'npm:_start:*' --names '_start:'", "start:docs-app": "pnpm --filter './packages/*' build; pnpm --stream --parallel --filter docs-app... start", "start:test-app": "pnpm --filter './packages/*' build; pnpm --stream --parallel --filter test-app... start", - "test": "pnpm turbo --filter test-app test" + "test": "pnpm turbo test" }, "devDependencies": { "concurrently": "^9.0.0", diff --git a/packages/form/LICENSE.md b/packages/form/LICENSE.md new file mode 100644 index 0000000..9438560 --- /dev/null +++ b/packages/form/LICENSE.md @@ -0,0 +1,9 @@ +The MIT License (MIT) + +Copyright (c) 2023 + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/form/README.md b/packages/form/README.md new file mode 100644 index 0000000..5fecfed --- /dev/null +++ b/packages/form/README.md @@ -0,0 +1,42 @@ +# @universal-ember/form + +[![CI](https://github.com/universal-ember/form/actions/workflows/ci.yml/badge.svg?branch=main&event=push)](https://github.com/universal-ember/form/actions/workflows/ci.yml) + +The Ember.js library that distills the common behavior and accessibility best practices of forms into reusable components, without any opinions on specific markup or styling. Use it to build your forms directly, or to build your opinionated forms component kit on top of it. + +## Key Features + +- Semantic form markup with accessibility best practices baked in +- Integrates native HTML5 validation with custom error rendering +- Extension points for custom JavaScript-based validation +- Optional ready-to-use integrations for `ember-changeset` and `yup` based validation +- Support for async state +- TypeScript / Glint support with tight types +- Fully tested + +## Compatibility + +- Ember.js v4.4 or above (CI-verified on v5.8+) +- a modern (vite) app, or a classic build via ember-auto-import v2 / Embroider + +## Installation + +``` +pnpm add @universal-ember/form +``` + +## Usage + +Visit our [documentation website](https://ue-form.pages.dev/). + +## Contributing + +See the [Contributing](CONTRIBUTING.md) guide for details. + +## Credits + +Originally from [CrowdStrike's ember-headless-form](https://github.com/crowdstrike/ember-headless-form) + +## License + +This project is licensed under the [MIT License](LICENSE.md). diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 752e6e8..7ee3ccf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -117,6 +117,9 @@ importers: '@ember/app-tsconfig': specifier: ^2.0.0 version: 2.0.0 + '@ember/test-helpers': + specifier: ^5.4.3 + version: 5.4.3(e27f1fbe8274ab813e761daf623f1bc2) '@embroider/core': specifier: ^4.4.3 version: 4.6.2(6846b34809607f33d3b82fac81bfadd9) @@ -144,9 +147,15 @@ importers: '@rollup/plugin-babel': specifier: ^7.1.0 version: 7.1.0(52ab2595d8ec294d960b34d56caae962) + '@types/qunit': + specifier: ^2.19.14 + version: 2.19.14 '@warp-drive/build-config': specifier: ^5.8.2 version: 5.8.2(441692fd1e7dbc4a7b3cba7430ac7990) + axe-core: + specifier: ^4.12.1 + version: 4.12.1 babel-plugin-ember-template-compilation: specifier: ^4.0.0 version: 4.0.0 @@ -156,6 +165,12 @@ importers: decorator-transforms: specifier: ^2.3.1 version: 2.4.0(@babel/core@7.29.7(supports-color@8.1.1)) + ember-a11y-testing: + specifier: ^8.0.1 + version: 8.0.1(a4953075f03af2f5b5e23c4c2adbcb38) + ember-qunit: + specifier: ^9.1.0 + version: 9.1.0(12446d7b9d303199558330a1329a9ebd) ember-source: specifier: ^7.1.0 version: 7.1.0(07d12e0a83666cb1162306c6a1884631) @@ -171,6 +186,15 @@ importers: prettier-plugin-ember-template-tag: specifier: ^2.1.3 version: 2.1.7(prettier@3.9.5)(supports-color@8.1.1) + qunit: + specifier: ^2.26.0 + version: 2.26.0 + qunit-dom: + specifier: ^3.6.0 + version: 3.6.0 + testem: + specifier: ^3.20.1 + version: 3.20.1(b4cd23e5c9ba151dcc5b9e2687011ab1) typescript: specifier: ~6.0.0 version: 6.0.3 From 5e798f938cdf8d3d5360c080034d70456e5d9e8b Mon Sep 17 00:00:00 2001 From: NullVoxPopuli-ai-agent <268630448+NullVoxPopuli-ai-agent@users.noreply.github.com> Date: Mon, 20 Jul 2026 11:53:31 -0400 Subject: [PATCH 2/2] docs-app: do not track ember-a11y-testing report output The middleware reporter writes a JSON report per test run; two of them were swept into the previous commit and failed prettier in CI. Co-Authored-By: Claude Fable 5 --- docs-app/.gitignore | 3 +++ docs-app/.prettierignore | 1 + docs-app/ember-a11y-report/2026-07-20T15-47-33-999Z.json | 1 - docs-app/ember-a11y-report/2026-07-20T15-49-22-239Z.json | 1 - 4 files changed, 4 insertions(+), 2 deletions(-) delete mode 100644 docs-app/ember-a11y-report/2026-07-20T15-47-33-999Z.json delete mode 100644 docs-app/ember-a11y-report/2026-07-20T15-49-22-239Z.json diff --git a/docs-app/.gitignore b/docs-app/.gitignore index 8b8a365..cd63079 100644 --- a/docs-app/.gitignore +++ b/docs-app/.gitignore @@ -4,3 +4,6 @@ node_modules/ .env* !.env.example tmp/ + +# a11y middleware reporter output (test artifact) +ember-a11y-report/ diff --git a/docs-app/.prettierignore b/docs-app/.prettierignore index 29c69b2..5bc605d 100644 --- a/docs-app/.prettierignore +++ b/docs-app/.prettierignore @@ -1,3 +1,4 @@ dist/ node_modules/ pnpm-lock.yaml +ember-a11y-report/ diff --git a/docs-app/ember-a11y-report/2026-07-20T15-47-33-999Z.json b/docs-app/ember-a11y-report/2026-07-20T15-47-33-999Z.json deleted file mode 100644 index fe51488..0000000 --- a/docs-app/ember-a11y-report/2026-07-20T15-47-33-999Z.json +++ /dev/null @@ -1 +0,0 @@ -[] diff --git a/docs-app/ember-a11y-report/2026-07-20T15-49-22-239Z.json b/docs-app/ember-a11y-report/2026-07-20T15-49-22-239Z.json deleted file mode 100644 index 0637a08..0000000 --- a/docs-app/ember-a11y-report/2026-07-20T15-49-22-239Z.json +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file