@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.
- package/.oxfmtrc.json +3 -0
- package/.oxlintrc.json +3 -0
- package/README.md +177 -0
- package/dist/extract-params.d.ts +45 -0
- package/dist/extract-params.d.ts.map +1 -0
- package/dist/extract-params.js +70 -0
- package/dist/extract-params.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/play-router-provider.d.ts +33 -0
- package/dist/play-router-provider.d.ts.map +1 -0
- package/dist/play-router-provider.js +31 -0
- package/dist/play-router-provider.js.map +1 -0
- package/dist/route-map.d.ts +101 -0
- package/dist/route-map.d.ts.map +1 -0
- package/dist/route-map.js +139 -0
- package/dist/route-map.js.map +1 -0
- package/dist/tanstack-router-bridge.d.ts +115 -0
- package/dist/tanstack-router-bridge.d.ts.map +1 -0
- package/dist/tanstack-router-bridge.js +112 -0
- package/dist/tanstack-router-bridge.js.map +1 -0
- package/dist/types.d.ts +26 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +9 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +10 -0
- package/dist/utils.js.map +1 -0
- package/examples/demo/README.md +100 -0
- package/examples/demo/docs/ARCHITECTURE.md +643 -0
- package/examples/demo/docs/INVARIANTS.md +461 -0
- package/examples/demo/docs/SWAP-REACT.md +635 -0
- package/examples/demo/index.html +16 -0
- package/examples/demo/package.json +39 -0
- package/examples/demo/src/App.tsx +148 -0
- package/examples/demo/src/components/About.tsx +49 -0
- package/examples/demo/src/components/Contact.tsx +43 -0
- package/examples/demo/src/components/Dashboard.tsx +46 -0
- package/examples/demo/src/components/DebugPanel.tsx +68 -0
- package/examples/demo/src/components/HeaderNav.tsx +103 -0
- package/examples/demo/src/components/Home.tsx +41 -0
- package/examples/demo/src/components/Login.tsx +82 -0
- package/examples/demo/src/components/Navigation.tsx +262 -0
- package/examples/demo/src/components/Profile.tsx +46 -0
- package/examples/demo/src/components/Register.tsx +109 -0
- package/examples/demo/src/components/Settings.tsx +92 -0
- package/examples/demo/src/components/index.ts +16 -0
- package/examples/demo/src/main.tsx +20 -0
- package/examples/demo/test/actor-authority.test.ts +50 -0
- 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
- 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
- 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
- package/examples/demo/test/browser/__screenshots__/back-forward-sync.browser.test.tsx/Back-Forward--After-authentication-flow-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/back-forward-sync.browser.test.tsx/Back-Forward--Multiple-rapid-navigations-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/back-forward-sync.browser.test.tsx/Back-Forward--Multiple-rapid-navigations-2.png +0 -0
- 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
- 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
- package/examples/demo/test/browser/__screenshots__/back-forward-sync.browser.test.tsx/Back-Forward--Works-correctly-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/back-forward-sync.browser.test.tsx/Back-Forward--Works-correctly-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/direct-navigation.browser.test.ts/Direct-navigation-to--about-loads-about-page-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/direct-navigation.browser.test.ts/Direct-navigation-to--contact-loads-contact-page-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/direct-navigation.browser.test.ts/Direct-navigation-to--home-loads-home-page-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/direct-navigation.browser.test.ts/Direct-navigation-to-protected-route-while-authenticated-loads-dashboard-1.png +0 -0
- 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
- package/examples/demo/test/browser/__screenshots__/exact-user-scenario.browser.test.tsx/Debug--Print-history-after-each-navigation-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/exact-user-scenario.browser.test.tsx/Debug--Print-history-after-each-navigation-2.png +0 -0
- 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
- 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
- package/examples/demo/test/browser/__screenshots__/guard-rejection.browser.test.tsx/E2E--Actor-Authority---infrastructure-cannot-override-guards-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/guard-rejection.browser.test.tsx/E2E--Actor-Authority---infrastructure-cannot-override-guards-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/guard-rejection.browser.test.tsx/E2E--Guards-reject-invalid-navigation-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/guard-rejection.browser.test.tsx/E2E--Guards-reject-invalid-navigation-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-investigation.browser.test.tsx/baseHistory-back---navigation--avoiding-window-history--1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-investigation.browser.test.tsx/baseHistory-back---navigation--avoiding-window-history--2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Back-forward-with-guard-transitions---authenticated-user-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Back-forward-with-guard-transitions---authenticated-user-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Back-forward-with-guard-transitions---unauthenticated-user-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Back-forward-with-guard-transitions---unauthenticated-user-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Back-with-guard---authenticated-user-navigates-back-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Back-with-guard---authenticated-user-navigates-back-2.png +0 -0
- 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
- 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
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Forward-button-after-back---unique-history-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Forward-button-after-back---unique-history-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Forward-button-after-back-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Forward-button-after-back-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Navigate-forward-then-back---unique-history-entries-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Navigate-forward-then-back---unique-history-entries-2.png +0 -0
- 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
- 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
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Single-back-navigation---about-to-home-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Single-back-navigation---about-to-home-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Single-back-navigation---contact-to-about-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Single-back-navigation---contact-to-about-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--View-syncs-with-URL-after-back-forward-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--View-syncs-with-URL-after-back-forward-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--View-syncs-with-URL-after-back-navigation-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--View-syncs-with-URL-after-back-navigation-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/login-flow.browser.test.tsx/E2E--User-can-log-in-and-see-dashboard-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/login-flow.browser.test.tsx/E2E--User-can-log-in-and-see-dashboard-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/navigation.browser.test.tsx/E2E--Navigation-reflects-actor-state-transitions-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/navigation.browser.test.tsx/E2E--Navigation-reflects-actor-state-transitions-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/protected-route-navigation.browser.test.tsx/Browser-back-forward-through-multiple-protected-routes-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/protected-route-navigation.browser.test.tsx/Browser-back-forward-through-multiple-protected-routes-2.png +0 -0
- 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
- 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
- package/examples/demo/test/browser/__screenshots__/protected-route-navigation.browser.test.tsx/RED--Browser-back-forward-through-multiple-protected-routes-1.png +0 -0
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- package/examples/demo/test/browser/__screenshots__/settings-query-freeze.browser.test.ts/Settings-with-query-parameters-works-correctly-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/settings-query-freeze.browser.test.ts/Settings-with-section-parameter-works-correctly-1.png +0 -0
- 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
- 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
- package/examples/demo/test/browser/__screenshots__/state-driven.browser.test.ts/DEMO-04b--Browser-navigation-with-SignalSyncedHistory-integration-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/state-driven.browser.test.ts/DEMO-04b--Browser-navigation-with-SignalSyncedHistory-integration-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/tanstack-integration.browser.test.tsx/TanStack-Router-Integration-renders-with-RouterProvider-context-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/tanstack-integration.browser.test.tsx/TanStack-Router-Integration-renders-with-RouterProvider-context-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/test-multiple-back.browser.test.tsx/Multiple-back--Navigate-forward-3x-then-back-3x-1.png +0 -0
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/Direct-URL-navigation-sends-play-route-event-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/Direct-URL-navigation-sends-xstate-route-event-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/Forward-button-sends-play-route-event-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/Forward-button-sends-play-route-event-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/Forward-button-sends-xstate-route-event-1.png +0 -0
- 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
- 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
- package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/Protected-route-sends-xstate-route-with-authentication-guard-1.png +0 -0
- package/examples/demo/test/browser/back-button-duplicate.browser.test.tsx +148 -0
- package/examples/demo/test/browser/back-forward-sync.browser.test.tsx +149 -0
- package/examples/demo/test/browser/direct-navigation.browser.test.ts +146 -0
- package/examples/demo/test/browser/exact-user-scenario.browser.test.tsx +207 -0
- package/examples/demo/test/browser/guard-rejection.browser.test.tsx +52 -0
- package/examples/demo/test/browser/history-investigation.browser.test.tsx +82 -0
- package/examples/demo/test/browser/history-navigation.browser.test.ts +351 -0
- package/examples/demo/test/browser/login-flow.browser.test.tsx +34 -0
- package/examples/demo/test/browser/navigation.browser.test.tsx +34 -0
- package/examples/demo/test/browser/protected-route-navigation.browser.test.tsx +161 -0
- package/examples/demo/test/browser/redirect-url-update.browser.test.tsx +140 -0
- package/examples/demo/test/browser/settings-parameter.browser.test.tsx +164 -0
- package/examples/demo/test/browser/settings-query-freeze.browser.test.ts +141 -0
- package/examples/demo/test/browser/state-driven.browser.test.ts +112 -0
- package/examples/demo/test/browser/tanstack-integration.browser.test.tsx +61 -0
- package/examples/demo/test/browser/uat-xstate-route-regression.browser.test.ts +58 -0
- package/examples/demo/test/browser/xstate-route-events.browser.test.ts +293 -0
- package/examples/demo/test/browser-back-view-rendering.test.ts +104 -0
- package/examples/demo/test/browser-e2e/auth-flow.browser.test.tsx +49 -0
- package/examples/demo/test/invalid-route-redirect.test.ts +40 -0
- package/examples/demo/test/passive-infra.test.ts +35 -0
- package/examples/demo/test/route-parameters.test.ts +539 -0
- package/examples/demo/test/signal-only.test.ts +54 -0
- package/examples/demo/test/strict-separation.test.ts +37 -0
- package/examples/demo/test/test-utils.ts +49 -0
- package/examples/demo/tsconfig.json +21 -0
- package/examples/demo/tsconfig.tsbuildinfo +1 -0
- package/examples/demo/vite.config.ts +13 -0
- package/examples/demo/vitest.browser.config.ts +72 -0
- package/examples/demo/vitest.config.e2e.browser.ts +28 -0
- package/examples/demo/vitest.config.ts +35 -0
- package/package.json +51 -0
- package/src/extract-params.ts +75 -0
- package/src/index.ts +31 -0
- package/src/play-router-provider.tsx +46 -0
- package/src/route-map.ts +158 -0
- package/src/tanstack-router-bridge.ts +135 -0
- package/src/types.ts +26 -0
- package/src/utils.ts +12 -0
- package/test/browser/__screenshots__/signal-synced-history.browser.test.ts/Browser-back-button-sends-route-navigate-event-to-actor-1.png +0 -0
- package/test/browser/__screenshots__/signal-synced-history.browser.test.ts/SignalSyncedHistory-prevents-circular-updates-1.png +0 -0
- package/test/browser/__screenshots__/signal-synced-history.browser.test.ts/SignalSyncedHistory-syncs-actor-route-to-browser-URL-1.png +0 -0
- package/test/browser/signal-synced-history.browser.test.ts +95 -0
- package/test/route-map.test.ts +107 -0
- package/test/tanstack-router-bridge.test.ts +318 -0
- package/test/urlpattern-integration.test.ts +145 -0
- package/tsconfig.json +16 -0
- package/tsconfig.tsbuildinfo +1 -0
- 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
|
+
});
|