'use strict'; const assert = require('assert'); const vm = require('vm'); const { renderControlPlaneViewHtml } = require('../../scripts/lib/control-pane/control-plane-view-ui'); const { renderProximityVizHtml } = require('../../scripts/lib/control-pane/proximity-viz'); const controlPlaneHtml = renderControlPlaneViewHtml(); assert.ok(controlPlaneHtml.includes('grid-template-rows: minmax(0, 1fr)')); assert.ok(controlPlaneHtml.includes('#stage { position: relative; height: 100%; min-height: 0;')); const proximityHtml = renderProximityVizHtml(); assert.ok(proximityHtml.includes('grid-template-rows: minmax(0, 1fr)')); assert.ok(proximityHtml.includes('#stage { position: relative; height: 100%; min-height: 0;')); // A recording 2D context, so a test can assert what the view actually drew // rather than only what the template happens to contain. function createContext() { const log = []; const context = { fillStyle: '', strokeStyle: '', lineWidth: 1, globalAlpha: 1, font: '', log }; const record = fn => (...args) => { log.push({ fn, args, fillStyle: context.fillStyle }); }; for (const fn of ['setTransform', 'clearRect', 'beginPath', 'moveTo', 'lineTo', 'stroke', 'arc', 'rect', 'closePath', 'fill', 'fillText', 'save', 'restore']) { context[fn] = record(fn); } return context; } // Group the draw calls into paths and keep the filled ones: those are the risk // markers, and the axis and pair-link paths only stroke. function markerShapes(context) { const paths = []; let current = null; for (const entry of context.log) { if (entry.fn === 'beginPath') { if (current) paths.push(current); current = []; continue; } if (!current) current = []; current.push(entry); } if (current) paths.push(current); return paths.filter(path => path.some(entry => entry.fn === 'fill')).map(path => { const shape = path.some(e => e.fn === 'arc') ? 'circle' : path.some(e => e.fn === 'rect') ? 'square' : 'triangle'; return { shape, color: path.find(e => e.fn === 'fill').fillStyle }; }); } function textOf(node) { return (node.textContent || '') + node.children.map(textOf).join(''); } function findAll(node, className) { const found = node.className === className ? [node] : []; for (const child of node.children) found.push(...findAll(child, className)); return found; } function element(tag, context) { const node = { tag: tag || 'div', className: '', children: [], style: {}, attributes: {}, clientWidth: 640, clientHeight: 480, parentElement: { getBoundingClientRect: () => ({ width: 640, height: 480 }) }, appendChild(child) { node.children.push(child); return child; }, setAttribute(name, value) { node.attributes[name] = value; }, getContext: () => context }; let text = ''; let writes = 0; Object.defineProperty(node, 'textContent', { get() { return text; }, set(value) { text = String(value); node.children = []; writes += 1; }, configurable: true }); Object.defineProperty(node, 'writes', { get() { return writes; }, configurable: true }); return node; } // A poll settles through several chained promise callbacks, so draining needs a // few turns of the event loop rather than a single tick. function settle() { return new Promise(resolve => { let remaining = 5; const step = () => (remaining-- > 0 ? setImmediate(step) : resolve()); step(); }); } // Drives the view's inline script against a queue of poll responses, so one run // can cover several polls and the state each one leaves behind. A response may // carry a `hold` promise to park until the test releases it, or `never: true` to // stay pending so the request timeout can be exercised. async function render(responses) { const context = createContext(); const elements = new Map(); const timers = []; const timeouts = []; const queue = responses.slice(); const listeners = new Map(); let jsonReads = 0; const document = { getElementById(id) { if (!elements.has(id)) elements.set(id, element(null, context)); return elements.get(id); }, createElement: tag => element(tag, context) }; const html = renderControlPlaneViewHtml(); const start = html.indexOf('', start); assert.ok(start >= 0 && end > start, 'fixed renderer template must contain its inline script'); const code = html.slice(start + '