import { describe, expect, it, vi } from 'vitest'; import type { TileDemand } from './tile-scheduler'; import type { SpatialResourceKey } from './tiles'; import type { RenderScene2d } from './types'; import type { SpatialTileResult } from './worker-protocol'; import { SpatialTilePresentationController } from './tile-presentation-controller'; const viewport = { x_min: -4, x_max: 4, y_min: -3, y_max: 3, width: 800, height: 600 }; const document = { viewport, expressions: [ { id: 'curve', input_mode: 'text' as const, source: 'x^2+y^2<=1', enabled: true, color: '#f00' } ] }; const target = { expressionId: 'curve', representation: 'inequality-mask' as const, modelRevision: 3 }; const demand: TileDemand = { document, viewport, targets: [target] }; function result(resource: Partial = {}): SpatialTileResult { const resolved = { ...target, quality: 1, level: 7, x: 0, y: 0, ...resource }; const scene: RenderScene2d = { viewport, coverage: viewport, layers: [], diagnostics: [], variables: [], expression_roles: {}, model_revisions: { curve: resolved.modelRevision } }; return { resource: resolved, scene }; } function harness(initial: SpatialTileResult[] = [], synchronous: SpatialTileResult[] = []) { let tiles = initial; let onresource: ((result: SpatialTileResult) => void) | undefined; const frames: FrameRequestCallback[] = []; const scheduler = { setDemand: vi.fn((_demand: TileDemand, callback: (result: SpatialTileResult) => void) => { onresource = callback; for (const next of synchronous) callback(next); }), clear: vi.fn() }; const publish = vi.fn((next: SpatialTileResult[]) => { tiles = next; }); const controller = new SpatialTilePresentationController({ read: () => tiles, publish, scheduler, requestFrame: (callback) => (frames.push(callback), frames.length), cancelFrame: (handle) => { frames[handle - 1] = () => undefined; } }); return { controller, scheduler, publish, emit: (next: SpatialTileResult) => onresource?.(next), callback: () => onresource, flush: () => frames.splice(0).forEach((callback) => callback(performance.now())), tiles: () => tiles }; } describe('SpatialTilePresentationController', () => { it('retains compatible visible tiles before scheduling changed demand', () => { const compatible = result(); const stale = result({ modelRevision: 2, x: 1 }); const test = harness([compatible, stale]); test.controller.setDemand(demand); expect(test.tiles()).toEqual([compatible]); expect(test.scheduler.setDemand).toHaveBeenCalledWith(demand, expect.any(Function)); }); it('batches scheduler results into one publication per frame', () => { const test = harness(); const first = result(); const second = result({ x: 1 }); test.controller.setDemand(demand); test.emit(first); test.emit(second); expect(test.publish).not.toHaveBeenCalled(); test.flush(); expect(test.publish).toHaveBeenCalledTimes(1); expect(test.tiles()).toEqual([first, second]); }); it('publishes scheduler cache hits as one synchronous batch', () => { const first = result(); const second = result({ x: 1 }); const test = harness([], [first, second]); test.controller.setDemand(demand); expect(test.publish).toHaveBeenCalledTimes(1); expect(test.tiles()).toEqual([first, second]); }); it('ignores stale callbacks after demand changes', () => { const test = harness(); test.controller.setDemand(demand); const staleCallback = test.callback(); test.controller.setDemand({ ...demand, viewport: { ...viewport, x_min: 4, x_max: 12 } }); staleCallback?.(result()); test.flush(); expect(test.tiles()).toEqual([]); }); it('drops retained and in-flight tiles when baked presentation style changes', () => { const redDemand = { ...demand, targets: [{ ...target, styleSignature: 'red' }] }; const redTile = result({ styleSignature: 'red' }); const test = harness([redTile]); test.controller.setDemand(redDemand); const staleCallback = test.callback(); test.controller.setDemand({ ...demand, targets: [{ ...target, styleSignature: 'green' }] }); staleCallback?.(redTile); test.flush(); expect(test.tiles()).toEqual([]); }); it('drops retained tiles when complex mode changes semantics', () => { const realDemand = { ...demand, document: { ...document, complex_mode: false } }; const complexDemand = { ...demand, document: { ...document, complex_mode: true } }; const existing = result(); const test = harness([existing]); test.controller.setDemand(realDemand); test.controller.setDemand(complexDemand); expect(test.tiles()).toEqual([]); }); it('clears scheduling and presentation when demand becomes inactive', () => { const existing = result(); const test = harness([existing]); test.controller.setDemand(demand); test.controller.setDemand(undefined); expect(test.scheduler.clear).toHaveBeenCalledTimes(1); expect(test.tiles()).toEqual([]); }); it('suspends a replaced scene without withdrawing its retained tiles', () => { const existing = result(); const test = harness([existing]); test.controller.setDemand(demand); const staleCallback = test.callback(); test.controller.suspend(); staleCallback?.(result({ x: 1 })); test.flush(); expect(test.scheduler.clear).toHaveBeenCalledTimes(1); expect(test.tiles()).toEqual([existing]); test.controller.setDemand(undefined); expect(test.tiles()).toEqual([]); }); });