import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' import type { BookElement } from '@trek/shared' import { render } from '../../helpers/render' import { TravelPreview } from '../../../src/components/Studio/TravelPreview' /** * The panel tiles (#1973). * * The point of these is that they are the real element at a smaller scale, not * an icon standing in for one — four map styles are four maps, and a generic * glyph cannot tell you which is which. So what is checked here is that the * element genuinely renders, that its proportions survive, and that the tile * takes its size from the element rather than from a number someone guessed. */ /* * The tile measures itself, and jsdom reports every element as zero-sized. This * gives the measured span a width the moment it is observed, which is what the * browser does on the first frame — see useElementSize.test.ts for the same * pattern applied to the hook itself. */ const OBSERVED_W = 190 class SizingResizeObserver { constructor(private cb: ResizeObserverCallback) {} observe(el: Element) { Object.defineProperty(el, 'offsetWidth', { value: OBSERVED_W, configurable: true }) Object.defineProperty(el, 'offsetHeight', { value: 60, configurable: true }) this.cb([], this as unknown as ResizeObserver) } unobserve() {} disconnect() {} } beforeEach(() => { Object.defineProperty(HTMLElement.prototype, 'offsetWidth', { get() { return OBSERVED_W }, configurable: true, }) vi.stubGlobal('ResizeObserver', SizingResizeObserver) }) afterEach(() => { vi.unstubAllGlobals() }) const typeset = { font: 'sans' as const, color: '#1a1a1a', accent: '#c2410c', textScale: 1, stale: false } const common = { rotation: 0, opacity: 1, locked: false } const statsEl = (w: number, h: number): BookElement => ({ ...common, ...typeset, id: 'st1', kind: 'stats', frame: { x: 40, y: 90, w, h }, metrics: ['distance', 'days'], layout: 'grid', showIcons: true, units: 'metric', values: { distance: 1_189_000, days: 14 }, } as unknown as BookElement) const tileHeight = (container: HTMLElement) => parseFloat((container.firstElementChild as HTMLElement).style.height) describe('TravelPreview', () => { it('draws the element itself, with its real figures', () => { const { container } = render() expect(container.textContent).toContain('1,189') expect(container.textContent).toContain('14') }) it('takes its height from the element, so a wide panel gets a shallow tile', () => { const wide = render() const tall = render() expect(tileHeight(wide.container)).toBeLessThan(tileHeight(tall.container)) }) it('clamps a very flat element rather than drawing a hairline', () => { const { container } = render() expect(tileHeight(container)).toBe(30) }) it('clamps a very tall element rather than running down the panel', () => { const { container } = render() expect(tileHeight(container)).toBe(80) }) /* * The tile is the frame. An element sitting at x=40 on the spread must draw * at the tile's own origin, or half of it would be outside the preview. */ it('draws at the origin, ignoring where the element sits on the spread', () => { const { container } = render() const drawn = container.querySelector('.st-travel-preview > span > div') as HTMLElement expect(drawn.style.left).toBe('0mm') expect(drawn.style.top).toBe('0mm') }) it('scales rather than squashes — one factor, applied to both axes', () => { const { container } = render() const scaler = container.querySelector('.st-travel-preview > span') as HTMLElement const m = /scale\(([\d.]+)\)/.exec(scaler.style.transform) expect(m).not.toBeNull() expect(Number(m![1])).toBeGreaterThan(0) expect(Number(m![1])).toBeLessThan(1) }) /* * The clipped right edge that started this: the drawn element must never be * wider than the tile it was measured against. */ it('never draws wider than the width it measured', () => { const { container } = render() const scaler = container.querySelector('.st-travel-preview > span') as HTMLElement const scale = Number(/scale\(([\d.]+)\)/.exec(scaler.style.transform)![1]) const drawnPx = 400 * (96 / 25.4) * scale expect(drawnPx).toBeLessThanOrEqual(OBSERVED_W + 0.5) expect(parseFloat(scaler.style.left)).toBeGreaterThanOrEqual(0) }) it('renders a map tile without a broken coordinate', () => { const map = { ...common, ...typeset, id: 'mp1', kind: 'map', frame: { x: 0, y: 0, w: 150, h: 120 }, style: 'paper', showLand: true, showRoute: true, showPins: true, showLabels: false, countries: ['IS'], points: [ { lat: 64.14, lng: -21.94, label: 'a' }, { lat: 65.68, lng: -18.12, label: 'b' }, ], } as unknown as BookElement const { container } = render() expect(container.querySelector('polyline')).not.toBeNull() expect(container.querySelector('svg')!.innerHTML).not.toContain('NaN') }) })