mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-20 14:06:27 +00:00
6e3eb591b5
Paddle tours are planned on the water by TREK's own router: a water network per paddle region, built from the same Geofabrik extract as the obstacles and searched in a worker thread of the server (no sidecar). Rivers, canals and lakes (direct or along the shore), portages round weirs, locks by policy, whitewater by boat, gaps marked. Found portages and locks become points of their own; an unplannable leg says why and offers a way out. - Shaping points: a click on the line sets a point that steers the route without being a stop (hikes and paddle tours); recompute a single leg. - Lake style direct/shore, suggested from the wind forecast. - Regions load by themselves where a paddler plans (admin setting: automatic, on request, off; size cap and a daily count), with requests for the admins, a notification, and the load followed on the leg. - Portage from the last point, portages follow footways via the walking router (or stay straight), "put in here" on slipways and put-ins. - MCP plan_paddle_route with the REST route's rules.
87 lines
3.7 KiB
TypeScript
87 lines
3.7 KiB
TypeScript
// FE-PADDLE-REGION-LOAD-001 to 005: the planner fetches the water data of a place no region covers (PAD-11C).
|
|
import { act, renderHook, waitFor } from '@testing-library/react';
|
|
import type { PaddleRegionLoadState } from '@trek/shared';
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
const load = vi.fn();
|
|
vi.mock('../../../repo/paddleRegionLoadRepo', () => ({
|
|
paddleRegionLoadRepo: { load: (...args: unknown[]) => load(...args) },
|
|
}));
|
|
|
|
const { usePaddleRegionLoad } = await import('./usePaddleRegionLoad');
|
|
|
|
const REF = { id: 'oberpfalz', name: 'Oberpfalz' };
|
|
const AT = { lat: 49.0123456, lng: 12.0987654 };
|
|
const failures = [
|
|
{ index: 0, code: 'no_region' as const, at: AT },
|
|
{ index: 1, code: 'no_region' as const, at: AT },
|
|
{ index: 2, code: 'no_water_path' as const, at: null },
|
|
];
|
|
const loading = (fraction: number): PaddleRegionLoadState => ({
|
|
state: 'loading',
|
|
region: REF,
|
|
progress: { phase: 'download', fraction },
|
|
ahead: 0,
|
|
});
|
|
|
|
describe('usePaddleRegionLoad', () => {
|
|
beforeEach(() => load.mockReset());
|
|
afterEach(() => vi.useRealTimers());
|
|
|
|
it('FE-PADDLE-REGION-LOAD-001: asks once per place, and shows its state on every leg ending there', async () => {
|
|
load.mockResolvedValue([loading(0.4)]);
|
|
const { result } = renderHook(() => usePaddleRegionLoad(failures, vi.fn()));
|
|
await waitFor(() => expect(result.current.byLeg.get(0)).toEqual(loading(0.4)));
|
|
expect(load).toHaveBeenCalledWith([{ lat: 49.01235, lng: 12.09877 }], false, expect.any(AbortSignal));
|
|
expect(result.current.byLeg.get(1)).toEqual(loading(0.4));
|
|
expect(result.current.byLeg.has(2)).toBe(false);
|
|
});
|
|
|
|
it('FE-PADDLE-REGION-LOAD-002: follows a load and plans again once, when the data is there', async () => {
|
|
vi.useFakeTimers();
|
|
load.mockResolvedValueOnce([loading(0.2)]).mockResolvedValue([{ state: 'ready', region: REF }]);
|
|
const onReady = vi.fn();
|
|
const { rerender } = renderHook(() => usePaddleRegionLoad(failures, onReady));
|
|
await act(async () => {
|
|
await vi.advanceTimersByTimeAsync(0);
|
|
});
|
|
expect(onReady).not.toHaveBeenCalled();
|
|
await act(async () => {
|
|
await vi.advanceTimersByTimeAsync(2_000);
|
|
});
|
|
expect(load).toHaveBeenCalledTimes(2);
|
|
expect(onReady).toHaveBeenCalledTimes(1);
|
|
rerender();
|
|
await act(async () => {
|
|
await vi.advanceTimersByTimeAsync(5_000);
|
|
});
|
|
expect(onReady).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('FE-PADDLE-REGION-LOAD-003: asks for nothing without a leg that lacks the data', async () => {
|
|
renderHook(() => usePaddleRegionLoad([{ index: 0, code: 'not_on_water', at: AT }], vi.fn()));
|
|
renderHook(() => usePaddleRegionLoad(null, vi.fn()));
|
|
await Promise.resolve();
|
|
expect(load).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('FE-PADDLE-REGION-LOAD-004: reads a failed region again on request', async () => {
|
|
load.mockResolvedValue([{ state: 'failed', region: REF }]);
|
|
const { result } = renderHook(() => usePaddleRegionLoad(failures, vi.fn()));
|
|
await waitFor(() => expect(result.current.byLeg.get(0)?.state).toBe('failed'));
|
|
act(() => result.current.retry());
|
|
await waitFor(() => expect(load).toHaveBeenLastCalledWith(expect.any(Array), true, expect.any(AbortSignal)));
|
|
});
|
|
|
|
it('FE-PADDLE-REGION-LOAD-005: keeps asking after a failed look, more slowly', async () => {
|
|
vi.useFakeTimers();
|
|
load.mockRejectedValueOnce(new Error('offline')).mockResolvedValue([{ state: 'declined', region: REF }]);
|
|
const { result } = renderHook(() => usePaddleRegionLoad(failures, vi.fn()));
|
|
await act(async () => {
|
|
await vi.advanceTimersByTimeAsync(10_000);
|
|
});
|
|
expect(load).toHaveBeenCalledTimes(2);
|
|
expect(result.current.byLeg.get(0)?.state).toBe('declined');
|
|
});
|
|
});
|