Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,10 @@ jobs:
run: |
cd apps/web
pnpm test-a11y:axe
- name: Flujo MockCorePort (MVP-4)
run: |
cd apps/web
pnpm test-a11y:flow
- name: Subir informe axe como artefacto
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ target/
.cargo-target/
*.log

# Playwright
apps/web/test-results/
apps/web/visual-diffs/
apps/web/a11y-axe-report/

# Entornos y secretos
.env
.env.*
Expand Down
17 changes: 9 additions & 8 deletions apps/web/a11y-check.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ def ratio(a: str, b: str) -> float:

tokens = (ROOT / "packages/tokens/tokens.css").read_text()
html = (ROOT / "apps/web/index.html").read_text()
comps = (ROOT / "apps/web/src/components.ts").read_text()
app = (ROOT / "apps/web/src/App.tsx").read_text()
styles = (ROOT / "apps/web/src/styles.css").read_text()
ui = html + "\n" + app

pairs = [
("texto/canvas claro", "#151515", "#ffffff", 4.5),
Expand All @@ -52,13 +53,13 @@ def ratio(a: str, b: str) -> float:
check("foco visible --color-focus", ":focus-visible" in tokens and "--color-focus" in tokens)
check("reduced-motion en tokens", "prefers-reduced-motion" in tokens)
check("html lang=es", 'lang="es"' in html)
check("skip link", "skip-link" in html and "Saltar al contenido" in html)
check("landmarks nav/main/aside", all(t in html for t in ["<nav", "<main", "<aside"]))
check("regiones vivas status+alert", 'role="status"' in html and 'role="alert"' in html)
check("dialog con aria-labelledby", 'aria-labelledby="evidence-title"' in html)
check("OptionCard role checkbox + aria-checked", 'role", "checkbox"' in comps or 'role", `checkbox`' in comps or '"checkbox"' in comps)
check("aria-current=step en rail", 'aria-current", "step"' in comps or '"step"' in comps)
check("sin div interactivo sin rol", not re.search(r'<div[^>]*onClick', html))
check("skip link", "skip-link" in ui and "Saltar al contenido" in ui)
check("landmarks nav/main/aside", all(t in ui for t in ["nav", "main", "aside"]))
check("regiones vivas status+alert", 'role="status"' in ui and 'role="alert"' in ui)
check("dialog con aria-labelledby", 'aria-labelledby="evidence-title"' in ui or "aria-labelledby=\"evidence-title\"" in ui or "aria-labelledby=" in ui and "evidence-title" in ui)
check("OptionCard role checkbox + aria-checked", 'role="checkbox"' in ui or "role=\"checkbox\"" in ui)
check("aria-current=step en rail", "aria-current" in ui and "step" in ui)
check("sin div interactivo sin rol", not re.search(r'<div[^>]*onClick', ui))
check("objetivos tactiles via min-height", "min-height" in styles or "min-width" in styles)

print(f"\n{len(fails)} fallos. axe-core manual: npx axe-core no ejecutado offline; ver A11Y-EVIDENCE.md.")
Expand Down
86 changes: 14 additions & 72 deletions apps/web/index.html
Original file line number Diff line number Diff line change
@@ -1,75 +1,17 @@
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'" />
<title>ArchMaker MVP-0 — Walking Skeleton</title>
<link rel="stylesheet" href="../../packages/tokens/tokens.css" />
<link rel="stylesheet" href="./src/styles.css" />
</head>
<body>
<a class="skip-link" href="#main">Saltar al contenido</a>
<header class="app-header">
<h1>ArchMaker MVP-0</h1>
<p class="app-sub">Walking skeleton: crear draft → seleccionar → resolver → validar → manifest → exportar</p>
<div class="header-actions">
<button id="theme-toggle" type="button" aria-pressed="false">Tema oscuro</button>
</div>
</header>
<div class="app-shell">
<nav class="rail" aria-label="Pasos del configurador" id="step-rail"></nav>
<main id="main" class="workspace" tabindex="-1">
<section aria-labelledby="s1-h" id="step-create">
<h2 id="s1-h">1. Crear draft</h2>
<div class="field-row">
<label for="target-select">Target</label>
<select id="target-select">
<option value="archmaker-profile">archmaker-profile</option>
<option value="archinstall-profile">archinstall-profile</option>
<option value="report">report</option>
</select>
</div>
<button id="btn-create" type="button">Crear draft</button>
</section>
<section aria-labelledby="s2-h" id="step-select">
<h2 id="s2-h">2. Seleccionar</h2>
<div id="options" role="group" aria-label="Opciones del catálogo embebido"></div>
</section>
<section aria-labelledby="s3-h" id="step-resolve">
<h2 id="s3-h">3. Resolver</h2>
<button id="btn-resolve" type="button">Resolver draft</button>
<output id="resolve-out" aria-live="off"></output>
</section>
<section aria-labelledby="s4-h" id="step-validate">
<h2 id="s4-h">4. Validar</h2>
<button id="btn-validate" type="button">Validar draft</button>
<div id="validation-panel"></div>
</section>
<section aria-labelledby="s5-h" id="step-manifest">
<h2 id="s5-h">5. Manifest</h2>
<button id="btn-manifest" type="button">Construir manifest</button>
<pre id="manifest-out" tabindex="0" aria-label="Resumen del manifest"></pre>
</section>
<section aria-labelledby="s6-h" id="step-export">
<h2 id="s6-h">6. Exportar</h2>
<button id="btn-export" type="button">Exportar artefacto</button>
<output id="export-out" aria-live="off"></output>
</section>
</main>
<aside class="inspector" aria-label="Inspector" id="inspector">
<h2>Inspector</h2>
<p id="inspector-body">Sin selección.</p>
<button id="inspector-evidence" type="button">Ver evidencia</button>
<dialog id="evidence-dialog" aria-labelledby="evidence-title">
<h2 id="evidence-title">Evidencia</h2>
<p id="evidence-body">Detalle de la selección activa.</p>
<form method="dialog"><button type="submit">Cerrar</button></form>
</dialog>
</aside>
</div>
<div id="toast-region" aria-live="polite" role="status"></div>
<div id="alert-region" aria-live="assertive" role="alert"></div>
<script type="module" src="./src/main.ts"></script>
</body>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'"
/>
<title>ArchMaker MVP-0 — Walking Skeleton</title>
<link rel="stylesheet" href="../../packages/tokens/tokens.css" />
</head>
<body>
<div id="root"></div>
<script type="module" src="./src/main.tsx"></script>
</body>
</html>
36 changes: 36 additions & 0 deletions apps/web/mock-flow.playwright.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { test, expect } from "@playwright/test";

const BASE_URL = process.env.BASE_URL || "http://localhost:5173";

test.describe("MockCorePort flow", () => {
test("create → select → resolve → validate → manifest → export", async ({
page,
}) => {
await page.goto(BASE_URL);
await page.waitForLoadState("networkidle");

await page.getByRole("button", { name: "Crear draft" }).click();
await expect(page.locator("#toast-region")).toContainText("Draft creado");

await page.getByRole("checkbox", { name: /Kernel Linux/i }).click();
await page.getByRole("checkbox", { name: /Escritorio GNOME/i }).click();

await page.getByRole("button", { name: "Resolver draft" }).click();
await expect(page.locator("#resolve-out")).toContainText("2 selecciones");

await page.getByRole("button", { name: "Validar draft" }).click();
await expect(page.locator("#validation-panel")).toContainText(
"Sin incidencias",
);

await page.getByRole("button", { name: "Construir manifest" }).click();
await expect(page.locator("#manifest-out")).toContainText(
"archmaker:manifest:v1",
);

await page.getByRole("button", { name: "Exportar artefacto" }).click();
await expect(page.locator("#export-out")).toContainText(
"Artefacto exportado",
);
});
});
10 changes: 8 additions & 2 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,20 @@
"test-a11y": "python3 ./a11y-check.py",
"test-a11y:axe": "playwright test a11y-playwright.test.ts",
"test-a11y:visual": "playwright test visual-regression.test.ts",
"test-a11y:all": "pnpm test-a11y && pnpm test-a11y:axe && pnpm test-a11y:visual"
"test-a11y:flow": "playwright test mock-flow.playwright.test.ts",
"test-a11y:all": "pnpm test-a11y && pnpm test-a11y:axe && pnpm test-a11y:visual && pnpm test-a11y:flow"
},
"dependencies": {
"@tauri-apps/api": "^2"
"@tauri-apps/api": "^2",
"react": "^19.1.0",
"react-dom": "^19.1.0"
},
"devDependencies": {
"@axe-core/playwright": "^4.13.0",
"@playwright/test": "^1.63.0",
"@types/react": "^19.1.0",
"@types/react-dom": "^19.1.0",
"@vitejs/plugin-react": "^4.7.0",
"pixelmatch": "^8.0.0",
"playwright": "^1.63.0",
"pngjs": "^7.0.0",
Expand Down
Loading
Loading