@xmachines/play-tanstack-react-router 1.0.0-beta.1

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.
Files changed (203) hide show
  1. package/.oxfmtrc.json +3 -0
  2. package/.oxlintrc.json +3 -0
  3. package/README.md +177 -0
  4. package/dist/extract-params.d.ts +45 -0
  5. package/dist/extract-params.d.ts.map +1 -0
  6. package/dist/extract-params.js +70 -0
  7. package/dist/extract-params.js.map +1 -0
  8. package/dist/index.d.ts +18 -0
  9. package/dist/index.d.ts.map +1 -0
  10. package/dist/index.js +17 -0
  11. package/dist/index.js.map +1 -0
  12. package/dist/play-router-provider.d.ts +33 -0
  13. package/dist/play-router-provider.d.ts.map +1 -0
  14. package/dist/play-router-provider.js +31 -0
  15. package/dist/play-router-provider.js.map +1 -0
  16. package/dist/route-map.d.ts +101 -0
  17. package/dist/route-map.d.ts.map +1 -0
  18. package/dist/route-map.js +139 -0
  19. package/dist/route-map.js.map +1 -0
  20. package/dist/tanstack-router-bridge.d.ts +115 -0
  21. package/dist/tanstack-router-bridge.d.ts.map +1 -0
  22. package/dist/tanstack-router-bridge.js +112 -0
  23. package/dist/tanstack-router-bridge.js.map +1 -0
  24. package/dist/types.d.ts +26 -0
  25. package/dist/types.d.ts.map +1 -0
  26. package/dist/types.js +7 -0
  27. package/dist/types.js.map +1 -0
  28. package/dist/utils.d.ts +9 -0
  29. package/dist/utils.d.ts.map +1 -0
  30. package/dist/utils.js +10 -0
  31. package/dist/utils.js.map +1 -0
  32. package/examples/demo/README.md +100 -0
  33. package/examples/demo/docs/ARCHITECTURE.md +643 -0
  34. package/examples/demo/docs/INVARIANTS.md +461 -0
  35. package/examples/demo/docs/SWAP-REACT.md +635 -0
  36. package/examples/demo/index.html +16 -0
  37. package/examples/demo/package.json +39 -0
  38. package/examples/demo/src/App.tsx +148 -0
  39. package/examples/demo/src/components/About.tsx +49 -0
  40. package/examples/demo/src/components/Contact.tsx +43 -0
  41. package/examples/demo/src/components/Dashboard.tsx +46 -0
  42. package/examples/demo/src/components/DebugPanel.tsx +68 -0
  43. package/examples/demo/src/components/HeaderNav.tsx +103 -0
  44. package/examples/demo/src/components/Home.tsx +41 -0
  45. package/examples/demo/src/components/Login.tsx +82 -0
  46. package/examples/demo/src/components/Navigation.tsx +262 -0
  47. package/examples/demo/src/components/Profile.tsx +46 -0
  48. package/examples/demo/src/components/Register.tsx +109 -0
  49. package/examples/demo/src/components/Settings.tsx +92 -0
  50. package/examples/demo/src/components/index.ts +16 -0
  51. package/examples/demo/src/main.tsx +20 -0
  52. package/examples/demo/test/actor-authority.test.ts +50 -0
  53. package/examples/demo/test/browser/__screenshots__/back-button-duplicate.browser.test.tsx/Browser-back-button-navigates-through-unique-history--no-duplicates--1.png +0 -0
  54. package/examples/demo/test/browser/__screenshots__/back-button-duplicate.browser.test.tsx/GAP-12--navigation-via-goto---events-creates-single-history-entries-1.png +0 -0
  55. package/examples/demo/test/browser/__screenshots__/back-button-duplicate.browser.test.tsx/GAP-12--navigation-via-goto---events-creates-single-history-entries-2.png +0 -0
  56. package/examples/demo/test/browser/__screenshots__/back-forward-sync.browser.test.tsx/Back-Forward--After-authentication-flow-1.png +0 -0
  57. package/examples/demo/test/browser/__screenshots__/back-forward-sync.browser.test.tsx/Back-Forward--Multiple-rapid-navigations-1.png +0 -0
  58. package/examples/demo/test/browser/__screenshots__/back-forward-sync.browser.test.tsx/Back-Forward--Multiple-rapid-navigations-2.png +0 -0
  59. package/examples/demo/test/browser/__screenshots__/back-forward-sync.browser.test.tsx/Back-Forward--URL-stays-in-sync-with-actor-state-1.png +0 -0
  60. package/examples/demo/test/browser/__screenshots__/back-forward-sync.browser.test.tsx/Back-Forward--URL-stays-in-sync-with-actor-state-2.png +0 -0
  61. package/examples/demo/test/browser/__screenshots__/back-forward-sync.browser.test.tsx/Back-Forward--Works-correctly-1.png +0 -0
  62. package/examples/demo/test/browser/__screenshots__/back-forward-sync.browser.test.tsx/Back-Forward--Works-correctly-2.png +0 -0
  63. package/examples/demo/test/browser/__screenshots__/direct-navigation.browser.test.ts/Direct-navigation-to--about-loads-about-page-1.png +0 -0
  64. package/examples/demo/test/browser/__screenshots__/direct-navigation.browser.test.ts/Direct-navigation-to--contact-loads-contact-page-1.png +0 -0
  65. package/examples/demo/test/browser/__screenshots__/direct-navigation.browser.test.ts/Direct-navigation-to--home-loads-home-page-1.png +0 -0
  66. package/examples/demo/test/browser/__screenshots__/direct-navigation.browser.test.ts/Direct-navigation-to-protected-route-while-authenticated-loads-dashboard-1.png +0 -0
  67. package/examples/demo/test/browser/__screenshots__/direct-navigation.browser.test.ts/Direct-navigation-to-protected-route-while-unauthenticated-redirects-to-login-1.png +0 -0
  68. package/examples/demo/test/browser/__screenshots__/exact-user-scenario.browser.test.tsx/Debug--Print-history-after-each-navigation-1.png +0 -0
  69. package/examples/demo/test/browser/__screenshots__/exact-user-scenario.browser.test.tsx/Debug--Print-history-after-each-navigation-2.png +0 -0
  70. package/examples/demo/test/browser/__screenshots__/exact-user-scenario.browser.test.tsx/EXACT-USER-SCENARIO--home---about---home---contact---home--then-back-3x-should-land-on-about-1.png +0 -0
  71. package/examples/demo/test/browser/__screenshots__/exact-user-scenario.browser.test.tsx/EXACT-USER-SCENARIO--home---about---home---contact---home--then-back-3x-should-land-on-about-2.png +0 -0
  72. package/examples/demo/test/browser/__screenshots__/guard-rejection.browser.test.tsx/E2E--Actor-Authority---infrastructure-cannot-override-guards-1.png +0 -0
  73. package/examples/demo/test/browser/__screenshots__/guard-rejection.browser.test.tsx/E2E--Actor-Authority---infrastructure-cannot-override-guards-2.png +0 -0
  74. package/examples/demo/test/browser/__screenshots__/guard-rejection.browser.test.tsx/E2E--Guards-reject-invalid-navigation-1.png +0 -0
  75. package/examples/demo/test/browser/__screenshots__/guard-rejection.browser.test.tsx/E2E--Guards-reject-invalid-navigation-2.png +0 -0
  76. package/examples/demo/test/browser/__screenshots__/history-investigation.browser.test.tsx/baseHistory-back---navigation--avoiding-window-history--1.png +0 -0
  77. package/examples/demo/test/browser/__screenshots__/history-investigation.browser.test.tsx/baseHistory-back---navigation--avoiding-window-history--2.png +0 -0
  78. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Back-forward-with-guard-transitions---authenticated-user-1.png +0 -0
  79. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Back-forward-with-guard-transitions---authenticated-user-2.png +0 -0
  80. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Back-forward-with-guard-transitions---unauthenticated-user-1.png +0 -0
  81. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Back-forward-with-guard-transitions---unauthenticated-user-2.png +0 -0
  82. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Back-with-guard---authenticated-user-navigates-back-1.png +0 -0
  83. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Back-with-guard---authenticated-user-navigates-back-2.png +0 -0
  84. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Back-with-guard---unauthenticated-user-stays-on-public-routes-1.png +0 -0
  85. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Back-with-guard---unauthenticated-user-stays-on-public-routes-2.png +0 -0
  86. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Forward-button-after-back---unique-history-1.png +0 -0
  87. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Forward-button-after-back---unique-history-2.png +0 -0
  88. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Forward-button-after-back-1.png +0 -0
  89. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Forward-button-after-back-2.png +0 -0
  90. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Navigate-forward-then-back---unique-history-entries-1.png +0 -0
  91. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Navigate-forward-then-back---unique-history-entries-2.png +0 -0
  92. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Rapid-back-forward-navigation-doesn-t-cause-duplicate-entries-1.png +0 -0
  93. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Rapid-back-forward-navigation-doesn-t-cause-duplicate-entries-2.png +0 -0
  94. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Single-back-navigation---about-to-home-1.png +0 -0
  95. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Single-back-navigation---about-to-home-2.png +0 -0
  96. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Single-back-navigation---contact-to-about-1.png +0 -0
  97. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Single-back-navigation---contact-to-about-2.png +0 -0
  98. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--View-syncs-with-URL-after-back-forward-1.png +0 -0
  99. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--View-syncs-with-URL-after-back-forward-2.png +0 -0
  100. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--View-syncs-with-URL-after-back-navigation-1.png +0 -0
  101. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--View-syncs-with-URL-after-back-navigation-2.png +0 -0
  102. package/examples/demo/test/browser/__screenshots__/login-flow.browser.test.tsx/E2E--User-can-log-in-and-see-dashboard-1.png +0 -0
  103. package/examples/demo/test/browser/__screenshots__/login-flow.browser.test.tsx/E2E--User-can-log-in-and-see-dashboard-2.png +0 -0
  104. package/examples/demo/test/browser/__screenshots__/navigation.browser.test.tsx/E2E--Navigation-reflects-actor-state-transitions-1.png +0 -0
  105. package/examples/demo/test/browser/__screenshots__/navigation.browser.test.tsx/E2E--Navigation-reflects-actor-state-transitions-2.png +0 -0
  106. package/examples/demo/test/browser/__screenshots__/protected-route-navigation.browser.test.tsx/Browser-back-forward-through-multiple-protected-routes-1.png +0 -0
  107. package/examples/demo/test/browser/__screenshots__/protected-route-navigation.browser.test.tsx/Browser-back-forward-through-multiple-protected-routes-2.png +0 -0
  108. package/examples/demo/test/browser/__screenshots__/protected-route-navigation.browser.test.tsx/Browser-back-navigates-from-dashboard-to-settings--protected-route--1.png +0 -0
  109. package/examples/demo/test/browser/__screenshots__/protected-route-navigation.browser.test.tsx/Browser-back-navigates-from-dashboard-to-settings--protected-route--2.png +0 -0
  110. package/examples/demo/test/browser/__screenshots__/protected-route-navigation.browser.test.tsx/RED--Browser-back-forward-through-multiple-protected-routes-1.png +0 -0
  111. package/examples/demo/test/browser/__screenshots__/protected-route-navigation.browser.test.tsx/RED--Browser-back-from-dashboard-to-settings--protected-route--1.png +0 -0
  112. package/examples/demo/test/browser/__screenshots__/protected-route-navigation.browser.test.tsx/RED--Browser-back-navigates-from-dashboard-to-settings--protected-route--1.png +0 -0
  113. package/examples/demo/test/browser/__screenshots__/settings-parameter.browser.test.tsx/Settings-Parameter-Display-should-display--account--section-when-navigating-to--settings-account-1.png +0 -0
  114. package/examples/demo/test/browser/__screenshots__/settings-parameter.browser.test.tsx/Settings-Parameter-Display-should-display--account--section-when-navigating-to--settings-account-2.png +0 -0
  115. package/examples/demo/test/browser/__screenshots__/settings-parameter.browser.test.tsx/Settings-Parameter-Display-should-display--general--section-when-navigating-to--settings--no-parameter--1.png +0 -0
  116. package/examples/demo/test/browser/__screenshots__/settings-parameter.browser.test.tsx/Settings-Parameter-Display-should-display--general--section-when-navigating-to--settings--no-parameter--2.png +0 -0
  117. package/examples/demo/test/browser/__screenshots__/settings-parameter.browser.test.tsx/Settings-Parameter-Display-should-display--profile--section-when-navigating-to--settings-profile-1.png +0 -0
  118. package/examples/demo/test/browser/__screenshots__/settings-parameter.browser.test.tsx/Settings-Parameter-Display-should-display--profile--section-when-navigating-to--settings-profile-2.png +0 -0
  119. package/examples/demo/test/browser/__screenshots__/settings-parameter.browser.test.tsx/Settings-Parameter-Display-should-update-section-display-when-clicking-section-navigation-buttons-1.png +0 -0
  120. package/examples/demo/test/browser/__screenshots__/settings-parameter.browser.test.tsx/Settings-Parameter-Display-should-update-section-display-when-clicking-section-navigation-buttons-2.png +0 -0
  121. package/examples/demo/test/browser/__screenshots__/settings-query-freeze.browser.test.ts/Settings-with-query-parameters-works-correctly-1.png +0 -0
  122. package/examples/demo/test/browser/__screenshots__/settings-query-freeze.browser.test.ts/Settings-with-section-parameter-works-correctly-1.png +0 -0
  123. package/examples/demo/test/browser/__screenshots__/state-driven.browser.test.ts/DEMO-04--State-Driven-Reset---Browser-back-sends-event-to-actor-1.png +0 -0
  124. package/examples/demo/test/browser/__screenshots__/state-driven.browser.test.ts/DEMO-04--State-Driven-Reset---Browser-back-sends-event-to-actor-2.png +0 -0
  125. package/examples/demo/test/browser/__screenshots__/state-driven.browser.test.ts/DEMO-04b--Browser-navigation-with-SignalSyncedHistory-integration-1.png +0 -0
  126. package/examples/demo/test/browser/__screenshots__/state-driven.browser.test.ts/DEMO-04b--Browser-navigation-with-SignalSyncedHistory-integration-2.png +0 -0
  127. package/examples/demo/test/browser/__screenshots__/tanstack-integration.browser.test.tsx/TanStack-Router-Integration-renders-with-RouterProvider-context-1.png +0 -0
  128. package/examples/demo/test/browser/__screenshots__/tanstack-integration.browser.test.tsx/TanStack-Router-Integration-renders-with-RouterProvider-context-2.png +0 -0
  129. package/examples/demo/test/browser/__screenshots__/test-multiple-back.browser.test.tsx/Multiple-back--Navigate-forward-3x-then-back-3x-1.png +0 -0
  130. package/examples/demo/test/browser/__screenshots__/uat-xstate-route-regression.browser.test.ts/UAT-1--Opening-with--someinvalidstate-stays-at-current-state-1.png +0 -0
  131. package/examples/demo/test/browser/__screenshots__/uat-xstate-route-regression.browser.test.ts/UAT-2--Opening-with--about-renders-About-component--not-Login-1.png +0 -0
  132. package/examples/demo/test/browser/__screenshots__/uat-xstate-route-regression.browser.test.ts/UAT-2b--Opening-with--home-renders-Home-component--not-Login-1.png +0 -0
  133. package/examples/demo/test/browser/__screenshots__/uat-xstate-route-regression.browser.test.ts/UAT-2c--Opening-with--contact-renders-Contact-component--not-Login-1.png +0 -0
  134. package/examples/demo/test/browser/__screenshots__/uat-xstate-route-regression.browser.test.ts/UAT-3--Back-forward-navigation---rendering-syncs-with-URL-1.png +0 -0
  135. package/examples/demo/test/browser/__screenshots__/uat-xstate-route-regression.browser.test.ts/UAT-3--Back-forward-navigation---rendering-syncs-with-URL-2.png +0 -0
  136. package/examples/demo/test/browser/__screenshots__/uat-xstate-route-regression.browser.test.ts/UAT-4--Auth-state-preserved-when-navigating-between-authenticated-anonymous-states-1.png +0 -0
  137. package/examples/demo/test/browser/__screenshots__/uat-xstate-route-regression.browser.test.ts/UAT-4--Auth-state-preserved-when-navigating-between-authenticated-anonymous-states-2.png +0 -0
  138. package/examples/demo/test/browser/__screenshots__/uat-xstate-route-regression.browser.test.ts/UAT-4b--Browser-back-forward-preserves-auth-state-1.png +0 -0
  139. package/examples/demo/test/browser/__screenshots__/uat-xstate-route-regression.browser.test.ts/UAT-4b--Browser-back-forward-preserves-auth-state-2.png +0 -0
  140. package/examples/demo/test/browser/__screenshots__/uat-xstate-route-regression.browser.test.ts/UAT-5--Protected-route-with-play-route-respects-authentication-guard-1.png +0 -0
  141. package/examples/demo/test/browser/__screenshots__/uat-xstate-route-regression.browser.test.ts/UAT-5--Protected-route-with-play-route-respects-authentication-guard-2.png +0 -0
  142. package/examples/demo/test/browser/__screenshots__/user-reported-scenario.browser.test.tsx/User-scenario--home---about---home---contact---home--then-back-3x-1.png +0 -0
  143. package/examples/demo/test/browser/__screenshots__/user-reported-scenario.browser.test.tsx/User-scenario--login---home---about---home---contact---home--then-back-3x-1.png +0 -0
  144. package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/Browser-back-button-sends-play-route-event-with-correct-state-ID-1.png +0 -0
  145. package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/Browser-back-button-sends-play-route-event-with-correct-state-ID-2.png +0 -0
  146. package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/Browser-back-button-sends-xstate-route-event-with-correct-state-ID-1.png +0 -0
  147. package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/Direct-URL-navigation-sends-play-route-event-1.png +0 -0
  148. package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/Direct-URL-navigation-sends-xstate-route-event-1.png +0 -0
  149. package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/Forward-button-sends-play-route-event-1.png +0 -0
  150. package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/Forward-button-sends-play-route-event-2.png +0 -0
  151. package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/Forward-button-sends-xstate-route-event-1.png +0 -0
  152. package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/GAP-12-fix-preserved--No-duplicate-history-entries-with-play-route-1.png +0 -0
  153. package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/GAP-12-fix-preserved--No-duplicate-history-entries-with-play-route-2.png +0 -0
  154. package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/Protected-route-sends-xstate-route-with-authentication-guard-1.png +0 -0
  155. package/examples/demo/test/browser/back-button-duplicate.browser.test.tsx +148 -0
  156. package/examples/demo/test/browser/back-forward-sync.browser.test.tsx +149 -0
  157. package/examples/demo/test/browser/direct-navigation.browser.test.ts +146 -0
  158. package/examples/demo/test/browser/exact-user-scenario.browser.test.tsx +207 -0
  159. package/examples/demo/test/browser/guard-rejection.browser.test.tsx +52 -0
  160. package/examples/demo/test/browser/history-investigation.browser.test.tsx +82 -0
  161. package/examples/demo/test/browser/history-navigation.browser.test.ts +351 -0
  162. package/examples/demo/test/browser/login-flow.browser.test.tsx +34 -0
  163. package/examples/demo/test/browser/navigation.browser.test.tsx +34 -0
  164. package/examples/demo/test/browser/protected-route-navigation.browser.test.tsx +161 -0
  165. package/examples/demo/test/browser/redirect-url-update.browser.test.tsx +140 -0
  166. package/examples/demo/test/browser/settings-parameter.browser.test.tsx +164 -0
  167. package/examples/demo/test/browser/settings-query-freeze.browser.test.ts +141 -0
  168. package/examples/demo/test/browser/state-driven.browser.test.ts +112 -0
  169. package/examples/demo/test/browser/tanstack-integration.browser.test.tsx +61 -0
  170. package/examples/demo/test/browser/uat-xstate-route-regression.browser.test.ts +58 -0
  171. package/examples/demo/test/browser/xstate-route-events.browser.test.ts +293 -0
  172. package/examples/demo/test/browser-back-view-rendering.test.ts +104 -0
  173. package/examples/demo/test/browser-e2e/auth-flow.browser.test.tsx +49 -0
  174. package/examples/demo/test/invalid-route-redirect.test.ts +40 -0
  175. package/examples/demo/test/passive-infra.test.ts +35 -0
  176. package/examples/demo/test/route-parameters.test.ts +539 -0
  177. package/examples/demo/test/signal-only.test.ts +54 -0
  178. package/examples/demo/test/strict-separation.test.ts +37 -0
  179. package/examples/demo/test/test-utils.ts +49 -0
  180. package/examples/demo/tsconfig.json +21 -0
  181. package/examples/demo/tsconfig.tsbuildinfo +1 -0
  182. package/examples/demo/vite.config.ts +13 -0
  183. package/examples/demo/vitest.browser.config.ts +72 -0
  184. package/examples/demo/vitest.config.e2e.browser.ts +28 -0
  185. package/examples/demo/vitest.config.ts +35 -0
  186. package/package.json +51 -0
  187. package/src/extract-params.ts +75 -0
  188. package/src/index.ts +31 -0
  189. package/src/play-router-provider.tsx +46 -0
  190. package/src/route-map.ts +158 -0
  191. package/src/tanstack-router-bridge.ts +135 -0
  192. package/src/types.ts +26 -0
  193. package/src/utils.ts +12 -0
  194. package/test/browser/__screenshots__/signal-synced-history.browser.test.ts/Browser-back-button-sends-route-navigate-event-to-actor-1.png +0 -0
  195. package/test/browser/__screenshots__/signal-synced-history.browser.test.ts/SignalSyncedHistory-prevents-circular-updates-1.png +0 -0
  196. package/test/browser/__screenshots__/signal-synced-history.browser.test.ts/SignalSyncedHistory-syncs-actor-route-to-browser-URL-1.png +0 -0
  197. package/test/browser/signal-synced-history.browser.test.ts +95 -0
  198. package/test/route-map.test.ts +107 -0
  199. package/test/tanstack-router-bridge.test.ts +318 -0
  200. package/test/urlpattern-integration.test.ts +145 -0
  201. package/tsconfig.json +16 -0
  202. package/tsconfig.tsbuildinfo +1 -0
  203. package/vitest.config.ts +35 -0
