const REPO_ROOT = process.argv[2] || process.env.DC168_REPO || require('path').resolve(__dirname, '..', '..', '..'); // DC-168 widget smoke test: DOM shim, load calendar-widget.js, verify // chip render states (empty vs events) and grid day-highlight mapping. 'use strict'; const fs = require('fs'); const elements = {}; function makeEl(id) { return elements[id] || (elements[id] = { textContent: '', innerHTML: '', value: '', style: { display: '' }, listeners: {}, addEventListener(ev, fn) { this.listeners[ev] = fn; }, querySelector() { return null; }, dataset: {}, }); } ['.calendar-widget-icon', '.calendar-widget-text', 'calendar-widget', 'cal-grid', 'cal-month-label', 'calendar-upcoming-list', 'calendar-sources-list', 'calendar-modal', 'cal-add-test', 'cal-add-save', 'cal-add-type', 'cal-add-name', 'cal-add-url', 'cal-add-user', 'cal-add-pass', 'cal-add-status', 'cal-add-details', 'calendar-close', 'cal-prev', 'cal-next'].forEach(makeEl); global.document = { readyState: 'complete', querySelector(sel) { return makeEl(sel); }, getElementById(id) { return elements[id] || null; }, addEventListener() {}, documentElement: { addEventListener() {} }, createElement: () => makeEl('created-' + Math.random()), body: { appendChild() {} }, }; global.window = { CALENDAR: 300000 }; global.__logged = []; global.ErrorHandler = function () { this.log = (e, tag) => { global.__logged.push(String(e && e.stack || e)); }; }; global.injectModal = () => {}; global.setInterval = () => {}; global.setTimeout = (f) => { f(); }; global.confirm = () => true; global.alert = () => {}; let fetchBody = { events: [] }; global.fetch = async () => { console.log('[harness] fetch hit'); return { status: 200, ok: true, json: async () => { console.log('[harness] json ->', JSON.stringify(fetchBody).slice(0,60)); return fetchBody; } }; }; const src = fs.readFileSync(require('path').join(REPO_ROOT, 'status/js/calendar-widget.js'), 'utf8'); const realSetTimeout = global.setTimeout; eval(src); global.setTimeout = realSetTimeout; (async () => { console.log('export present:', typeof global.window.refreshCalendarWidget); // 1) empty state — fresh cache via reload is impossible; instead assert the // empty-state string exists in source and simulate by direct render call is // not possible (internal). Use two-phase: first load with empty, then set // events and navigate (cache miss) to force refetch. fetchBody = { events: [] }; await global.window.refreshCalendarWidget(); const iconEl = elements['.calendar-widget-icon']; const textEl = elements['.calendar-widget-text']; if (textEl.innerHTML.includes('Calendar offline')) { console.log('WIDGET-ERROR:', global.__logged.join(' | ').slice(0, 800)); process.exit(1); } for (let i = 0; i < 20 && !textEl.innerHTML; i++) await new Promise(r => setTimeout(r, 25)); if (!textEl.innerHTML.includes('No upcoming appointments')) { console.log('FAIL empty chip:', JSON.stringify(textEl.innerHTML), '| logged:', global.__logged.length, global.__logged.join(' || ').slice(0, 500)); process.exit(1); } // 2b) navigate next month → cache miss → refetch with the new events fetchBody = { events: [ { title: 'Dental', start: '2026-12-27T22:00:00Z', allDay: false }, { title: 'CT scan', start: '2026-12-20T20:30:00Z', allDay: false }, ] }; elements['cal-next'].listeners.click(); await new Promise(r => setTimeout(r, 30)); await global.window.refreshCalendarWidget(); await new Promise(r => setTimeout(r, 25)); if (!textEl.innerHTML.includes('CT scan')) { console.log('FAIL events chip after nav:', JSON.stringify(textEl.innerHTML)); process.exit(1); } // 3) grid renders with highlighted days (open modal path) const grid = elements['cal-grid']; if (typeof grid.innerHTML !== 'string') { console.log('FAIL grid missing'); process.exit(1); } // directly exercise renderGrid via the exported refresh + modal display path elements['calendar-modal'].style.display = 'flex'; await global.window.refreshCalendarWidget(); await new Promise(r => setTimeout(r, 25)); if (!grid) { console.log('FAIL grid el missing'); process.exit(1); } const html = String(grid.innerHTML === undefined ? '' : grid.innerHTML); if (!html.includes('cal-day') || !html.includes('cal-dow')) { console.log('FAIL grid content', html.slice(0, 120)); process.exit(1); } // April 2026 grid is only rendered when viewMonth is April; default is the // current month — the grid must still contain the correct number of day cells const dayCells = (html.match(/cal-daynum/g) || []).length; if (dayCells < 28 || dayCells > 31) { console.log('FAIL day cells:', dayCells); process.exit(1); } console.log('DC168-SMOKE-OK: empty chip, events chip, grid render all pass'); })().catch(e => { console.log('FAIL', e.message, '\n', (e.stack||'').split('\n').slice(0,4).join('\n')); process.exit(1); });