import { expect } from '@open-wc/testing'; import './nile-date-time-picker'; import '../nile-calendar'; import '../nile-input'; import type { Wall, Payload } from './nile-date-time-picker.test-helpers'; import { offsetAt, wallAt, parseBracketed, fmtWall, makePicker, apply, checkPayload, roundTrip, instantsFor, runtimeZones, transitionShape, selectAndApply, HOST_TZ, Failures, allTransitions, } from './nile-date-time-picker.test-helpers'; /* * Section K (adversarial audit), part 3 of 4: PHASE 5, 6, 7, 8, 9, 11. * Split out of nile-date-time-picker.test.ts so CI can run it on its own shard; * helpers shared across the split files live in nile-date-time-picker.test-helpers.ts. */ describe('PHASE 5 — DST overlap: both occurrences must be reachable and distinct', () => { it('P5.1 an ambiguous wall clock resolves to a stated occurrence, consistently', async () => { const f = new Failures(); const zones = runtimeZones(); const trs = allTransitions(zones).filter(x => transitionShape(x.t, x.zone).kind === 'backward'); const notes: string[] = []; let n = 0; let pickFirst = 0; let pickSecond = 0; const secondZones: string[] = []; for (const { zone, t } of trs) { const sh = transitionShape(t, zone); const w: Wall = sh.insideWall; const both = instantsFor(w, zone); if (both.length !== 2) continue; // must truly be ambiguous // (a) plain wall-clock selection picks ONE of them, and says so truthfully const el = await makePicker({}); const p = await selectAndApply(el, w, zone); const ctx = `OVERLAP tz=${zone} want=${fmtWall(w)}`; f.add(ctx, checkPayload(p, zone)); if (p && p.unixEpoch !== null) { if (both.indexOf(p.unixEpoch) === -1) { f.add(ctx, [{ code: 'P5_NOT_AN_OCCURRENCE', detail: `epoch=${p.unixEpoch} is neither ${both[0]} nor ${both[1]}`, }]); } f.add(ctx, (await roundTrip(p)).violations); } // (b) the bracketed form with an EXPLICIT offset must select the intended // occurrence — the two must not collapse onto one epoch. const mk = (epoch: number) => { const ww = wallAt(epoch, zone); const off = offsetAt(epoch, zone); const sign = off < 0 ? '-' : '+'; const a = Math.abs(off); const hh = String(Math.floor(a / 3600000)).padStart(2, '0'); const mm = String(Math.floor((a % 3600000) / 60000)).padStart(2, '0'); return `${fmtWall(ww)}[GMT${sign}${hh}:${mm} ${zone}]`; }; const v1 = mk(both[0]); const v2 = mk(both[1]); const e1 = await apply(await makePicker({ value: v1, timezone: zone })); const e2 = await apply(await makePicker({ value: v2, timezone: zone })); f.add(`${ctx} first`, checkPayload(e1)); f.add(`${ctx} second`, checkPayload(e2)); if (e1 && e2 && e1.unixEpoch !== null && e2.unixEpoch !== null) { if (e1.unixEpoch !== both[0]) { f.add(ctx, [{ code: 'P5_FIRST_OCCURRENCE_LOST', detail: `want=${both[0]} got=${e1.unixEpoch} from ${v1}` }]); } if (e2.unixEpoch !== both[1]) { f.add(ctx, [{ code: 'P5_SECOND_OCCURRENCE_LOST', detail: `want=${both[1]} got=${e2.unixEpoch} from ${v2}` }]); } if (e1.unixEpoch === e2.unixEpoch) { f.add(ctx, [{ code: 'P5_OCCURRENCES_COLLAPSED', detail: `both -> ${e1.unixEpoch}` }]); } } if (p && p.unixEpoch !== null) { if (p.unixEpoch === both[0]) pickFirst += 1; else if (p.unixEpoch === both[1]) { pickSecond += 1; secondZones.push(zone); } } if (n < 5 && p) { notes.push(`${zone}: ${fmtWall(w)} occurrences=[${both}] plainSelect=${p.unixEpoch} ` + `(${p.unixEpoch === both[0] ? 'FIRST' : p.unixEpoch === both[1] ? 'SECOND' : 'NEITHER'})`); } n += 1; } // eslint-disable-next-line no-console console.warn(`[audit-p5] overlap probed=${n} violations=${f.count} ` + `PICKS_FIRST=${pickFirst} PICKS_SECOND=${pickSecond} ` + `secondZonesSample=${secondZones.slice(0, 12).join(',')}\n ${notes.join('\n ')}`); expect(n, 'must actually find overlaps to test').to.be.greaterThan(10); expect(f.count, `overlap violations:${f.report()}`).to.equal(0); }).timeout(1800000); }); /* ========================================================================== PART 8 — PHASE 6: host-timezone independence (was p6-host.test.ts) ========================================================================== */ type Case = { label: string; value?: string; tz: string; wall?: Wall }; const W = (year: number, month: number, day: number, hours: number, minutes: number, seconds: number, ms: number): Wall => ({ year, month, day, hours, minutes, seconds, ms }); const CASES: Case[] = [ // ── instant-bearing: epoch MUST be identical on every host ────────────── { label: 'Z-utc', value: '2026-06-15T09:00:00.000Z', tz: 'UTC' }, { label: 'Z-kolkata', value: '2026-06-15T09:00:00.000Z', tz: 'Asia/Kolkata' }, { label: 'Z-ny', value: '2026-06-15T09:00:00.000Z', tz: 'America/New_York' }, { label: 'Z-ms999', value: '2026-06-15T09:00:00.999Z', tz: 'Pacific/Chatham' }, { label: 'off+0530', value: '2026-06-15T14:30:00.000+05:30', tz: 'Asia/Kolkata' }, { label: 'off-0400', value: '2026-06-15T05:00:00.000-04:00', tz: 'America/New_York' }, { label: 'off+1400', value: '2026-06-15T23:00:00.000+14:00', tz: 'Pacific/Kiritimati' }, { label: 'brk-kolkata', value: '2026-06-15T14:30:00.000[GMT+05:30 Asia/Kolkata]', tz: 'Asia/Kolkata' }, { label: 'brk-alias', value: '2026-06-15T14:30:00.000[GMT+05:30 Asia/Calcutta]', tz: 'Asia/Calcutta' }, { label: 'brk-dst-1st', value: '2026-11-01T01:30:00.000[GMT-04:00 America/New_York]', tz: 'America/New_York' }, { label: 'brk-dst-2nd', value: '2026-11-01T01:30:00.000[GMT-05:00 America/New_York]', tz: 'America/New_York' }, { label: 'brk-lordhowe', value: '2026-06-15T14:30:00.000[GMT+10:30 Australia/Lord_Howe]', tz: 'Australia/Lord_Howe' }, // ── offset-less + date-only: component-zone semantics, also host-free ──── { label: 'offless-kolkata', value: '2026-06-15T14:30:00.000', tz: 'Asia/Kolkata' }, { label: 'offless-ny', value: '2026-06-15T14:30:00.000', tz: 'America/New_York' }, { label: 'offless-gap-ny', value: '2026-03-08T02:30:00.000', tz: 'America/New_York' }, { label: 'offless-overlap-ny', value: '2026-11-01T01:30:00.000', tz: 'America/New_York' }, { label: 'dateonly-kolkata', value: '2026-06-15', tz: 'Asia/Kolkata' }, { label: 'dateonly-kiritimati', value: '2026-06-15', tz: 'Pacific/Kiritimati' }, // ── UI-selected wall clocks in awkward zones ──────────────────────────── { label: 'sel-chatham', wall: W(2026, 9, 27, 3, 30, 0, 0), tz: 'Pacific/Chatham' }, { label: 'sel-lordhowe', wall: W(2026, 4, 5, 1, 30, 0, 0), tz: 'Australia/Lord_Howe' }, { label: 'sel-kathmandu', wall: W(2026, 6, 15, 14, 45, 7, 123), tz: 'Asia/Kathmandu' }, { label: 'sel-stjohns', wall: W(2026, 3, 8, 0, 30, 0, 0), tz: 'America/St_Johns' }, { label: 'sel-eucla', wall: W(2026, 12, 31, 23, 59, 59, 999), tz: 'Australia/Eucla' }, { label: 'sel-marquesas', wall: W(2026, 1, 1, 0, 0, 0, 1), tz: 'Pacific/Marquesas' }, ]; describe('PHASE 6 — host-timezone independence', () => { it('P6.1 emits the cross-host digest and checks payload invariants locally', async () => { const f = new Failures(); const lines: string[] = []; for (const c of CASES) { let p; if (c.wall) { const el = await makePicker({}); p = await selectAndApply(el, c.wall, c.tz); } else { const el = await makePicker({ value: c.value!, timezone: c.tz }); p = await apply(el); } f.add(`${c.label} host=${HOST_TZ}`, checkPayload(p)); lines.push(`P6|${c.label}|${p ? p.unixEpoch : 'NONE'}|${p ? p.timezone : 'NONE'}|${p ? p.value : 'NONE'}`); } // eslint-disable-next-line no-console console.warn(`[audit-p6] host=${HOST_TZ}\n${lines.join('\n')}`); expect(f.count, `host=${HOST_TZ} payload violations:${f.report()}`).to.equal(0); }).timeout(900000); it('P6.2 the oracle itself is host-independent (sanity on the measuring stick)', () => { // If these differed by host, every other comparison would be meaningless. const t = Date.UTC(2026, 5, 15, 9, 0, 0, 0); const probe = [ instantsFor(W(2026, 6, 15, 14, 30, 0, 0), 'Asia/Kolkata').join(','), instantsFor(W(2026, 3, 8, 2, 30, 0, 0), 'America/New_York').length, instantsFor(W(2026, 11, 1, 1, 30, 0, 0), 'America/New_York').length, fmtWall(W(2026, 6, 15, 14, 30, 0, 0)), t, ].join('|'); // eslint-disable-next-line no-console console.warn(`[audit-p6-oracle] host=${HOST_TZ} digest=${probe}`); expect(probe, 'Asia/Kolkata 14:30 == 09:00Z').to.contain('1781514000000'); }); }); /* ========================================================================== PART 7 — PHASE 7/8/9/11/12: ms, boundaries, formats, round-trips, fuzzing (was p7912-formats.test.ts) ========================================================================== */ const ZONES = ['UTC', 'Asia/Kolkata', 'Asia/Kathmandu', 'America/New_York', 'Australia/Lord_Howe', 'Pacific/Chatham', 'Pacific/Kiritimati', 'America/St_Johns']; describe('PHASE 7 — milliseconds and precision', () => { const MS = [0, 1, 9, 10, 99, 100, 250, 500, 999]; it('P7.1 every millisecond value survives select -> Apply -> reload -> Apply', async () => { const f = new Failures(); for (const z of ZONES) { for (const ms of MS) { const w: Wall = { year: 2026, month: 6, day: 15, hours: 14, minutes: 30, seconds: 45, ms }; const el = await makePicker({}); const p = await selectAndApply(el, w, z); const ctx = `tz=${z} ms=${ms}`; f.add(ctx, checkPayload(p, z)); if (p && p.unixEpoch !== null) { if (p.unixEpoch % 1000 !== ms) { f.add(ctx, [{ code: 'P7_MS_LOST', detail: `epoch%1000=${p.unixEpoch % 1000} want=${ms} value=${p.value}` }]); } // ms must NOT contaminate the offset: +05:30 must never become +05:29 const off = offsetAt(p.unixEpoch, z); if (off % 60000 !== 0) { f.add(ctx, [{ code: 'P7_OFFSET_NOT_WHOLE_MINUTES', detail: `offset=${off}ms` }]); } const parsed = parseBracketed(p.value!); if (parsed && parsed.offsetMs !== off) { f.add(ctx, [{ code: 'P7_OFFSET_CONTAMINATED', detail: `printed=${parsed.offsetMs} true=${off}` }]); } f.add(ctx, (await roundTrip(p)).violations); } } } expect(f.count, `ms violations:${f.report()}`).to.equal(0); }).timeout(600000); it('P7.2 records CURRENT behaviour of 1- and 2-digit fractional seconds', async () => { const notes: string[] = []; for (const raw of ['2026-06-15T14:30:00.2', '2026-06-15T14:30:00.25', '2026-06-15T14:30:00.250', '2026-06-15T14:30:00.2Z', '2026-06-15T14:30:00.25Z']) { const el = await makePicker({ value: raw, timezone: 'Asia/Kolkata' }); const p = await apply(el); notes.push(`${raw} -> epoch=${p && p.unixEpoch} ms=${p && p.unixEpoch !== null ? p.unixEpoch % 1000 : 'n/a'} value=${p && p.value}`); } // eslint-disable-next-line no-console console.warn(`[audit-p7] fractional-second CURRENT behaviour:\n ${notes.join('\n ')}`); expect(notes.length).to.equal(5); }).timeout(120000); }); describe('PHASE 8 — time boundaries, no accidental day rollover', () => { const TIMES = [ [0, 0, 0, 0], [0, 0, 0, 1], [11, 59, 59, 999], [12, 0, 0, 0], [23, 59, 59, 998], [23, 59, 59, 999], ]; const DAYS: Array<[number, number, number, string]> = [ [2026, 6, 15, 'normal'], [2026, 1, 31, 'month-end'], [2026, 12, 31, 'year-end'], [2024, 2, 29, 'leap-day'], [2026, 3, 8, 'US DST day'], [2026, 11, 1, 'US fall-back day'], ]; it('P8.1 boundary times keep their calendar day (or truthfully report the shift)', async () => { const f = new Failures(); for (const z of ['UTC', 'Asia/Kolkata', 'America/New_York', 'Pacific/Kiritimati']) { for (const [y, mo, d, label] of DAYS) { for (const [h, mi, s, ms] of TIMES) { const w: Wall = { year: y, month: mo, day: d, hours: h, minutes: mi, seconds: s, ms }; const exists = instantsFor(w, z).length > 0; const el = await makePicker({}); const p = await selectAndApply(el, w, z); const ctx = `tz=${z} ${label} ${fmtWall(w)}`; f.add(ctx, checkPayload(p, z)); if (p && p.unixEpoch !== null && exists) { const back = wallAt(p.unixEpoch, z); if (back.day !== d || back.month !== mo || back.year !== y) { f.add(ctx, [{ code: 'P8_DAY_ROLLOVER', detail: `selected ${y}-${mo}-${d} but epoch reads ${back.year}-${back.month}-${back.day} in ${z}`, }]); } if (back.hours !== h || back.minutes !== mi || back.seconds !== s || back.ms !== ms) { f.add(ctx, [{ code: 'P8_TIME_DRIFT', detail: `got ${fmtWall(back)}` }]); } } } } } expect(f.count, `boundary violations:${f.report()}`).to.equal(0); }).timeout(900000); }); describe('PHASE 9 — input format matrix', () => { type Row = { label: string; value: string | null; tz: string; validInstant?: number }; it('P9.1 valid inputs preserve their instant through reload', async () => { const f = new Failures(); const rows: Row[] = [ { label: 'Z', value: '2026-06-15T09:00:00.000Z', tz: 'Asia/Kolkata', validInstant: Date.UTC(2026, 5, 15, 9) }, { label: '+offset', value: '2026-06-15T14:30:00.000+05:30', tz: 'Asia/Kolkata', validInstant: Date.UTC(2026, 5, 15, 9) }, { label: '-offset', value: '2026-06-15T05:00:00.000-04:00', tz: 'America/New_York', validInstant: Date.UTC(2026, 5, 15, 9) }, { label: 'bracketed UTC', value: '2026-06-15T09:00:00.000[GMT+00:00 UTC]', tz: 'UTC' }, { label: 'bracketed +', value: '2026-06-15T14:30:00.000[GMT+05:30 Asia/Kolkata]', tz: 'Asia/Kolkata' }, { label: 'bracketed -', value: '2026-06-15T05:00:00.000[GMT-04:00 America/New_York]', tz: 'America/New_York' }, { label: 'alias', value: '2026-06-15T14:30:00.000[GMT+05:30 Asia/Calcutta]', tz: 'Asia/Calcutta' }, { label: 'offset-less', value: '2026-06-15T14:30:00.000', tz: 'Asia/Kolkata' }, { label: 'date-only', value: '2026-06-15', tz: 'Asia/Kolkata' }, { label: 'DST 1st occ', value: '2026-11-01T01:30:00.000[GMT-04:00 America/New_York]', tz: 'America/New_York' }, { label: 'DST 2nd occ', value: '2026-11-01T01:30:00.000[GMT-05:00 America/New_York]', tz: 'America/New_York' }, ]; const notes: string[] = []; for (const r of rows) { const el = await makePicker({ value: r.value, timezone: r.tz }); const p = await apply(el); const ctx = `${r.label} value=${r.value}`; f.add(ctx, checkPayload(p)); if (r.validInstant !== undefined && p && p.unixEpoch !== r.validInstant) { f.add(ctx, [{ code: 'P9_INSTANT_CHANGED', detail: `want=${r.validInstant} got=${p.unixEpoch}` }]); } if (p && p.unixEpoch !== null) f.add(ctx, (await roundTrip(p)).violations); notes.push(`${r.label.padEnd(14)} -> epoch=${p && p.unixEpoch} tz=${p && p.timezone} value=${p && p.value}`); } // eslint-disable-next-line no-console console.warn(`[audit-p9] valid-input matrix:\n ${notes.join('\n ')}`); expect(f.count, `format-matrix violations:${f.report()}`).to.equal(0); }).timeout(600000); it('P9.2 classifies INVALID / edge inputs (REJECT / FALLBACK / CLAMP / NORMALIZE)', async () => { const rows: Array<{ label: string; value: string | null; tz: string }> = [ { label: 'null', value: null, tz: 'Asia/Kolkata' }, { label: 'empty string', value: '', tz: 'Asia/Kolkata' }, { label: 'malformed', value: 'not-a-date', tz: 'Asia/Kolkata' }, { label: 'impossible date', value: '2026-02-31T10:00:00.000', tz: 'Asia/Kolkata' }, { label: 'impossible date-only', value: '2026-02-31', tz: 'Asia/Kolkata' }, { label: 'invalid tz in bracket', value: '2026-06-15T14:30:00.000[GMT+05:30 Mars/Phobos]', tz: 'UTC' }, { label: 'wrong offset for zone', value: '2026-06-15T14:30:00.000[GMT+09:00 Asia/Kolkata]', tz: 'Asia/Kolkata' }, { label: 'gap wall clock', value: '2026-03-08T02:30:00.000', tz: 'America/New_York' }, { label: 'hour 24', value: '2026-06-15T24:00:00.000', tz: 'UTC' }, { label: 'month 13', value: '2026-13-15T10:00:00.000', tz: 'UTC' }, { label: 'year 0001', value: '0001-01-01T00:00:00.000Z', tz: 'UTC' }, { label: 'year 9999', value: '9999-12-31T23:59:59.999Z', tz: 'UTC' }, ]; const notes: string[] = []; const f = new Failures(); // An empty / unparseable value leaves NO selection, and Apply is a no-op without a selection (handleApply → // hasSelection()), so these must emit NOTHING. Every other row resolves to an instant and must emit a payload. const EMITS_NOTHING = new Set(['null', 'empty string', 'malformed', 'month 13']); for (const r of rows) { const el = await makePicker({ value: r.value, timezone: r.tz }); const p = await apply(el); if (EMITS_NOTHING.has(r.label)) { if (p !== null) f.add(`${r.label} value=${r.value}`, [{ code: 'P9_EMITTED_FOR_NO_SELECTION', detail: `emitted ${JSON.stringify(p)}` }]); notes.push(`${r.label.padEnd(22)} NO_PAYLOAD (expected)`); continue; } // Whatever it does, the payload must never LIE about its own epoch. f.add(`${r.label} value=${r.value}`, checkPayload(p)); const cls = p === null ? 'NO_PAYLOAD' : p.unixEpoch === null ? 'REJECT(null epoch)' : 'ACCEPTED'; notes.push(`${r.label.padEnd(22)} ${cls.padEnd(20)} epoch=${p && p.unixEpoch} tz=${p && p.timezone} value=${p && p.value}`); } // eslint-disable-next-line no-console console.warn(`[audit-p9] invalid/edge input classification:\n ${notes.join('\n ')}`); expect(f.count, `invalid-input payload lies:${f.report()}`).to.equal(0); }).timeout(600000); }); describe('PHASE 11 — repeated round trips must not drift', () => { it('P11.1 100 consecutive emit->reload->Apply cycles hold the epoch to the ms', async () => { const f = new Failures(); const seeds: Array<{ label: string; value: string; tz: string }> = [ { label: 'ms=999 half-hour zone', value: '2026-06-15T14:30:45.999[GMT+05:30 Asia/Kolkata]', tz: 'Asia/Kolkata' }, { label: 'quarter-hour zone', value: '2026-06-15T14:30:45.001[GMT+05:45 Asia/Kathmandu]', tz: 'Asia/Kathmandu' }, { label: 'alias', value: '2026-06-15T14:30:45.123[GMT+05:30 Asia/Calcutta]', tz: 'Asia/Calcutta' }, { label: 'DST overlap 2nd', value: '2026-11-01T01:30:00.000[GMT-05:00 America/New_York]', tz: 'America/New_York' }, { label: 'DST gap resolved', value: '2026-03-08T03:30:00.000[GMT-04:00 America/New_York]', tz: 'America/New_York' }, { label: '+14 extreme', value: '2026-06-15T14:30:45.500[GMT+14:00 Pacific/Kiritimati]', tz: 'Pacific/Kiritimati' }, { label: 'negative extreme', value: '2026-06-15T14:30:45.500[GMT-11:00 Pacific/Niue]', tz: 'Pacific/Niue' }, { label: 'Lord Howe :30', value: '2026-06-15T14:30:45.250[GMT+10:30 Australia/Lord_Howe]', tz: 'Australia/Lord_Howe' }, ]; for (const s of seeds) { let cur: Payload | null = await apply(await makePicker({ value: s.value, timezone: s.tz })); if (!cur || cur.unixEpoch === null) { f.add(s.label, [{ code: 'P11_NO_SEED', detail: s.value }]); continue; } const first = cur.unixEpoch; for (let i = 0; i < 100; i++) { const next: Payload | null = await apply(await makePicker({ value: cur!.value, timezone: cur!.timezone })); f.add(`${s.label} iter=${i}`, checkPayload(next)); if (!next || next.unixEpoch !== first) { f.add(s.label, [{ code: 'P11_DRIFT', detail: `iter=${i} first=${first} now=${next && next.unixEpoch} delta=${next && next.unixEpoch !== null ? next.unixEpoch - first : 'n/a'}ms`, }]); break; } cur = next; } } // eslint-disable-next-line no-console console.warn(`[audit-p11] 8 seeds x 100 cycles, violations=${f.count}`); expect(f.count, `round-trip drift:${f.report()}`).to.equal(0); }).timeout(1800000); });