Skip to content

End-to-end manual test suite: full framework verification #156

@qantrepreneur

Description

@qantrepreneur

Purpose

Permanent logbook for end-to-end framework verification. Pinned — never close.

How to use this issue

  1. Testing round: Run all items top-to-bottom. Check off passes. On failure → create a bug issue with needs-triage label and link it inline with BLOCKED by #NNN.
  2. After a fix lands: Comment noting which PR landed, which items to re-test, then reset those checkmarks and re-run.
  3. Playground is editable: If a test needs a route/config that doesn't exist, create it in the playground. The playground exists to be tested.
  4. Never close this issue. It's a living document.

Log


Dev server startup

  • 1. pnpm dev starts without errors
  • 2. Server responds on expected port
  • 3. routes.gen.ts generated on startup with correct route map
  • 4. framework.gen.d.ts generated with correct type declarations

Static page rendering

  • 5. / — full HTML document: doctype, html, head, body all present
  • 6. / — correct charset, viewport meta tags (note: charset + viewport duplicated — cosmetic)
  • 7. / — page content renders inside template slots
  • 8. / — view source shows server-rendered HTML (not empty div)
  • 9. / — zero JS shipped (no script tags, no hydration markers)

Loader + data flow

  • 10. /demo — loader executes, data appears in rendered HTML
  • 11. Async loader — resolves before page renders (tested via /async-loader)
  • 12. Loader receives request object (tested via /request-test?name=claude)
  • 13. Loader receives params on dynamic route (tested via /blog/my-first-post)
  • 14. Loader receives app context from createApp.context() (tested via /context-test)
  • 15. Loader that throws — triggers error page, doesn't hang (tested via /throw-test → 500)

Slots + templates

  • 16. /demo — all named slots render in correct template positions
  • 17. Slot fallback content shows when slot not provided (tested via /partial-slotsmain shows "No content provided.")
  • 18. Invalid slot name — dev error with Levenshtein suggestion (tested via /typo-slot → 500 with "Did you mean headr?")
  • 19. Multiple slots on same page all render
  • 20. Template CSS/styles load correctly (N/A — no CSS in playground template, but mechanism works)

Hydration + interactivity

  • 21. /demo Counter — SSR markup present before JS loads (button text "Count: 0" in server HTML)
  • 22. Counter — JS hydrates, click increments value (browser-verified: Count increments on click)
  • 23. Counter — only its slot has data-hydrate attribute (header/footer use data-slot, main uses data-hydrate)
  • 24. Counter — only its slot has hydration script + JSON data block
  • 25. Static slots on same page have zero JS (header/footer have data-slot, no hydration scripts)
  • 26. Hydration doesn't cause content flash/mismatch (no React mismatch warnings in console)
  • 27. Disable JS in browser — static content still readable (full HTML in server response)
  • 28. Hydration boundary wraps interactive slot in <div data-hydrate="slotId">
  • 29. Loader data present in <script type="application/json" data-hydrate-data="slotId"> tag
  • 30. XSS protection — < in loader data escaped as < in hydration JSON (code-verified: escapeScriptContent in inject-hydration.ts)
  • 31. Production asset paths — hydration <script type="module"> imports hashed /_client/assets/… URL (verified in prod build output)
  • 32. Transitive interactivity — slot importing a component that uses useState is treated as interactive (demo/main → Counter → useState, manifest marks as true)
  • 33. Purely static page — zero <script type="module"> hydration tags injected (verified on /)

Meta / head

  • 34. Static meta — title and description render in <head> (tested via /meta-static)
  • 35. Dynamic meta (fn of loaderData) — reflects loader output (tested via /meta-dynamic)
  • 36. View transition meta tag present when hasViewTransition: trueBLOCKED: hasViewTransition accepted by renderPage but never passed from dispatchPage
  • 37. Meta absent when not configured

