73d724f76a
TDD plan for porting Angular OnlineBoardUrlBuilder/Parser to pure TypeScript functions covering all 6 URL types (start, flight, departure, arrival, route, details) with roundtrip and edge case tests.
6.9 KiB
6.9 KiB
Phase 2B — URL Serializer/Parser
Parent:
2026-04-14-phase-2-online-board-master.md(sub-plan 2B) Status: Ready to execute
Goal
TDD port of the Angular OnlineBoardUrlBuilderService / OnlineBoardUrlParserService into pure TypeScript functions with zero Angular dependencies. Byte-exact URL parity with Angular.
Deliverable
src/features/online-board/url.ts — pure functions, no side effects, no Date objects.
URL Format Reference (from Angular analysis)
| Type | Pattern | Example |
|---|---|---|
| Start | /onlineboard or /onlineboard/ |
/onlineboard |
| Flight | /onlineboard/flight/{carrier}{flightNumber}{suffix}-{yyyyMMdd} |
/onlineboard/flight/SU0100D-20250115 |
| Departure | /onlineboard/departure/{station}-{yyyyMMdd}[-{HHmmHHmm}] |
/onlineboard/departure/SVO-20250115-08001800 |
| Arrival | /onlineboard/arrival/{station}-{yyyyMMdd}[-{HHmmHHmm}] |
/onlineboard/arrival/LED-20250115 |
| Route | /onlineboard/route/{dep}-{arr}-{yyyyMMdd}[-{HHmmHHmm}] |
/onlineboard/route/SVO-LED-20250115 |
| Details | /onlineboard/{carrier}{flightNumber}{suffix}-{yyyyMMdd} |
/onlineboard/SU0100-20250115 |
Key rules
- Date format:
yyyyMMdd(8 digits, no separators) - Time range:
{HHmm}{HHmm}(8 digits continuous, from-to) - Flight number: zero-padded to 4 digits in the URL (e.g.,
100becomes0100), total flightNumber+suffix = last 5 chars - Carrier code: 2 characters (IATA), or 3 characters if 3rd char is a letter (rare 3-char carriers)
- Suffix: optional single trailing letter on a flight identifier (e.g.,
DinSU0100D) - Station codes: 3-letter IATA airport codes
Exported API
type OnlineBoardParams =
| { type: "start" }
| { type: "flight"; carrier: string; flightNumber: string; suffix?: string; date: string }
| { type: "departure"; station: string; date: string; timeFrom?: string; timeTo?: string }
| { type: "arrival"; station: string; date: string; timeFrom?: string; timeTo?: string }
| { type: "route"; departure: string; arrival: string; date: string; timeFrom?: string; timeTo?: string }
| { type: "details"; carrier: string; flightNumber: string; suffix?: string; date: string };
function parseOnlineBoardUrl(path: string): OnlineBoardParams | null;
function buildOnlineBoardUrl(params: OnlineBoardParams): string;
// Lower-level helpers (also exported for 2C/2E consumption)
function parseFlightUrlParams(raw: string): IParsedFlightId | null;
function buildFlightUrlParams(id: IParsedFlightId): string;
function parseStationUrlParams(raw: string): { station: string; date: string; timeFrom?: string; timeTo?: string } | null;
function parseRouteUrlParams(raw: string): { departure: string; arrival: string; date: string; timeFrom?: string; timeTo?: string } | null;
Tasks
Task 1: Write failing tests for parseFlightUrlParams
File: src/features/online-board/url.test.ts
Test cases:
SU0100-20250115->{ carrier: "SU", flightNumber: "0100", date: "20250115" }SU0100D-20250115->{ carrier: "SU", flightNumber: "0100", suffix: "D", date: "20250115" }SU100-20250115->{ carrier: "SU", flightNumber: "100", date: "20250115" }(3-digit)SU1234-20250115->{ carrier: "SU", flightNumber: "1234", date: "20250115" }(4-digit)- Empty string ->
null - Missing date part ->
null
Task 2: Implement parseFlightUrlParams to pass tests
Task 3: Write failing tests for buildFlightUrlParams
Test cases:
{ carrier: "SU", flightNumber: "100", date: "20250115" }->SU0100-20250115(padded){ carrier: "SU", flightNumber: "0100", suffix: "D", date: "20250115" }->SU0100D-20250115{ carrier: "SU", flightNumber: "1234", date: "20250115" }->SU1234-20250115
Task 4: Implement buildFlightUrlParams to pass tests
Task 5: Write failing tests for parseStationUrlParams
Test cases:
SVO-20250115->{ station: "SVO", date: "20250115" }SVO-20250115-08001800->{ station: "SVO", date: "20250115", timeFrom: "0800", timeTo: "1800" }- Empty string ->
null
Task 6: Implement parseStationUrlParams to pass tests
Task 7: Write failing tests for parseRouteUrlParams
Test cases:
SVO-LED-20250115->{ departure: "SVO", arrival: "LED", date: "20250115" }SVO-LED-20250115-08001800->{ departure: "SVO", arrival: "LED", date: "20250115", timeFrom: "0800", timeTo: "1800" }- Empty string ->
null
Task 8: Implement parseRouteUrlParams to pass tests
Task 9: Write failing tests for parseOnlineBoardUrl
Test cases:
/onlineboard->{ type: "start" }/onlineboard/->{ type: "start" }/onlineboard/flight/SU0100-20250115->{ type: "flight", carrier: "SU", flightNumber: "0100", date: "20250115" }/onlineboard/departure/SVO-20250115->{ type: "departure", station: "SVO", date: "20250115" }/onlineboard/arrival/LED-20250115-08001800->{ type: "arrival", station: "LED", date: "20250115", timeFrom: "0800", timeTo: "1800" }/onlineboard/route/SVO-LED-20250115->{ type: "route", departure: "SVO", arrival: "LED", date: "20250115" }/onlineboard/SU0100-20250115->{ type: "details", carrier: "SU", flightNumber: "0100", date: "20250115" }/some/other/path->null- Empty string ->
null
Task 10: Implement parseOnlineBoardUrl to pass tests
Task 11: Write failing tests for buildOnlineBoardUrl
Test cases:
{ type: "start" }->onlineboard{ type: "flight", carrier: "SU", flightNumber: "100", date: "20250115" }->onlineboard/flight/SU0100-20250115{ type: "departure", station: "SVO", date: "20250115" }->onlineboard/departure/SVO-20250115{ type: "departure", station: "SVO", date: "20250115", timeFrom: "0800", timeTo: "1800" }->onlineboard/departure/SVO-20250115-08001800{ type: "arrival", station: "LED", date: "20250115" }->onlineboard/arrival/LED-20250115{ type: "route", departure: "SVO", arrival: "LED", date: "20250115" }->onlineboard/route/SVO-LED-20250115{ type: "details", carrier: "SU", flightNumber: "0100", date: "20250115" }->onlineboard/SU0100-20250115
Task 12: Implement buildOnlineBoardUrl to pass tests
Task 13: Roundtrip tests
For every URL type: buildOnlineBoardUrl(parseOnlineBoardUrl(url)) === url (after normalization).
Task 14: Edge case tests
- Suffixed flights roundtrip:
SU0100D-20250115 - 3-digit flight numbers: build pads to 4, parse handles both
- Time range only partially specified (only timeFrom, no timeTo) -> no time range in URL
- Invalid date format -> null
- Unknown route prefix -> null
Task 15: Export from barrel
Update src/features/online-board/index.ts to re-export the public API.
Task 16: Verification
Run pnpm typecheck && pnpm lint && pnpm test.
Exit criteria
- All tests pass
parseOnlineBoardUrlreturns null for invalid inputs, never throws- Zero
anytypes - Pure functions only — no side effects, no imports from Angular
- Dates are strings (
yyyyMMdd), not Date objects