| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| import { test, expect } from '@playwright/test'; |
|
|
| test.describe('sticky map containment', () => { |
| test('does not overlap the trace UI when scrolled to bottom', async ({ page }) => { |
| await page.setViewportSize({ width: 1440, height: 900 }); |
| await page.goto('/q/sample'); |
|
|
| |
| await page.waitForFunction( |
| () => Boolean((window as unknown as { __riprapMap?: unknown }).__riprapMap), |
| undefined, |
| { timeout: 15_000 } |
| ); |
|
|
| |
| await page.locator('#region-trace').scrollIntoViewIfNeeded(); |
| await page.waitForTimeout(300); |
|
|
| const rects = await page.evaluate(() => { |
| const m = document.getElementById('region-map')?.getBoundingClientRect(); |
| const t = document.getElementById('region-trace')?.getBoundingClientRect(); |
| const e = document.querySelector('[aria-label="Evidence"]')?.getBoundingClientRect(); |
| return { m, t, e }; |
| }); |
|
|
| expect(rects.m, 'map rect').toBeTruthy(); |
| expect(rects.t, 'trace rect').toBeTruthy(); |
| if (!rects.m || !rects.t) return; |
|
|
| |
| const xOverlap = !(rects.m.right <= rects.t.left || rects.t.right <= rects.m.left); |
| const yOverlap = !(rects.m.bottom <= rects.t.top || rects.t.bottom <= rects.m.top); |
| const overlaps = xOverlap && yOverlap; |
|
|
| expect(overlaps, |
| `map should not visually overlap the trace UI when scrolled to it. ` + |
| `Map rect: ${JSON.stringify(rects.m)}; Trace rect: ${JSON.stringify(rects.t)}.` |
| ).toBe(false); |
|
|
| |
| expect(rects.m.bottom).toBeLessThanOrEqual(rects.t.top + 1); |
| }); |
|
|
| test('side rail is position:sticky with citations stacked beneath the map (DOM order)', async ({ page }) => { |
| await page.setViewportSize({ width: 1440, height: 900 }); |
| await page.goto('/q/sample'); |
|
|
| await page.waitForFunction( |
| () => Boolean((window as unknown as { __riprapMap?: unknown }).__riprapMap), |
| undefined, |
| { timeout: 15_000 } |
| ); |
|
|
| |
| |
| |
| |
| |
| |
| |
| const facts = await page.evaluate(() => { |
| const rail = document.querySelector('.app-region-side') as HTMLElement | null; |
| if (!rail) return null; |
| const cs = getComputedStyle(rail); |
| const map = document.getElementById('region-map'); |
| const cites = document.querySelector('.citation-drawer'); |
| const order = map && cites && |
| (map.compareDocumentPosition(cites) & Node.DOCUMENT_POSITION_FOLLOWING) ? 'map-first' : 'wrong'; |
| return { |
| position: cs.position, |
| top: cs.top, |
| overflowY: cs.overflowY, |
| order |
| }; |
| }); |
| expect(facts).toBeTruthy(); |
| if (!facts) return; |
| expect(facts.position).toBe('sticky'); |
| expect(facts.top).toBe('80px'); |
| expect(facts.overflowY).toBe('auto'); |
| expect(facts.order).toBe('map-first'); |
| }); |
|
|
| test('citations are reachable from the side rail (not buried under sticky map)', async ({ page }) => { |
| |
| |
| |
| |
| |
| await page.setViewportSize({ width: 1440, height: 900 }); |
| await page.goto('/q/sample'); |
|
|
| await page.waitForFunction( |
| () => Boolean((window as unknown as { __riprapMap?: unknown }).__riprapMap), |
| undefined, |
| { timeout: 15_000 } |
| ); |
|
|
| await page.evaluate(() => window.scrollTo({ top: 400, behavior: 'instant' })); |
| await page.waitForTimeout(250); |
|
|
| |
| |
| const inside = await page.evaluate(() => { |
| const rail = document.querySelector('.app-region-side') as HTMLElement | null; |
| const cites = document.querySelector('.citation-drawer') as HTMLElement | null; |
| if (!rail || !cites) return null; |
| const inside = rail.contains(cites); |
| const railRect = rail.getBoundingClientRect(); |
| const railVisible = railRect.top < window.innerHeight && railRect.bottom > 0; |
| const railScrollable = rail.scrollHeight > rail.clientHeight; |
| |
| |
| const map = document.getElementById('region-map'); |
| const mapBeforeCites = |
| map && |
| map.compareDocumentPosition(cites) & Node.DOCUMENT_POSITION_FOLLOWING; |
| return { inside, railVisible, railScrollable, mapBeforeCites: Boolean(mapBeforeCites) }; |
| }); |
|
|
| expect(inside, 'rail / cites geometry').toBeTruthy(); |
| if (!inside) return; |
| expect(inside.inside, 'citations inside side rail').toBe(true); |
| expect(inside.railVisible, 'side rail visible in viewport').toBe(true); |
| expect(inside.mapBeforeCites, 'map renders before cites in DOM').toBe(true); |
| }); |
| }); |
|
|