import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import { getScenarioRuntime, RudderClient } from '../../src/client/RudderClient.js'; import { createFakeTokenStore } from '../helpers/FakeTokenStore.js'; import { createFakeGateway, type FakeGateway } from '../helpers/fakeGateway.js'; import { createMemoryPlanStore, stubDeterministicUuid } from '../helpers/memoryPlanStore.js'; import { plan } from '../helpers/plan.js'; import type { StoreSession, WaitSession } from '../../src/scenario/engine/sessions.js'; const MINUTE = 60_000; describe('E2E: scenario state survives a reload', () => { let gateway: FakeGateway; // Shared backing store mimics IndexedDB persisting across page loads. const backing = { value: null as string | null }; function clientWithSharedStore(): RudderClient { return new RudderClient({ baseUrl: 'https://api.test.rudder.build', projectKey: 'test-project', tokenStore: createFakeTokenStore(), runtime: { planStateStore: createMemoryPlanStore(backing), loginEvent: null }, }); } beforeEach(() => { stubDeterministicUuid(); vi.useFakeTimers(); backing.value = null; gateway = createFakeGateway(); gateway.onEvent( 'session_start', plan('offer_flow') .node('wait_intro', 'wait', { duration: 1, unit: 'minutes' }) .node('offer', 'store', { message: 'Limited starter pack!' }) .edge('wait_intro', 'onComplete', 'offer') .build(), ); gateway.install(); }); afterEach(() => { vi.useRealTimers(); vi.unstubAllGlobals(); }); it('a wait started before reload resumes and fires the offer after reload', async () => { // --- Session 1: trigger the scenario, then "close the tab" mid-wait --- const client1 = clientWithSharedStore(); await client1.auth.loginWithDevice(); const runtime1 = await getScenarioRuntime(client1); await runtime1.send('session_start'); expect(runtime1.isRunning).toBe(true); expect(backing.value).toBeTruthy(); // run was persisted // --- Session 2: fresh client, same persisted state (page reload) --- const client2 = clientWithSharedStore(); const runtime2 = await getScenarioRuntime(client2); const resumedWaits: WaitSession[] = []; let offer: StoreSession | undefined; runtime2.onWait = (s) => resumedWaits.push(s); runtime2.onStore = (s) => { offer = s; }; await client2.auth.loginWithDevice(); // The wait node was rehydrated and re-dispatched. expect(runtime2.isRunning).toBe(true); expect(resumedWaits).toHaveLength(1); expect(offer).toBeUndefined(); // The remaining wait time still elapses → the offer surfaces on the new client. await vi.advanceTimersByTimeAsync(MINUTE); expect(offer).toBeDefined(); expect(offer!.get('message', '')).toBe('Limited starter pack!'); }); });