@polyxd/verifier 0.2.2 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE +45 -0
- package/README.md +21 -1
- package/dist/browser.d.ts +19 -2
- package/dist/browser.js +50 -17
- package/dist/browser.js.map +1 -1
- package/dist/fingerprint.d.ts +19 -0
- package/dist/fingerprint.js +40 -0
- package/dist/fingerprint.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/patterns.generated.d.ts +3 -0
- package/dist/patterns.generated.js +312 -0
- package/dist/patterns.generated.js.map +1 -0
- package/dist/rendered.d.ts +2 -7
- package/dist/rendered.js.map +1 -1
- package/dist/static.d.ts +17 -2
- package/dist/static.js +32 -6
- package/dist/static.js.map +1 -1
- package/dist/verify.d.ts +7 -1
- package/dist/verify.js +5 -3
- package/dist/verify.js.map +1 -1
- package/harness/README.md +61 -0
- package/harness-dist/assets/index-Cdk80tid.js +56 -0
- package/harness-dist/assets/index-CtDlIYLx.css +1 -0
- package/harness-dist/index.html +2 -2
- package/harness-web-dist/index.html +17 -0
- package/harness-web-dist/polyxd-web.css +2 -0
- package/harness-web-dist/polyxd-web.js +8 -0
- package/package.json +33 -7
- package/harness-dist/assets/index-CwoLLetr.js +0 -56
- package/harness-dist/assets/index-DlVtilOB.css +0 -1
package/NOTICE
CHANGED
|
@@ -135,6 +135,51 @@ Upstream: https://github.com/adobe/spectrum-tokens
|
|
|
135
135
|
|
|
136
136
|
Vendored under `packages/ds-spectrum/scripts/sources/`, with the upstream licence file beside it.
|
|
137
137
|
|
|
138
|
+
## Logos
|
|
139
|
+
|
|
140
|
+
Twelve packs ship their design system's official logo, unaltered, as `logo.svg`
|
|
141
|
+
(Bootstrap and Radix also ship the owner's white version as `logo-white.svg`),
|
|
142
|
+
downloaded from the owner's own site, repository or brand page. Where a design
|
|
143
|
+
system has no mark of its own (Polaris, Spectrum, Primer), the pack ships its
|
|
144
|
+
company's logo. Each is used only to identify the design system a pack is
|
|
145
|
+
modelled on, beside its name. The logos are trademarks of their owners, not
|
|
146
|
+
Polyxd's, and are not covered by Polyxd's licence:
|
|
147
|
+
|
|
148
|
+
- Material 3 (`@polyxd/ds-material3`) — Google LLC
|
|
149
|
+
- Carbon (`@polyxd/ds-carbon`) — IBM Corp.
|
|
150
|
+
- Fluent 2 (`@polyxd/ds-fluent`) — Microsoft Corporation
|
|
151
|
+
- Polaris (`@polyxd/ds-polaris`) — the Shopify logo, Shopify Inc.
|
|
152
|
+
- Spectrum 2 (`@polyxd/ds-spectrum`) — the Adobe logo, Adobe Inc.
|
|
153
|
+
- Primer (`@polyxd/ds-primer`) — the GitHub logo, GitHub, Inc.
|
|
154
|
+
- shadcn/ui (`@polyxd/ds-shadcn`) — shadcn
|
|
155
|
+
- Ant Design (`@polyxd/ds-antd`) — Ant Group
|
|
156
|
+
- Bootstrap (`@polyxd/ds-bootstrap`) — the Bootstrap team
|
|
157
|
+
- Chakra UI (`@polyxd/ds-chakra`) — Chakra Systems Inc.
|
|
158
|
+
- Mantine (`@polyxd/ds-mantine`) — Vitaly Rtishchev
|
|
159
|
+
- Radix UI (`@polyxd/ds-radix`) — WorkOS
|
|
160
|
+
|
|
161
|
+
Google, IBM, Microsoft, Shopify, Adobe and GitHub restrict the use of their
|
|
162
|
+
logos without permission; each pack's manifest (`logo`) and README record the
|
|
163
|
+
source and the owner's guidelines. GOV.UK's crown and logotype are protected and
|
|
164
|
+
may not be used by a service that is not on GOV.UK, so `@polyxd/ds-govuk` ships
|
|
165
|
+
no logo and is named in words only.
|
|
166
|
+
|
|
167
|
+
The twelve template packs' `logo.svg` files are Polyxd's own marks, drawn from
|
|
168
|
+
each template's tokens.
|
|
169
|
+
|
|
170
|
+
## Template packs
|
|
171
|
+
|
|
172
|
+
Twelve packs are original Polyxd templates, not readings of any design system:
|
|
173
|
+
`@polyxd/ds-sketch`, `@polyxd/ds-wireframe`, `@polyxd/ds-editorial`,
|
|
174
|
+
`@polyxd/ds-brutalist`, `@polyxd/ds-glass`, `@polyxd/ds-terminal`,
|
|
175
|
+
`@polyxd/ds-pastel`, `@polyxd/ds-civic`, `@polyxd/ds-finance`,
|
|
176
|
+
`@polyxd/ds-health`, `@polyxd/ds-neon` and `@polyxd/ds-mono`. Every value in
|
|
177
|
+
them was chosen for the template, they vendor nothing, and they are Apache-2.0
|
|
178
|
+
like the rest of Polyxd. The typefaces some of them name (Caveat, Patrick Hand,
|
|
179
|
+
Fraunces, Nunito, JetBrains Mono, Space Grotesk) are under the SIL Open Font
|
|
180
|
+
License and are loaded from Google Fonts by the page that uses them, never
|
|
181
|
+
distributed here.
|
|
182
|
+
|
|
138
183
|
## Specification
|
|
139
184
|
|
|
140
185
|
### A2UI
|
package/README.md
CHANGED
|
@@ -3,11 +3,25 @@
|
|
|
3
3
|
Scores a Polyxd UI document the way it will actually be used: rendered, in every design system, by people and by agents.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
-
npm install -D @polyxd/verifier
|
|
6
|
+
npm install -D @polyxd/verifier playwright
|
|
7
7
|
npx playwright install chromium # once
|
|
8
8
|
npx polyxd-verify my-ui.json --themes carbon --modes light --widths 390 --json report.json
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
Playwright is an optional peer dependency: the rendered checks need it, so install it beside the verifier as above. Nothing loads it until a browser is launched, and without it `launch()` says what to install.
|
|
12
|
+
|
|
13
|
+
## Document checks only
|
|
14
|
+
|
|
15
|
+
`@polyxd/verifier/static` is the document checks on their own: `staticAudit(doc, { registry, rules, emphasisBudget, missingData })`. It imports no Playwright and reads no files (the spec's pattern checks are built into it), so it runs in Node, browsers and Workers, and installing the verifier for it alone doesn't pull in Playwright.
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { staticAudit } from "@polyxd/verifier/static";
|
|
19
|
+
|
|
20
|
+
const findings = staticAudit(doc, { registry });
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`@polyxd/runtime` uses it as its default checks, and `@polyxd/mcp` and `@polyxd/server` use it for their verify tools. `npm run build:patterns -w @polyxd/verifier` regenerates the pattern checks from the spec; a test fails when they are out of date.
|
|
24
|
+
|
|
11
25
|
Inside the Polyxd repository, `npm run verify:examples -w @polyxd/verifier` runs every spec example through the full matrix with agent tasks.
|
|
12
26
|
|
|
13
27
|
## What it checks
|
|
@@ -21,6 +35,12 @@ Inside the Polyxd repository, `npm run verify:examples -w @polyxd/verifier` runs
|
|
|
21
35
|
|
|
22
36
|
Default matrix: Material 3, Carbon and Ant Design × light and dark × 390px and 1100px.
|
|
23
37
|
|
|
38
|
+
## Renderers
|
|
39
|
+
|
|
40
|
+
The verifier renders through a **harness**: a page that draws `window.__PXD__ = { document, theme, mode }` into `#root` and records what the surface sends (`harness/README.md` is the contract). Two ship: the React renderer's (`harness/`) and the Web Components renderer's (`harness-web/`, `@polyxd/web`'s preview bundle). Pass any renderer's page as `verifyDocument(doc, { harness: { url } | { html } })`; `HARNESSES.react` and `HARNESSES.web` name the shipped ones.
|
|
41
|
+
|
|
42
|
+
`npm run conformance -w @polyxd/verifier` renders every spec example and every demo document with both shipped renderers in 13 packs × light/dark × 390/1100 and compares the rendered fingerprint (`fingerprint: true` on `verifyDocument`: components in order, ARIA snapshot, visible text; `compareFingerprints` explains a difference), the findings and the agent tasks. Both must score 100 and the fingerprints must match.
|
|
43
|
+
|
|
24
44
|
## Score (v0)
|
|
25
45
|
|
|
26
46
|
Start at 100. Each distinct failing error check costs 20, each distinct warning 4, and failed agent runs cost up to 40 in proportion. A document that fails the schema scores 0. The weights are a starting point; the gold set (`bench/gold`) exists to calibrate them against designers' rankings.
|
package/dist/browser.d.ts
CHANGED
|
@@ -1,16 +1,33 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { Browser, Page } from "playwright";
|
|
2
|
+
/**
|
|
3
|
+
* A page that renders `window.__PXD__` (see harness/README.md): the built-in React harness, the
|
|
4
|
+
* Web Components harness, or any renderer's own page, by URL or as a directory of static files.
|
|
5
|
+
*/
|
|
6
|
+
export type RendererHarness = {
|
|
7
|
+
name?: string;
|
|
8
|
+
url: string;
|
|
9
|
+
} | {
|
|
10
|
+
name?: string;
|
|
11
|
+
html: string | URL;
|
|
12
|
+
};
|
|
13
|
+
/** The harnesses this package ships, built by `npm run build:harness`. */
|
|
14
|
+
export declare const HARNESSES: Record<"react" | "web", RendererHarness>;
|
|
2
15
|
export interface RenderTarget {
|
|
3
16
|
theme: string;
|
|
4
17
|
mode: "light" | "dark";
|
|
5
18
|
/** Viewport width in CSS pixels, e.g. 390 (phone) or 1100 (desktop) */
|
|
6
19
|
width: number;
|
|
7
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Starts headless Chromium for the rendered checks. Playwright is an optional peer dependency, so
|
|
23
|
+
* nothing loads it until now: the document checks (`@polyxd/verifier/static`) never need it.
|
|
24
|
+
*/
|
|
8
25
|
export declare function launch(): Promise<Browser>;
|
|
9
26
|
/**
|
|
10
27
|
* Opens a page with one document rendered in one theme/mode/width. The harness is served from
|
|
11
28
|
* disk through request interception, so nothing listens on a port.
|
|
12
29
|
*/
|
|
13
|
-
export declare function renderPage(browser: Browser, document: unknown, target: RenderTarget): Promise<{
|
|
30
|
+
export declare function renderPage(browser: Browser, document: unknown, target: RenderTarget, harness?: RendererHarness): Promise<{
|
|
14
31
|
page: Page;
|
|
15
32
|
errors: string[];
|
|
16
33
|
}>;
|
package/dist/browser.js
CHANGED
|
@@ -1,37 +1,70 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { extname } from "node:path";
|
|
4
|
-
import { chromium } from "playwright";
|
|
5
|
-
const HARNESS = new URL("../harness-dist/", import.meta.url);
|
|
6
4
|
const ORIGIN = "http://harness.polyxd.local";
|
|
5
|
+
/** The harnesses this package ships, built by `npm run build:harness`. */
|
|
6
|
+
export const HARNESSES = {
|
|
7
|
+
react: { name: "react", html: new URL("../harness-dist/index.html", import.meta.url) },
|
|
8
|
+
web: { name: "web", html: new URL("../harness-web-dist/index.html", import.meta.url) },
|
|
9
|
+
};
|
|
7
10
|
const TYPES = { ".html": "text/html", ".js": "text/javascript", ".css": "text/css", ".svg": "image/svg+xml" };
|
|
8
|
-
|
|
11
|
+
let axeSource;
|
|
12
|
+
const axe = () => (axeSource ??= readFile(createRequire(import.meta.url).resolve("axe-core/axe.min.js"), "utf8"));
|
|
13
|
+
/**
|
|
14
|
+
* Starts headless Chromium for the rendered checks. Playwright is an optional peer dependency, so
|
|
15
|
+
* nothing loads it until now: the document checks (`@polyxd/verifier/static`) never need it.
|
|
16
|
+
*/
|
|
9
17
|
export async function launch() {
|
|
10
|
-
|
|
18
|
+
let playwright;
|
|
19
|
+
try {
|
|
20
|
+
playwright = await import("playwright");
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
if (err.code !== "ERR_MODULE_NOT_FOUND")
|
|
24
|
+
throw err;
|
|
25
|
+
throw new Error("The rendered checks need Playwright, which @polyxd/verifier doesn't install for you: run `npm install -D playwright && npx playwright install chromium`. The document checks alone are in @polyxd/verifier/static.");
|
|
26
|
+
}
|
|
27
|
+
return playwright.chromium.launch();
|
|
11
28
|
}
|
|
12
29
|
/**
|
|
13
30
|
* Opens a page with one document rendered in one theme/mode/width. The harness is served from
|
|
14
31
|
* disk through request interception, so nothing listens on a port.
|
|
15
32
|
*/
|
|
16
|
-
export async function renderPage(browser, document, target) {
|
|
33
|
+
export async function renderPage(browser, document, target, harness = HARNESSES.react) {
|
|
17
34
|
const page = await browser.newPage({ viewport: { width: target.width, height: 900 }, reducedMotion: "reduce" });
|
|
18
35
|
const errors = [];
|
|
19
36
|
page.on("pageerror", (e) => errors.push(e.message));
|
|
20
37
|
page.on("console", (m) => m.type() === "error" && errors.push(m.text()));
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
38
|
+
let url;
|
|
39
|
+
if ("url" in harness)
|
|
40
|
+
url = harness.url;
|
|
41
|
+
else {
|
|
42
|
+
// A directory of static files is served from disk through request interception, so nothing listens on a port.
|
|
43
|
+
const index = harness.html instanceof URL ? harness.html : new URL(harness.html, `file://${process.cwd()}/`);
|
|
44
|
+
const dir = new URL("./", index);
|
|
45
|
+
await page.route(`${ORIGIN}/**`, async (route) => {
|
|
46
|
+
const path = new URL(route.request().url()).pathname.slice(1) || "index.html";
|
|
47
|
+
try {
|
|
48
|
+
const body = await readFile(new URL(path, dir));
|
|
49
|
+
await route.fulfill({ body, contentType: TYPES[extname(path)] ?? "application/octet-stream" });
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
await route.fulfill({ status: 404 });
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
url = `${ORIGIN}/${index.pathname.split("/").pop() ?? "index.html"}`;
|
|
56
|
+
}
|
|
31
57
|
await page.addInitScript((cfg) => (window.__PXD__ = cfg), { document, theme: target.theme, mode: target.mode });
|
|
32
|
-
await page.goto(
|
|
58
|
+
await page.goto(url);
|
|
33
59
|
await page.waitForFunction(() => window.__pxdReady === true, undefined, { timeout: 10_000 });
|
|
34
|
-
|
|
60
|
+
// Entrances (a panel fading in) finish before anything is measured, so a contrast or layout
|
|
61
|
+
// reading never depends on how far into an animation a renderer's first paint was. Endless
|
|
62
|
+
// animations (a spinner) are left running.
|
|
63
|
+
await page.evaluate(() => {
|
|
64
|
+
const doc = globalThis.document;
|
|
65
|
+
return Promise.all(doc.getAnimations().filter((a) => a.effect?.getTiming().iterations !== Infinity).map((a) => a.finished.catch(() => undefined)));
|
|
66
|
+
});
|
|
67
|
+
await page.addScriptTag({ content: await axe() });
|
|
35
68
|
return { page, errors };
|
|
36
69
|
}
|
|
37
70
|
/** Actions the rendered UI has sent to the host so far. */
|
package/dist/browser.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser.js","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"browser.js","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Page } from "playwright";
|
|
2
|
+
/**
|
|
3
|
+
* What a rendered surface is, independent of who rendered it: the components in reading order,
|
|
4
|
+
* the accessibility tree (roles, names, states, in order) and the visible text. Two renderers
|
|
5
|
+
* are conformant when a document gives the same fingerprint in each, in every pack, mode and
|
|
6
|
+
* width. This is stricter than consistency.ts's signature, which reads the document; this
|
|
7
|
+
* reads the page.
|
|
8
|
+
*/
|
|
9
|
+
export interface Fingerprint {
|
|
10
|
+
/** [component id, component name] for every element a component rendered, in DOM order */
|
|
11
|
+
components: [string, string][];
|
|
12
|
+
/** Playwright's ARIA snapshot of the surface: roles and accessible names in reading order */
|
|
13
|
+
aria: string;
|
|
14
|
+
/** The surface's visible text, whitespace collapsed */
|
|
15
|
+
text: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function renderedFingerprint(page: Page): Promise<Fingerprint>;
|
|
18
|
+
/** The differences between two fingerprints, in words; empty when they match. */
|
|
19
|
+
export declare function compareFingerprints(a: Fingerprint, b: Fingerprint, aName?: string, bName?: string): string[];
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export async function renderedFingerprint(page) {
|
|
2
|
+
const surface = page.locator(".pxd-surface").first();
|
|
3
|
+
const [components, text] = await page.evaluate(() => {
|
|
4
|
+
const root = document.querySelector(".pxd-surface");
|
|
5
|
+
const components = [...root.querySelectorAll("[data-pxd-id]")].map((el) => [el.dataset.pxdId, el.dataset.pxdComponent ?? ""]);
|
|
6
|
+
return [components, root.innerText.replace(/\s+/g, " ").trim()];
|
|
7
|
+
});
|
|
8
|
+
const aria = normaliseAria(await surface.ariaSnapshot());
|
|
9
|
+
return { components, aria, text };
|
|
10
|
+
}
|
|
11
|
+
/** Generated ids differ per renderer (React's :r1:, the Web renderer's pxd-…); they name nothing a person sees. */
|
|
12
|
+
function normaliseAria(snapshot) {
|
|
13
|
+
return snapshot
|
|
14
|
+
.split("\n")
|
|
15
|
+
.map((l) => l.replace(/\s+$/, "").replace(/^(\s*- \/url: ")#[^"]*"$/, "$1#id\""))
|
|
16
|
+
.join("\n");
|
|
17
|
+
}
|
|
18
|
+
/** The differences between two fingerprints, in words; empty when they match. */
|
|
19
|
+
export function compareFingerprints(a, b, aName = "react", bName = "web") {
|
|
20
|
+
const out = [];
|
|
21
|
+
if (a.components.length !== b.components.length || a.components.some(([id, c], i) => b.components[i]?.[0] !== id || b.components[i]?.[1] !== c)) {
|
|
22
|
+
const first = a.components.findIndex(([id, c], i) => b.components[i]?.[0] !== id || b.components[i]?.[1] !== c);
|
|
23
|
+
const at = first < 0 ? a.components.length : first;
|
|
24
|
+
out.push(`components differ at #${at}: ${aName} ${JSON.stringify(a.components[at])} vs ${bName} ${JSON.stringify(b.components[at])} (${a.components.length} vs ${b.components.length})`);
|
|
25
|
+
}
|
|
26
|
+
if (a.aria !== b.aria) {
|
|
27
|
+
const al = a.aria.split("\n");
|
|
28
|
+
const bl = b.aria.split("\n");
|
|
29
|
+
const i = al.findIndex((l, n) => bl[n] !== l);
|
|
30
|
+
out.push(`aria differs at line ${i + 1}:\n ${aName}: ${al[i] ?? "(end)"}\n ${bName}: ${bl[i] ?? "(end)"}`);
|
|
31
|
+
}
|
|
32
|
+
if (a.text !== b.text) {
|
|
33
|
+
let i = 0;
|
|
34
|
+
while (i < a.text.length && a.text[i] === b.text[i])
|
|
35
|
+
i++;
|
|
36
|
+
out.push(`text differs at ${i}: ${aName} "…${a.text.slice(Math.max(0, i - 30), i + 40)}" vs ${bName} "…${b.text.slice(Math.max(0, i - 30), i + 40)}"`);
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=fingerprint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fingerprint.js","sourceRoot":"","sources":["../src/fingerprint.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,5 +3,6 @@ export { staticAudit, type StaticOptions } from "./static.ts";
|
|
|
3
3
|
export { axeAudit, layoutAudit, type Finding } from "./rendered.ts";
|
|
4
4
|
export { runTask, type Task, type Step, type AgentResult } from "./agent.ts";
|
|
5
5
|
export { compare, signature, type Consistency } from "./consistency.ts";
|
|
6
|
-
export { launch, renderPage, type RenderTarget } from "./browser.ts";
|
|
6
|
+
export { launch, renderPage, HARNESSES, type RenderTarget, type RendererHarness } from "./browser.ts";
|
|
7
|
+
export { renderedFingerprint, compareFingerprints, type Fingerprint } from "./fingerprint.ts";
|
|
7
8
|
export { rewardFor, wiredCapabilities, probeActionable, coverageOf, type Reward, type RequestExpectation } from "./reward.ts";
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ export { staticAudit } from "./static.js";
|
|
|
3
3
|
export { axeAudit, layoutAudit } from "./rendered.js";
|
|
4
4
|
export { runTask } from "./agent.js";
|
|
5
5
|
export { compare, signature } from "./consistency.js";
|
|
6
|
-
export { launch, renderPage } from "./browser.js";
|
|
6
|
+
export { launch, renderPage, HARNESSES } from "./browser.js";
|
|
7
|
+
export { renderedFingerprint, compareFingerprints } from "./fingerprint.js";
|
|
7
8
|
export { rewardFor, wiredCapabilities, probeActionable, coverageOf } from "./reward.js";
|
|
8
9
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,QAAQ,EAAsD,MAAM,aAAa,CAAC;AAClH,OAAO,EAAE,WAAW,EAAsB,MAAM,aAAa,CAAC;AAC9D,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAgB,MAAM,eAAe,CAAC;AACpE,OAAO,EAAE,OAAO,EAA0C,MAAM,YAAY,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,SAAS,EAAoB,MAAM,kBAAkB,CAAC;AACxE,OAAO,EAAE,MAAM,EAAE,UAAU,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,QAAQ,EAAsD,MAAM,aAAa,CAAC;AAClH,OAAO,EAAE,WAAW,EAAsB,MAAM,aAAa,CAAC;AAC9D,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAgB,MAAM,eAAe,CAAC;AACpE,OAAO,EAAE,OAAO,EAA0C,MAAM,YAAY,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,SAAS,EAAoB,MAAM,kBAAkB,CAAC;AACxE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAA2C,MAAM,cAAc,CAAC;AACtG,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAoB,MAAM,kBAAkB,CAAC;AAC9F,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,eAAe,EAAE,UAAU,EAAwC,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
/** Each spec pattern's checks, by pattern id. */
|
|
2
|
+
export const PATTERN_CHECKS = {
|
|
3
|
+
"compare-and-choose": [
|
|
4
|
+
{
|
|
5
|
+
"id": "one-comparison",
|
|
6
|
+
"description": "Exactly one Comparison",
|
|
7
|
+
"severity": "error",
|
|
8
|
+
"rule": {
|
|
9
|
+
"check": "contains",
|
|
10
|
+
"component": "Comparison",
|
|
11
|
+
"min": 1,
|
|
12
|
+
"max": 1
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"id": "can-choose",
|
|
17
|
+
"description": "Each option can be chosen",
|
|
18
|
+
"severity": "error",
|
|
19
|
+
"rule": {
|
|
20
|
+
"check": "requires",
|
|
21
|
+
"component": "Comparison",
|
|
22
|
+
"props": [
|
|
23
|
+
"choose"
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"id": "explains",
|
|
29
|
+
"description": "A one-line summary or the recommendation's reason helps the decision",
|
|
30
|
+
"severity": "warning",
|
|
31
|
+
"rule": {
|
|
32
|
+
"check": "anyOf",
|
|
33
|
+
"checks": [
|
|
34
|
+
{
|
|
35
|
+
"check": "requires",
|
|
36
|
+
"component": "Comparison",
|
|
37
|
+
"props": [
|
|
38
|
+
"summary"
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"check": "requires",
|
|
43
|
+
"component": "Comparison",
|
|
44
|
+
"props": [
|
|
45
|
+
"recommendedReason"
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
]
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
"confirm-destructive": [
|
|
53
|
+
{
|
|
54
|
+
"id": "root-is-confirm",
|
|
55
|
+
"description": "The surface is a confirmation dialog",
|
|
56
|
+
"severity": "error",
|
|
57
|
+
"rule": {
|
|
58
|
+
"check": "rootIs",
|
|
59
|
+
"components": [
|
|
60
|
+
"Confirm"
|
|
61
|
+
]
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"id": "states-consequence",
|
|
66
|
+
"description": "It says what will happen and whether it can be undone",
|
|
67
|
+
"severity": "error",
|
|
68
|
+
"rule": {
|
|
69
|
+
"check": "requires",
|
|
70
|
+
"component": "Confirm",
|
|
71
|
+
"props": [
|
|
72
|
+
"consequence"
|
|
73
|
+
]
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"id": "specific-confirm-label",
|
|
78
|
+
"description": "The confirm button says what it does, never just 'OK' or 'Yes'",
|
|
79
|
+
"severity": "error",
|
|
80
|
+
"rule": {
|
|
81
|
+
"check": "noLabelMatches",
|
|
82
|
+
"pattern": "^(OK|Ok|Yes|Confirm|Submit|Done)$"
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"id": "typed-for-destructive",
|
|
87
|
+
"description": "Irreversible, high-impact deletions ask the user to type a confirmation",
|
|
88
|
+
"severity": "warning",
|
|
89
|
+
"rule": {
|
|
90
|
+
"check": "requires",
|
|
91
|
+
"component": "Confirm",
|
|
92
|
+
"where": {
|
|
93
|
+
"severity": "destructive"
|
|
94
|
+
},
|
|
95
|
+
"props": [
|
|
96
|
+
"typeToConfirm"
|
|
97
|
+
]
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
],
|
|
101
|
+
"filter-and-browse": [
|
|
102
|
+
{
|
|
103
|
+
"id": "has-results",
|
|
104
|
+
"description": "Results are shown as a Collection or Table",
|
|
105
|
+
"severity": "error",
|
|
106
|
+
"rule": {
|
|
107
|
+
"check": "anyOf",
|
|
108
|
+
"checks": [
|
|
109
|
+
{
|
|
110
|
+
"check": "contains",
|
|
111
|
+
"component": "Collection"
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"check": "contains",
|
|
115
|
+
"component": "Table"
|
|
116
|
+
}
|
|
117
|
+
]
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"id": "filters-first",
|
|
122
|
+
"description": "Filters come before the results",
|
|
123
|
+
"severity": "error",
|
|
124
|
+
"rule": {
|
|
125
|
+
"check": "precedes",
|
|
126
|
+
"before": [
|
|
127
|
+
"TextInput",
|
|
128
|
+
"Choice",
|
|
129
|
+
"RangeInput",
|
|
130
|
+
"DateInput"
|
|
131
|
+
],
|
|
132
|
+
"after": [
|
|
133
|
+
"Collection",
|
|
134
|
+
"Table"
|
|
135
|
+
]
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
"id": "empty-state",
|
|
140
|
+
"description": "Empty results explain what to do",
|
|
141
|
+
"severity": "error",
|
|
142
|
+
"rule": {
|
|
143
|
+
"check": "allOf",
|
|
144
|
+
"checks": [
|
|
145
|
+
{
|
|
146
|
+
"check": "requires",
|
|
147
|
+
"component": "Collection",
|
|
148
|
+
"props": [
|
|
149
|
+
"empty"
|
|
150
|
+
]
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
"check": "requires",
|
|
154
|
+
"component": "Table",
|
|
155
|
+
"props": [
|
|
156
|
+
"empty"
|
|
157
|
+
]
|
|
158
|
+
}
|
|
159
|
+
]
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
],
|
|
163
|
+
"multi-step-form": [
|
|
164
|
+
{
|
|
165
|
+
"id": "form-or-steps",
|
|
166
|
+
"description": "Inputs are collected in a Form or in Steps",
|
|
167
|
+
"severity": "error",
|
|
168
|
+
"rule": {
|
|
169
|
+
"check": "anyOf",
|
|
170
|
+
"checks": [
|
|
171
|
+
{
|
|
172
|
+
"check": "contains",
|
|
173
|
+
"component": "Form"
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
"check": "contains",
|
|
177
|
+
"component": "Steps"
|
|
178
|
+
}
|
|
179
|
+
]
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
"id": "short-views",
|
|
184
|
+
"description": "No more than 6 inputs are visible at once; longer tasks are split into steps",
|
|
185
|
+
"severity": "error",
|
|
186
|
+
"rule": {
|
|
187
|
+
"check": "maxInputsPerView",
|
|
188
|
+
"max": 6
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
"id": "specific-submit",
|
|
193
|
+
"description": "The submit button says what happens next",
|
|
194
|
+
"severity": "warning",
|
|
195
|
+
"rule": {
|
|
196
|
+
"check": "noLabelMatches",
|
|
197
|
+
"pattern": "^(OK|Ok|Yes|Confirm|Submit|Done)$"
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
],
|
|
201
|
+
"review-and-submit": [
|
|
202
|
+
{
|
|
203
|
+
"id": "has-summary",
|
|
204
|
+
"description": "What will be submitted is shown as label/value pairs",
|
|
205
|
+
"severity": "error",
|
|
206
|
+
"rule": {
|
|
207
|
+
"check": "contains",
|
|
208
|
+
"component": "DetailList"
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
"id": "submits",
|
|
213
|
+
"description": "There is a way to submit",
|
|
214
|
+
"severity": "error",
|
|
215
|
+
"rule": {
|
|
216
|
+
"check": "anyOf",
|
|
217
|
+
"checks": [
|
|
218
|
+
{
|
|
219
|
+
"check": "contains",
|
|
220
|
+
"component": "Form"
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
"check": "contains",
|
|
224
|
+
"component": "Steps"
|
|
225
|
+
}
|
|
226
|
+
]
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
"id": "summary-before-inputs",
|
|
231
|
+
"description": "Review content comes before any last inputs",
|
|
232
|
+
"severity": "warning",
|
|
233
|
+
"rule": {
|
|
234
|
+
"check": "anyOf",
|
|
235
|
+
"checks": [
|
|
236
|
+
{
|
|
237
|
+
"check": "not",
|
|
238
|
+
"checks": [
|
|
239
|
+
{
|
|
240
|
+
"check": "contains",
|
|
241
|
+
"component": "Toggle"
|
|
242
|
+
}
|
|
243
|
+
]
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
"check": "precedes",
|
|
247
|
+
"before": [
|
|
248
|
+
"DetailList"
|
|
249
|
+
],
|
|
250
|
+
"after": [
|
|
251
|
+
"Toggle"
|
|
252
|
+
]
|
|
253
|
+
}
|
|
254
|
+
]
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
"id": "commitment-label",
|
|
259
|
+
"description": "The submit label states the commitment, not 'Submit'",
|
|
260
|
+
"severity": "error",
|
|
261
|
+
"rule": {
|
|
262
|
+
"check": "noLabelMatches",
|
|
263
|
+
"pattern": "^(OK|Ok|Yes|Confirm|Submit|Done|Continue)$"
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
],
|
|
267
|
+
"undo-over-confirm": [
|
|
268
|
+
{
|
|
269
|
+
"id": "has-undo-status",
|
|
270
|
+
"description": "An undo notice says what happened",
|
|
271
|
+
"severity": "error",
|
|
272
|
+
"rule": {
|
|
273
|
+
"check": "contains",
|
|
274
|
+
"component": "Status",
|
|
275
|
+
"where": {
|
|
276
|
+
"kind": "undo"
|
|
277
|
+
},
|
|
278
|
+
"min": 1
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
"id": "undo-has-action",
|
|
283
|
+
"description": "The notice offers Undo",
|
|
284
|
+
"severity": "error",
|
|
285
|
+
"rule": {
|
|
286
|
+
"check": "requires",
|
|
287
|
+
"component": "Status",
|
|
288
|
+
"where": {
|
|
289
|
+
"kind": "undo"
|
|
290
|
+
},
|
|
291
|
+
"props": [
|
|
292
|
+
"action"
|
|
293
|
+
]
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
"id": "no-confirm",
|
|
298
|
+
"description": "Nothing asks for confirmation first",
|
|
299
|
+
"severity": "warning",
|
|
300
|
+
"rule": {
|
|
301
|
+
"check": "not",
|
|
302
|
+
"checks": [
|
|
303
|
+
{
|
|
304
|
+
"check": "contains",
|
|
305
|
+
"component": "Confirm"
|
|
306
|
+
}
|
|
307
|
+
]
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
]
|
|
311
|
+
};
|
|
312
|
+
//# sourceMappingURL=patterns.generated.js.map
|