From b626d5e066463e560c1ca368c0ebdd9ebd83f831 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 17 May 2026 17:50:01 +0000 Subject: [PATCH] Add test_phase.py for Phase 4 - Phase 4 branch points to claude/debug-api-errors-gd730 - Phase 4 EXTRA_FILES checks out app/config.yml and app/pages/home.md from the branch (needed for message: key test and callout test cases) - Updated Phase 4 verify checklist covers all spec requirements: basic types, title row, no-title, markdown body, icon override, message: key, dark mode - Added EXTRA_FILES mechanism for per-phase content file checkout - Improved checklist formatting with section headers https://claude.ai/code/session_01UP8Wo2CKPNhvvTkzX48CWF --- test_phase.py | 223 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 223 insertions(+) create mode 100644 test_phase.py diff --git a/test_phase.py b/test_phase.py new file mode 100644 index 0000000..9104574 --- /dev/null +++ b/test_phase.py @@ -0,0 +1,223 @@ +#!/usr/bin/env python3 +""" +MD-CMS v0.4 phase test runner. + +Usage: + python3 test_phase.py [phase] + + phase: 1-7 (default: run all phases sequentially) + +Each phase fetches the corresponding branch, checks out the renderer and +content files for that phase, starts a local HTTP server, and opens the +browser. Press Enter when done to continue to the next phase. +""" + +import functools +import http.server +import subprocess +import sys +import threading +import time +import webbrowser +from pathlib import Path + +PORT = 8800 + +PHASES = { + 1: ("main", "theme.yml and colour system"), + 2: ("v0.4_phase2", "Icon system — local SVGs, no Google Fonts"), + 3: ("v0.4_phase3", "Asset validation in mdcms build"), + 4: ("claude/debug-api-errors-gd730", "Callout tags"), + 5: ("v0.4_phase5", "Table of contents tag"), + 6: ("v0.4_phase6", "Offline / fetch-deps"), + 7: ("v0.4_phase7", "PWA — service worker and manifest"), +} + +VERIFY = { + 1: [ + "Existing site renders correctly with theme.yml present", + "Missing theme: key falls back gracefully to hardcoded defaults", + "Accent colour and dark/light mode colours apply from theme.yml", + ], + 2: [ + "All UI icons render correctly from local SVG files (no Google Icons font)", + "Theme toggle, search, hamburger, nav arrows all show icons", + "Broken image displays for a missing icon (test by renaming one SVG)", + "Icon name normalisation: 'arrow-right' and 'Arrow Right' both resolve", + ], + 3: [ + "Run: python3 mdcms.py build --path app/ (NOT the installed mdcms command)", + "Warning printed for assets/images/missing-photo.png (referenced in pages/about.md)", + "No warning for assets/images/logo.png (file exists)", + "Build continues and completes after warnings", + ], + 4: [ + "── Basic types ─────────────────────────────────────────────────", + "callout-info, callout-warning, callout-success, callout-error all render", + "Each has: coloured left border + low-opacity background in the right colour", + "── Title row ───────────────────────────────────────────────────", + "Title row shows: icon (inlined SVG) + bold title text in the accent colour", + "Title text is correct for each type: Information / Warning / Success / Error", + "── No-title callout ────────────────────────────────────────────", + "Callout with no title key: no title row rendered, just the body", + "── Markdown body ───────────────────────────────────────────────", + "Body renders full markdown: bold, italic, lists, inline code, links", + "── Custom icon override ────────────────────────────────────────", + "icon: warning on a callout-info renders the warning SVG, not the info SVG", + "── Config-defined message: key ─────────────────────────────────", + "message: aitranslation resolves title 'PLEASE NOTE:' and body from config.yml", + "── Dark mode ───────────────────────────────────────────────────", + "Toggle dark mode: all four callout types still look correct", + "Colours adapt (border and background tint remain visible on dark background)", + ], + 5: [ + "TOC tag renders a section-grouped page list", + "Only pages visible for active category are listed", + "Draft pages are excluded", + "TOC page itself is excluded from the listing", + "Section headings and sort order are correct", + ], + 6: [ + "mdcms fetch-deps downloads JS/CSS to assets/required/vendors/", + "Patched index.html makes no external network requests", + "Fonts load correctly from local paths", + "Site loads fully offline after fetch-deps", + ], + 7: [ + "service-worker.js and manifest.json generated when pwa: yes", + "Full site accessible offline after one online visit", + "Cache updates correctly on new build deployment", + "Offline message displays when cache evicted", + ], +} + +# Files checked out from the phase branch on every phase switch. +# Renderer + presentational config. Content files (home.md, config.yml) are +# added per phase in EXTRA_FILES so the right test content is loaded. +RENDERER_FILES = [ + "app/index.html", + "app/theme.yml", + "app/assets/icons", + "mdcms.py", +] + +# Per-phase extra files to check out from the phase branch. +# Use this for content or config files that differ between phases. +EXTRA_FILES = { + 4: [ + "app/config.yml", # has callouts: block for message: key test + "app/pages/home.md", # has Phase 4 callout test cases + ], +} + + +def checkout_phase(phase: int) -> bool: + branch, _ = PHASES[phase] + repo_root = Path(__file__).parent + + print(f"\n Fetching branch {branch} from origin...") + result = subprocess.run( + ["git", "fetch", "origin", branch], + cwd=repo_root, + capture_output=True, + text=True, + ) + if result.returncode != 0: + print(f" ERROR fetching: {result.stderr.strip()}") + return False + + files = RENDERER_FILES + EXTRA_FILES.get(phase, []) + print(f" Checking out {len(files)} file groups from origin/{branch}...") + result = subprocess.run( + ["git", "checkout", f"origin/{branch}", "--"] + files, + cwd=repo_root, + capture_output=True, + text=True, + ) + if result.returncode != 0: + print(f" ERROR checking out files: {result.stderr.strip()}") + return False + + print(" Rebuilding nav.yml...") + result = subprocess.run( + ["python3", "mdcms.py", "build", "--path", "app/"], + cwd=repo_root, + capture_output=True, + text=True, + ) + if result.returncode != 0: + print(f" WARNING: mdcms build failed: {result.stderr.strip()}") + else: + out = result.stdout.strip() + if out: + print(f" {out}") + + print(" Ready.\n") + return True + + +def serve(app_dir: Path): + handler = functools.partial( + http.server.SimpleHTTPRequestHandler, + directory=str(app_dir), + ) + handler.log_message = lambda *a: None + with http.server.HTTPServer(("", PORT), handler) as httpd: + httpd.serve_forever() + + +def run_phase(phase: int): + branch, description = PHASES[phase] + + print("\n" + "=" * 62) + print(f" Phase {phase}: {description}") + print(f" Branch: {branch}") + print("=" * 62) + + repo_root = Path(__file__).parent + app_dir = repo_root / "app" + + if phase > 1: + if not checkout_phase(phase): + print(" Checkout failed — skipping this phase.") + return + + print(f" Serving app/ at http://localhost:{PORT}\n") + print(" Checklist:") + for item in VERIFY[phase]: + if item.startswith("──"): + print(f"\n {item}") + else: + print(f" [ ] {item}") + + t = threading.Thread(target=serve, args=(app_dir,), daemon=True) + t.start() + time.sleep(0.3) + webbrowser.open(f"http://localhost:{PORT}") + + print("\n Press Enter when done (Ctrl+C to abort)...") + try: + input() + except KeyboardInterrupt: + print("\n Aborted.") + + +def main(): + if len(sys.argv) == 2: + try: + phase = int(sys.argv[1]) + except ValueError: + print("Usage: python3 test_phase.py [1-7]") + sys.exit(1) + if phase not in PHASES: + print(f"Phase must be 1-7, got {phase}") + sys.exit(1) + run_phase(phase) + else: + for phase in sorted(PHASES): + run_phase(phase) + print(f"\n Phase {phase} done.") + + +if __name__ == "__main__": + main()