fix(router): update route rune synchronously in navigate()
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 1m4s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m42s
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 1m4s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m42s
navigate() wrote location.hash and left router.route to the asynchronous
hashchange event. bootstrap flips app.ready in the same tick right after
navigate('/login') on an unauthenticated cold start, so for one frame the app
rendered with app.ready=true and the stale route ('lobby', from the empty hash)
— briefly mounting the lobby shell (tab bar + a doomed games.list) under the new
login screen before hashchange settled it.
Update router.route synchronously inside navigate(); the later hashchange
re-parses to the same value. This removes the boot lobby-flash (and its spurious
games.list 401) and, as the root cause, the offline e2e flake: enterLobby could
latch that transient lobby tab-bar instead of clicking through login, then the
Settings-tab click at line 44 fought the tab detaching into the login slide
(~7% of runs, both engines).
Also:
- offline.spec enterLobby: wait for the guest button and click it, instead of a
point-in-time count() that a pre-login splash frame sampled as 0 (skipping the
click and hanging / latching the transient).
- New e2e router.spec pins the synchronous-route property (RED on the old code,
which returned the previous route; GREEN now) via a mock-only __router seam.
Verify: check 0 / unit 490 / build / bundle 114.3/115 / full e2e 200 / the
offline+router stress at 40x on both engines 160/160 (was 6/80 failing).
This commit is contained in:
@@ -13,10 +13,12 @@ async function forceStandalone(page: Page): Promise<void> {
|
||||
});
|
||||
}
|
||||
|
||||
// After a load or reload, land in the lobby (dismiss the login screen if it is shown).
|
||||
// After a load, land in the lobby. The mock cold-starts on the login screen (no seeded session), so
|
||||
// click through as guest. Waiting for the button (rather than a point-in-time count()) is what makes
|
||||
// this deterministic: a count() sampled during the pre-login splash frame returned 0, skipped the
|
||||
// click, and then hung — or latched a transient lobby tab-bar — instead of opening the real lobby.
|
||||
async function enterLobby(page: Page): Promise<void> {
|
||||
const guest = page.getByRole('button', { name: /guest|гост/i });
|
||||
if (await guest.count()) await guest.first().click();
|
||||
await page.getByRole('button', { name: /guest|гост/i }).first().click();
|
||||
// The lobby tab bar has three tabs in a fixed order: New (0), Stats (1), Settings (2). nth() is
|
||||
// robust to locale, emoji variation selectors and the coachmark anchors (which the first-run
|
||||
// onboarding strips after it completes).
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { expect, test } from './fixtures';
|
||||
|
||||
// The hash router must update its reactive `route` rune synchronously inside navigate(), not defer it
|
||||
// to the asynchronous `hashchange` event. Bootstrap flips `app.ready` in the same tick right after
|
||||
// `navigate('/login')` on an unauthenticated cold start; a route that trailed the hash for one frame
|
||||
// let App.svelte render the stale route (the empty-hash lobby) under the new login screen — a visible
|
||||
// lobby-shell flash plus a doomed games.list. It also made the offline e2e flaky: enterLobby could
|
||||
// latch that transient lobby tab-bar instead of clicking through the login screen.
|
||||
|
||||
test('navigate updates the reactive route synchronously (no hashchange lag)', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
// The bundle has loaded and installed the mock __router seam once the login screen is up.
|
||||
await expect(page.getByRole('button', { name: /guest/i })).toBeVisible();
|
||||
|
||||
// Read the route in the SAME microtask as the navigate: the fix makes it the new route at once;
|
||||
// the pre-fix code returned the previous route (it waited for the async hashchange).
|
||||
const routeAfterNavigate = await page.evaluate(() => {
|
||||
const r = (window as unknown as { __router: { navigate(p: string): void; route(): string } }).__router;
|
||||
r.navigate('/settings');
|
||||
return r.route();
|
||||
});
|
||||
expect(routeAfterNavigate).toBe('settings');
|
||||
|
||||
// The hash was written too, so a reload/back still resolves the same route.
|
||||
expect(new URL(page.url()).hash).toBe('#/settings');
|
||||
});
|
||||
Reference in New Issue
Block a user