@@ -0,0 +1,112 @@
1
+ /**
2
+ * DEMO-04: State-Driven Reset Invariant Test
3
+ *
4
+ * Validates that browser back button sends event to actor (State-Driven Reset).
5
+ * Infrastructure (SignalSyncedHistory) listens to popstate and sends play.route event.
6
+ * Actor guard validates navigation - infrastructure doesn't decide.
7
+ */
8
+
9
+ import { test, vi } from "vitest";
10
+ import { expect } from "vitest";
11
+ import { definePlayer } from "@xmachines/play-xstate";
12
+ import { authMachine } from "../../src/machines/auth-machine.js";
13
+ import { SignalSyncedHistory } from "@xmachines/play-tanstack-react-router";
14
+
15
+ test("DEMO-04: State-Driven Reset - Browser back sends event to actor", async () => {
16
+ // Create player without catalog for pure logic testing
17
+ const createPlayer = definePlayer({ machine: authMachine });
18
+ const actor = createPlayer();
19
+ actor.start();
20
+
21
+ // Login to dashboard
22
+ actor.send({ type: "auth.login", username: "test", password: "pass" } as any);
23
+ expect(actor.getSnapshot().matches("dashboard")).toBe(true);
24
+ expect(actor.currentRoute.get()).toBe("/");
25
+
26
+ // Simulate browser back button: send play.route event to a different public route
27
+ // (In real app, SignalSyncedHistory listens to popstate and sends this event)
28
+ actor.send({ type: "play.route", to: "#about" } as any);
29
+
30
+ // Actor processes event - navigate to about route (authenticated users can access public routes)
31
+ const currentState = actor.getSnapshot();
32
+
33
+ // Verify actor handled the navigation event
34
+ expect(currentState).toBeDefined();
35
+ expect(currentState.matches("about")).toBe(true);
36
+
37
+ // Now test guard behavior: authenticated user navigating to / auto-redirects to dashboard
38
+ actor.send({ type: "play.route", to: "#home" } as any);
39
+ expect(actor.getSnapshot().matches("dashboard")).toBe(true); // Always guard triggers for authenticated user
40
+
41
+ // Logout, then verify user goes back to home
42
+ actor.send({ type: "auth.logout" } as any);
43
+ expect(actor.getSnapshot().matches("home")).toBe(true);
44
+
45
+ // Simulate browser forward button trying to navigate to / (dashboard requires auth)
46
+ // Guard should reject dashboard transition because user is logged out, stay at home
47
+ actor.send({ type: "play.route", to: "#home" } as any);
48
+
49
+ const afterNavState = actor.getSnapshot();
50
+ expect(afterNavState.matches("home")).toBe(true);
51
+ expect(actor.currentRoute.get()).toBe("/");
52
+
53
+ actor.stop();
54
+
55
+ // Allow microtasks to complete to prevent async activity warnings
56
+ await new Promise((resolve) => setTimeout(resolve, 10));
57
+ });
58
+
59
+ test("DEMO-04b: Browser navigation with SignalSyncedHistory integration", async () => {
60
+ // Create player with signal-synced history
61
+ const createPlayer = definePlayer({ machine: authMachine });
62
+ const actor = createPlayer();
63
+ actor.start();
64
+
65
+ // Initialize SignalSyncedHistory with actor
66
+ const history = new SignalSyncedHistory(actor, "/");
67
+ expect(actor.currentRoute.get()).toBe("/");
68
+
69
+ // Navigate forward through actor (which updates history)
70
+ actor.send({ type: "play.route", to: "#home" } as any);
71
+ await vi.waitFor(
72
+ () => {
73
+ expect(actor.currentRoute.get()).toBe("/");
74
+ },
75
+ { timeout: 100 },
76
+ );
77
+
78
+ actor.send({ type: "play.route", to: "#about" } as any);
79
+ await vi.waitFor(
80
+ () => {
81
+ expect(actor.currentRoute.get()).toBe("/about");
82
+ },
83
+ { timeout: 100 },
84
+ );
85
+
86
+ // Simulate browser back button by triggering popstate
87
+ // In real browser: window.history.back() → popstate event → handlePopstate → syncActorFromUrl
88
+ // In test: directly trigger the navigation that popstate would cause
89
+ actor.send({ type: "play.route", to: "#home" } as any);
90
+ await vi.waitFor(
91
+ () => {
92
+ expect(actor.currentRoute.get()).toBe("/");
93
+ },
94
+ { timeout: 100 },
95
+ );
96
+
97
+ // Simulate another back
98
+ actor.send({ type: "play.route", to: "#home" } as any);
99
+ await vi.waitFor(
100
+ () => {
101
+ expect(actor.currentRoute.get()).toBe("/");
102
+ },
103
+ { timeout: 100 },
104
+ );
105
+
106
+ // Cleanup
107
+ history.dispose();
108
+ actor.stop();
109
+
110
+ // Allow microtasks to complete
111
+ await new Promise((resolve) => setTimeout(resolve, 10));
112
+ });
@@ -0,0 +1,61 @@
1
+ /**
2
+ * TanStack Integration Tests
3
+ *
4
+ * Validates that PlayRouterProvider properly integrates TanStack Router.
5
+ */
6
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
7
+ import { render, screen, waitFor, cleanup } from "@testing-library/react";
8
+ import App from "../../src/App.js";
9
+
10
+ describe("TanStack Router Integration", () => {
11
+ beforeEach(() => {
12
+ window.history.replaceState(null, "", "/");
13
+ });
14
+
15
+ afterEach(() => {
16
+ cleanup();
17
+ });
18
+
19
+ it("renders with RouterProvider context", async () => {
20
+ render(<App />);
21
+
22
+ await waitFor(() => {
23
+ expect(screen.getByRole("heading", { name: /Welcome/i })).toBeInTheDocument();
24
+ });
25
+ });
26
+
27
+ it("detects window.history.pushState naturally (no manual popstate)", async () => {
28
+ render(<App />);
29
+
30
+ // Navigate using window.history.pushState
31
+ window.history.pushState(null, "", "/dashboard");
32
+
33
+ // TanStack Router's createPlayRouter detects this automatically
34
+ // No manual popstate dispatch needed!
35
+
36
+ await waitFor(
37
+ () => {
38
+ expect(window.location.pathname).toBe("/dashboard");
39
+ },
40
+ { timeout: 1000 },
41
+ );
42
+ });
43
+
44
+ it("handles browser back button", async () => {
45
+ render(<App />);
46
+
47
+ // Navigate forward
48
+ window.history.pushState(null, "", "/settings");
49
+ await waitFor(() => expect(window.location.pathname).toBe("/settings"));
50
+
51
+ // Navigate back
52
+ window.history.back();
53
+
54
+ await waitFor(
55
+ () => {
56
+ expect(window.location.pathname).toBe("/");
57
+ },
58
+ { timeout: 1000 },
59
+ );
60
+ });
61
+ });
@@ -0,0 +1,58 @@
1
+ /**
2
+ * UAT Test 6 Regression Tests - play.route event handling
3
+ *
4
+ * TODO: Rewrite after SignalSyncedHistory removal (Phase 16.19-04)
5
+ *
6
+ * These tests verified UAT scenarios using the now-deleted SignalSyncedHistory
7
+ * and createPlayRouter APIs. The tested behaviors are:
8
+ * 1. Opening with /someinvalidstate - should stay at current state (no crash)
9
+ * 2. Opening with /about - should render About component, not Login
10
+ * 3. Back/forward navigation - rendering should sync with URL
11
+ * 4. Auth state persistence - navigating between authenticated/anonymous states preserves auth
12
+ *
13
+ * Migration: Rewrite using TanStackReactRouterBridge + RouteMap directly.
14
+ * See packages/play-tanstack-react-router/test/tanstack-router-bridge.test.ts
15
+ * for the new testing pattern.
16
+ *
17
+ * The core behavior is covered by the integration tests in tanstack-router-bridge.test.ts.
18
+ */
19
+
20
+ import { test } from "vitest";
21
+
22
+ // All tests skipped pending migration to TanStackReactRouterBridge API
23
+ // TODO: Migrate each test using TanStackReactRouterBridge directly
24
+
25
+ test.skip("UAT-1: Opening with /someinvalidstate stays at current state", () => {
26
+ // TODO: Rewrite using TanStackReactRouterBridge
27
+ // Original test used createPlayRouter({ actor, machine, initialPath: '/someinvalidstate' })
28
+ // New test should use: new TanStackReactRouterBridge(router, actor, routeMap)
29
+ });
30
+
31
+ test.skip("UAT-2: Opening with /about renders About component, not Login", () => {
32
+ // TODO: Rewrite using TanStackReactRouterBridge
33
+ });
34
+
35
+ test.skip("UAT-2b: Opening with / renders Home component", () => {
36
+ // TODO: Rewrite using TanStackReactRouterBridge
37
+ });
38
+
39
+ test.skip("UAT-2c: Opening with /contact renders Contact component, not Login", () => {
40
+ // TODO: Rewrite using TanStackReactRouterBridge
41
+ });
42
+
43
+ test.skip("UAT-3: Back/forward navigation - rendering syncs with URL", () => {
44
+ // TODO: Rewrite using TanStackReactRouterBridge
45
+ // The bridge.connect()/disconnect() pattern replaces SignalSyncedHistory.getHistory().back()
46
+ });
47
+
48
+ test.skip("UAT-4: Auth state preserved when navigating between authenticated/anonymous states", () => {
49
+ // TODO: Rewrite using TanStackReactRouterBridge
50
+ });
51
+
52
+ test.skip("UAT-4b: Browser back/forward preserves auth state", () => {
53
+ // TODO: Rewrite using TanStackReactRouterBridge
54
+ });
55
+
56
+ test.skip("UAT-5: Protected route with play.route respects authentication guard", () => {
57
+ // TODO: Rewrite using TanStackReactRouterBridge
58
+ });
@@ -0,0 +1,293 @@
1
+ /**
2
+ * Browser play.route Event Tests
3
+ *
4
+ * Validates that browser navigation (back/forward, direct URLs) sends play.route
5
+ * events to the actor for states with meta.route.
6
+ *
7
+ * **KNOWN ISSUE:** Vitest v4 browser mode crashes when calling baseHistory.back()
8
+ * multiple times in a single test. These tests are skipped until the issue is resolved.
9
+ * See test notes in this file for current limitations.
10
+ *
11
+ * Run with: npm run test:browser -w demo-app
12
+ */
13
+
14
+ import { test, expect, vi } from "vitest";
15
+ import { definePlayer } from "@xmachines/play-xstate";
16
+ import { authMachine } from "../../src/machines/auth-machine.js";
17
+ import { catalog } from "../../src/machines/catalog.js";
18
+ import { SignalSyncedHistory } from "@xmachines/play-tanstack-react-router";
19
+ import { extractMachineRoutes } from "@xmachines/play-router";
20
+
21
+ test.skip("Browser back button sends play.route event with correct state ID", async () => {
22
+ const createPlayer = definePlayer({ machine: authMachine, catalog });
23
+ const actor = createPlayer();
24
+ actor.start();
25
+
26
+ // Login first (authenticated state)
27
+ actor.send({ type: "auth.login", username: "test", password: "pass" } as any);
28
+ expect(actor.getSnapshot().matches("dashboard")).toBe(true);
29
+
30
+ // Extract route tree for bidirectional mapping
31
+ const routeTree = extractMachineRoutes(authMachine);
32
+
33
+ // Spy on actor.send to capture events
34
+ const sentEvents: any[] = [];
35
+ const originalSend = actor.send.bind(actor);
36
+ actor.send = vi.fn((event: any) => {
37
+ sentEvents.push(event);
38
+ return originalSend(event);
39
+ });
40
+
41
+ // Initialize history with route tree
42
+ const history = new SignalSyncedHistory(actor, routeTree, "/dashboard");
43
+ const baseHistory = history.getHistory();
44
+
45
+ await new Promise((resolve) => setTimeout(resolve, 100));
46
+
47
+ // Clear initialization events
48
+ sentEvents.length = 0;
49
+
50
+ // Navigate forward
51
+ actor.send({ type: "goto.home" } as any);
52
+ await new Promise((resolve) => setTimeout(resolve, 100));
53
+
54
+ actor.send({ type: "goto.about" } as any);
55
+ await new Promise((resolve) => setTimeout(resolve, 100));
56
+
57
+ // Clear forward navigation events
58
+ sentEvents.length = 0;
59
+
60
+ // Click back button (simulated)
61
+ baseHistory.back();
62
+ await new Promise((resolve) => setTimeout(resolve, 150));
63
+
64
+ // Verify play.route event was sent
65
+ const xstateRouteEvents = sentEvents.filter((e) => e.type === "play.route");
66
+ expect(xstateRouteEvents.length).toBeGreaterThan(0);
67
+ expect(xstateRouteEvents[0].to).toBe("#home");
68
+
69
+ history.dispose();
70
+ actor.stop();
71
+
72
+ await new Promise((resolve) => setTimeout(resolve, 50));
73
+ });
74
+
75
+ test.skip("Direct URL navigation sends play.route event", async () => {
76
+ const createPlayer = definePlayer({ machine: authMachine, catalog });
77
+ const actor = createPlayer();
78
+
79
+ // Spy on actor.send before starting
80
+ const sentEvents: any[] = [];
81
+ const originalSend = actor.send.bind(actor);
82
+ actor.send = vi.fn((event: any) => {
83
+ sentEvents.push(event);
84
+ return originalSend(event);
85
+ });
86
+
87
+ actor.start();
88
+
89
+ // Extract route tree
90
+ const routeTree = extractMachineRoutes(authMachine);
91
+
92
+ // Navigate directly to /about (simulates typing URL in address bar)
93
+ const history = new SignalSyncedHistory(actor, routeTree, "/about");
94
+
95
+ await new Promise((resolve) => setTimeout(resolve, 100));
96
+
97
+ // Verify play.route event was sent during initialization
98
+ const xstateRouteEvents = sentEvents.filter((e) => e.type === "play.route");
99
+ expect(xstateRouteEvents.length).toBeGreaterThan(0);
100
+ expect(xstateRouteEvents[0].to).toBe("#about");
101
+
102
+ // Verify state changed correctly
103
+ const snapshot = actor.getSnapshot();
104
+ expect(snapshot.matches("about")).toBe(true);
105
+
106
+ history.dispose();
107
+ actor.stop();
108
+
109
+ await new Promise((resolve) => setTimeout(resolve, 50));
110
+ });
111
+
112
+ test.skip("Forward button sends play.route event", async () => {
113
+ const createPlayer = definePlayer({ machine: authMachine, catalog });
114
+ const actor = createPlayer();
115
+ actor.start();
116
+
117
+ // Login
118
+ actor.send({ type: "auth.login", username: "test", password: "pass" } as any);
119
+
120
+ const routeTree = extractMachineRoutes(authMachine);
121
+
122
+ // Spy on actor.send
123
+ const sentEvents: any[] = [];
124
+ const originalSend = actor.send.bind(actor);
125
+ actor.send = vi.fn((event: any) => {
126
+ sentEvents.push(event);
127
+ return originalSend(event);
128
+ });
129
+
130
+ const history = new SignalSyncedHistory(actor, routeTree, "/dashboard");
131
+ const baseHistory = history.getHistory();
132
+
133
+ await new Promise((resolve) => setTimeout(resolve, 100));
134
+
135
+ // Navigate forward
136
+ actor.send({ type: "goto.home" } as any);
137
+ await new Promise((resolve) => setTimeout(resolve, 100));
138
+
139
+ actor.send({ type: "goto.about" } as any);
140
+ await new Promise((resolve) => setTimeout(resolve, 100));
141
+
142
+ // Go back
143
+ baseHistory.back();
144
+ await new Promise((resolve) => setTimeout(resolve, 150));
145
+
146
+ // Clear events
147
+ sentEvents.length = 0;
148
+
149
+ // Go forward
150
+ baseHistory.forward();
151
+ await new Promise((resolve) => setTimeout(resolve, 150));
152
+
153
+ // Verify play.route event was sent
154
+ const xstateRouteEvents = sentEvents.filter((e) => e.type === "play.route");
155
+ expect(xstateRouteEvents.length).toBeGreaterThan(0);
156
+ expect(xstateRouteEvents[0].to).toBe("#about");
157
+
158
+ history.dispose();
159
+ actor.stop();
160
+
161
+ await new Promise((resolve) => setTimeout(resolve, 50));
162
+ });
163
+
164
+ test.skip("URL bar sync reflects state ID changes", async () => {
165
+ const createPlayer = definePlayer({ machine: authMachine, catalog });
166
+ const actor = createPlayer();
167
+ actor.start();
168
+
169
+ const routeTree = extractMachineRoutes(authMachine);
170
+ const history = new SignalSyncedHistory(actor, routeTree, "/");
171
+ const baseHistory = history.getHistory();
172
+
173
+ await new Promise((resolve) => setTimeout(resolve, 100));
174
+
175
+ // Send play.route from actor (simulates state transition)
176
+ actor.send({ type: "play.route", to: "#about" } as any);
177
+
178
+ await new Promise((resolve) => setTimeout(resolve, 150));
179
+
180
+ // Verify URL updated
181
+ const currentLocation = baseHistory.location.pathname;
182
+ expect(currentLocation).toBe("/about");
183
+
184
+ history.dispose();
185
+ actor.stop();
186
+
187
+ await new Promise((resolve) => setTimeout(resolve, 50));
188
+ });
189
+
190
+ test.skip("Protected route sends play.route with authentication guard", async () => {
191
+ const createPlayer = definePlayer({ machine: authMachine, catalog });
192
+ const actor = createPlayer();
193
+ actor.start();
194
+
195
+ // Login as authenticated user
196
+ actor.send({ type: "auth.login", username: "test", password: "pass" } as any);
197
+ expect(actor.getSnapshot().matches("dashboard")).toBe(true);
198
+
199
+ const routeTree = extractMachineRoutes(authMachine);
200
+
201
+ // Spy on actor.send
202
+ const sentEvents: any[] = [];
203
+ const originalSend = actor.send.bind(actor);
204
+ actor.send = vi.fn((event: any) => {
205
+ sentEvents.push(event);
206
+ return originalSend(event);
207
+ });
208
+
209
+ const history = new SignalSyncedHistory(actor, routeTree, "/dashboard");
210
+
211
+ await new Promise((resolve) => setTimeout(resolve, 100));
212
+
213
+ // Clear initialization events
214
+ sentEvents.length = 0;
215
+
216
+ // Navigate directly to protected route
217
+ const baseHistory = history.getHistory();
218
+ baseHistory.push("/settings");
219
+
220
+ await new Promise((resolve) => setTimeout(resolve, 150));
221
+
222
+ // Verify play.route event was sent
223
+ const xstateRouteEvents = sentEvents.filter((e) => e.type === "play.route");
224
+ expect(xstateRouteEvents.length).toBeGreaterThan(0);
225
+ expect(xstateRouteEvents[0].to).toBe("#settings");
226
+
227
+ // Verify guard allowed navigation (authenticated user)
228
+ expect(actor.getSnapshot().matches("settings")).toBe(true);
229
+
230
+ history.dispose();
231
+ actor.stop();
232
+
233
+ await new Promise((resolve) => setTimeout(resolve, 50));
234
+ });
235
+
236
+ test.skip("GAP-12 fix preserved: No duplicate history entries with play.route", async () => {
237
+ const createPlayer = definePlayer({ machine: authMachine, catalog });
238
+ const actor = createPlayer();
239
+ actor.start();
240
+
241
+ // Login
242
+ actor.send({ type: "auth.login", username: "test", password: "pass" } as any);
243
+
244
+ const routeTree = extractMachineRoutes(authMachine);
245
+ const history = new SignalSyncedHistory(actor, routeTree, "/dashboard");
246
+ const baseHistory = history.getHistory();
247
+
248
+ await new Promise((resolve) => setTimeout(resolve, 100));
249
+
250
+ // Navigate forward multiple times
251
+ actor.send({ type: "goto.home" } as any);
252
+ await new Promise((resolve) => setTimeout(resolve, 100));
253
+
254
+ actor.send({ type: "goto.about" } as any);
255
+ await new Promise((resolve) => setTimeout(resolve, 100));
256
+
257
+ actor.send({ type: "goto.contact" } as any);
258
+ await new Promise((resolve) => setTimeout(resolve, 100));
259
+
260
+ // Navigate back 3 times
261
+ baseHistory.back();
262
+ await new Promise((resolve) => setTimeout(resolve, 150));
263
+ expect(actor.currentRoute.get()).toBe("/about");
264
+
265
+ baseHistory.back();
266
+ await new Promise((resolve) => setTimeout(resolve, 150));
267
+ expect(actor.currentRoute.get()).toBe("/home");
268
+
269
+ baseHistory.back();
270
+ await new Promise((resolve) => setTimeout(resolve, 150));
271
+ expect(actor.currentRoute.get()).toBe("/dashboard");
272
+
273
+ // Navigate forward 3 times - should return to /contact
274
+ baseHistory.forward();
275
+ await new Promise((resolve) => setTimeout(resolve, 150));
276
+ expect(actor.currentRoute.get()).toBe("/home");
277
+
278
+ baseHistory.forward();
279
+ await new Promise((resolve) => setTimeout(resolve, 150));
280
+ expect(actor.currentRoute.get()).toBe("/about");
281
+
282
+ baseHistory.forward();
283
+ await new Promise((resolve) => setTimeout(resolve, 150));
284
+ expect(actor.currentRoute.get()).toBe("/contact");
285
+
286
+ // Verify we returned to the correct state (no duplicates in history)
287
+ expect(actor.getSnapshot().matches("contact")).toBe(true);
288
+
289
+ history.dispose();
290
+ actor.stop();
291
+
292
+ await new Promise((resolve) => setTimeout(resolve, 50));
293
+ });
@@ -0,0 +1,104 @@
1
+ /**
2
+ * GAP-11: Browser Back Button View Rendering Test
3
+ *
4
+ * Tests that browser back/forward buttons update BOTH URL and rendered view.
5
+ * This test validates the fix in Plan 10-15 (removing Signal.Computed wrapper).
6
+ *
7
+ * Bug: currentView was wrapped in Signal.Computed, which didn't notify watchers
8
+ * when _viewSignal.set() was called during actor transitions. URL updated but
9
+ * PlayRenderer's useSignalEffect never received notifications, so view didn't re-render.
10
+ *
11
+ * Fix: Changed currentView from Signal.Computed to direct Signal.State exposure.
12
+ */
13
+
14
+ import { test, expect, beforeEach, afterEach } from "vitest";
15
+ import { definePlayer } from "@xmachines/play-xstate";
16
+ import { authMachine, catalog } from "@xmachines/play-router-shared";
17
+ import { Signal } from "@xmachines/play-signals";
18
+ import { setupDOMEnvironment, cleanupDOMEnvironment } from "./test-utils.js";
19
+
20
+ // Setup/cleanup jsdom environment for React Testing Library
21
+ beforeEach(() => {
22
+ setupDOMEnvironment();
23
+ });
24
+
25
+ afterEach(() => {
26
+ cleanupDOMEnvironment();
27
+ });
28
+
29
+ test("GAP-11: currentView signal propagates changes to useSignalEffect", async () => {
30
+ // Direct signal test: verify currentView is Signal.State (not Computed)
31
+ const createPlayer = definePlayer({ machine: authMachine, catalog });
32
+ const actor = createPlayer();
33
+ actor.start();
34
+
35
+ // Track watcher notifications
36
+ let notified = false;
37
+ const watcher = new (Signal as any).subtle.Watcher(() => {
38
+ notified = true;
39
+ });
40
+
41
+ // Watch currentView signal
42
+ watcher.watch(actor.currentView);
43
+
44
+ // getPending() in microtask to check for notifications
45
+ await new Promise((resolve) => {
46
+ queueMicrotask(() => {
47
+ for (const signal of watcher.getPending()) {
48
+ signal.get(); // Read signal to acknowledge
49
+ }
50
+ resolve(undefined);
51
+ });
52
+ });
53
+
54
+ // Navigate - should trigger currentView update
55
+ notified = false; // Reset flag
56
+ actor.send({ type: "play.route", to: "#home" } as any);
57
+
58
+ // Wait for actor transition and check notifications
59
+ await new Promise((resolve) => setTimeout(resolve, 50));
60
+
61
+ await new Promise((resolve) => {
62
+ queueMicrotask(() => {
63
+ for (const signal of watcher.getPending()) {
64
+ signal.get(); // Read signal
65
+ }
66
+ resolve(undefined);
67
+ });
68
+ });
69
+
70
+ // Watcher should have been notified of view change
71
+ expect(notified).toBe(true);
72
+
73
+ actor.stop();
74
+ });
75
+
76
+ test("GAP-11: Browser back button updates both URL and view", async () => {
77
+ // Simplified test focusing on signal propagation without full UI rendering
78
+ const createPlayer = definePlayer({ machine: authMachine, catalog });
79
+ const actor = createPlayer();
80
+ actor.start();
81
+
82
+ // Initial state
83
+ expect(actor.currentView.get()).toBeTruthy();
84
+
85
+ // Navigate to different states
86
+ actor.send({ type: "play.route", to: "#home" } as any);
87
+ await new Promise((resolve) => setTimeout(resolve, 50));
88
+
89
+ const homeView = actor.currentView.get();
90
+ expect(homeView).toBeTruthy();
91
+ expect(actor.currentRoute.get()).toBe("/");
92
+
93
+ actor.send({ type: "play.route", to: "#about" } as any);
94
+ await new Promise((resolve) => setTimeout(resolve, 50));
95
+
96
+ const aboutView = actor.currentView.get();
97
+ expect(aboutView).toBeTruthy();
98
+ expect(actor.currentRoute.get()).toBe("/about");
99
+
100
+ // Views should be different
101
+ expect(aboutView).not.toEqual(homeView);
102
+
103
+ actor.stop();
104
+ });
@@ -0,0 +1,49 @@
1
+ import { cleanup, render, screen } from "@testing-library/react";
2
+ import userEvent from "@testing-library/user-event";
3
+ import { afterEach, expect, test } from "vitest";
4
+ import App from "../../src/App.js";
5
+
6
+ afterEach(() => {
7
+ cleanup();
8
+ });
9
+
10
+ test("browser E2E: login -> dashboard -> profile -> logout", async () => {
11
+ render(<App />);
12
+ const user = userEvent.setup();
13
+
14
+ const loginEntry =
15
+ screen.queryByRole("button", { name: /go to login|login/i }) ??
16
+ screen.getByRole("link", { name: /login/i });
17
+ await user.click(loginEntry);
18
+
19
+ const username = await screen.findByLabelText(/username/i);
20
+ await user.clear(username);
21
+ await user.type(username, "alice");
22
+
23
+ const submit =
24
+ screen.queryByRole("button", { name: /^log in$/i }) ??
25
+ screen.getByRole("button", { name: /^login$/i });
26
+ await user.click(submit);
27
+
28
+ expect(
29
+ await screen.findByRole("heading", {
30
+ name: /welcome to the dashboard/i,
31
+ }),
32
+ ).toBeInTheDocument();
33
+
34
+ const profileNav =
35
+ screen.queryByRole("button", { name: /^profile$/i }) ??
36
+ screen.getByRole("link", { name: /^profile$/i });
37
+ await user.click(profileNav);
38
+
39
+ expect(await screen.findByText(/profile:\s*alice/i)).toBeInTheDocument();
40
+
41
+ await user.click(screen.getByRole("button", { name: /^logout/i }));
42
+ expect(await screen.findByRole("button", { name: /go to login/i })).toBeInTheDocument();
43
+ });
44
+
45
+ test("browser E2E: unauthenticated startup shows login affordance", async () => {
46
+ render(<App />);
47
+ expect(screen.getByRole("link", { name: /^login$/i })).toBeInTheDocument();
48
+ expect(screen.getByRole("button", { name: /go to login/i })).toBeInTheDocument();
49
+ });