feat: initialize sunrise with 100-player shared rooms
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
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); }
|
||||
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'].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일');
|
||||
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');
|
||||
});
|
||||
Reference in New Issue
Block a user