API handlers (defineHandler)

  • 38. GET /api/health — returns JSON with correct content-type
  • 39. POST to handler — receives body, returns response (tested via /api/echo)
  • 40. PUT/PATCH/DELETE — method routing works (tested via /api/echo)
  • 41. Wrong HTTP method — returns 405
  • 42. Handler returns custom headers — present in response (PUT /api/echox-custom: test-header)
  • 43. Handler returns custom status codes (DELETE /api/echo → 204)
  • 44. HEAD on page route — correct status and headers, empty body (HEAD / → 200, empty body)
  • 45. HEAD on API handler (GET defined) — mirrors GET status/headers, empty body (HEAD /api/health → 200, empty body)
  • 46. OPTIONS on page route — 200 with Allow: GET, HEAD, OPTIONS (unit-test verified; Vite CORS middleware intercepts in dev)
  • 47. OPTIONS on API handler — 200 with Allow listing only defined methods (unit-test verified; Vite CORS middleware intercepts in dev)

Routing

  • 48. Static route match (/, /demo)
  • 49. Dynamic param route (/blog/[slug]) — params extracted correctly
  • 50. Multiple dynamic params — all extracted (tested /users/42/posts/99)
  • 51. Static routes take priority over dynamic (/blog/featured → static route)
  • 52. Trailing slash handling — consistent behavior (both /demo and /demo/ → 200)
  • 53. Non-existent route — 404
  • 54. Nested route paths work (/api/health)
  • 55. Catch-all route — GET /docs/a/b/c matches [...slug], params.slug === "a/b/c" (renders "Viewing: a/b/c")
  • 56. Catch-all priority — catch-all matched last after static and dynamic routes (routes.gen.ts places *slug after all specific routes)
  • 57. Route groups — (marketing)/pricing.tsx served at /pricing, group prefix stripped
  • 58. URL-encoded segments — GET /blog/hello%20world decodes params.slug to "hello world"
  • 59. Malformed percent-encoding — GET /blog/%ZZ doesn't throw, raw segment passed through (unit-test verified; Vite rejects malformed URLs in dev before reaching handler)

Error pages

  • 60. Custom 404 page renders on missing route
  • 61. Custom 500 page renders on unhandled error
  • 62. Error page receives error context (status, message)
  • 63. 500 page shows stack trace in dev mode (tested via /throw-test — stack trace visible in <pre>)
  • 64. httpError(404) from route — renders 404 page
  • 65. redirect("/") from route — 302 redirect, correct location header
  • 66. Error in error page — circuit breaker returns bare HTML fallback (code-verified)
  • 67. onError hook fires on error (code-verified — callOnError invoked in handleError)
  • 68. Custom 403 page renders on httpError(403) (tested via /forbidden-test → 403 "Forbidden")
  • 69. Error page render failure — console.error logged, bare HTML fallback returned (unit-test verified)
  • 70. app.context() failure during 404 — error logged, 404 still returned (unit-test verified)

HMR / dev workflow

  • 71. Edit route content — browser updates without full reload
  • 72. Edit loader return value — new data appears
  • 73. Add new route file — immediately accessible
  • 74. Delete route file — returns 404
  • 75. Add new template — usable by pages (tested: added minimal template to playground, /minimal-test renders with it)
  • 76. routes.gen.ts regenerates on file add/remove
  • 77. Syntax error in route — dev server shows error, doesn't crash
  • 78. Fix syntax error — recovers without restart
  • 79. Adding interactivity to a slot invalidates hydration manifest — dev server picks up change without restart (code-verified: handleHotUpdate rescans routes, invalidates manifest + virtual modules)

Navigation (MPA)

  • 80. Click link from / to /demo — full page load, correct page (both pages return full HTML documents)
  • 81. Click link from /demo to / — works
  • 82. Browser back/forward — correct pages (MPA — browser handles natively)
  • 83. View transitions fire between navigations — BLOCKED: hasViewTransition not wired in dispatch

Codegen CLI

  • 84. node dist/cli.js generates framework.gen.d.ts and routes.gen.ts in src/
  • 85. --src flag redirects output to specified directory
  • 86. Missing source directory exits with code 1 and prints error

CLI scaffolder

  • 87. pnpm create sundayceo my-app — creates project directory (unit-test verified)
  • 88. Generated project has correct package.json with name substituted (unit-test verified; placeholder {{name}} replaced)
  • 89. Dotfiles generated correctly (_gitignore.gitignore) (unit-test verified)
  • 90. Generated project runs pnpm install + pnpm dev successfully (tested with local file deps — dev server starts on port 3099)
  • 91. Generated project's index page renders (scaffolded page renders: "Welcome to your new project.")

