import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' import type L from 'leaflet' import { renderWithProviders, screen } from '../../../../tests/helpers/render' // AZ-498 — self-hosted satellite tiles + drop classic/satellite toggle. // // Colocated under src/features/flights/__tests__/ per module-layout's "Tests" // guidance: keeps the cross-component import surface clean (these tests // reach into 05_flights internals — `./FlightMap`, `./MiniMap`, `./types` — // which is intra-component access). Tests/ is reserved for cross-cutting // black-box suites whose imports must go through public-API barrels. // // Covers the spec's fast-profile ACs: // AC-1 — env-resolved getTileUrl() returns the env var verbatim. // AC-2 — when the env var is unset, getTileUrl() returns the dev default // `http://localhost:5100/tiles/{z}/{x}/{y}` (cycle-2 assumption). // AC-3 — every the SPA renders sets crossOrigin="use-credentials" // so the browser attaches the satellite-provider auth cookie. // AC-4 — the classic/satellite toggle, the `mapType` state, and the // `MiniMap.Props.mapType` prop are all gone. // // Notes: // - AC-5 is statically enforced by tsc on the new ImportMetaEnv shape + // the `.env.example` audit; no runtime test needed. // - AC-6, AC-7 are e2e/contract; AC-8 in the original spec misattributed // `tile_split_zoom*` (image-annotation surface) — see implementation // report. AC-9 is enforced by STC-ARCH-01 / STC-ARCH-02. interface TileLayerProps { url?: string crossOrigin?: string attribution?: string } interface MapContainerProps { children?: React.ReactNode className?: string } vi.mock('react-leaflet', () => ({ MapContainer: ({ children, className }: MapContainerProps) => (
{children}
), TileLayer: (props: TileLayerProps) => ( ), Marker: () => null, Popup: () => null, Polyline: () => null, Rectangle: () => null, CircleMarker: () => null, useMap: () => ({ on: () => undefined, off: () => undefined, setView: () => undefined, removeLayer: () => undefined, getCenter: () => ({ lat: 0, lng: 0 }), invalidateSize: () => undefined, }), useMapEvents: () => null, })) // Leaflet itself is touched at import time by FlightMap (`L.polyline`, // `L.Symbol.arrowHead`). Mock the bits the component reaches for so the // import doesn't blow up under jsdom. vi.mock('leaflet', () => { const Lstub = { polyline: () => ({ addTo: () => Lstub.polyline(), on: () => undefined }), polylineDecorator: () => ({ addTo: () => undefined }), Symbol: { arrowHead: () => ({}) }, Icon: { Default: class { mergeOptions() {} } }, Marker: class {}, Layer: class {}, LatLngBounds: class {}, } return { default: Lstub } }) vi.mock('leaflet/dist/leaflet.css', () => ({})) vi.mock('leaflet-polylinedecorator', () => ({})) vi.mock('../DrawControl', () => ({ default: () => null })) vi.mock('../MapPoint', () => ({ default: () => null })) vi.mock('../mapIcons', () => ({ currentPositionIcon: {} })) import FlightMap from '../FlightMap' import MiniMap from '../MiniMap' import { getTileUrl, DEFAULT_SATELLITE_TILE_URL } from '../types' const stubLatLng = { lat: 0, lng: 0 } as unknown as L.LatLng const fixedPosition = { lat: 50, lng: 30 } const baseFlightMapProps = { points: [], calculatedPointInfo: [], currentPosition: fixedPosition, rectangles: [], setRectangles: () => undefined, rectangleColor: 'red', actionMode: 'points' as const, onAddPoint: () => undefined, onUpdatePoint: () => undefined, onRemovePoint: () => undefined, onAltitudeChange: () => undefined, onMetaChange: () => undefined, onPolylineClick: () => undefined, onPositionChange: () => undefined, onMapMove: () => undefined, } describe('AZ-498 — getTileUrl() env resolution', () => { afterEach(() => { vi.unstubAllEnvs() }) it('AC-1: returns the env-set VITE_SATELLITE_TILE_URL verbatim', () => { // Arrange vi.stubEnv('VITE_SATELLITE_TILE_URL', 'http://satellite-provider:5100/tiles/{z}/{x}/{y}') // Assert expect(getTileUrl()).toBe('http://satellite-provider:5100/tiles/{z}/{x}/{y}') }) it('AC-2: returns the dev default when VITE_SATELLITE_TILE_URL is unset', () => { // Arrange vi.stubEnv('VITE_SATELLITE_TILE_URL', '') // Assert expect(getTileUrl()).toBe(DEFAULT_SATELLITE_TILE_URL) expect(DEFAULT_SATELLITE_TILE_URL).toBe('http://localhost:5100/tiles/{z}/{x}/{y}') }) it('AC-2: strips trailing slashes off the env-set URL', () => { // Arrange vi.stubEnv('VITE_SATELLITE_TILE_URL', 'http://satellite-provider:5100/tiles/{z}/{x}/{y}/') // Assert expect(getTileUrl()).toBe('http://satellite-provider:5100/tiles/{z}/{x}/{y}') }) }) describe('AZ-498 — FlightMap satellite-only TileLayer', () => { beforeEach(() => { vi.stubEnv('VITE_SATELLITE_TILE_URL', '') }) afterEach(() => { vi.unstubAllEnvs() }) it('AC-3: declares crossOrigin="use-credentials"', () => { // Act renderWithProviders(, { withoutAuth: true }) // Assert const tile = screen.getByTestId('tile-layer') expect(tile.getAttribute('data-cross-origin')).toBe('use-credentials') }) it('AC-3: renders the dev-default URL when env is unset', () => { // Act renderWithProviders(, { withoutAuth: true }) // Assert const tile = screen.getByTestId('tile-layer') expect(tile.getAttribute('data-tile-url')).toBe(DEFAULT_SATELLITE_TILE_URL) }) it('AC-4: the classic/satellite toggle button is gone', () => { // Act renderWithProviders(, { withoutAuth: true }) // Assert expect(screen.queryByRole('button', { name: /satellite|classic/i })).toBeNull() // Only one is mounted (no per-mode branching). expect(screen.getAllByTestId('tile-layer')).toHaveLength(1) }) }) describe('AZ-498 — MiniMap satellite-only TileLayer', () => { beforeEach(() => { vi.stubEnv('VITE_SATELLITE_TILE_URL', '') }) afterEach(() => { vi.unstubAllEnvs() }) it('AC-3: MiniMap declares crossOrigin="use-credentials"', () => { // Act renderWithProviders( , { withoutAuth: true }, ) // Assert const tile = screen.getByTestId('tile-layer') expect(tile.getAttribute('data-cross-origin')).toBe('use-credentials') }) it('AC-4: MiniMap mounts with only `pointPosition` prop (no `mapType`)', () => { // Act — explicitly omit mapType; if MiniMap still required it, TS would // error at compile time. The runtime render also confirms the component // mounts with just the position prop. renderWithProviders( , { withoutAuth: true }, ) // Assert expect(screen.getByTestId('tile-layer')).toBeInTheDocument() }) })