Files
language-dashboard/tests/test_dashboard_motion.js
T

110 lines
4.1 KiB
JavaScript

'use strict';
const assert = require('node:assert/strict');
const {readFileSync} = require('node:fs');
const {test} = require('node:test');
const vm = require('node:vm');
// Load the actual helpers without bootstrapping API polling or page controls.
const source = readFileSync(`${__dirname}/../static/dashboard.js`, 'utf8');
const helpers = source.slice(0, source.indexOf('\nfunction closeSidebar()'));
function node(overrides = {}) {
return {
textContent: '', style: {}, calls: [],
getClientRects: () => [1], matches: () => false,
querySelectorAll: () => [],
animate(frames, options) {
const animation = {cancelled: false, cancel() { this.cancelled = true; }};
this.calls.push({frames, options, animation});
return animation;
},
...overrides,
};
}
function harness() {
const nodes = new Map([['#dashboardConfig', {textContent: '{}'}]]);
const preference = {matches: false, addEventListener(type, listener) { this.listener = listener; }};
const document = {
hidden: false,
querySelector: selector => nodes.get(selector), querySelectorAll: () => [],
getAnimations: () => [],
};
const context = vm.createContext({document, window: {matchMedia: () => preference}, localStorage: {getItem: () => null}});
vm.runInContext(helpers, context);
return {nodes, preference, document, context};
}
test('animations respect reduced motion, background tabs, hidden nodes and older browsers', () => {
const h = harness();
const target = node();
h.context.animate(target, []);
assert.equal(target.calls.length, 1);
h.preference.matches = true;
h.context.animate(target, []);
h.preference.matches = false;
h.document.hidden = true;
h.context.animate(target, []);
h.document.hidden = false;
target.getClientRects = () => [];
h.context.animate(target, []);
assert.equal(target.calls.length, 1);
assert.doesNotThrow(() => h.context.animate(node({animate: undefined}), []));
});
test('new animations cancel older ones and delayed entrances stay hidden until they start', () => {
const h = harness();
const target = node();
h.context.animate(target, []);
h.context.animate(target, [], {delay: 35});
assert.equal(target.calls[0].animation.cancelled, true);
assert.equal(target.calls[1].options.fill, 'backwards');
assert.equal(target.calls[1].options.delay, 35);
});
test('entrance work and stagger delays are bounded for large libraries', () => {
const h = harness();
const targets = Array.from({length: 1000}, () => node());
h.context.animateEntrance(node({querySelectorAll: () => targets}));
assert.equal(targets.filter(target => target.calls.length).length, 24);
assert.equal(targets[23].calls[0].options.delay, 210);
});
test('unchanged metrics do not animate again', () => {
const h = harness();
const target = node({matches: () => true});
h.nodes.set('#cpuValue', target);
vm.runInContext("set('cpuValue', '12.0%'); set('cpuValue', '12.0%');", h.context);
assert.equal(target.textContent, '12.0%');
assert.equal(target.calls.length, 1);
});
test('polling preserves progress and does not replay container entrances', () => {
const h = harness();
let markup = '', bars = [], writes = 0;
const card = node();
const root = node({querySelectorAll: selector => selector === '.track > span' ? bars : [card]});
Object.defineProperty(root, 'innerHTML', {
get: () => markup,
set(value) { markup = value; writes++; bars = [node({style: {width: value}})]; },
});
h.nodes.set('#progress', root);
h.context.renderContent('progress', '25%');
assert.equal(bars[0].calls[0].frames[0].width, '0%');
h.context.renderContent('progress', '40%');
assert.equal(bars[0].calls[0].frames[0].width, '25%');
assert.equal(bars[0].calls[0].frames[1].width, '40%');
assert.equal(card.calls.length, 1);
h.context.renderContent('progress', '40%');
assert.equal(writes, 2);
});
test('enabling reduced motion cancels animations already in flight', () => {
const h = harness();
const animation = {cancelled: false, cancel() { this.cancelled = true; }};
h.document.getAnimations = () => [animation];
h.preference.listener({matches: true});
assert.equal(animation.cancelled, true);
});