test(scripts): cover mobile-first markers, check, cookiecutter structure

This commit is contained in:
opencode-agent 2026-08-05 03:34:35 +00:00
parent f0d71c3dd3
commit e1fd8f61f2
3 changed files with 85 additions and 1 deletions

View file

@ -258,6 +258,11 @@ def test_fullstack_structure(render):
assert (render / "frontend/src/lib/utils/cn.svelte.ts").exists() assert (render / "frontend/src/lib/utils/cn.svelte.ts").exists()
assert (render / "frontend/src/lib/hooks/is-mobile.svelte.ts").exists() assert (render / "frontend/src/lib/hooks/is-mobile.svelte.ts").exists()
assert (render / "frontend/tests/e2e/app.spec.ts").exists() assert (render / "frontend/tests/e2e/app.spec.ts").exists()
# issue #278: mobile-first — PWA manifest + SVG icon + mobile + a11y specs
assert (render / "frontend/static/manifest.webmanifest").exists()
assert (render / "frontend/static/icon.svg").exists()
assert (render / "frontend/tests/e2e/mobile.spec.ts").exists()
assert (render / "frontend/tests/e2e/accessibility.spec.ts").exists()
# removed: counter.svelte.js, Header.svelte, jsconfig.json, +page.js # removed: counter.svelte.js, Header.svelte, jsconfig.json, +page.js
# root CI runs both # root CI runs both
assert (render / ".github/workflows/ci.yml").exists() assert (render / ".github/workflows/ci.yml").exists()

View file

@ -102,6 +102,28 @@ def test_frontend_stack_markers_keys():
] ]
def test_mobile_first_markers_keys():
"""MOBILE_FIRST_MARKERS (issue #278) has the expected keys.
Distinct from FRONTEND_STACK_MARKERS so the stack check stays focused on
the Tailwind/shadcn/TS trio while mobile-first (PWA + mobile Playwright +
a11y) is a separate dict.
"""
assert set(pc.MOBILE_FIRST_MARKERS.keys()) == {
"fullstack_files",
"fullstack_app_html_markers",
}
assert pc.MOBILE_FIRST_MARKERS["fullstack_files"] == [
"frontend/static/manifest.webmanifest",
"frontend/tests/e2e/mobile.spec.ts",
"frontend/tests/e2e/accessibility.spec.ts",
]
assert pc.MOBILE_FIRST_MARKERS["fullstack_app_html_markers"] == [
"viewport",
"manifest",
]
# ── re-export sanity (backward compat) ───────────────────────────────────── # ── re-export sanity (backward compat) ─────────────────────────────────────

View file

@ -241,12 +241,30 @@ def _make_fullstack_repo(tmp_path: Path) -> None:
# Fullstack cookiecutter template includes Tailwind v4 + shadcn-svelte + TS # Fullstack cookiecutter template includes Tailwind v4 + shadcn-svelte + TS
# (issue #266): package.json deps + components.json + tsconfig.json are the # (issue #266): package.json deps + components.json + tsconfig.json are the
# 4 frontend stack markers checked by ``_check_frontend_stack``. # 4 frontend stack markers checked by ``_check_frontend_stack``.
# Issue #278: mobile-first markers (manifest, mobile.spec.ts, a11y.spec.ts,
# app.html viewport+manifest, @axe-core/playwright) are also present so the
# default fixture mirrors a freshly generated fullstack cookiecutter repo.
(tmp_path / "frontend" / "package.json").write_text( (tmp_path / "frontend" / "package.json").write_text(
'{"name": "test-frontend", "dependencies": {"tailwindcss": "^4.0.0", ' '{"name": "test-frontend", "dependencies": {"tailwindcss": "^4.0.0", '
'"bits-ui": "^1.0.0"}}\n' '"bits-ui": "^1.0.0"}, "devDependencies": {"@axe-core/playwright": "^4.10.0"}}\n'
) )
(tmp_path / "frontend" / "tsconfig.json").write_text('{"compilerOptions": {}}\n') (tmp_path / "frontend" / "tsconfig.json").write_text('{"compilerOptions": {}}\n')
(tmp_path / "frontend" / "components.json").write_text("{}\n") (tmp_path / "frontend" / "components.json").write_text("{}\n")
(tmp_path / "frontend" / "static").mkdir(parents=True, exist_ok=True)
(tmp_path / "frontend" / "static" / "manifest.webmanifest").write_text("{}\n")
(tmp_path / "frontend" / "static" / "icon.svg").write_text("<svg></svg>\n")
(tmp_path / "frontend" / "src").mkdir(parents=True, exist_ok=True)
(tmp_path / "frontend" / "src" / "app.html").write_text(
'<!doctype html><html><head>'
'<meta name="viewport" content="width=device-width, initial-scale=1" />'
'<link rel="manifest" href="/manifest.webmanifest" />'
'</head><body></body></html>\n'
)
(tmp_path / "frontend" / "tests" / "e2e").mkdir(parents=True, exist_ok=True)
(tmp_path / "frontend" / "tests" / "e2e" / "mobile.spec.ts").write_text("// mobile\n")
(tmp_path / "frontend" / "tests" / "e2e" / "accessibility.spec.ts").write_text(
"// a11y\n"
)
# ── parse_remote_url ───────────────────────────────────────────────────────── # ── parse_remote_url ─────────────────────────────────────────────────────────
@ -2243,6 +2261,45 @@ def test_fullstack_frontend_stack_missing_tsconfig(tmp_path, ctx):
assert "tsconfig.json" in frontend[0].detail assert "tsconfig.json" in frontend[0].detail
# ── issue #278: mobile-first guarantee (fullstack only) ─────────────────────
def test_fullstack_mobile_first_ok(tmp_path, ctx):
"""All mobile-first markers present (manifest, mobile.spec.ts, a11y.spec.ts,
app.html viewport+manifest, @axe-core/playwright) OK."""
_make_fullstack_repo(tmp_path)
group = ps.check_structure(ps.ProjectType.FULLSTACK, ctx)
mobile = [c for c in group.checks if c.name == "mobile-first"]
assert mobile, f"expected 'mobile-first' check, got: {group.checks}"
assert mobile[0].status == ps.CheckStatus.OK, (
f"expected OK, got {mobile[0].status}: {mobile[0].detail}"
)
def test_fullstack_mobile_first_warns_missing_manifest(tmp_path, ctx):
"""Missing ``frontend/static/manifest.webmanifest`` → WARN (non-blocking)."""
_make_fullstack_repo(tmp_path)
(tmp_path / "frontend" / "static" / "manifest.webmanifest").unlink()
group = ps.check_structure(ps.ProjectType.FULLSTACK, ctx)
mobile = [c for c in group.checks if c.name == "mobile-first"]
assert mobile and mobile[0].status == ps.CheckStatus.WARN, (
f"expected WARN for missing manifest, got: {mobile}"
)
assert "manifest.webmanifest" in mobile[0].detail
def test_fullstack_mobile_first_skips_backend(tmp_path, ctx):
"""Backend project type → ``_check_mobile_first`` not invoked (skip).
Mobile-first is a FULLSTACK-only guarantee; the structure group for
backend must not contain a "mobile-first" check.
"""
_make_backend_repo(tmp_path)
group = ps.check_structure(ps.ProjectType.BACKEND, ctx)
mobile = [c for c in group.checks if c.name == "mobile-first"]
assert not mobile, f"backend must skip mobile-first, got: {mobile}"
# ── issue #274: db/models auto-detect from deps ────────────────────────────── # ── issue #274: db/models auto-detect from deps ──────────────────────────────