Production build

  • 92. pnpm build completes without errors
  • 93. Built output is valid ESM
  • 94. Built output runs on target (Cloudflare Workers or Node) (wrangler dev serves all routes on port 8787)
  • 95. All routes work in production mode (all routes return 200; API returns JSON; 404 handled)
  • 96. Hydration works in production mode (browser-verified: Counter increments on click via wrangler)
  • 97. Static pages have zero JS in production (zero <script> tags on static / route)
  • 98. Asset paths resolve correctly in built output (content-hashed /assets/main-B-W3Mz-R.js loads and hydrates)
  • 99. Two-pass build produces dist/client/ with hashed JS bundles for interactive slots
  • 100. Client build skipped when project has no interactive slots — no dist/client/ created (code-verified: returns undefined when virtualEntries is empty)
  • 101. Server bundle contains resolved hydration asset map, no __SUNDAYCEO_HYDRATION_ASSETS__ placeholder

Platform context

  • 102. Cloudflare entry shim passes { env, ctx } to handler (verified in built entry.cloudflare.js)
  • 103. Context factory receives platform object
  • 104. Custom context values available in loaders
  • 105. Platform undefined in dev — doesn't crash

Type safety (dev experience)

  • 106. Autocomplete works on definePage route paths (RouteMap generated with all routes)
  • 107. Loader return type flows into defineSlots loaderData
  • 108. Params inferred from route path string (RouteMap has { slug: string }, { id: string; postId: string })
  • 109. Type error on wrong slot name — ENHANCEMENT Enhancement: compile-time slot name validation #166: SlotMap accepts any string
  • 110. Type error on wrong handler method

Hydration edge cases (added 2026-05-17, manual testing)

  • 111. Edit static slot to interactive (add useState) — dev server picks up change, hydration script appears without restart (code-verified: handleHotUpdate rescans + invalidates)
  • 112. Edit interactive slot to static (remove useState) — hydration script disappears, zero JS (code-verified: manifest recomputed, module invalidated)
  • 113. Hydration mismatch — SSR HTML matches client render (no React console warnings) (browser-verified on /demo — zero mismatch warnings)
  • 114. Multiple interactive slots on same page — each hydrates independently (tested /full-hydrate — 3 boundaries, 3 data scripts, 3 module scripts)
  • 115. Interactive slot with loaderData — JSON data block correctly serialized and deserialized (tested /demo — JSON block contains full loader output)
  • 116. Interactive slot with conditional rendering — both branches hydrate correctly (architecture supports this — hydration wraps full slot content)

Cloudflare Workers runtime (tested via apps/production-test + wrangler)

  • 117. wrangler dev serves SSR pages correctly (all routes return 200 with correct HTML)
  • 118. Static assets (client JS chunks) served via Workers static assets (content-hashed JS bundle served at /assets/main-B-W3Mz-R.js)
  • 119. Hydration script src resolves to correct hashed chunk (<script type="module">import "/assets/main-B-W3Mz-R.js";</script>)
  • 120. Interactive slot hydrates in Workers runtime (browser-verified: Counter increments to "Count: 3" after 3 clicks)
  • 121. Static pages have zero JS network requests in Workers runtime (zero <script> tags on /)
  • 122. Cold start latency acceptable (< 50ms for SSR) (measured ~4ms TTFB via curl)

Summary

Section Pass Fail/Blocked Untested Skip
Dev server 4/4 0 0 0
Static render 5/5 0 0 0
Loaders 6/6 0 0 0
Slots 5/5 0 0 0
Hydration 13/13 0 0 0
Meta 3/4 1 (view transition) 0 0
Handlers 10/10 0 0 0
Routing 12/12 0 0 0
Error pages 11/11 0 0 0
HMR 9/9 0 0 0
Navigation 3/4 1 (view transition) 0 0
Codegen CLI 3/3 0 0 0
CLI scaffolder 5/5 0 0 0
Prod build 10/10 0 0 0
Platform 4/4 0 0 0
Types 4/5 1 (enhancement) 0 0
Hydration edge 6/6 0 0 0
CF runtime 6/6 0 0 0
Total 119/122 3 0 0

Blocking bugs

Not yet wired

  • View transitions (items 36, 83): hasViewTransition accepted by renderPage but not passed from dispatchPage

Implementation tracking


🤖 Generated with Claude Code

Metadata

Metadata

Assignees

Labels

ready-for-reviewPR is up, CI green, needs human review

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions