mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
123 lines
5.2 KiB
TypeScript
123 lines
5.2 KiB
TypeScript
import { test, expect } from "@playwright/test";
|
|
import { API_URL, STATIONS, resultsUrl, sampleDepartDate, staffToken } from "../../fixtures/data";
|
|
import { UI_IDS } from "../../../apps/edr-passenger-api/test/fixtures/seed-ui";
|
|
|
|
/**
|
|
* Portal search empty-state — asserts /search's outboundReason drives a message specific to WHY
|
|
* zero results came back, instead of the old one-size-fits-all "No trains available" copy (see
|
|
* SearchService.classifyEmptySearch + results/page.tsx's emptyReasonCopy). Each test isolates its
|
|
* own route/station/schedule (never mutates the shared seed fixtures) so it can run alongside the
|
|
* rest of the portal suite without disturbing other specs' assumptions about the seeded trip.
|
|
*/
|
|
|
|
function auth() {
|
|
return { Authorization: `Bearer ${staffToken()}` };
|
|
}
|
|
|
|
/** Days-from-now at 06:00Z, matching seed-ui's sampleDepartAt() convention (same-day in Addis TZ). */
|
|
function daysFromNowDate(days: number): string {
|
|
const d = new Date();
|
|
d.setUTCDate(d.getUTCDate() + days);
|
|
d.setUTCHours(6, 0, 0, 0);
|
|
return d.toISOString().slice(0, 10);
|
|
}
|
|
|
|
/** resultsUrl() hardcodes STATIONS.A→C and sampleDepartDate() — these tests need other pairs/dates. */
|
|
function customResultsUrl(originId: string, destinationId: string, date: string, adults = 1) {
|
|
const p = new URLSearchParams({
|
|
origin: originId,
|
|
destination: destinationId,
|
|
date,
|
|
tripType: "ONE_WAY",
|
|
adults: String(adults),
|
|
children: "0",
|
|
nationality: "ETHIOPIAN",
|
|
});
|
|
return `/booking/results?${p.toString()}`;
|
|
}
|
|
|
|
test("empty-state: fully booked shows a fully-booked message, not a generic one", async ({ page }) => {
|
|
// The seeded trip (UI_IDS.schedule) has 48 seats total — asking for far more than that guarantees
|
|
// zero seat classes satisfy the party size, without needing to actually consume real seats.
|
|
await page.goto(resultsUrl({ adults: 500 }), { waitUntil: "domcontentloaded" });
|
|
|
|
await expect(page.getByRole("heading", { name: "Fully booked" })).toBeVisible({ timeout: 20_000 });
|
|
await expect(page.getByText(/fully booked on/i)).toBeVisible();
|
|
});
|
|
|
|
test("empty-state: no schedule that day shows the generic no-trains message", async ({ page }) => {
|
|
// ROUTE_ID/STATIONS A→C is a real, active route — just pick a date far enough out that no
|
|
// schedule was ever created for it.
|
|
const farDate = daysFromNowDate(300);
|
|
await page.goto(customResultsUrl(STATIONS.A, STATIONS.C, farDate), { waitUntil: "domcontentloaded" });
|
|
|
|
await expect(page.getByRole("heading", { name: "No trains available" })).toBeVisible({ timeout: 20_000 });
|
|
await expect(page.getByText(/no trains available on/i)).toBeVisible();
|
|
});
|
|
|
|
test("empty-state: a cancelled departure says so, not \"no trains available\"", async ({ page, request }) => {
|
|
const routeRes = await request.post(`${API_URL}/routes`, {
|
|
headers: auth(),
|
|
data: {
|
|
code: `E2E-EMPTY-CANCELLED-${Date.now()}`,
|
|
name: "E2E Empty Cancelled Route",
|
|
effectiveFrom: "2020-01-01T00:00:00Z",
|
|
stops: [
|
|
{ stationId: STATIONS.A, sequence: 1, distanceKm: 0 },
|
|
{ stationId: STATIONS.C, sequence: 2, distanceKm: 100 },
|
|
],
|
|
},
|
|
});
|
|
expect(routeRes.ok()).toBeTruthy();
|
|
const route = (await routeRes.json())?.data;
|
|
|
|
const templateRes = await request.put(`${API_URL}/routes/${route.id}/coaches`, {
|
|
headers: auth(),
|
|
data: { coaches: [{ coachId: UI_IDS.coach, positionNumber: 1 }] },
|
|
});
|
|
expect(templateRes.ok()).toBeTruthy();
|
|
|
|
const date = daysFromNowDate(60);
|
|
const dep = new Date(`${date}T06:00:00Z`);
|
|
const arr = new Date(dep.getTime() + 4 * 3600_000);
|
|
const scheduleRes = await request.post(`${API_URL}/schedules`, {
|
|
headers: auth(),
|
|
data: { trainId: UI_IDS.train, routeId: route.id, departureAt: dep.toISOString(), arrivalAt: arr.toISOString() },
|
|
});
|
|
expect(scheduleRes.ok()).toBeTruthy();
|
|
const schedule = (await scheduleRes.json())?.data;
|
|
|
|
const cancelRes = await request.patch(`${API_URL}/schedules/${schedule.id}/status`, {
|
|
headers: auth(),
|
|
data: { status: "CANCELLED" },
|
|
});
|
|
expect(cancelRes.ok()).toBeTruthy();
|
|
|
|
await page.goto(customResultsUrl(STATIONS.A, STATIONS.C, date), { waitUntil: "domcontentloaded" });
|
|
|
|
await expect(page.getByText("Departure cancelled")).toBeVisible({ timeout: 20_000 });
|
|
await expect(page.getByText(/was cancelled/i)).toBeVisible();
|
|
});
|
|
|
|
test("empty-state: an unconnected station pair says EDR doesn't run there, with no date picker", async ({
|
|
page,
|
|
request,
|
|
}) => {
|
|
const stationRes = await request.post(`${API_URL}/stations`, {
|
|
headers: auth(),
|
|
data: { code: `E2E-ISO-${Date.now() % 100000}`, name: "E2E Isolated Station", city: "Nowhere" },
|
|
});
|
|
expect(stationRes.ok()).toBeTruthy();
|
|
const isolatedStation = (await stationRes.json())?.data;
|
|
|
|
await page.goto(customResultsUrl(STATIONS.A, isolatedStation.id, sampleDepartDate()), {
|
|
waitUntil: "domcontentloaded",
|
|
});
|
|
|
|
await expect(page.getByText("Route not available")).toBeVisible({ timeout: 20_000 });
|
|
await expect(page.getByText(/doesn't currently run trains between/i)).toBeVisible();
|
|
// NO_ROUTE is not fixable by picking another date — no calendar/"Change Date" affordance.
|
|
await expect(page.getByText(/change date/i)).toHaveCount(0);
|
|
await expect(page.getByRole("button", { name: /modify search/i })).toBeVisible();
|
|
});
|