import { afterEach, describe, expect, it, vi } from 'vitest';
import { App } from '../src/app.js';
import { CrowdPulsePanel } from '../src/components/CrowdPulsePanel.js';
import { NightOwlPanel } from '../src/components/NightOwlPanel.js';
import { PlayerCard } from '../src/components/PlayerCard.js';
import { VideoPlayer } from '../src/components/VideoPlayer.js';
function elementStub() {
return {
innerHTML: '',
textContent: '',
style: {},
};
}
function stubElements(map) {
vi.stubGlobal('document', {
getElementById: vi.fn((id) => map[id] || null),
});
}
function htmlStub(children = {}) {
return {
innerHTML: '',
textContent: '',
style: {},
dataset: {},
hidden: false,
tagName: 'DIV',
replaceChildren() {
this.innerHTML = '';
},
insertAdjacentHTML(_position, html) {
this.innerHTML = String(html);
},
querySelector(selector) {
return children[selector] || null;
},
querySelectorAll(selector) {
return children[selector] || [];
},
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
};
}
describe('App frontend security', () => {
afterEach(() => {
vi.unstubAllGlobals();
});
it('escapes HTML before rendering live travel route data', () => {
const status = elementStub();
const options = elementStub();
stubElements({
'live-travel-status': status,
'live-travel-options': options,
});
const app = new App();
app.venueMapService = {};
app.liveTravelPlan = {
venue: { name: '
' },
distanceKm: 1.2,
routeDistanceKm: 1.6,
hasUserLocation: true,
bestGate: { name: '' },
recommended: {
label: 'Transit',
etaMinutes: 12,
},
alternatives: [
{
label: '