Release 2.16.0
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile, web, pulse-signage-web) (push) Successful in 1m46s
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile.player, player, pulse-signage-player) (push) Successful in 33s

This commit is contained in:
lzstealth committed 2026-10-20 18:52:36 +01:00
1 parent 821593dd8f
commit 3514d514a3
36 files changed
+2329 -346

No files matched your search

+28 -10
View File
@@ -275,6 +275,14 @@ test('template preview preserves the live canvas aspect and visual settings', ()
assert.match(livePreview.html, /left:10%;top:10%;width:50%;height:50%;z-index:2;/);
});
test('template and slide region outlines do not impose a twelve-pixel minimum', () => {
const css = fs.readFileSync(require.resolve('../src/web/public/css/theme-custom.css'), 'utf8');
const rule = css.match(/^\.designer-rect\s*\{([^}]+)\}/m);
assert.ok(rule);
assert.match(rule[1], /min-width:\s*0\s*;/);
assert.match(rule[1], /min-height:\s*0\s*;/);
});
test('template preview and live output keep matching visual CSS contracts', () => {
const previewCss = fs.readFileSync(require.resolve('../src/web/public/css/theme-custom.css'), 'utf8');
const playerCss = fs.readFileSync(require.resolve('../src/player/public/css/player.css'), 'utf8');
@@ -315,20 +323,23 @@ test('template animation controls mark the form dirty', () => {
test('template animation durations use percent in the advanced modal', () => {
const modal = fs.readFileSync(require.resolve('../src/web/views/signage/templates/animation-advanced-modal.hbs'), 'utf8');
const advancedFields = fs.readFileSync(require.resolve('../src/web/views/signage/templates/animation-advanced-fields.hbs'), 'utf8');
const script = fs.readFileSync(require.resolve('../src/web/public/js/templates/template-designer.js'), 'utf8');
const utilsScript = fs.readFileSync(require.resolve('../src/web/public/js/templates/template-designer-utils.js'), 'utf8');
const sharedControls = fs.readFileSync(require.resolve('../src/web/public/js/shared/region-animation-controls.js'), 'utf8');
assert.match(modal, /Duration %/);
assert.match(modal, /data-animation-modal-field="duration_ms"/);
assert.match(modal, /max="200"/);
assert.match(modal, /data-animation-modal-field="iterations"/);
assert.match(modal, /max="999"/);
assert.ok(script.includes('function getAnimationDurationPercent(durationMs)'));
assert.ok(script.includes('function getAnimationDurationMs(durationPercent)'));
assert.ok(script.includes('durationInput.value = String(getAnimationDurationPercent(step.duration_ms));'));
assert.ok(script.includes("nextStep.duration_ms = getAnimationDurationMs(durationInput ? durationInput.value : '');"));
assert.match(modal, /signage\/templates\/animation-advanced-fields/);
assert.match(advancedFields, /Duration %/);
assert.match(advancedFields, /data-animation-modal-field="duration_ms"/);
assert.match(advancedFields, /max="200"/);
assert.match(advancedFields, /data-animation-modal-field="iterations"/);
assert.match(advancedFields, /max="999"/);
assert.ok(script.includes('window.pulseRegionAnimationControls.populateAdvanced(animationAdvancedModal, config)'));
assert.ok(script.includes('window.pulseRegionAnimationControls.readAdvanced(animationAdvancedModal, getAnimationConfig(activeAnimationCard))'));
assert.ok(sharedControls.includes('Math.round(Number(value) / 10)'));
assert.ok(sharedControls.includes('Math.round(Number(value)) || 100)) * 10'));
assert.ok(script.includes('MAX_ANIMATION_REPEAT_COUNT = 999'));
assert.match(script, /Math\.min\(MAX_ANIMATION_REPEAT_COUNT, Math\.max\(1, readNumberField\(iterationsInput \? iterationsInput\.value : ''\) \|\| 1\)\)/);
assert.match(sharedControls, /Math\.min\(999, Math\.max\(1, Math\.round\(Number\(value\)\) \|\| 1\)\)/);
assert.match(utilsScript, /MAX_ANIMATION_REPEAT_COUNT = 999/);
assert.match(utilsScript, /clamp\(Math\.round\(Number\(value\.iterations\)\), 1, MAX_ANIMATION_REPEAT_COUNT\)/);
});
@@ -488,4 +499,11 @@ test('normalizeBackgroundGradient accepts linear colors and clamps the angle', (
angle: 360
}));
assert.equal(normalizeBackgroundGradient('{}'), null);
});
test('template snap toggle places its switch after the label', () => {
const template = fs.readFileSync(require.resolve('../src/web/views/signage/templates/form.hbs'), 'utf8');
const css = fs.readFileSync(require.resolve('../src/web/public/css/theme-custom.css'), 'utf8');
assert.match(template, /class="form-check form-switch template-snap-control[^>]*"><label for="template-layout-snap"[^>]*>Snap to guides<\/label><input type="checkbox" id="template-layout-snap"/);
assert.match(css, /\.template-snap-control \.form-check-input\s*\{[^}]*order:\s*2/);
});