Files
pulse-signage/test/timetable-group-form.test.js
T
2026-08-09 11:57:40 +01:00

229 lines
6.1 KiB
JavaScript

const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const vm = require('node:vm');
function createClassList() {
const classes = new Set();
return {
add(name) {
classes.add(name);
},
remove(name) {
classes.delete(name);
},
contains(name) {
return classes.has(name);
}
};
}
function createInput(name, value) {
const listeners = Object.create(null);
const attributes = Object.create(null);
return {
name: name,
value: value || '',
dataset: {},
classList: createClassList(),
attributes: attributes,
validityMessage: '',
addEventListener(type, handler) {
if (!listeners[type]) {
listeners[type] = [];
}
listeners[type].push(handler);
},
dispatchEvent(event) {
const handlers = listeners[event.type] || [];
handlers.forEach(function (handler) {
handler.call(this, event);
}, this);
},
setCustomValidity(message) {
this.validityMessage = String(message || '');
},
setAttribute(name, value) {
attributes[name] = String(value);
},
removeAttribute(name) {
delete attributes[name];
}
};
}
function createRow(startValue, endValue) {
const startInput = createInput('entry_start_datetime[]', startValue);
const endInput = createInput('entry_end_datetime[]', endValue);
const titleInput = createInput('entry_title[]', 'Opening');
const descriptionInput = createInput('entry_short_description[]', '');
const idInput = createInput('entry_id[]', '1');
const removeButton = {
addEventListener() {}
};
const row = {
startInput: startInput,
endInput: endInput,
querySelector(selector) {
if (selector === '[name="entry_start_datetime[]"]') {
return startInput;
}
if (selector === '[name="entry_end_datetime[]"]') {
return endInput;
}
if (selector === '[name="entry_title[]"]') {
return titleInput;
}
if (selector === '[name="entry_short_description[]"]') {
return descriptionInput;
}
if (selector === '[name="entry_id[]"]') {
return idInput;
}
if (selector === '[data-remove-timetable-entry]') {
return removeButton;
}
return null;
},
remove() {
row.removed = true;
}
};
return row;
}
test('timetable group form highlights an end time that is too early', () => {
const row = createRow('2026-08-07T12:00', '2026-08-07T12:00');
const timezoneInput = createInput('timezone', 'Europe/Berlin');
const body = {
rows: [row],
querySelectorAll(selector) {
return selector === '[data-timetable-entry-row]' ? this.rows.slice() : [];
},
appendChild(node) {
this.rows.push(node);
return node;
}
};
const addButton = {
listeners: Object.create(null),
addEventListener(type, handler) {
this.listeners[type] = handler;
},
click() {
if (this.listeners.click) {
this.listeners.click();
}
}
};
const form = {
dataset: {},
listeners: Object.create(null),
addEventListener(type, handler) {
this.listeners[type] = handler;
}
};
const templateRow = createRow('', '');
const template = {
content: {
cloneNode() {
return {
querySelector(selector) {
return selector === '[data-timetable-entry-row]' ? templateRow : null;
}
};
}
}
};
const sandbox = {
document: {
getElementById(id) {
if (id === 'timetable-group-form') {
return form;
}
if (id === 'timetable-group-timezone') {
return timezoneInput;
}
if (id === 'timetable-entry-row-template') {
return template;
}
return null;
},
querySelector(selector) {
if (selector === '[data-timetable-entries-body]') {
return body;
}
if (selector === '[data-add-timetable-entry]') {
return addButton;
}
return null;
}
},
window: {},
Date: Date,
Number: Number,
String: String,
Array: Array,
Object: Object,
Math: Math,
JSON: JSON,
console: console,
module: { exports: {} },
exports: {}
};
sandbox.window = sandbox;
const scriptPath = path.join(__dirname, '..', 'src', 'web', 'public', 'js', 'data-sources', 'timetable-group-form.js');
const script = fs.readFileSync(scriptPath, 'utf8');
vm.runInNewContext(script, sandbox, { filename: scriptPath });
assert.equal(row.startInput.value, '2026-08-07T12:00');
assert.equal(row.endInput.value, '2026-08-07T12:00');
assert.equal(row.endInput.validityMessage, 'End time must be at least 1 minute after the start time.');
assert.equal(row.endInput.classList.contains('is-invalid'), true);
assert.equal(row.endInput.attributes['aria-invalid'], 'true');
row.endInput.value = '2026-08-07T12:01';
row.endInput.dispatchEvent({ type: 'input' });
assert.equal(row.endInput.validityMessage, '');
assert.equal(row.endInput.classList.contains('is-invalid'), false);
assert.equal(Object.prototype.hasOwnProperty.call(row.endInput.attributes, 'aria-invalid'), false);
timezoneInput.value = 'Europe/London';
timezoneInput.dispatchEvent({ type: 'change' });
assert.equal(row.startInput.value, '2026-08-07T12:00');
assert.equal(row.endInput.value, '2026-08-07T12:01');
assert.equal(form.dataset.timetableTimezone, 'Europe/London');
assert.equal(form.dataset.dirty, 'true');
const formData = {
deleted: [],
appended: [],
delete(name) {
this.deleted.push(name);
},
append(name, value) {
this.appended.push([name, value]);
}
};
form.listeners.formdata({ formData: formData });
const startEntry = formData.appended.find(function (item) {
return item[0] === 'entry_start_datetime[]';
});
const endEntry = formData.appended.find(function (item) {
return item[0] === 'entry_end_datetime[]';
});
assert.equal(startEntry[1], '2026-08-07T11:00:00.000Z');
assert.equal(endEntry[1], '2026-08-07T11:01:00.000Z');
});