mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
Adding exact reason why train is not available to users search results
This commit is contained in:
122
e2e-ui/specs/portal/search-empty-reasons.spec.ts
Normal file
122
e2e-ui/specs/portal/search-empty-reasons.spec.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
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();
|
||||
});
|
||||
Reference in New Issue
Block a user