release: v2.11.2
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
|
||||
const { DEFAULT_ACCOUNT_EMAIL_TEMPLATES, formatAccountEmailExpiry, renderAccountEmailTemplate } = require('../src/data/account-email-templates');
|
||||
|
||||
test('account email expiry values render with singular and plural units', () => {
|
||||
assert.equal(formatAccountEmailExpiry(1, 'minutes'), '1 minute');
|
||||
assert.equal(formatAccountEmailExpiry(30, 'minutes'), '30 minutes');
|
||||
assert.equal(formatAccountEmailExpiry(24, 'hours'), '24 hours');
|
||||
assert.equal(renderAccountEmailTemplate('Subject [[expiry_time]]', 'Expires in [[expiry_time]].', { expiry_time: '45 minutes' }).text, 'Expires in 45 minutes.');
|
||||
});
|
||||
|
||||
test('default account email templates render both the action button and inline link', () => {
|
||||
const rendered = renderAccountEmailTemplate('Subject', DEFAULT_ACCOUNT_EMAIL_TEMPLATES.verificationBody, {
|
||||
url: 'https://example.test/verify',
|
||||
display_name: 'Alex',
|
||||
expiry_time: '30 minutes',
|
||||
action_label: 'Verify email address'
|
||||
});
|
||||
|
||||
assert.match(rendered.html, /<strong>Alex<\/strong>/);
|
||||
assert.match(rendered.html, /This <u>link<\/u> expires in <em>30 minutes<\/em>\./);
|
||||
assert.match(rendered.html, /Confirm this email address: <a href="https:\/\/example\.test\/verify">https:\/\/example\.test\/verify<\/a>/);
|
||||
assert.doesNotMatch(rendered.html, /\[\[(?:action_button|url)\]\]/);
|
||||
});
|
||||
@@ -24,6 +24,7 @@ test('audit log uses its own read permission and shared pagination partial', asy
|
||||
category: 'authentication',
|
||||
event_type: 'login.success',
|
||||
target_label: 'newer',
|
||||
user_agent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36',
|
||||
details_json: JSON.stringify({
|
||||
changes: {
|
||||
content: {
|
||||
@@ -86,6 +87,8 @@ test('audit log uses its own read permission and shared pagination partial', asy
|
||||
assert.match(response.body, /data-local-datetime/);
|
||||
assert.match(response.body, /audit-event-type-options/);
|
||||
assert.match(response.body, /login\.success/);
|
||||
assert.match(response.body, />Chrome 150\.0\.0\.0 on Windows</);
|
||||
assert.doesNotMatch(response.body, /Mozilla\/5\.0 \(Windows NT 10\.0; Win64; x64\)/);
|
||||
assert.match(response.body, /content\.body\.color/);
|
||||
assert.match(response.body, />red</);
|
||||
assert.match(response.body, />blue</);
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user