mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-19 21:31:46 +00:00
test: expand frontend test suite to 82% coverage
Adds ~45 new and updated test files covering Admin, Collab, Dashboard, Map, Memories, PDF, Photos, Planner, Settings, Vacay, Weather components, pages, stores, and a WebSocket integration test.
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { http, HttpResponse } from 'msw'
|
||||
import { server } from '../../../tests/helpers/msw/server'
|
||||
import {
|
||||
calculateRoute,
|
||||
calculateSegments,
|
||||
optimizeRoute,
|
||||
generateGoogleMapsUrl,
|
||||
} from './RouteCalculator'
|
||||
|
||||
const OSRM_BASE = 'https://router.project-osrm.org/route/v1'
|
||||
|
||||
const buildOsrmRouteResponse = (distance = 5000, duration = 360) => ({
|
||||
code: 'Ok',
|
||||
routes: [
|
||||
{
|
||||
geometry: { coordinates: [[2.3522, 48.8566], [2.3600, 48.8600]] },
|
||||
distance,
|
||||
duration,
|
||||
legs: [{ distance, duration }],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const wp1 = { lat: 48.8566, lng: 2.3522 }
|
||||
const wp2 = { lat: 48.8600, lng: 2.3600 }
|
||||
|
||||
// ── calculateRoute ─────────────────────────────────────────────────────────────
|
||||
|
||||
describe('calculateRoute', () => {
|
||||
it('FE-COMP-ROUTECALCULATOR-001: throws when fewer than 2 waypoints', async () => {
|
||||
await expect(calculateRoute([wp1])).rejects.toThrow('At least 2 waypoints required')
|
||||
})
|
||||
|
||||
it('FE-COMP-ROUTECALCULATOR-002: returns parsed coordinates on success', async () => {
|
||||
server.use(
|
||||
http.get(`${OSRM_BASE}/driving/:coords`, () =>
|
||||
HttpResponse.json(buildOsrmRouteResponse())
|
||||
)
|
||||
)
|
||||
const result = await calculateRoute([wp1, wp2])
|
||||
expect(result.coordinates).toEqual([[48.8566, 2.3522], [48.8600, 2.3600]])
|
||||
})
|
||||
|
||||
it('FE-COMP-ROUTECALCULATOR-003: returns formatted distance text for >= 1000 m', async () => {
|
||||
server.use(
|
||||
http.get(`${OSRM_BASE}/driving/:coords`, () =>
|
||||
HttpResponse.json(buildOsrmRouteResponse(1500, 360))
|
||||
)
|
||||
)
|
||||
const result = await calculateRoute([wp1, wp2])
|
||||
expect(result.distanceText).toBe('1.5 km')
|
||||
})
|
||||
|
||||
it('FE-COMP-ROUTECALCULATOR-004: returns formatted distance in meters for short routes', async () => {
|
||||
server.use(
|
||||
http.get(`${OSRM_BASE}/driving/:coords`, () =>
|
||||
HttpResponse.json(buildOsrmRouteResponse(800, 360))
|
||||
)
|
||||
)
|
||||
const result = await calculateRoute([wp1, wp2])
|
||||
expect(result.distanceText).toBe('800 m')
|
||||
})
|
||||
|
||||
it('FE-COMP-ROUTECALCULATOR-005: walking profile overrides duration with distance-based calculation', async () => {
|
||||
const distance = 5000
|
||||
const osrmDuration = 999
|
||||
server.use(
|
||||
http.get(`${OSRM_BASE}/walking/:coords`, () =>
|
||||
HttpResponse.json(buildOsrmRouteResponse(distance, osrmDuration))
|
||||
)
|
||||
)
|
||||
const result = await calculateRoute([wp1, wp2], 'walking')
|
||||
const expectedDuration = distance / (5000 / 3600)
|
||||
expect(result.duration).toBeCloseTo(expectedDuration)
|
||||
expect(result.duration).not.toBe(osrmDuration)
|
||||
})
|
||||
|
||||
it('FE-COMP-ROUTECALCULATOR-006: throws when OSRM returns non-ok HTTP status', async () => {
|
||||
server.use(
|
||||
http.get(`${OSRM_BASE}/driving/:coords`, () =>
|
||||
HttpResponse.json({}, { status: 500 })
|
||||
)
|
||||
)
|
||||
await expect(calculateRoute([wp1, wp2])).rejects.toThrow('Route could not be calculated')
|
||||
})
|
||||
|
||||
it('FE-COMP-ROUTECALCULATOR-007: throws when OSRM code is not Ok', async () => {
|
||||
server.use(
|
||||
http.get(`${OSRM_BASE}/driving/:coords`, () =>
|
||||
HttpResponse.json({ code: 'NoRoute', routes: [] })
|
||||
)
|
||||
)
|
||||
await expect(calculateRoute([wp1, wp2])).rejects.toThrow('No route found')
|
||||
})
|
||||
|
||||
it('FE-COMP-ROUTECALCULATOR-008: respects AbortSignal', async () => {
|
||||
server.use(
|
||||
http.get(`${OSRM_BASE}/driving/:coords`, () =>
|
||||
HttpResponse.json(buildOsrmRouteResponse())
|
||||
)
|
||||
)
|
||||
const controller = new AbortController()
|
||||
controller.abort()
|
||||
await expect(calculateRoute([wp1, wp2], 'driving', { signal: controller.signal })).rejects.toThrow()
|
||||
})
|
||||
})
|
||||
|
||||
// ── calculateSegments ──────────────────────────────────────────────────────────
|
||||
|
||||
describe('calculateSegments', () => {
|
||||
it('FE-COMP-ROUTECALCULATOR-009: returns empty array for fewer than 2 waypoints', async () => {
|
||||
const result = await calculateSegments([wp1])
|
||||
expect(result).toEqual([])
|
||||
})
|
||||
|
||||
it('FE-COMP-ROUTECALCULATOR-010: returns segment midpoints and travel times', async () => {
|
||||
server.use(
|
||||
http.get(`${OSRM_BASE}/driving/:coords`, () =>
|
||||
HttpResponse.json({
|
||||
code: 'Ok',
|
||||
routes: [
|
||||
{
|
||||
legs: [{ distance: 1000, duration: 120 }],
|
||||
},
|
||||
],
|
||||
})
|
||||
)
|
||||
)
|
||||
const result = await calculateSegments([wp1, wp2])
|
||||
expect(result).toHaveLength(1)
|
||||
const seg = result[0]
|
||||
const expectedMid: [number, number] = [
|
||||
(wp1.lat + wp2.lat) / 2,
|
||||
(wp1.lng + wp2.lng) / 2,
|
||||
]
|
||||
expect(seg.mid[0]).toBeCloseTo(expectedMid[0])
|
||||
expect(seg.mid[1]).toBeCloseTo(expectedMid[1])
|
||||
expect(seg.drivingText).toBe('2 min')
|
||||
})
|
||||
})
|
||||
|
||||
// ── optimizeRoute ──────────────────────────────────────────────────────────────
|
||||
|
||||
describe('optimizeRoute', () => {
|
||||
it('FE-COMP-ROUTECALCULATOR-011: returns input unchanged for 2 or fewer places', () => {
|
||||
const places = [wp1, wp2]
|
||||
const result = optimizeRoute(places)
|
||||
expect(result).toHaveLength(2)
|
||||
expect(result).toBe(places)
|
||||
})
|
||||
|
||||
it('FE-COMP-ROUTECALCULATOR-012: nearest-neighbor reorders 3 waypoints correctly', () => {
|
||||
// Note: filter uses `p.lat && p.lng`, so avoid zero values
|
||||
const a = { lat: 1, lng: 1 }
|
||||
const b = { lat: 10, lng: 1 }
|
||||
const c = { lat: 2, lng: 1 }
|
||||
const result = optimizeRoute([a, b, c])
|
||||
// Starting from a(1,1), nearest is c(2,1) (dist=1), then b(10,1) (dist=8)
|
||||
expect(result[0]).toEqual(a)
|
||||
expect(result[1]).toEqual(c)
|
||||
expect(result[2]).toEqual(b)
|
||||
})
|
||||
})
|
||||
|
||||
// ── generateGoogleMapsUrl ──────────────────────────────────────────────────────
|
||||
|
||||
describe('generateGoogleMapsUrl', () => {
|
||||
it('FE-COMP-ROUTECALCULATOR-013: returns null for empty places', () => {
|
||||
expect(generateGoogleMapsUrl([])).toBeNull()
|
||||
})
|
||||
|
||||
it('FE-COMP-ROUTECALCULATOR-014: single place returns search URL', () => {
|
||||
const result = generateGoogleMapsUrl([{ lat: 48.85, lng: 2.35 }])
|
||||
expect(result).toBe('https://www.google.com/maps/search/?api=1&query=48.85,2.35')
|
||||
})
|
||||
|
||||
it('FE-COMP-ROUTECALCULATOR-015: multiple places returns directions URL', () => {
|
||||
const result = generateGoogleMapsUrl([
|
||||
{ lat: 48.85, lng: 2.35 },
|
||||
{ lat: 48.86, lng: 2.36 },
|
||||
])
|
||||
expect(result).toMatch(/^https:\/\/www\.google\.com\/maps\/dir\//)
|
||||
expect(result).toContain('48.85,2.35')
|
||||
expect(result).toContain('48.86,2.36')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user