Files
hyperframes/packages/producer/tests/mapbox-adapter/src/index.html
T
Miguel Ángel 75d92b55e0 feat(core): add readiness adapters for map and visualization libraries (#1548)
Add readiness-only runtime adapters for Mapbox GL JS, Leaflet, Google
Maps, MapLibre GL JS, and D3. Each adapter gates `__renderReady` until
the library's async initialization completes, preventing the renderer
from capturing blank or half-loaded frames.

Built on the `getReadyPromise` adapter contract from #1543. A shared
`createReadinessAdapter()` helper in `_readiness.ts` owns the
settled-tracking WeakSet, promise-identity stability, and
`Promise.allSettled` gate — each adapter provides only its type, window
global name, and `waitFor` callback.

Readiness signals per library:
- Mapbox / MapLibre: `map.loaded()` + `map.on('load', ...)`
- Leaflet: `map.whenReady(cb)`
- Google Maps: `map.addListener('tilesloaded', cb)` with handle cleanup
- D3: `transition.end()` promise

50 unit tests across 5 test files covering happy path, no-instances,
stable promise identity, post-settle drain, loaded-before-subscribe
race, and listener cleanup. 5 producer regression tests with
Docker-generated baselines for end-to-end render verification.
2026-06-18 01:21:27 -04:00

134 lines
4.5 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<style>
body { margin: 0; background: #1a1a2e; }
[data-composition-id="mapbox-test"] { position: relative; overflow: hidden; font-family: 'Segoe UI', system-ui, sans-serif; }
#map-canvas { position: absolute; top: 0; left: 0; }
#label {
position: absolute; top: 36px; left: 48px; z-index: 10;
color: #fff; font-size: 32px; font-weight: 700;
background: rgba(26, 26, 46, 0.9); padding: 12px 24px; border-radius: 10px;
border: 2px solid #e94560; opacity: 0;
}
#status {
position: absolute; bottom: 36px; right: 48px; z-index: 10;
color: #16213e; font-size: 18px; font-weight: 600; font-family: monospace;
background: #0f3460; padding: 8px 16px; border-radius: 6px;
opacity: 0; color: #e94560;
}
</style>
</head>
<body>
<div id="root" data-composition-id="mapbox-test" data-width="1920" data-height="1080" data-start="0" data-duration="4">
<canvas id="map-canvas" width="1920" height="1080"></canvas>
<div id="label">Mapbox adapter test</div>
<div id="status">mock: loaded</div>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<script>
window.__timelines = window.__timelines || {};
window.__hfMapbox = window.__hfMapbox || [];
// Draw procedural map visualization on canvas
var canvas = document.getElementById('map-canvas');
var ctx = canvas.getContext('2d');
// Background gradient
var grad = ctx.createLinearGradient(0, 0, 1920, 1080);
grad.addColorStop(0, '#16213e');
grad.addColorStop(0.5, '#0f3460');
grad.addColorStop(1, '#1a1a2e');
ctx.fillStyle = grad;
ctx.fillRect(0, 0, 1920, 1080);
// Grid lines (like a map grid)
ctx.strokeStyle = 'rgba(233, 69, 96, 0.15)';
ctx.lineWidth = 1;
for (var x = 0; x < 1920; x += 60) {
ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, 1080); ctx.stroke();
}
for (var y = 0; y < 1080; y += 60) {
ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(1920, y); ctx.stroke();
}
// Colored regions (simulating map zones)
var regions = [
{ x: 120, y: 180, w: 360, h: 300, color: 'rgba(233, 69, 96, 0.25)' },
{ x: 540, y: 120, w: 420, h: 240, color: 'rgba(15, 52, 96, 0.5)' },
{ x: 1020, y: 300, w: 480, h: 360, color: 'rgba(233, 69, 96, 0.15)' },
{ x: 300, y: 540, w: 540, h: 300, color: 'rgba(22, 33, 62, 0.6)' },
{ x: 900, y: 660, w: 600, h: 240, color: 'rgba(233, 69, 96, 0.2)' },
{ x: 1500, y: 120, w: 300, h: 480, color: 'rgba(15, 52, 96, 0.4)' },
];
for (var i = 0; i < regions.length; i++) {
var r = regions[i];
ctx.fillStyle = r.color;
ctx.fillRect(r.x, r.y, r.w, r.h);
ctx.strokeStyle = 'rgba(233, 69, 96, 0.4)';
ctx.lineWidth = 2;
ctx.strokeRect(r.x, r.y, r.w, r.h);
}
// Dots simulating POIs
var pois = [
[240, 330], [480, 240], [720, 200], [1200, 480], [1080, 420],
[600, 660], [840, 780], [1380, 300], [1560, 360], [360, 720]
];
for (var j = 0; j < pois.length; j++) {
ctx.beginPath();
ctx.arc(pois[j][0], pois[j][1], 8, 0, Math.PI * 2);
ctx.fillStyle = '#e94560';
ctx.fill();
ctx.strokeStyle = '#fff';
ctx.lineWidth = 2;
ctx.stroke();
}
// Label on canvas
ctx.font = 'bold 24px monospace';
ctx.fillStyle = 'rgba(233, 69, 96, 0.6)';
ctx.fillText('MOCK MAPBOX GL', 1400, 1040);
// Mock Mapbox map object
var loadCallbacks = [];
var isLoaded = false;
var mockMap = {
loaded: function() { return isLoaded; },
on: function(event, cb) {
if (event === 'load') {
if (isLoaded) { cb(); }
else { loadCallbacks.push(cb); }
}
},
getCenter: function() { return { lng: -122.4194, lat: 37.7749 }; },
getZoom: function() { return 12; }
};
// Simulate async load
setTimeout(function() {
isLoaded = true;
for (var k = 0; k < loadCallbacks.length; k++) {
loadCallbacks[k]();
}
}, 100);
// Register for adapter
window.__hfMapbox = [mockMap];
// GSAP timeline
var compId = 'mapbox-test';
var tl = gsap.timeline({ paused: true });
tl.to('#label', { opacity: 1, duration: 0.5, ease: 'power2.out' }, 0.2)
.from('#label', { y: -25, duration: 0.5, ease: 'power2.out' }, 0.2)
.to('#status', { opacity: 1, duration: 0.4, ease: 'power2.out' }, 0.9)
.from('#status', { x: 20, duration: 0.4, ease: 'power2.out' }, 0.9);
window.__timelines[compId] = tl;
</script>
</div>
</body>
</html>