| 1 | // week.mjs — B8 and B9 measured in a real browser. |
| 2 | // |
| 3 | // Driven by TestWeekAndAllDayFormInARealBrowser, which seeds the week |
| 4 | // of 5 October 2026 with an all-day note and two timed events on the |
| 5 | // Wednesday, plus an all-day event whose edit URL it passes in. |
| 6 | // |
| 7 | // 1. Every timed column starts at the same height, whatever sits in |
| 8 | // the all-day row above it. |
| 9 | // 2. The 10:00 chip is drawn at the 10:00 line, not an hour down. |
| 10 | // 3. A 30-minute chip's title is painted inside the chip. |
| 11 | // 4. An all-day event's edit form holds its dates — checked through |
| 12 | // input.value, which is what a submit sends. |
| 13 | import { chromium } from 'playwright'; |
| 14 | |
| 15 | const [origin, cookieName, cookieValue, editPath] = process.argv.slice(2); |
| 16 | if (!origin || !cookieName || !cookieValue || !editPath) { |
| 17 | console.error('usage: week.mjs <origin> <cookieName> <cookieValue> <editPath>'); |
| 18 | process.exit(2); |
| 19 | } |
| 20 | |
| 21 | const failures = []; |
| 22 | function check(ok, what) { |
| 23 | if (ok) { console.log('ok: ' + what); return; } |
| 24 | failures.push(what); |
| 25 | console.error('FAIL: ' + what); |
| 26 | } |
| 27 | |
| 28 | const browser = await chromium.launch(); |
| 29 | const ctx = await browser.newContext({ viewport: { width: 1440, height: 900 } }); |
| 30 | await ctx.addCookies([{ name: cookieName, value: cookieValue, url: origin, httpOnly: true }]); |
| 31 | const page = await ctx.newPage(); |
| 32 | |
| 33 | // ── 1–3. The week ───────────────────────────────────────────────────── |
| 34 | await page.goto(origin + '/week/2026-10-07', { waitUntil: 'networkidle' }); |
| 35 | const geo = await page.evaluate(() => { |
| 36 | const cols = [...document.querySelectorAll('[cal-column]')].map((c) => c.getBoundingClientRect().top); |
| 37 | const hourPx = document.querySelector('[cal-hour]').getBoundingClientRect().height; |
| 38 | const chip = (title) => { |
| 39 | const t = [...document.querySelectorAll('[cal-placed] [cal-chip-title]')].find((s) => s.textContent.includes(title)); |
| 40 | if (!t) return null; |
| 41 | const chipBox = t.closest('[cal-chip]').getBoundingClientRect(); |
| 42 | const titleBox = t.getBoundingClientRect(); |
| 43 | const colTop = t.closest('[cal-column]').getBoundingClientRect().top; |
| 44 | return { chipTop: chipBox.top, chipBottom: chipBox.bottom, titleTop: titleBox.top, titleBottom: titleBox.bottom, titleWidth: titleBox.width, colTop }; |
| 45 | }; |
| 46 | // Where the 10:00 LABEL draws its line: the label box minus the |
| 47 | // translate that lifts it onto the rule it names. |
| 48 | const label = document.querySelectorAll('[cal-hour]')[10]; |
| 49 | const lift = new DOMMatrix(getComputedStyle(label).transform).m42; |
| 50 | const tenLine = label.getBoundingClientRect().top - lift; |
| 51 | return { cols, hourPx, tenLine, focus: chip('Focus time'), quick: chip('Quick sync') }; |
| 52 | }); |
| 53 | console.log(JSON.stringify(geo)); |
| 54 | check(geo.cols.length === 7, `seven timed columns (got ${geo.cols.length})`); |
| 55 | check(geo.cols.every((top) => Math.abs(top - geo.cols[0]) < 0.5), `every timed column starts at the same height (${geo.cols.join(', ')})`); |
| 56 | check(geo.focus && Math.abs(geo.focus.chipTop - geo.tenLine) < 2, |
| 57 | `the 10:00 chip is beside the 10:00 label (chip ${geo.focus?.chipTop}, label line ${geo.tenLine})`); |
| 58 | check(geo.quick && geo.quick.titleWidth > 8 && geo.quick.titleBottom - geo.quick.titleTop >= 12 && |
| 59 | geo.quick.titleTop >= geo.quick.chipTop - 0.5 && geo.quick.titleBottom <= geo.quick.chipBottom + 0.5, |
| 60 | `the 30-minute chip paints its title inside itself (${JSON.stringify(geo.quick)})`); |
| 61 | await page.screenshot({ path: '/tmp/calendar-week-allday.png', fullPage: false }); |
| 62 | |
| 63 | // ── 4. The all-day edit form, with and without script ───────────────── |
| 64 | for (const js of [true, false]) { |
| 65 | const c = await browser.newContext({ javaScriptEnabled: js }); |
| 66 | await c.addCookies([{ name: cookieName, value: cookieValue, url: origin, httpOnly: true }]); |
| 67 | const p = await c.newPage(); |
| 68 | await p.goto(origin + editPath, { waitUntil: 'networkidle' }); |
| 69 | const values = await p.evaluate(() => ({ |
| 70 | start: document.querySelector('input[name="start"]').value, |
| 71 | end: document.querySelector('input[name="end"]').value, |
| 72 | })); |
| 73 | check(values.start === '2026-10-12' && values.end === '2026-10-14', |
| 74 | `all-day edit form holds its dates, js=${js} (${JSON.stringify(values)})`); |
| 75 | await c.close(); |
| 76 | } |
| 77 | |
| 78 | await browser.close(); |
| 79 | if (failures.length) { |
| 80 | console.error(`${failures.length} check(s) failed`); |
| 81 | process.exit(1); |
| 82 | } |
| 83 | console.log('all week/all-day checks passed'); |
| 84 | |