Wegwichtel/test/bootstrap-loader.test.js

166 lines
4.2 KiB
JavaScript

import assert from 'node:assert/strict';
import fs from 'node:fs/promises';
import path from 'node:path';
import test from 'node:test';
import vm from 'node:vm';
const root = path.resolve(import.meta.dirname, '..');
class ClassList {
constructor() {
this.values = new Set();
}
add(...names) {
names.forEach(name => this.values.add(name));
}
remove(...names) {
names.forEach(name => this.values.delete(name));
}
contains(name) {
return this.values.has(name);
}
}
class FakeElement extends EventTarget {
constructor(id = '') {
super();
this.id = id;
this.hidden = false;
this.value = 0;
this.textContent = '';
this.src = '';
this.async = true;
this.classList = new ClassList();
this.attributes = new Map();
this.focused = false;
}
focus() {
this.focused = true;
}
setAttribute(name, value) {
this.attributes.set(name, String(value));
}
closest() {
return null;
}
}
function pointerEvent() {
const event = new Event('pointerup', { cancelable: true });
Object.defineProperties(event, {
isPrimary: { value: true },
pointerType: { value: 'touch' },
button: { value: 0 }
});
return event;
}
test('bootstrap remains visible after loading and opens the app only after deliberate activation', async () => {
const sourcePath = path.join(root, 'public/js/bootstrap-loader.js');
const source = await fs.readFile(sourcePath, 'utf8');
const ids = [
'bootstrap-screen',
'bootstrap-progress',
'bootstrap-error',
'bootstrap-retry',
'bootstrap-enter',
'bootstrap-ready',
'storage-cleanup-status',
'app-shell'
];
const elements = new Map(ids.map(id => [id, new FakeElement(id)]));
const steps = new Map([
'jquery',
'jquery-ui',
'modules',
'server',
'routes',
'location'
].map(name => [name, new FakeElement(name)]));
const navigations = [];
function jquery() {
return {
button() {
return this;
}
};
}
const context = {
Wegwichtel: {
PrivacyStorage: {
async cleanupExpired(callback) {
callback(0, 1);
callback(1, 1);
return { removed: 1, total: 1 };
}
},
App: {
async initialize({ markStep }) {
markStep('server', 'done');
markStep('routes', 'done');
markStep('location', 'done');
},
navigate(...args) {
navigations.push(args);
}
}
},
document: {
getElementById: id => elements.get(id),
querySelector(selector) {
const match = selector.match(/^\[data-step="(.+)"\]$/);
return match ? steps.get(match[1]) : null;
},
querySelectorAll(selector) {
if (selector === '#bootstrap-steps .done') {
return [...steps.values()].filter(step => step.classList.contains('done'));
}
return [];
},
createElement: () => new FakeElement('script'),
head: {
appendChild(script) {
if (script.src.includes('/jquery-4.0.0.min.js')) {
jquery.fn = { jquery: '4.0.0' };
context.jQuery = jquery;
}
if (script.src.includes('/jquery-ui-1.14.2.min.js')) {
jquery.ui = { version: '1.14.2' };
}
queueMicrotask(() => script.onload());
}
}
},
Event,
EventTarget,
Promise,
console,
encodeURIComponent,
queueMicrotask,
location: { reload() {} }
};
context.globalThis = context;
vm.runInNewContext(source, context, { filename: sourcePath });
await new Promise(resolve => setTimeout(resolve, 20));
assert.equal(elements.get('bootstrap-screen').hidden, false);
assert.equal(elements.get('bootstrap-screen').attributes.get('aria-busy'), 'false');
assert.equal(elements.get('bootstrap-enter').hidden, false);
assert.equal(elements.get('storage-cleanup-status').textContent, 'Entferne alte Streckendaten aus dem Speicher (1 / 1)');
assert.equal(navigations.length, 0);
elements.get('bootstrap-screen').dispatchEvent(pointerEvent());
assert.equal(elements.get('bootstrap-screen').hidden, true);
assert.equal(elements.get('app-shell').hidden, false);
assert.equal(navigations[0][0], '#routes-page');
assert.equal(navigations[0][1].replace, true);
});