UNPKG

ripple

Version:

Ripple is an elegant TypeScript UI framework

206 lines (170 loc) 9.47 kB
/** * Granular hydration tests for nested control flow combinations. * * Each test isolates one specific control flow pairing so failures * point directly at the responsible block type. Progression: * * for+if → for+switch → if+switch → for+if+switch (single/multi/disabled) * → switch+try (baseline) * → for+switch+try → for+if+try → for+if+switch+try (single then multi) * * The try-containing variants mirror MixedControlFlowStatic exactly. */ import { describe, it, expect } from 'vitest'; import { hydrateComponent, container, stripHydrationMarkers } from '../setup-hydration.js'; import * as ServerComponents from './compiled/server/nested-control-flow.js'; import * as ClientComponents from './compiled/client/nested-control-flow.js'; describe('hydration > nested control flow (granular)', () => { // ── for + if ──────────────────────────────────────────────────────────────── describe('for + if', () => { it('hydrates 3-item list where 2 are shown and 1 is hidden', async () => { await hydrateComponent(ServerComponents.ForIf, ClientComponents.ForIf); const items = Array.from(container.querySelectorAll('.item')).map((n) => n.textContent); expect(items).toEqual(['One', 'Two']); expect(container.querySelector('.item-3')).toBeNull(); }); }); // ── for + switch ───────────────────────────────────────────────────────────── describe('for + switch', () => { it('hydrates 3-item list where each item uses a switch', async () => { await hydrateComponent(ServerComponents.ForSwitch, ClientComponents.ForSwitch); const items = Array.from(container.querySelectorAll('.item')).map((n) => n.textContent); expect(items).toEqual(['A-1', 'B-2', 'A-3']); expect(container.querySelector('.kind-a.item-1')).not.toBeNull(); expect(container.querySelector('.kind-b.item-2')).not.toBeNull(); expect(container.querySelector('.kind-a.item-3')).not.toBeNull(); }); }); // ── if + switch (no for) ───────────────────────────────────────────────────── describe('if + switch', () => { it('hydrates an if wrapping a switch when the condition is true', async () => { await hydrateComponent(ServerComponents.IfSwitch, ClientComponents.IfSwitch); expect(container.querySelector('.case-a')?.textContent).toBe('Case A'); expect(container.querySelector('.case-default')).toBeNull(); }); it('hydrates an if wrapping a switch when the condition is false', async () => { await hydrateComponent(ServerComponents.IfSwitchHidden, ClientComponents.IfSwitchHidden); expect(container.querySelector('.case-a')).toBeNull(); // Sibling node after the if+switch must still hydrate correctly expect(container.querySelector('.after')?.textContent).toBe('after'); }); }); // ── for + if + switch (single item — no inter-item cursor advance) ──────────── describe('for + if + switch (single item)', () => { it('hydrates a single-item for+if+switch without cursor advance between items', async () => { await hydrateComponent( ServerComponents.ForIfSwitchSingle, ClientComponents.ForIfSwitchSingle, ); const items = Array.from(container.querySelectorAll('.item')).map((n) => n.textContent); expect(items).toEqual(['A-1']); expect(container.querySelector('.item-1.kind-a')).not.toBeNull(); }); }); // ── for + if + switch (multiple items — the regression) ────────────────────── describe('for + if + switch (multiple items)', () => { it('hydrates a two-item for+if+switch (cursor must advance correctly between items)', async () => { await hydrateComponent(ServerComponents.ForIfSwitchMulti, ClientComponents.ForIfSwitchMulti); const items = Array.from(container.querySelectorAll('.item')).map((n) => n.textContent); expect(items).toEqual(['A-1', 'B-2']); expect(container.querySelector('.item-1.kind-a')).not.toBeNull(); expect(container.querySelector('.item-2.kind-b')).not.toBeNull(); }); it('hydrates three-item list where the middle item has if=false', async () => { await hydrateComponent( ServerComponents.ForIfSwitchWithDisabled, ClientComponents.ForIfSwitchWithDisabled, ); const items = Array.from(container.querySelectorAll('.item')).map((n) => n.textContent); // item-2 has show=false so only item-1 and item-3 render expect(items).toEqual(['A-1', 'A-3']); expect(container.querySelector('.item-2')).toBeNull(); }); }); // ── switch + try (no for/if — baseline for try cursor behaviour) ───────────── describe('switch + try', () => { it('hydrates resolved content from server', async () => { await hydrateComponent(ServerComponents.SwitchTry, ClientComponents.SwitchTry); // Server resolves trackAsync fully, so resolved content is in the SSR HTML expect(container.querySelector('.resolved-a')?.textContent).toBe('A resolved'); expect(container.querySelector('.pending-a')).toBeNull(); }); }); // ── for + switch + try (no if) ──────────────────────────────────────────────── describe('for + switch + try', () => { it('hydrates resolved content for each item from server', async () => { await hydrateComponent(ServerComponents.ForSwitchTry, ClientComponents.ForSwitchTry); const items = Array.from(container.querySelectorAll('.item')).map((n) => n.textContent); expect(items).toEqual(['A-1', 'B-2']); }); }); // ── for + if + try (no switch) ──────────────────────────────────────────────── describe('for + if + try', () => { it('hydrates resolved content for each item from server', async () => { await hydrateComponent(ServerComponents.ForIfTry, ClientComponents.ForIfTry); const items = Array.from(container.querySelectorAll('.item')).map((n) => n.textContent); expect(items).toEqual(['item-1', 'item-2']); }); }); // ── for + if + switch + try ─────────────────────────────────────────────────── describe('for + if + switch + try', () => { it('hydrates resolved content for single-item for+if+switch+try', async () => { await hydrateComponent( ServerComponents.ForIfSwitchTrySingle, ClientComponents.ForIfSwitchTrySingle, ); const items = Array.from(container.querySelectorAll('.item')).map((n) => n.textContent); expect(items).toEqual(['A-1']); }); it('hydrates resolved content for two-item for+if+switch+try (cursor must advance correctly between items)', async () => { await hydrateComponent( ServerComponents.ForIfSwitchTryMulti, ClientComponents.ForIfSwitchTryMulti, ); const items = Array.from(container.querySelectorAll('.item')).map((n) => n.textContent); expect(items).toEqual(['A-1', 'B-2']); expect(container.querySelector('.item-1.kind-a')).not.toBeNull(); expect(container.querySelector('.item-2.kind-b')).not.toBeNull(); }); }); }); describe.skip('streaming ssr > nested control flow with try/pending', () => { describe('switch + try', () => { it('shows pending fallback immediately after hydration', async () => { await hydrateComponent(ServerComponents.SwitchTry, ClientComponents.SwitchTry); expect(container.querySelector('.pending-a')?.textContent).toBe('A pending'); }); }); describe('for + switch + try', () => { it('shows pending fallback for each item immediately after hydration', async () => { await hydrateComponent(ServerComponents.ForSwitchTry, ClientComponents.ForSwitchTry); const pending = Array.from(container.querySelectorAll('.pending')).map((n) => n.textContent); expect(pending).toEqual(['pending 1', 'pending 2']); }); }); describe('for + if + try', () => { it('shows pending fallback for each item immediately after hydration', async () => { await hydrateComponent(ServerComponents.ForIfTry, ClientComponents.ForIfTry); const pending = Array.from(container.querySelectorAll('.pending')).map((n) => n.textContent); expect(pending).toEqual(['pending 1', 'pending 2']); }); }); describe('for + if + switch + try', () => { it('shows pending fallback for single-item for+if+switch+try immediately after hydration', async () => { await hydrateComponent( ServerComponents.ForIfSwitchTrySingle, ClientComponents.ForIfSwitchTrySingle, ); const pending = Array.from(container.querySelectorAll('.pending')).map((n) => n.textContent); expect(pending).toEqual(['pending 1']); }); it('shows pending fallback for two-item for+if+switch+try immediately after hydration', async () => { await hydrateComponent( ServerComponents.ForIfSwitchTryMulti, ClientComponents.ForIfSwitchTryMulti, ); const pending = Array.from(container.querySelectorAll('.pending')).map((n) => n.textContent); expect(pending).toEqual(['pending 1', 'pending 2']); }); }); });