release: v2.11.2
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile, web, pulse-signage-web) (push) Successful in 2m0s
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile.player, player, pulse-signage-player) (push) Successful in 31s

This commit is contained in:
2026-09-05 18:14:38 +01:00
parent 7fec2154e0
commit c4ae69d25f
40 changed files with 314 additions and 352 deletions
+72
View File
@@ -12,6 +12,14 @@ const {
const renderTemplateAddPage = require('../src/web/routes/signage/templates/add');
const renderTemplateEditPage = require('../src/web/routes/signage/templates/edit');
const { buildDuplicateTemplateName, buildDuplicateTemplate } = require('../src/web/routes/signage/templates/duplicate');
const { buildThumbnailPreviewPayload } = require('../src/web/lib/media/slide-thumbnail-preview');
function readCssDeclarations(css, selector) {
const escapedSelector = selector.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const match = css.match(new RegExp(escapedSelector + '\\s*\\{([^}]*)\\}'));
assert.ok(match, `CSS rule not found: ${selector}`);
return Object.fromEntries(match[1].split(';').map((declaration) => declaration.trim().split(':')).filter(([property, value]) => property && value).map(([property, ...value]) => [property.trim(), value.join(':').trim()]));
}
test('extractTemplateRegions normalizes JSON regions and filters invalid rows', () => {
const regions = extractTemplateRegions({
@@ -175,6 +183,70 @@ test('template edit page confirms deletion', () => {
assert.doesNotMatch(html, /id="delete-template-form"/);
});
test('template preview preserves the live canvas aspect and visual settings', () => {
const template = {
id: 12,
name: 'Portrait-ish display',
canvas_size_id: 3,
canvas_size_width: 1440,
canvas_size_height: 900,
background_image_path: '/media/uploads/background.png',
background_color: '#123456',
background_gradient: JSON.stringify({
type: 'linear',
angle: 135,
stops: [
{ color: '#123456', position: 0 },
{ color: '#abcdef', position: 100 }
]
}),
regions: [{
region_key: 'hero',
region_type: 'text',
label: 'Hero',
x: 144,
y: 90,
width: 720,
height: 450,
z_index: 2
}],
region_usage: []
};
const editorHtml = renderTemplateEditPage(template, {
canvasSizes: [{ id: 3, name: 'Custom', width: 1440, height: 900 }]
}, '', { permissionKeys: ['templates.update'] });
const livePreview = buildThumbnailPreviewPayload({
template,
content: { hero: { type: 'text', value: 'Preview content' } }
}, { baseUrl: 'https://signage.test' });
assert.match(editorHtml, /style="aspect-ratio: 1440 \/ 900;"/);
assert.deepEqual(
{ width: 1440, height: 900 },
{ width: livePreview.canvasWidth, height: livePreview.canvasHeight }
);
assert.equal(livePreview.backgroundColor, template.background_color);
assert.equal(livePreview.backgroundGradient, template.background_gradient);
assert.equal(livePreview.backgroundImagePath, template.background_image_path);
assert.match(livePreview.html, /left:10%;top:10%;width:50%;height:50%;z-index:2;/);
});
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');
const layoutProperties = ['position', 'overflow', 'box-sizing'];
const mediaProperties = ['width', 'height', 'object-fit', 'display'];
assert.deepEqual(
Object.fromEntries(layoutProperties.map((property) => [property, readCssDeclarations(previewCss, '.slide-preview-region')[property]])),
Object.fromEntries(layoutProperties.map((property) => [property, readCssDeclarations(playerCss, '.template-region')[property]]))
);
assert.deepEqual(
Object.fromEntries(mediaProperties.map((property) => [property, readCssDeclarations(previewCss, '.slide-preview-image')[property]])),
Object.fromEntries(mediaProperties.map((property) => [property, readCssDeclarations(playerCss, '.template-region.image img')[property]]))
);
});
test('template list template includes duplicate action', () => {
const template = fs.readFileSync(require.resolve('../src/web/views/signage/templates/list.hbs'), 'utf8');