// Public fixtures only. No network or production mutations. import assert from 'node:assert/strict'; import { parseHTML } from 'linkedom'; import { createServer } from 'vite'; const { window } = parseHTML('
'); Object.assign(globalThis, { window, document: window.document, HTMLElement: window.HTMLElement, IS_REACT_ACT_ENVIRONMENT: true }); window.location = new URL('https://test.invalid/'); const { default: React, act } = await import('react'); const { createRoot } = await import('react-dom/client'); const { renderToString } = await import('react-dom/server'); const { MemoryRouter } = await import('react-router-dom'); const vite = await createServer({ server: { middlewareMode: true, hmr: false }, appType: 'custom' }); const { useApi } = await vite.ssrLoadModule('/src/lib/useApi.js'); const { ApiDataProvider } = await vite.ssrLoadModule('/src/context/ApiData.jsx'); const { SiteDataProvider } = await vite.ssrLoadModule('/src/context/SiteData.jsx'); const { default: HeroSlider } = await vite.ssrLoadModule('/src/components/HeroSlider.jsx'); const { api } = await vite.ssrLoadModule('/src/api.js'); let calls = 0; let siteCalls = 0; api.branches = api.settings = async () => { siteCalls++; return { data: [] }; }; const pending = []; const load = (signal) => { calls++; return new Promise((resolve) => pending.push({ signal, resolve })); }; function Probe({ query }) { const result = useApi(load, [query], query); return React.createElement('output', {}, result.data || 'loading'); } const container = document.getElementById('root'); const root = createRoot(container); const snapshot = { first: { data: 'SSR first' }, second: { data: 'SSR second' }, site: { phone: 'test' }, __receivedAt: Date.now() }; async function mount(query, initial = snapshot, key = 'same') { await act(async () => root.render(React.createElement(React.StrictMode, {}, React.createElement(ApiDataProvider, { value: initial }, React.createElement(SiteDataProvider, {}, React.createElement(Probe, { key, query })))))); } const banners = Array.from({ length: 6 }, (_, id) => ({ id, image: `/test-${id}.webp`, title: `Banner ${id}` })); const images = () => [...container.querySelectorAll('.hero-slide img[src]')].map((node) => node.getAttribute('src')); try { await mount('first'); assert.equal(calls, 0, 'Hydration including StrictMode must not repeat catalog request'); assert.equal(siteCalls, 0, 'No redundant branch/settings requests'); assert.equal(container.querySelector('output').textContent, 'SSR first'); await mount('second'); assert.equal(calls, 0); assert.equal(container.querySelector('output').textContent, 'SSR second'); await mount('uncached'); assert.equal(calls, 1, 'Client-only route still loads'); await mount('new-query'); await act(async () => pending.at(-2).resolve({ data: 'STALE' })); assert.doesNotMatch(container.textContent, /STALE/); await act(async () => pending.at(-1).resolve({ data: 'Fresh query' })); assert.match(container.textContent, /Fresh query/); await mount('first', { ...snapshot, __receivedAt: Date.now() - 31_000 }, 'expired'); assert.ok(calls >= 3, 'Old SSR snapshot must revalidate'); for (const request of pending) await act(async () => request.resolve({ data: 'Refreshed' })); const ssr = renderToString(React.createElement(MemoryRouter, {}, React.createElement(HeroSlider, { banners }))); assert.equal(new Set([...ssr.matchAll(/src="(\/test-[^"]+)"/g)].map((m) => m[1])).size, 3, 'SSR exposes only current/neighbor images'); await act(async () => root.render(React.createElement(MemoryRouter, {}, React.createElement(HeroSlider, { banners })))); assert.deepEqual([...new Set(images())].sort(), ['/test-0.webp', '/test-1.webp', '/test-5.webp']); assert.equal(container.querySelector('.hero__item--active picture img').getAttribute('fetchpriority'), 'high'); const next = container.querySelector('[aria-label="Следующий слайд"]'); for (let i = 0; i < 6; i++) await act(async () => next.dispatchEvent(new window.Event('click', { bubbles: true }))); assert.equal(new Set(images()).size, 6, 'All banners load on demand as the visitor browses'); console.log('PASS: SSR catalog/site requests 0, uncached/expired queries load, stale replies ignored; initial banner images 6 → 3, all slides reachable'); } finally { await act(async () => root.unmount()); await vite.close(); }