mirror of
https://github.com/azaion/ui.git
synced 2026-06-24 16:31:11 +00:00
ff522b0821
ci/woodpecker/push/build-arm Pipeline failed
Migrate src/features/flights to the v2 tactical-ops design — the last page still on the legacy az-* palette — keeping all existing planner behavior (Leaflet map, draw modes, import/export, altitude dialog). - Restyle every flights surface to v2 tokens and shared classes: flight roster sidebar (search, rows, telemetry card), params panel, waypoint list, altitude/JSON dialogs, map-point popup, altitude chart, wind inputs, mini-map. - Rebuild the params panel to the mockup order (draw-mode selector, Mission Config, Waypoints) with existing controls appended. - Add HUD overlays on the real Leaflet map (telemetry, legend, compass, zoom/recenter toolbar, bottom status strip); disable the default zoom control, add a dark tactical-grid backdrop, and use the legend glyphs (diamond/square/octagon) plus a pulsing amber current-position beacon. - Add a functional GPS-Denied panel: orthophoto upload (local), live-GPS readout fed by the existing SSE stream, and a GPS-correction form that patches waypoint coordinates. - Extract a shared drawModes config used by the panel and collapse rail. - Add flights.v2 i18n keys to en.json and ua.json (parity preserved).
218 lines
7.2 KiB
TypeScript
218 lines
7.2 KiB
TypeScript
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 <TileLayer> 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) => (
|
|
<div data-testid="map-container" className={className}>{children}</div>
|
|
),
|
|
TileLayer: (props: TileLayerProps) => (
|
|
<img
|
|
data-testid="tile-layer"
|
|
data-tile-url={props.url ?? ''}
|
|
data-cross-origin={props.crossOrigin ?? ''}
|
|
data-attribution={props.attribution ?? ''}
|
|
alt=""
|
|
/>
|
|
),
|
|
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: <TileLayer> declares crossOrigin="use-credentials"', () => {
|
|
// Act
|
|
renderWithProviders(<FlightMap {...baseFlightMapProps} />, { withoutAuth: true })
|
|
|
|
// Assert
|
|
const tile = screen.getByTestId('tile-layer')
|
|
expect(tile.getAttribute('data-cross-origin')).toBe('use-credentials')
|
|
})
|
|
|
|
it('AC-3: <TileLayer> renders the dev-default URL when env is unset', () => {
|
|
// Act
|
|
renderWithProviders(<FlightMap {...baseFlightMapProps} />, { 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(<FlightMap {...baseFlightMapProps} />, { withoutAuth: true })
|
|
|
|
// Assert
|
|
expect(screen.queryByRole('button', { name: /satellite|classic/i })).toBeNull()
|
|
// Only one <TileLayer> 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 <TileLayer> declares crossOrigin="use-credentials"', () => {
|
|
// Act
|
|
renderWithProviders(
|
|
<MiniMap pointPosition={{ x: 0, y: 0, latlng: stubLatLng }} />,
|
|
{ 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(
|
|
<MiniMap pointPosition={{ x: 0, y: 0, latlng: stubLatLng }} />,
|
|
{ withoutAuth: true },
|
|
)
|
|
|
|
// Assert
|
|
expect(screen.getByTestId('tile-layer')).toBeInTheDocument()
|
|
})
|
|
})
|