376 lines
11 KiB
JavaScript
376 lines
11 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import { createRequire } from 'node:module';
|
|
import test from 'node:test';
|
|
|
|
const require = createRequire(import.meta.url);
|
|
const HDREnhancer = require('../src/hdr-enhance.js');
|
|
|
|
function createMatchMedia(matches) {
|
|
return (query) => ({
|
|
media: query,
|
|
matches: Boolean(matches[query]),
|
|
addEventListener() {},
|
|
removeEventListener() {}
|
|
});
|
|
}
|
|
|
|
function createFakeDocument() {
|
|
const nodesById = new Map();
|
|
const attach = (node, parentList) => {
|
|
parentList.push(node);
|
|
if (node.id) nodesById.set(node.id, node);
|
|
return node;
|
|
};
|
|
const documentElement = {
|
|
dataset: {},
|
|
style: {
|
|
values: {},
|
|
setProperty(name, value) {
|
|
this.values[name] = value;
|
|
},
|
|
removeProperty(name) {
|
|
delete this.values[name];
|
|
}
|
|
}
|
|
};
|
|
const body = {
|
|
dataset: {},
|
|
children: [],
|
|
appendChild(node) {
|
|
return attach(node, this.children);
|
|
},
|
|
removeAttribute(name) {
|
|
if (name === 'data-hdr-enhanced') delete this.dataset.hdrEnhanced;
|
|
if (name === 'data-hdr-mode') delete this.dataset.hdrMode;
|
|
if (name === 'data-hdr-media') delete this.dataset.hdrMedia;
|
|
if (name === 'data-hdr-text') delete this.dataset.hdrText;
|
|
}
|
|
};
|
|
const head = {
|
|
children: [],
|
|
appendChild(node) {
|
|
return attach(node, this.children);
|
|
}
|
|
};
|
|
|
|
return {
|
|
body,
|
|
documentElement,
|
|
head,
|
|
createElement(tagName) {
|
|
return {
|
|
tagName: tagName.toUpperCase(),
|
|
id: '',
|
|
className: '',
|
|
textContent: '',
|
|
innerHTML: '',
|
|
dataset: {},
|
|
style: {},
|
|
children: [],
|
|
attributes: {},
|
|
setAttribute(name, value) {
|
|
this.attributes[name] = String(value);
|
|
},
|
|
appendChild(child) {
|
|
return attach(child, this.children);
|
|
},
|
|
remove() {
|
|
nodesById.delete(this.id);
|
|
head.children = head.children.filter((node) => node !== this);
|
|
body.children = body.children.filter((node) => node !== this);
|
|
}
|
|
};
|
|
},
|
|
getElementById(id) {
|
|
return nodesById.get(id) || null;
|
|
},
|
|
querySelectorAll() {
|
|
return [];
|
|
}
|
|
};
|
|
}
|
|
|
|
test('detectCapabilities prefers the widest available gamut and HDR dynamic range', () => {
|
|
const capabilities = HDREnhancer.detectCapabilities({
|
|
matchMedia: createMatchMedia({
|
|
'(dynamic-range: high)': true,
|
|
'(color-gamut: rec2020)': true,
|
|
'(color-gamut: p3)': true,
|
|
'(prefers-reduced-motion: reduce)': false
|
|
}),
|
|
CSS: {
|
|
supports(property, value) {
|
|
return (
|
|
property === 'color' && value === 'color(display-p3 1 0 0)'
|
|
) || (
|
|
property === 'dynamic-range-limit' && value === 'no-limit'
|
|
);
|
|
}
|
|
}
|
|
});
|
|
|
|
assert.equal(capabilities.dynamicRange, 'high');
|
|
assert.equal(capabilities.colorGamut, 'rec2020');
|
|
assert.equal(capabilities.supportsDisplayP3, true);
|
|
assert.equal(capabilities.supportsDynamicRangeLimit, true);
|
|
assert.equal(capabilities.prefersReducedMotion, false);
|
|
});
|
|
|
|
test('createStyleText includes HDR, P3, pure-black, no-limit, and reduced-motion rules', () => {
|
|
const css = HDREnhancer.createStyleText({
|
|
intensity: 1.2,
|
|
animated: true,
|
|
text: true,
|
|
textBoost: 2.4
|
|
}, {
|
|
dynamicRange: 'high',
|
|
colorGamut: 'p3',
|
|
supportsDisplayP3: true,
|
|
supportsHdrColor: true,
|
|
prefersReducedMotion: false
|
|
});
|
|
|
|
assert.match(css, /@media \(dynamic-range: high\)/);
|
|
assert.match(css, /@media \(color-gamut: p3\)/);
|
|
assert.match(css, /dynamic-range-limit: no-limit/);
|
|
assert.match(css, /background: #000000 !important/);
|
|
assert.match(css, /body\[data-hdr-enhanced="true"\]::before/);
|
|
assert.match(css, /color\(display-p3/);
|
|
assert.match(css, /--hdr-fallback-cyan/);
|
|
assert.match(css, /--hdr-text-boost: 2\.40/);
|
|
assert.match(css, /data-hdr-text/);
|
|
assert.match(css, /--hdr-texture-url/);
|
|
assert.match(css, /background-clip: text/);
|
|
assert.match(css, /-webkit-text-fill-color: transparent/);
|
|
assert.match(css, /hdr-color/);
|
|
assert.match(css, /:where\(body\[data-hdr-enhanced="true"\] > :not/);
|
|
assert.match(css, /prefers-reduced-motion: reduce/);
|
|
});
|
|
|
|
test('createMediaConfig defaults to HDR video on HDR-capable outputs only', () => {
|
|
const hdr = HDREnhancer.createMediaConfig({
|
|
assetBase: 'https://example.com/',
|
|
media: true
|
|
}, {
|
|
dynamicRange: 'high'
|
|
});
|
|
const sdr = HDREnhancer.createMediaConfig({
|
|
assetBase: 'https://example.com/',
|
|
media: true
|
|
}, {
|
|
dynamicRange: 'standard'
|
|
});
|
|
const disabled = HDREnhancer.createMediaConfig({
|
|
assetBase: 'https://example.com/',
|
|
media: false
|
|
}, {
|
|
dynamicRange: 'high'
|
|
});
|
|
|
|
assert.equal(hdr.enabled, true);
|
|
assert.equal(hdr.type, 'video');
|
|
assert.equal(hdr.src, 'https://example.com/media/hdr-galaxy-fireworks.mp4');
|
|
assert.deepEqual(hdr.sources, [
|
|
{
|
|
src: 'https://example.com/media/hdr-galaxy-fireworks.mp4',
|
|
type: 'video/mp4; codecs="hvc1"'
|
|
},
|
|
{
|
|
src: 'https://example.com/media/hdr-galaxy-fireworks.webm',
|
|
type: 'video/webm; codecs="vp09.02.10.10.01.09.16.09.01"'
|
|
}
|
|
]);
|
|
assert.equal(sdr.enabled, false);
|
|
assert.equal(disabled.enabled, false);
|
|
});
|
|
|
|
test('createMediaConfig can inject a swappable HDR image stage', () => {
|
|
const hdr = HDREnhancer.createMediaConfig({
|
|
assetBase: 'https://example.com/',
|
|
media: true,
|
|
mediaType: 'image',
|
|
imageSrc: '/assets/custom-hdr.png'
|
|
}, {
|
|
dynamicRange: 'high'
|
|
});
|
|
|
|
assert.equal(hdr.enabled, true);
|
|
assert.equal(hdr.type, 'image');
|
|
assert.equal(hdr.src, 'https://example.com/assets/custom-hdr.png');
|
|
assert.equal(hdr.sources.length, 1);
|
|
assert.equal(hdr.sources[0].type, 'image/png');
|
|
});
|
|
|
|
test('enhance injects pure-black HDR media once, then disable removes it', () => {
|
|
const document = createFakeDocument();
|
|
const window = {
|
|
document,
|
|
matchMedia: createMatchMedia({
|
|
'(dynamic-range: high)': true,
|
|
'(color-gamut: p3)': true
|
|
}),
|
|
CSS: {
|
|
supports() {
|
|
return true;
|
|
}
|
|
}
|
|
};
|
|
|
|
const first = HDREnhancer.enhance({ window, intensity: 1.1 });
|
|
const second = HDREnhancer.enhance({ window, intensity: 1.1 });
|
|
|
|
assert.equal(first.active, true);
|
|
assert.equal(second.active, true);
|
|
assert.equal(second.reused, true);
|
|
assert.equal(document.head.children.filter((node) => node.id === 'hdr-enhancer-style').length, 1);
|
|
assert.equal(document.body.children.filter((node) => node.id === 'hdr-enhancer-overlay').length, 0);
|
|
assert.equal(document.body.children.filter((node) => node.id === 'hdr-enhancer-video').length, 1);
|
|
assert.equal(document.body.dataset.hdrEnhanced, 'true');
|
|
assert.equal(document.body.dataset.hdrMode, 'p3');
|
|
assert.equal(document.body.dataset.hdrMedia, 'true');
|
|
|
|
const disabled = HDREnhancer.disable({ window });
|
|
|
|
assert.equal(disabled.active, false);
|
|
assert.equal(document.getElementById('hdr-enhancer-style'), null);
|
|
assert.equal(document.getElementById('hdr-enhancer-overlay'), null);
|
|
assert.equal(document.getElementById('hdr-enhancer-video'), null);
|
|
assert.equal(document.body.dataset.hdrEnhanced, undefined);
|
|
assert.equal(document.body.dataset.hdrMedia, undefined);
|
|
});
|
|
|
|
test('readScriptOptions maps script data attributes into plugin options', () => {
|
|
const options = HDREnhancer.readScriptOptions({
|
|
dataset: {
|
|
hdrIntensity: '1.35',
|
|
hdrAnimated: 'false',
|
|
hdrOverlay: 'true',
|
|
hdrScanlines: 'false',
|
|
hdrBadge: 'false',
|
|
hdrEffects: 'true',
|
|
hdrEffect: 'cosmic-fireworks',
|
|
hdrQuality: 'high',
|
|
hdrFps: '48',
|
|
hdrMaxDpr: '1.5',
|
|
hdrMotion: 'ambient',
|
|
hdrMedia: 'true',
|
|
hdrBlackout: 'true',
|
|
hdrVideoSrc: '/media/custom-hdr.mp4',
|
|
hdrWebmSrc: '/media/custom-hdr.webm',
|
|
hdrImageSrc: '/media/custom-hdr.png',
|
|
hdrMediaType: 'image',
|
|
hdrText: 'true',
|
|
hdrTextBoost: '2.65',
|
|
hdrTextSrc: '/media/custom-text-hdr.png'
|
|
},
|
|
src: 'https://cdn.example.com/dist/hdr-web-plugin.js'
|
|
});
|
|
|
|
assert.deepEqual(options, {
|
|
intensity: 1.35,
|
|
animated: false,
|
|
overlay: true,
|
|
scanlines: false,
|
|
badge: false,
|
|
effects: true,
|
|
effect: 'cosmic-fireworks',
|
|
quality: 'high',
|
|
fps: 48,
|
|
maxDpr: 1.5,
|
|
motion: 'ambient',
|
|
media: true,
|
|
blackout: true,
|
|
videoSrc: '/media/custom-hdr.mp4',
|
|
webmSrc: '/media/custom-hdr.webm',
|
|
imageSrc: '/media/custom-hdr.png',
|
|
mediaType: 'image',
|
|
text: true,
|
|
textBoost: 2.65,
|
|
textSrc: '/media/custom-text-hdr.png',
|
|
assetBase: 'https://cdn.example.com/'
|
|
});
|
|
});
|
|
|
|
test('plugin exposes stable metadata for script tag consumers', () => {
|
|
assert.equal(HDREnhancer.pluginName, 'HDR Web Plugin');
|
|
assert.match(HDREnhancer.version, /^\d+\.\d+\.\d+$/);
|
|
assert.equal(typeof HDREnhancer.install, 'function');
|
|
});
|
|
|
|
test('dist plugin entry exports the same public API', () => {
|
|
const DistHDREnhancer = require('../dist/hdr-web-plugin.js');
|
|
|
|
assert.equal(DistHDREnhancer.pluginName, HDREnhancer.pluginName);
|
|
assert.equal(DistHDREnhancer.version, HDREnhancer.version);
|
|
assert.equal(typeof DistHDREnhancer.enhance, 'function');
|
|
assert.equal(typeof DistHDREnhancer.disable, 'function');
|
|
});
|
|
|
|
test('createEffectConfig enables cosmic fireworks with reduced-motion fallback', () => {
|
|
const active = HDREnhancer.createEffectConfig({
|
|
intensity: 1.5,
|
|
effect: 'cosmic-fireworks',
|
|
effects: true,
|
|
motion: 'ambient'
|
|
}, {
|
|
prefersReducedMotion: false,
|
|
colorGamut: 'p3'
|
|
});
|
|
const reduced = HDREnhancer.createEffectConfig({
|
|
intensity: 1.5,
|
|
effect: 'cosmic-fireworks',
|
|
effects: true
|
|
}, {
|
|
prefersReducedMotion: true,
|
|
colorGamut: 'p3'
|
|
});
|
|
|
|
assert.equal(active.enabled, true);
|
|
assert.equal(active.effect, 'cosmic-fireworks');
|
|
assert.equal(active.fireworkBursts, true);
|
|
assert.equal(active.galaxy, true);
|
|
assert.equal(active.motionScale, 1);
|
|
assert.equal(reduced.motionScale, 0.18);
|
|
});
|
|
|
|
test('createEffectConfig defaults to a static low-power budget', () => {
|
|
const config = HDREnhancer.createEffectConfig({
|
|
intensity: 1.55,
|
|
effect: 'cosmic-fireworks',
|
|
effects: true
|
|
}, {
|
|
prefersReducedMotion: false,
|
|
colorGamut: 'p3'
|
|
});
|
|
|
|
assert.equal(config.quality, 'low');
|
|
assert.equal(config.motion, 'static');
|
|
assert.equal(config.shouldAnimate, false);
|
|
assert.equal(config.targetFps <= 1, true);
|
|
assert.equal(config.dprCap, 1);
|
|
assert.equal(config.starCount <= 50, true);
|
|
assert.equal(config.galaxyCount <= 58, true);
|
|
assert.equal(config.burstParticleCount, 0);
|
|
assert.equal(config.maxSparks, 0);
|
|
});
|
|
|
|
test('createEffectConfig requires explicit ambient motion for continuous animation', () => {
|
|
const config = HDREnhancer.createEffectConfig({
|
|
intensity: 1.55,
|
|
effect: 'cosmic-fireworks',
|
|
effects: true,
|
|
quality: 'balanced',
|
|
motion: 'ambient'
|
|
}, {
|
|
prefersReducedMotion: false,
|
|
colorGamut: 'p3'
|
|
});
|
|
|
|
assert.equal(config.quality, 'balanced');
|
|
assert.equal(config.motion, 'ambient');
|
|
assert.equal(config.shouldAnimate, true);
|
|
assert.equal(config.targetFps <= 24, true);
|
|
assert.equal(config.dprCap <= 1.15, true);
|
|
assert.equal(config.maxSparks <= 90, true);
|
|
});
|