Release v2.6.19
This commit is contained in:
@@ -0,0 +1,295 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const vm = require('node:vm');
|
||||
|
||||
function loadScheduleModule(overrides) {
|
||||
const timeDatePlaceholdersScript = fs.readFileSync(require.resolve('../src/web/public/js/shared/time-date-placeholders.js'), 'utf8');
|
||||
const placeholderScript = fs.readFileSync(require.resolve('../src/web/public/js/shared/placeholder-utils.js'), 'utf8');
|
||||
const scriptPath = require.resolve('../src/web/public/js/regions/type/schedule.js');
|
||||
const script = fs.readFileSync(scriptPath, 'utf8');
|
||||
const registry = new Map();
|
||||
const customWindow = overrides && overrides.window ? overrides.window : {};
|
||||
const sandbox = {
|
||||
document: {
|
||||
addEventListener() {}
|
||||
},
|
||||
window: {
|
||||
pulseRegionTypes: {
|
||||
register(type, module) {
|
||||
registry.set(type, module);
|
||||
}
|
||||
},
|
||||
pulseRegionUtils: {
|
||||
escapeHtml(value) {
|
||||
return String(value === undefined || value === null ? '' : value)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
},
|
||||
sanitizeRichText(value) {
|
||||
return String(value === undefined || value === null ? '' : value);
|
||||
}
|
||||
},
|
||||
placeholderChips: {
|
||||
renderChips(tokens) {
|
||||
return tokens.map((token) => '<span class="chip">{{' + token + '}}</span>').join(' ');
|
||||
}
|
||||
},
|
||||
initialData: {
|
||||
timetableGroups: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Main board',
|
||||
timezone: 'UTC',
|
||||
entries: [
|
||||
{
|
||||
id: 101,
|
||||
title: 'Launch',
|
||||
short_description: 'Doors open',
|
||||
start_datetime: '2026-08-09T10:00:00.000Z',
|
||||
end_datetime: '2026-08-09T11:00:00.000Z'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
Intl: Intl,
|
||||
Date: Date,
|
||||
Object: Object,
|
||||
Array: Array,
|
||||
Number: Number,
|
||||
String: String,
|
||||
Boolean: Boolean,
|
||||
Math: Math,
|
||||
JSON: JSON,
|
||||
RegExp: RegExp,
|
||||
console: console
|
||||
},
|
||||
...customWindow
|
||||
};
|
||||
sandbox.window = Object.assign({}, sandbox.window, customWindow);
|
||||
|
||||
vm.runInNewContext(timeDatePlaceholdersScript, sandbox, { filename: 'time-date-placeholders.js' });
|
||||
vm.runInNewContext(placeholderScript, sandbox, { filename: 'placeholder-utils.js' });
|
||||
if (customWindow.placeholderUtils) {
|
||||
sandbox.window.placeholderUtils = customWindow.placeholderUtils;
|
||||
}
|
||||
vm.runInNewContext(script, sandbox, { filename: scriptPath });
|
||||
return registry.get('timetable');
|
||||
}
|
||||
|
||||
test('timetable region editor lists timezone transforms in helper text', () => {
|
||||
const module = loadScheduleModule();
|
||||
const html = module.renderEditorCard({
|
||||
region: { id: 47, label: 'Timetable' },
|
||||
current: {
|
||||
value: '<p>{{title}} {{start.tz()}} {{start.tz_short()}}</p>',
|
||||
timetable_group_id: 1,
|
||||
display_mode: 'current',
|
||||
max_items: 3
|
||||
},
|
||||
timetableGroups: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Main board',
|
||||
timezone: 'UTC',
|
||||
entries: []
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
assert.match(html, /Placeholder values support transforms/);
|
||||
assert.match(html, /<code>\{\{start\.tz\(\)\}\}<\/code>/);
|
||||
assert.match(html, /<code>\{\{start\.tz_short\(\)\}\}<\/code>/);
|
||||
assert.doesNotMatch(html, /class="chip">\{\{start\.tz\(\)\}\}<\/span>/);
|
||||
});
|
||||
|
||||
test('timetable region editor shows supported date format tokens', () => {
|
||||
const module = loadScheduleModule();
|
||||
const html = module.renderEditorCard({
|
||||
region: { id: 47, label: 'Timetable' },
|
||||
current: {
|
||||
value: '<p>{{start.format("MMM D, YYYY h:mm A")}}</p>',
|
||||
timetable_group_id: 1,
|
||||
display_mode: 'current',
|
||||
max_items: 3
|
||||
},
|
||||
timetableGroups: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Main board',
|
||||
timezone: 'UTC',
|
||||
entries: []
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
assert.match(html, /Supported date format tokens/);
|
||||
assert.match(html, /<th scope="col">Format<\/th>/);
|
||||
assert.ok(html.indexOf('<td><code>D</code></td><td>1-31</td><td>The day of the month</td>') < html.indexOf('<td><code>ddd</code></td><td>Mon</td><td>The abbreviated weekday name</td>'));
|
||||
assert.ok(html.indexOf('<td><code>ddd</code></td><td>Mon</td><td>The abbreviated weekday name</td>') < html.indexOf('<td><code>M</code></td><td>1-12</td><td>The month, beginning at 1</td>'));
|
||||
assert.ok(html.indexOf('<td><code>YY</code></td><td>18</td><td>The two-digit year</td>') < html.indexOf('<td><code>YYYY</code></td><td>2018</td><td>The four-digit year</td>'));
|
||||
assert.doesNotMatch(html, /<td><code>y<\/code><\/td>/);
|
||||
assert.doesNotMatch(html, /<td><code>yyyy<\/code><\/td>/);
|
||||
assert.doesNotMatch(html, /<td><code>yy<\/code><\/td>/);
|
||||
assert.doesNotMatch(html, /<td><code>tz<\/code><\/td>/);
|
||||
assert.doesNotMatch(html, /<td><code>tz_short<\/code><\/td>/);
|
||||
assert.match(html, /Use these tokens inside <code>\.format\(\.\.\.\)<\/code>/);
|
||||
});
|
||||
|
||||
test('timetable region preview resolves timezone placeholders from explicit timezone transforms', () => {
|
||||
const module = loadScheduleModule();
|
||||
const preview = module.renderPreview(
|
||||
{ id: 47, width: 500, height: 280 },
|
||||
{
|
||||
value: '<p>{{start.tz("UTC")}} {{start.tz_short("UTC")}}</p>',
|
||||
timetable_group_id: 1,
|
||||
display_mode: 'both',
|
||||
max_items: 3
|
||||
},
|
||||
{
|
||||
timetableGroups: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Main board',
|
||||
timezone: 'UTC',
|
||||
entries: [
|
||||
{
|
||||
id: 101,
|
||||
title: 'Launch',
|
||||
short_description: 'Doors open',
|
||||
start_datetime: '2026-08-10T10:00:00.000Z',
|
||||
end_datetime: '2026-08-10T11:00:00.000Z'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
);
|
||||
|
||||
assert.match(preview, /UTC/);
|
||||
});
|
||||
|
||||
test('timetable placeholder format transform respects uppercase and lowercase day period tokens', () => {
|
||||
const placeholderScript = fs.readFileSync(require.resolve('../src/web/public/js/shared/placeholder-utils.js'), 'utf8');
|
||||
const sandbox = {
|
||||
window: {},
|
||||
Intl: Intl,
|
||||
Date: Date,
|
||||
Object: Object,
|
||||
Array: Array,
|
||||
Number: Number,
|
||||
String: String,
|
||||
Boolean: Boolean,
|
||||
Math: Math,
|
||||
JSON: JSON,
|
||||
RegExp: RegExp,
|
||||
console: console
|
||||
};
|
||||
|
||||
vm.runInNewContext(placeholderScript, sandbox, { filename: 'placeholder-utils.js' });
|
||||
|
||||
const upper = sandbox.window.placeholderUtils.resolvePlaceholderExpression(
|
||||
{ start: '2026-08-09T13:05:00.000Z' },
|
||||
'start.format("MMM D, YYYY h:mm A")'
|
||||
);
|
||||
const lower = sandbox.window.placeholderUtils.resolvePlaceholderExpression(
|
||||
{ start: '2026-08-09T13:05:00.000Z' },
|
||||
'start.format("MMM D, YYYY h:mm a")'
|
||||
);
|
||||
|
||||
assert.match(String(upper), /PM$/);
|
||||
assert.match(String(lower), /pm$/);
|
||||
});
|
||||
|
||||
test('timetable timezone abbreviation changes across daylight saving boundaries', () => {
|
||||
const module = loadScheduleModule();
|
||||
const preview = module.renderPreview(
|
||||
{ id: 47, width: 500, height: 280 },
|
||||
{
|
||||
value: '<p>{{start.tz_short("Europe/London")}}</p>',
|
||||
timetable_group_id: 1,
|
||||
display_mode: 'both',
|
||||
max_items: 10
|
||||
},
|
||||
{
|
||||
timetableGroups: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'London board',
|
||||
timezone: 'Europe/London',
|
||||
entries: [
|
||||
{
|
||||
id: 201,
|
||||
title: 'Before DST ends',
|
||||
short_description: '',
|
||||
start_datetime: '2026-10-24T12:00:00.000Z',
|
||||
end_datetime: '2026-10-24T13:00:00.000Z'
|
||||
},
|
||||
{
|
||||
id: 202,
|
||||
title: 'After DST ends',
|
||||
short_description: '',
|
||||
start_datetime: '2026-10-26T12:00:00.000Z',
|
||||
end_datetime: '2026-10-26T13:00:00.000Z'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
);
|
||||
|
||||
assert.match(preview, /BST/);
|
||||
assert.match(preview, /(GMT|UTC)/);
|
||||
});
|
||||
|
||||
test('timetable preview does not force the group timezone into placeholder transforms', () => {
|
||||
const calls = [];
|
||||
const module = loadScheduleModule({
|
||||
window: {
|
||||
placeholderUtils: {
|
||||
resolvePlaceholderExpression(value, expression, options) {
|
||||
calls.push({ expression, options: options || {} });
|
||||
return options && options.timeZone ? options.timeZone : 'local';
|
||||
},
|
||||
formatPlaceholderValue(value) {
|
||||
return String(value === undefined || value === null ? '' : value);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const preview = module.renderPreview(
|
||||
{ id: 47, width: 500, height: 280 },
|
||||
{
|
||||
value: '<p>{{start.tz_short()}}</p>',
|
||||
timetable_group_id: 1,
|
||||
display_mode: 'both',
|
||||
max_items: 3
|
||||
},
|
||||
{
|
||||
timetableGroups: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Madrid board',
|
||||
timezone: 'Europe/Madrid',
|
||||
entries: [
|
||||
{
|
||||
id: 101,
|
||||
title: 'Launch',
|
||||
short_description: 'Doors open',
|
||||
start_datetime: '2026-08-10T10:00:00.000Z',
|
||||
end_datetime: '2026-08-10T11:00:00.000Z'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
);
|
||||
|
||||
assert.match(preview, /local/);
|
||||
assert.equal(calls.length > 0 ? Boolean(calls[0].options && calls[0].options.timeZone) : false, false);
|
||||
});
|
||||
Reference in New Issue
Block a user