Files
pulse-signage/test/content-references.test.js
T
lzstealth 9ca372429b
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile, web, pulse-signage-web) (push) Successful in 1m17s
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile.player, player, pulse-signage-player) (push) Successful in 32s
Release v2.19.1: repair managed font references
2026-10-24 20:26:00 +01:00

123 lines
5.6 KiB
JavaScript

const test = require('node:test');
const assert = require('node:assert/strict');
const { collectContentReferences, replaceContentReferences } = require('../src/data/content-references');
test('content reference extraction indexes typed sources, media paths, and fonts by owner region', () => {
const references = collectContentReferences('template', 42, {
brand: {
type: 'html',
value: '<img src="https://signage.example/media/uploads/brand.png"><span style="font-family: \'Acme Display\', sans-serif">Brand</span>',
font_family: 'Acme Display'
},
agenda: { type: 'timetable', timetable_group_id: 7 },
feed: { type: 'rss', feed_id: '8' },
forecast: { type: 'weather', weather_location_id: 9, weather_icon_set_id: 'night' },
api: { type: 'api', source_id: 10 }
});
const values = references.map(reference => [reference.owner_key, reference.reference_type, reference.reference_value]);
assert.deepEqual(values, [
['brand', 'media', '/media/uploads/brand.png'],
['brand', 'font-family', 'acme display'],
['agenda', 'timetable-group', '7'],
['feed', 'rss-feed', '8'],
['forecast', 'weather-location', '9'],
['forecast', 'weather-icon-set', 'night'],
['api', 'api-request', '10']
]);
assert.ok(references.every(reference => reference.owner_type === 'template' && reference.owner_id === 42 && Buffer.isBuffer(reference.reference_hash)));
});
test('content reference extraction deduplicates repeated references within a region', () => {
const references = collectContentReferences('slide', 12, {
hero: { value: '/media/uploads/a.png', secondary: '/media/uploads/a.png', source_id: 4, copy: { source_id: 4 } }
});
assert.deepEqual(references.map(reference => [reference.reference_type, reference.reference_value]), [
['media', '/media/uploads/a.png'],
['api-request', '4']
]);
});
test('content reference extraction decodes HTML-encoded font names before parsing CSS', () => {
const references = collectContentReferences('slide', 12, {
numeric: { value: '<span style="font-family: &#39;Ubuntu&#39;;">Ubuntu</span>', font_family: 'Arial' },
named: { value: '<span style="font-family: &quot;Ubuntu Sans&quot;, sans-serif;">Ubuntu</span>' },
hex: { value: '<span style="font-family: &#x27;Ubuntu&#x27;;">Ubuntu</span>', font_family: 'Ubuntu' }
});
assert.deepEqual(references.map(reference => [reference.owner_key, reference.reference_value]), [
['numeric', 'ubuntu'],
['named', 'ubuntu sans'],
['hex', 'ubuntu']
]);
});
test('content reference replacement preserves unchanged rows and only deletes or inserts the delta', async () => {
const calls = [];
const connection = {
async query(sql, params) {
calls.push([sql, params]);
if (sql.startsWith('SELECT id, owner_key')) {
return [[
{ id: 101, owner_key: 'hero', reference_type: 'media', reference_value: '/media/uploads/keep.png' },
{ id: 102, owner_key: 'api', reference_type: 'api-request', reference_value: '4' },
{ id: 103, owner_key: 'old', reference_type: 'font-family', reference_value: 'arial' }
]];
}
return [{}];
}
};
await replaceContentReferences(connection, 'slide', 22, {
hero: { value: '/media/uploads/keep.png' },
api: { source_id: 5 },
feed: { feed_id: 8 }
});
const deletion = calls.find(([sql]) => sql.startsWith('DELETE FROM c_content_references'));
assert.deepEqual(deletion[1], ['slide', 22, [102, 103]]);
const insertion = calls.find(([sql]) => sql.startsWith('INSERT IGNORE INTO c_content_references'));
assert.equal(insertion[1][0].length, 2);
assert.deepEqual(insertion[1][0].map(row => [row[3], row[4]]), [['api-request', '5'], ['rss-feed', '8']]);
assert.ok(!calls.some(([sql]) => sql === 'DELETE FROM c_content_references WHERE owner_type = ? AND owner_id = ?'));
});
test('content reference replacement repairs encoded font references and removes the base font', async () => {
const calls = [];
const connection = {
async query(sql, params) {
calls.push([sql, params]);
if (sql.startsWith('SELECT id, owner_key')) {
return [[
{ id: 101, owner_key: 'second_place', reference_type: 'font-family', reference_value: '&#39' },
{ id: 102, owner_key: 'second_place', reference_type: 'font-family', reference_value: 'arial' }
]];
}
return [{}];
}
};
await replaceContentReferences(connection, 'slide', 22, {
second_place: { value: '<span style="font-family: &#39;Ubuntu&#39;;">Second</span>', font_family: 'Arial' }
});
const deletion = calls.find(([sql]) => sql.startsWith('DELETE FROM c_content_references'));
assert.deepEqual(deletion[1], ['slide', 22, [101, 102]]);
const insertion = calls.find(([sql]) => sql.startsWith('INSERT IGNORE INTO c_content_references'));
assert.deepEqual(insertion[1][0].map(row => [row[2], row[3], row[4]]), [
['second_place', 'font-family', 'ubuntu']
]);
});
test('content references omit built-in font stacks but retain managed font families', () => {
const references = collectContentReferences('slide', 117, {
base: { font_family: 'Arial, Helvetica, sans-serif' },
quoted: { fontFamily: "'TREBUCHET MS', Geneva, sans-serif" },
generic: { value: '<p style="font-family: system-ui, serif, monospace, cursive, fantasy;">Base</p>' },
managed: { font_family: 'Arial', value: '<span style="font-family: &#39;Ubuntu Regular&#39;, Helvetica, sans-serif;">Second</span>' }
});
assert.deepEqual(references.map(reference => [reference.owner_key, reference.reference_value]), [
['managed', 'ubuntu regular']
]);
});