D<\/code><\/td>| Day number<\/td> | 16<\/code><\/td><\/tr>/);
assert.match(html, /YYYY<\/code><\/td>| Four-digit year<\/td> | 2026<\/code><\/td><\/tr>/);
assert.match(html, /Date format tokens/);
assert.match(html, /| Token<\/th> | Meaning<\/th> | Example<\/th>/);
assert.match(html, / | D<\/code><\/td>| Day number<\/td> | 16<\/code><\/td><\/tr>/);
assert.match(html, /YYYY<\/code><\/td>| Four-digit year<\/td> | 2026<\/code><\/td><\/tr>/);
assert.doesNotMatch(html, /tz<\/code><\/td>/);
assert.match(html, /Use tz\(\)<\/code> for a short timezone name and tz_long\(\)<\/code> for the full timezone name/);
assert.match(html, /Use tz\(\)<\/code> for a short timezone name and tz_long\(\)<\/code> for the full timezone name/);
});
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: '{{start.tz("UTC")}} {{start.tz_long("UTC")}} ',
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: '2099-08-20T10:00:00.000Z',
end_datetime: '2099-08-20T11: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: '{{start.tz("Europe/London")}} ',
timetable_group_id: 1,
display_mode: 'all',
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: '{{start.tz_long()}} ',
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: '2099-08-20T10:00:00.000Z',
end_datetime: '2099-08-20T11:00:00.000Z'
}
]
}
]
}
);
assert.match(preview, /local/);
assert.equal(calls.length > 0 ? Boolean(calls[0].options && calls[0].options.timeZone) : false, false);
});
| | | | | | | | |