tessera-learn 0.2.0 → 0.2.2
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/AGENTS.md +297 -535
- package/README.md +3 -3
- package/dist/{audit-BA5o0ick.js → audit-B9VHgVjk.js} +195 -42
- package/dist/{audit-BA5o0ick.js.map → audit-B9VHgVjk.js.map} +1 -1
- package/dist/{build-commands-C0OnV-Vg.js → build-commands-D127jw0J.js} +2 -2
- package/dist/{build-commands-C0OnV-Vg.js.map → build-commands-D127jw0J.js.map} +1 -1
- package/dist/{inline-config-CroQ-_2Y.js → inline-config-eHjv9XuA.js} +2 -2
- package/dist/{inline-config-CroQ-_2Y.js.map → inline-config-eHjv9XuA.js.map} +1 -1
- package/dist/plugin/cli.js +27 -9
- package/dist/plugin/cli.js.map +1 -1
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +2 -2
- package/dist/{plugin-W_rk3Pit.js → plugin--8H9xQIl.js} +2 -2
- package/dist/{plugin-W_rk3Pit.js.map → plugin--8H9xQIl.js.map} +1 -1
- package/package.json +1 -1
- package/src/components/FillInTheBlank.svelte +3 -27
- package/src/components/Matching.svelte +4 -26
- package/src/components/MultipleChoice.svelte +8 -27
- package/src/components/QuestionShell.svelte +35 -0
- package/src/components/Sorting.svelte +4 -26
- package/src/plugin/a11y/audit.ts +306 -45
- package/src/plugin/course-root.ts +37 -9
- package/src/runtime/App.svelte +20 -1
- package/src/runtime/adapters/cmi5.ts +5 -14
- package/src/runtime/adapters/index.ts +41 -38
- package/src/runtime/adapters/scorm12.ts +1 -1
- package/src/virtual.d.ts +6 -0
package/README.md
CHANGED
|
@@ -12,11 +12,11 @@ You probably don't want to install this package directly. Use the scaffolder:
|
|
|
12
12
|
pnpm create tessera@latest my-course
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
That creates a workspace with Tessera wired up, a seed course, and
|
|
15
|
+
That creates a workspace with Tessera wired up, a seed course, and a root `AGENTS.md` that points to the authoring guide. Add more courses with `pnpm tessera new <name>`.
|
|
16
16
|
|
|
17
17
|
## What's included
|
|
18
18
|
|
|
19
|
-
- **Hooks** (`tessera-learn`): `useQuestion`, `useQuiz`, `useNavigation`, `useProgress`, `usePersistence`, `useXAPI`.
|
|
19
|
+
- **Hooks** (`tessera-learn`): `useQuestion`, `useQuiz`, `useNavigation`, `useProgress`, `useCompletion`, `usePersistence`, `useXAPI`.
|
|
20
20
|
- **Vite plugin** (`tessera-learn/plugin`): `tesseraPlugin()` — wires page/layout discovery, the LMS adapter, the `$shared` alias, and the export pipeline. The `tessera` CLI (`new`/`dev`/`export`/`validate`/`a11y`/`check`) runs Vite with this plugin for you, so scaffolded workspaces need no `vite.config.js`.
|
|
21
21
|
- **Built-in components** (`tessera-learn`): `Callout`, `Image`, `Audio`, `Video`, `Accordion` / `AccordionItem`, `Carousel` / `CarouselSlide`, `RevealModal`, `Quiz`, `MultipleChoice`, `FillInTheBlank`, `Matching`, `Sorting`, `DefaultLayout`.
|
|
22
22
|
- **LMS adapters**: SCORM 1.2, SCORM 2004 4th Edition, cmi5, static Web — selected via `course.config.js` `export.standard`.
|
|
@@ -26,7 +26,7 @@ See `AGENTS.md` for usage, signatures, and authoring conventions.
|
|
|
26
26
|
|
|
27
27
|
## Documentation
|
|
28
28
|
|
|
29
|
-
The full authoring guide ships with this package at `node_modules/tessera-learn/AGENTS.md
|
|
29
|
+
The full authoring guide ships with this package at `node_modules/tessera-learn/AGENTS.md` — scaffolded projects get a small root `AGENTS.md` that points to it — and is on [GitHub](https://github.com/redmodd/tessera/blob/main/packages/tessera-learn/AGENTS.md).
|
|
30
30
|
|
|
31
31
|
## License
|
|
32
32
|
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
1
2
|
import { basename, dirname, extname, relative, resolve } from "node:path";
|
|
2
3
|
import { existsSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
3
4
|
import JSON5 from "json5";
|
|
4
5
|
import { Parser } from "acorn";
|
|
5
6
|
import { tsPlugin } from "@sveltejs/acorn-typescript";
|
|
6
7
|
import { parse } from "svelte/compiler";
|
|
8
|
+
import { spawn } from "node:child_process";
|
|
7
9
|
//#region src/plugin/ast.ts
|
|
8
10
|
const rootCache = /* @__PURE__ */ new Map();
|
|
9
11
|
/** Drop every cached root. Call at the start of a run to scope the cache. */
|
|
@@ -1385,9 +1387,132 @@ function axeTags(standard) {
|
|
|
1385
1387
|
function axeIgnoreRules(ignore) {
|
|
1386
1388
|
return ignore.filter((id) => !id.startsWith("tessera/") && !id.startsWith("a11y_"));
|
|
1387
1389
|
}
|
|
1390
|
+
const MAX_HTML_LENGTH = 200;
|
|
1391
|
+
const MAX_ELEMENTS_SHOWN = 5;
|
|
1392
|
+
function mapNodeDetail(node) {
|
|
1393
|
+
const target = Array.isArray(node?.target) ? node.target.flat(Infinity).join(" ") : String(node?.target ?? "");
|
|
1394
|
+
const html = String(node?.html ?? "");
|
|
1395
|
+
return {
|
|
1396
|
+
target,
|
|
1397
|
+
html: html.length > MAX_HTML_LENGTH ? `${html.slice(0, MAX_HTML_LENGTH - 1)}…` : html,
|
|
1398
|
+
summary: String(node?.failureSummary ?? "").replace(/\s*\n\s*/g, " ").trim()
|
|
1399
|
+
};
|
|
1400
|
+
}
|
|
1401
|
+
function mapViolation(v) {
|
|
1402
|
+
return {
|
|
1403
|
+
id: v.id,
|
|
1404
|
+
impact: v.impact ?? null,
|
|
1405
|
+
help: v.help,
|
|
1406
|
+
helpUrl: v.helpUrl,
|
|
1407
|
+
nodes: v.nodes.length,
|
|
1408
|
+
elements: v.nodes.map(mapNodeDetail)
|
|
1409
|
+
};
|
|
1410
|
+
}
|
|
1388
1411
|
function isMissingBrowserError(message) {
|
|
1389
1412
|
return /Executable doesn't exist|playwright install/i.test(message);
|
|
1390
1413
|
}
|
|
1414
|
+
function isMissingDepsError(message) {
|
|
1415
|
+
return /Host system is missing dependencies|missing dependencies to run browser/i.test(message);
|
|
1416
|
+
}
|
|
1417
|
+
const INSTALL_CHROMIUM = "pnpm exec playwright install chromium";
|
|
1418
|
+
const PLAYWRIGHT_SPECS = ["playwright", "@playwright/test"];
|
|
1419
|
+
function reportManualInstall(lead) {
|
|
1420
|
+
console.error(`\x1b[31m[tessera a11y]\x1b[0m ${lead}\n Install it once:\n ${INSTALL_CHROMIUM}`);
|
|
1421
|
+
}
|
|
1422
|
+
function resolvePlaywrightBin() {
|
|
1423
|
+
const require = createRequire(import.meta.url);
|
|
1424
|
+
for (const spec of PLAYWRIGHT_SPECS) try {
|
|
1425
|
+
const pkgPath = require.resolve(`${spec}/package.json`);
|
|
1426
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
1427
|
+
const binRel = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.playwright;
|
|
1428
|
+
if (!binRel) continue;
|
|
1429
|
+
return {
|
|
1430
|
+
command: process.execPath,
|
|
1431
|
+
args: [
|
|
1432
|
+
resolve(dirname(pkgPath), binRel),
|
|
1433
|
+
"install",
|
|
1434
|
+
"chromium"
|
|
1435
|
+
]
|
|
1436
|
+
};
|
|
1437
|
+
} catch {
|
|
1438
|
+
continue;
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
const INSTALL_TIMEOUT_MS = 10 * 6e4;
|
|
1442
|
+
async function installChromium(workspaceRoot, spawnFn = spawn, timeoutMs = INSTALL_TIMEOUT_MS) {
|
|
1443
|
+
const bin = resolvePlaywrightBin();
|
|
1444
|
+
if (!bin) {
|
|
1445
|
+
console.error(`\x1b[31m[tessera a11y]\x1b[0m Could not locate the Playwright CLI to install Chromium.`);
|
|
1446
|
+
return false;
|
|
1447
|
+
}
|
|
1448
|
+
return new Promise((resolvePromise) => {
|
|
1449
|
+
const child = spawnFn(bin.command, bin.args, {
|
|
1450
|
+
stdio: "inherit",
|
|
1451
|
+
cwd: workspaceRoot
|
|
1452
|
+
});
|
|
1453
|
+
let settled = false;
|
|
1454
|
+
const finish = (ok) => {
|
|
1455
|
+
if (settled) return;
|
|
1456
|
+
settled = true;
|
|
1457
|
+
clearTimeout(timer);
|
|
1458
|
+
resolvePromise(ok);
|
|
1459
|
+
};
|
|
1460
|
+
const timer = setTimeout(() => {
|
|
1461
|
+
console.error(`\x1b[31m[tessera a11y]\x1b[0m Chromium install timed out after ${Math.round(timeoutMs / 6e4)} min; aborting.`);
|
|
1462
|
+
child.kill?.("SIGKILL");
|
|
1463
|
+
finish(false);
|
|
1464
|
+
}, timeoutMs);
|
|
1465
|
+
timer.unref?.();
|
|
1466
|
+
child.on("error", (err) => {
|
|
1467
|
+
console.error(`\x1b[31m[tessera a11y]\x1b[0m Failed to start the Chromium install: ${err.message}`);
|
|
1468
|
+
finish(false);
|
|
1469
|
+
});
|
|
1470
|
+
child.on("exit", (code) => finish(code === 0));
|
|
1471
|
+
});
|
|
1472
|
+
}
|
|
1473
|
+
function reportLaunchFailure(message, isLinux) {
|
|
1474
|
+
console.error(`\x1b[31m[tessera a11y]\x1b[0m Chromium is installed but failed to launch.\n` + (isLinux ? ` Install system dependencies:\n pnpm exec playwright install --with-deps chromium\n` : ``) + ` Original error: ${message}`);
|
|
1475
|
+
}
|
|
1476
|
+
async function launchWithInstall({ launch, install, isLinux = process.platform === "linux" }) {
|
|
1477
|
+
try {
|
|
1478
|
+
return {
|
|
1479
|
+
ok: true,
|
|
1480
|
+
browser: await launch()
|
|
1481
|
+
};
|
|
1482
|
+
} catch (err) {
|
|
1483
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1484
|
+
if (isMissingDepsError(message)) {
|
|
1485
|
+
reportLaunchFailure(message, isLinux);
|
|
1486
|
+
return {
|
|
1487
|
+
ok: false,
|
|
1488
|
+
code: 1
|
|
1489
|
+
};
|
|
1490
|
+
}
|
|
1491
|
+
if (!isMissingBrowserError(message)) throw err;
|
|
1492
|
+
console.log("[tessera a11y] Chromium isn't installed for Playwright. Installing it once now…");
|
|
1493
|
+
if (!await install()) {
|
|
1494
|
+
reportManualInstall("Chromium isn't installed for Playwright.");
|
|
1495
|
+
return {
|
|
1496
|
+
ok: false,
|
|
1497
|
+
code: 1
|
|
1498
|
+
};
|
|
1499
|
+
}
|
|
1500
|
+
try {
|
|
1501
|
+
return {
|
|
1502
|
+
ok: true,
|
|
1503
|
+
browser: await launch()
|
|
1504
|
+
};
|
|
1505
|
+
} catch (retryErr) {
|
|
1506
|
+
const retryMessage = retryErr instanceof Error ? retryErr.message : String(retryErr);
|
|
1507
|
+
if (isMissingBrowserError(retryMessage) && !isMissingDepsError(retryMessage)) reportManualInstall("Chromium still isn't installed after the install step.");
|
|
1508
|
+
else reportLaunchFailure(retryMessage, isLinux);
|
|
1509
|
+
return {
|
|
1510
|
+
ok: false,
|
|
1511
|
+
code: 1
|
|
1512
|
+
};
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1391
1516
|
function isFailing(v, thresholdRank) {
|
|
1392
1517
|
return !v.impact || IMPACT_RANK[v.impact] >= thresholdRank;
|
|
1393
1518
|
}
|
|
@@ -1396,7 +1521,7 @@ async function tryImport(specifier) {
|
|
|
1396
1521
|
}
|
|
1397
1522
|
async function loadDeps() {
|
|
1398
1523
|
let chromium;
|
|
1399
|
-
for (const spec of
|
|
1524
|
+
for (const spec of PLAYWRIGHT_SPECS) try {
|
|
1400
1525
|
const mod = await tryImport(spec);
|
|
1401
1526
|
if (mod.chromium) {
|
|
1402
1527
|
chromium = mod.chromium;
|
|
@@ -1436,7 +1561,10 @@ async function runAudit(projectRoot, workspaceRoot, options = {}) {
|
|
|
1436
1561
|
const threshold = options.threshold ?? "serious";
|
|
1437
1562
|
const deps = await loadDeps();
|
|
1438
1563
|
if (!deps.ok) {
|
|
1439
|
-
console.error(
|
|
1564
|
+
console.error(`[31m[tessera a11y][0m Tier 2 needs Playwright + axe-core, which aren't installed.
|
|
1565
|
+
Install them to run the runtime audit:
|
|
1566
|
+
pnpm add -D playwright @axe-core/playwright
|
|
1567
|
+
${INSTALL_CHROMIUM}`);
|
|
1440
1568
|
return 1;
|
|
1441
1569
|
}
|
|
1442
1570
|
const { chromium, AxeBuilder } = deps.deps;
|
|
@@ -1446,7 +1574,7 @@ async function runAudit(projectRoot, workspaceRoot, options = {}) {
|
|
|
1446
1574
|
const disableRules = axeIgnoreRules(settings.ignore);
|
|
1447
1575
|
const manifest = generateManifest(resolve(projectRoot, "pages"));
|
|
1448
1576
|
const vite = await import("vite");
|
|
1449
|
-
const { resolveTesseraConfig } = await import("./inline-config-
|
|
1577
|
+
const { resolveTesseraConfig } = await import("./inline-config-eHjv9XuA.js");
|
|
1450
1578
|
const auditBaseConfig = await resolveTesseraConfig(projectRoot, workspaceRoot, {
|
|
1451
1579
|
command: "build",
|
|
1452
1580
|
mode: "production"
|
|
@@ -1482,16 +1610,12 @@ async function runAudit(projectRoot, workspaceRoot, options = {}) {
|
|
|
1482
1610
|
console.error("[tessera a11y] Could not determine preview server URL.");
|
|
1483
1611
|
return 1;
|
|
1484
1612
|
}
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
}
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
return 1;
|
|
1492
|
-
}
|
|
1493
|
-
throw err;
|
|
1494
|
-
}
|
|
1613
|
+
const launched = await launchWithInstall({
|
|
1614
|
+
launch: () => chromium.launch(),
|
|
1615
|
+
install: () => installChromium(workspaceRoot)
|
|
1616
|
+
});
|
|
1617
|
+
if (!launched.ok) return launched.code;
|
|
1618
|
+
const browser = launched.browser;
|
|
1495
1619
|
const pages = [];
|
|
1496
1620
|
try {
|
|
1497
1621
|
const page = await (await browser.newContext()).newPage();
|
|
@@ -1502,31 +1626,31 @@ async function runAudit(projectRoot, workspaceRoot, options = {}) {
|
|
|
1502
1626
|
const scan = async () => {
|
|
1503
1627
|
const builder = new AxeBuilder({ page }).withTags(tags);
|
|
1504
1628
|
if (disableRules.length > 0) builder.disableRules(disableRules);
|
|
1505
|
-
return (await builder.analyze()).violations.map(
|
|
1506
|
-
id: v.id,
|
|
1507
|
-
impact: v.impact ?? null,
|
|
1508
|
-
help: v.help,
|
|
1509
|
-
helpUrl: v.helpUrl,
|
|
1510
|
-
nodes: v.nodes.length
|
|
1511
|
-
}));
|
|
1629
|
+
return (await builder.analyze()).violations.map(mapViolation);
|
|
1512
1630
|
};
|
|
1513
|
-
const
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
await page.waitForFunction((idx) => document.querySelectorAll("button.tessera-nav-page")[idx]?.getAttribute("aria-current") === "page", i, { timeout: 2e4 });
|
|
1524
|
-
await page.waitForLoadState("networkidle");
|
|
1525
|
-
pages.push({
|
|
1526
|
-
index: i,
|
|
1631
|
+
const recordPage = async (index, title) => {
|
|
1632
|
+
const loadFailed = await page.evaluate(() => document.getElementById("tessera-app")?.dataset.tesseraPageError === "true");
|
|
1633
|
+
if (loadFailed) return {
|
|
1634
|
+
index,
|
|
1635
|
+
title,
|
|
1636
|
+
violations: [],
|
|
1637
|
+
loadFailed
|
|
1638
|
+
};
|
|
1639
|
+
return {
|
|
1640
|
+
index,
|
|
1527
1641
|
title,
|
|
1528
1642
|
violations: await scan()
|
|
1529
|
-
}
|
|
1643
|
+
};
|
|
1644
|
+
};
|
|
1645
|
+
const totalPages = manifest.pages.length;
|
|
1646
|
+
if (!await page.evaluate(() => typeof window.__tesseraAudit?.goToIndex === "function")) {
|
|
1647
|
+
if (totalPages > 1) console.warn(`\x1b[33m[tessera a11y]\x1b[0m Could not enumerate pages; auditing the entry page only (1 of ${totalPages}). The report records the reduced scope.`);
|
|
1648
|
+
pages.push(await recordPage(0, manifest.pages[0]?.title ?? "(entry)"));
|
|
1649
|
+
} else for (let i = 0; i < totalPages; i++) {
|
|
1650
|
+
await page.evaluate((idx) => window.__tesseraAudit.goToIndex(idx), i);
|
|
1651
|
+
await page.waitForFunction((idx) => document.getElementById("tessera-app")?.dataset.tesseraPageIndex === String(idx), i, { timeout: 2e4 });
|
|
1652
|
+
await page.waitForLoadState("networkidle");
|
|
1653
|
+
pages.push(await recordPage(i, manifest.pages[i]?.title ?? `Page ${i + 1}`));
|
|
1530
1654
|
}
|
|
1531
1655
|
} finally {
|
|
1532
1656
|
await browser.close();
|
|
@@ -1534,17 +1658,24 @@ async function runAudit(projectRoot, workspaceRoot, options = {}) {
|
|
|
1534
1658
|
const thresholdRank = IMPACT_RANK[threshold];
|
|
1535
1659
|
let totalViolations = 0;
|
|
1536
1660
|
let failingViolations = 0;
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
if (
|
|
1661
|
+
let pagesFailedToLoad = 0;
|
|
1662
|
+
for (const p of pages) {
|
|
1663
|
+
if (p.loadFailed) pagesFailedToLoad++;
|
|
1664
|
+
for (const v of p.violations) {
|
|
1665
|
+
totalViolations++;
|
|
1666
|
+
if (isFailing(v, thresholdRank)) failingViolations++;
|
|
1667
|
+
}
|
|
1540
1668
|
}
|
|
1541
1669
|
const report = {
|
|
1542
1670
|
standard: settings.standard,
|
|
1543
1671
|
threshold,
|
|
1544
1672
|
pages,
|
|
1673
|
+
pagesAudited: pages.length,
|
|
1674
|
+
totalPages: manifest.pages.length,
|
|
1675
|
+
pagesFailedToLoad,
|
|
1545
1676
|
totalViolations,
|
|
1546
1677
|
failingViolations,
|
|
1547
|
-
passed: failingViolations === 0
|
|
1678
|
+
passed: failingViolations === 0 && pagesFailedToLoad === 0
|
|
1548
1679
|
};
|
|
1549
1680
|
const reportPath = resolve(projectRoot, "a11y-report.json");
|
|
1550
1681
|
writeFileSync(reportPath, JSON.stringify(report, null, 2), "utf-8");
|
|
@@ -1562,19 +1693,41 @@ async function runAudit(projectRoot, workspaceRoot, options = {}) {
|
|
|
1562
1693
|
function printSummary(report, reportPath) {
|
|
1563
1694
|
const thresholdRank = IMPACT_RANK[report.threshold];
|
|
1564
1695
|
for (const p of report.pages) {
|
|
1696
|
+
if (p.loadFailed) {
|
|
1697
|
+
console.log(`\x1b[31m ✗\x1b[0m ${p.title} — failed to load`);
|
|
1698
|
+
continue;
|
|
1699
|
+
}
|
|
1565
1700
|
if (p.violations.length === 0) {
|
|
1566
1701
|
console.log(`\x1b[32m ✓\x1b[0m ${p.title}`);
|
|
1567
1702
|
continue;
|
|
1568
1703
|
}
|
|
1569
1704
|
const mark = p.violations.some((v) => isFailing(v, thresholdRank)) ? "\x1B[31m ✗\x1B[0m" : "\x1B[33m ⚠\x1B[0m";
|
|
1570
1705
|
console.log(`${mark} ${p.title}`);
|
|
1571
|
-
for (const v of p.violations)
|
|
1706
|
+
for (const v of p.violations) {
|
|
1707
|
+
console.log(` [${v.impact ?? "n/a"}] ${v.id} — ${v.help} (${v.nodes} node${v.nodes === 1 ? "" : "s"})`);
|
|
1708
|
+
for (const el of v.elements.slice(0, MAX_ELEMENTS_SHOWN)) {
|
|
1709
|
+
console.log(`\x1b[90m → ${el.target || "(unknown element)"}\x1b[0m`);
|
|
1710
|
+
if (el.summary) console.log(`\x1b[90m ${el.summary}\x1b[0m`);
|
|
1711
|
+
}
|
|
1712
|
+
const hidden = v.elements.length - MAX_ELEMENTS_SHOWN;
|
|
1713
|
+
if (hidden > 0) console.log(`\x1b[90m … and ${hidden} more — see a11y-report.json\x1b[0m`);
|
|
1714
|
+
}
|
|
1572
1715
|
}
|
|
1573
1716
|
console.log(`\n[tessera a11y] Report written to ${reportPath}`);
|
|
1717
|
+
if (report.pagesAudited < report.totalPages) console.log(`\x1b[33m[tessera a11y] Covered ${report.pagesAudited} of ${report.totalPages} page(s)\x1b[0m — reduced scope, the rest were not audited.`);
|
|
1718
|
+
else if (report.pagesFailedToLoad > 0) {
|
|
1719
|
+
const scanned = report.pagesAudited - report.pagesFailedToLoad;
|
|
1720
|
+
console.log(`[tessera a11y] Reached all ${report.totalPages} page(s); scanned ${scanned}, ${report.pagesFailedToLoad} failed to load.`);
|
|
1721
|
+
} else console.log(`[tessera a11y] Covered all ${report.totalPages} page(s).`);
|
|
1574
1722
|
if (report.passed) console.log(`\x1b[32m[tessera a11y] Passed\x1b[0m — ${report.totalViolations} total finding(s), none at/above "${report.threshold}".`);
|
|
1575
|
-
else
|
|
1723
|
+
else {
|
|
1724
|
+
const reasons = [];
|
|
1725
|
+
if (report.failingViolations > 0) reasons.push(`${report.failingViolations} finding(s) at/above "${report.threshold}" (of ${report.totalViolations} total)`);
|
|
1726
|
+
if (report.pagesFailedToLoad > 0) reasons.push(`${report.pagesFailedToLoad} page(s) failed to load`);
|
|
1727
|
+
console.log(`\x1b[31m[tessera a11y] Failed\x1b[0m — ${reasons.join("; ")}.`);
|
|
1728
|
+
}
|
|
1576
1729
|
}
|
|
1577
1730
|
//#endregion
|
|
1578
1731
|
export { isPlausibleLanguageTag as a, validateProject as c, isIgnored as i, generateManifest as l, runAudit as n, normalizeA11y as o, resolvePackageRoot as r, reportValidationIssues as s, AUDIT_ENV_FLAG as t, readCourseConfig as u };
|
|
1579
1732
|
|
|
1580
|
-
//# sourceMappingURL=audit-
|
|
1733
|
+
//# sourceMappingURL=audit-B9VHgVjk.js.map
|