Add the Blender flashlight asset with a 600 lm beam and reduced bloom, shared-mode self badge, and compact phone controls. Relocate the summit marker, blend terrain seams, widen walking routes, and align local server configuration on port 2027.
114 lines
5.8 KiB
TypeScript
114 lines
5.8 KiB
TypeScript
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { TimeController } from '../src/ui/timeController.ts';
|
|
|
|
class Field extends EventTarget {
|
|
value = '';
|
|
max = '';
|
|
step = '';
|
|
disabled = false;
|
|
validity = { valid: true };
|
|
attributes = new Map<string, string>();
|
|
textContent = '';
|
|
setAttribute(name: string, value: string) { this.attributes.set(name, value); }
|
|
getAttribute(name: string) { return this.attributes.get(name) ?? null; }
|
|
matches() { return this.disabled; }
|
|
closest() { return this; }
|
|
}
|
|
|
|
test('live ticks preserve date edits; custom freezes; live and visibility resync wall clock', (t) => {
|
|
const fields = new Map(['#date', '#date-display', '#time-slider', '#time-output', '#live-button', '#time-toggle', '#compact-time'].map(id => [id, new Field()]));
|
|
let dialogOpen = false;
|
|
const doc = Object.assign(new EventTarget(), {
|
|
activeElement: null as Field | null,
|
|
querySelector: (id: string) => id === 'dialog[open]' ? (dialogOpen ? {} : null) : fields.get(id),
|
|
});
|
|
const originalDocument = Object.getOwnPropertyDescriptor(globalThis, 'document');
|
|
const originalWindow = Object.getOwnPropertyDescriptor(globalThis, 'window');
|
|
Object.defineProperty(globalThis, 'document', { configurable: true, value: doc });
|
|
Object.defineProperty(globalThis, 'window', { configurable: true, value: new EventTarget() });
|
|
t.mock.timers.enable({ apis: ['Date', 'setInterval'], now: Date.parse('2026-10-29T00:00:00Z') });
|
|
const calls: { date: Date; mode: string }[] = [];
|
|
const controller = new TimeController((date, mode) => calls.push({ date, mode }));
|
|
assert.equal(fields.get('#live-button')!.attributes.get('aria-pressed'), 'true');
|
|
assert.equal(fields.get('#date-display')!.textContent, '2026년 10월 29일');
|
|
assert.equal(fields.get('#compact-time')!.textContent, '09:00');
|
|
fields.get('#time-toggle')!.dispatchEvent(new Event('click'));
|
|
assert.equal(fields.get('#time-toggle')!.getAttribute('aria-expanded'), 'true');
|
|
fields.get('#time-toggle')!.dispatchEvent(new Event('click'));
|
|
assert.equal(fields.get('#time-toggle')!.getAttribute('aria-expanded'), 'false');
|
|
const arrow = (key: string) => {
|
|
const event = Object.assign(new Event('keydown', { cancelable: true }), { key });
|
|
window.dispatchEvent(event);
|
|
return event;
|
|
};
|
|
assert.equal(arrow('ArrowRight').defaultPrevented, true);
|
|
assert.equal(fields.get('#time-output')!.value, '09:00:10');
|
|
assert.equal(controller.mode, 'CUSTOM');
|
|
assert.equal(fields.get('#live-button')!.attributes.get('aria-pressed'), 'false');
|
|
arrow('ArrowLeft');
|
|
assert.equal(fields.get('#time-output')!.value, '09:00:00');
|
|
dialogOpen = true;
|
|
assert.equal(arrow('ArrowRight').defaultPrevented, false);
|
|
assert.equal(fields.get('#time-output')!.value, '09:00:00');
|
|
dialogOpen = false;
|
|
doc.activeElement = fields.get('#time-slider')!;
|
|
assert.equal(arrow('ArrowRight').defaultPrevented, false, 'focused slider keeps its native keyboard handling');
|
|
doc.activeElement = null;
|
|
fields.get('#live-button')!.dispatchEvent(new Event('click'));
|
|
t.after(() => {
|
|
controller.dispose();
|
|
for (const [key, descriptor] of [['document', originalDocument], ['window', originalWindow]] as const) {
|
|
if (descriptor) Object.defineProperty(globalThis, key, descriptor);
|
|
else Reflect.deleteProperty(globalThis, key);
|
|
}
|
|
});
|
|
const date = fields.get('#date')!;
|
|
doc.activeElement = date;
|
|
assert.equal(arrow('ArrowRight').defaultPrevented, false, 'date editing keeps its native keyboard handling');
|
|
date.value = '';
|
|
t.mock.timers.tick(1000);
|
|
assert.equal(date.value, '', 'live refresh must not overwrite an unfinished date edit');
|
|
date.value = '2026-12-21';
|
|
fields.get('#time-slider')!.value = '28810';
|
|
date.dispatchEvent(new Event('change'));
|
|
assert.equal(calls.at(-1)!.mode, 'CUSTOM');
|
|
assert.equal(calls.at(-1)!.date.toISOString(), '2026-12-20T23:00:10.000Z');
|
|
assert.equal(fields.get('#time-output')!.value, '08:00:10');
|
|
assert.equal(fields.get('#date-display')!.textContent, '2026년 12월 21일');
|
|
assert.equal(fields.get('#live-button')!.attributes.get('aria-pressed'), 'false');
|
|
assert.equal(fields.get('#time-slider')!.step, '10');
|
|
fields.get('#time-slider')!.value = '86390';
|
|
fields.get('#time-slider')!.dispatchEvent(new Event('input'));
|
|
assert.equal(calls.at(-1)!.date.toISOString(), '2026-12-21T14:59:50.000Z');
|
|
assert.equal(fields.get('#time-output')!.value, '23:59:50');
|
|
assert.equal(fields.get('#live-button')!.attributes.get('aria-pressed'), 'false');
|
|
const count = calls.length;
|
|
t.mock.timers.tick(3000);
|
|
doc.dispatchEvent(new Event('visibilitychange'));
|
|
assert.equal(calls.length, count);
|
|
doc.activeElement = null;
|
|
fields.get('#live-button')!.dispatchEvent(new Event('click'));
|
|
assert.equal(calls.at(-1)!.mode, 'LIVE');
|
|
assert.equal(fields.get('#live-button')!.attributes.get('aria-pressed'), 'true');
|
|
assert.equal(calls.at(-1)!.date.getTime(), Date.now());
|
|
t.mock.timers.setTime(Date.parse('2026-10-30T00:00:00Z'));
|
|
doc.dispatchEvent(new Event('visibilitychange'));
|
|
assert.equal(date.value, '2026-10-30');
|
|
const serverDate = new Date('2026-11-01T21:00:00Z');
|
|
controller.setSharedClock(() => serverDate);
|
|
assert.equal(fields.get('#date')!.disabled, true);
|
|
assert.equal(calls.at(-1)!.date.getTime(), +serverDate);
|
|
doc.activeElement = null;
|
|
assert.equal(arrow('ArrowRight').defaultPrevented, false);
|
|
assert.equal(calls.at(-1)!.date.getTime(), +serverDate, 'arrows cannot change shared time');
|
|
date.value = '2027-01-01';
|
|
date.dispatchEvent(new Event('change'));
|
|
assert.equal(controller.mode, 'LIVE', 'programmatic events cannot change shared time');
|
|
controller.setSharedClock(null);
|
|
assert.equal(fields.get('#date')!.disabled, false);
|
|
date.value = '2027-01-01';
|
|
date.dispatchEvent(new Event('change'));
|
|
assert.equal(controller.mode, 'CUSTOM');
|
|
});
|