Files
flights_web/src/features/online-board/seo.test.ts
T
gnezim e20686b11f Wire SEO and JSON-LD into all 6 Online Board route pages
Each route page now renders <SeoHead> with title, description,
canonical, hreflang, OG, and Twitter card from the SEO builders.
Search pages render <JsonLdRenderer> with ItemList of Flight
schemas. Details page renders Flight JSON-LD. Barrel exports
updated with 2F SEO and JSON-LD functions.
2026-04-15 08:37:47 +03:00

294 lines
8.4 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
buildOnlineBoardStartSeo,
buildFlightSearchSeo,
buildDepartureSearchSeo,
buildArrivalSearchSeo,
buildRouteSearchSeo,
buildFlightDetailsSeo,
} from "./seo.js";
import type { ISimpleFlight } from "./types.js";
/** Stub t() that returns the key + interpolation vars for assertion. */
function stubT(key: string, opts?: Record<string, unknown>): string {
if (opts && Object.keys(opts).length > 0) {
const vars = Object.entries(opts)
.map(([k, v]) => `${k}=${v}`)
.join(",");
return `${key}|${vars}`;
}
return key;
}
const CANONICAL = "https://www.aeroflot.ru";
describe("buildOnlineBoardStartSeo", () => {
it("uses MAIN translation keys for title and description", () => {
const result = buildOnlineBoardStartSeo(stubT, "ru", CANONICAL);
expect(result.title).toBe("SEO.BOARD.MAIN.TITLE");
expect(result.description).toBe("SEO.BOARD.MAIN.DESCRIPTION");
});
it("sets canonical to /{locale}/onlineboard", () => {
const result = buildOnlineBoardStartSeo(stubT, "en", CANONICAL);
expect(result.canonical).toBe("https://www.aeroflot.ru/en/onlineboard");
});
it("includes hreflang with 10 entries (9 langs + x-default)", () => {
const result = buildOnlineBoardStartSeo(stubT, "ru", CANONICAL);
expect(result.hreflang).toHaveLength(10);
});
it("sets og tags", () => {
const result = buildOnlineBoardStartSeo(stubT, "ru", CANONICAL);
expect(result.og.title).toBe(result.title);
expect(result.og.description).toBe(result.description);
expect(result.og.url).toBe(result.canonical);
expect(result.og.type).toBe("website");
expect(result.og.locale).toBe("ru");
expect(result.og.siteName).toBe("Aeroflot");
expect(result.og.image).toContain("aeroflot");
});
it("sets twitter card", () => {
const result = buildOnlineBoardStartSeo(stubT, "ru", CANONICAL);
expect(result.twitter).toBeDefined();
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- test assertion guards above
expect(result.twitter!.card).toBe("summary");
});
});
describe("buildFlightSearchSeo", () => {
const params = {
type: "flight" as const,
carrier: "SU",
flightNumber: "0100",
date: "20250115",
};
it("uses FLIGHT-SEARCH translation keys with interpolation", () => {
const result = buildFlightSearchSeo(stubT, params, "ru", CANONICAL);
expect(result.title).toContain("SEO.BOARD.FLIGHT-SEARCH.TITLE");
expect(result.title).toContain("flightNumber=SU 0100");
expect(result.description).toContain("SEO.BOARD.FLIGHT-SEARCH.DESCRIPTION");
});
it("sets canonical to the flight search URL", () => {
const result = buildFlightSearchSeo(stubT, params, "ru", CANONICAL);
expect(result.canonical).toBe(
"https://www.aeroflot.ru/ru/onlineboard/flight/SU0100-20250115",
);
});
it("includes hreflang entries", () => {
const result = buildFlightSearchSeo(stubT, params, "ru", CANONICAL);
expect(result.hreflang).toHaveLength(10);
});
});
describe("buildDepartureSearchSeo", () => {
const params = {
type: "departure" as const,
station: "SVO",
date: "20250115",
};
it("uses DEPARTURE-SEARCH translation keys with city name", () => {
const result = buildDepartureSearchSeo(
stubT,
params,
"ru",
CANONICAL,
{ departure: "Moscow" },
);
expect(result.title).toContain("SEO.BOARD.DEPARTURE-SEARCH.TITLE");
expect(result.title).toContain("departureCity=Moscow");
});
it("falls back to station code when no city name provided", () => {
const result = buildDepartureSearchSeo(stubT, params, "ru", CANONICAL);
expect(result.title).toContain("departureCity=SVO");
});
it("sets canonical to the departure search URL", () => {
const result = buildDepartureSearchSeo(stubT, params, "en", CANONICAL);
expect(result.canonical).toBe(
"https://www.aeroflot.ru/en/onlineboard/departure/SVO-20250115",
);
});
});
describe("buildArrivalSearchSeo", () => {
const params = {
type: "arrival" as const,
station: "JFK",
date: "20250115",
};
it("uses ARRIVAL-SEARCH translation keys with city name", () => {
const result = buildArrivalSearchSeo(
stubT,
params,
"ru",
CANONICAL,
{ arrival: "New York" },
);
expect(result.title).toContain("SEO.BOARD.ARRIVAL-SEARCH.TITLE");
expect(result.title).toContain("arrivalCity=New York");
});
it("falls back to station code when no city name provided", () => {
const result = buildArrivalSearchSeo(stubT, params, "ru", CANONICAL);
expect(result.title).toContain("arrivalCity=JFK");
});
it("sets canonical to the arrival search URL", () => {
const result = buildArrivalSearchSeo(stubT, params, "ru", CANONICAL);
expect(result.canonical).toBe(
"https://www.aeroflot.ru/ru/onlineboard/arrival/JFK-20250115",
);
});
});
describe("buildRouteSearchSeo", () => {
const params = {
type: "route" as const,
departure: "SVO",
arrival: "JFK",
date: "20250115",
};
it("uses ROUTE-SEARCH translation keys with both city names", () => {
const result = buildRouteSearchSeo(
stubT,
params,
"ru",
CANONICAL,
{ departure: "Moscow", arrival: "New York" },
);
expect(result.title).toContain("SEO.BOARD.ROUTE-SEARCH.TITLE");
expect(result.title).toContain("departureCity=Moscow");
expect(result.title).toContain("arrivalCity=New York");
});
it("falls back to IATA codes when no city names provided", () => {
const result = buildRouteSearchSeo(stubT, params, "ru", CANONICAL);
expect(result.title).toContain("departureCity=SVO");
expect(result.title).toContain("arrivalCity=JFK");
});
it("sets canonical to the route search URL", () => {
const result = buildRouteSearchSeo(stubT, params, "en", CANONICAL);
expect(result.canonical).toBe(
"https://www.aeroflot.ru/en/onlineboard/route/SVO-JFK-20250115",
);
});
});
describe("buildFlightDetailsSeo", () => {
const flight: ISimpleFlight = {
id: "SU100-20250115",
routeType: "Direct",
flightId: {
carrier: "SU",
flightNumber: "0100",
suffix: "",
date: "20250115",
},
flyingTime: "10h 30m",
operatingBy: {},
status: "Scheduled",
leg: {
index: 0,
status: "Scheduled",
flyingTime: "10h 30m",
updated: "2025-01-15T10:00:00Z",
dayChange: 0,
equipment: { name: "Boeing 777-300ER", code: "773" },
flags: { checkinAvailable: false, returnToAirport: false, routeChanged: false },
operatingBy: {},
departure: {
scheduled: {
airport: "Sheremetyevo International Airport",
airportCode: "SVO",
city: "Moscow",
cityCode: "MOW",
countryCode: "RU",
},
checkingStatus: "closed",
times: {
scheduledDeparture: {
dayChange: { value: 0, title: "" },
local: "2025-01-15T10:00:00",
localTime: "10:00",
tzOffset: 3,
utc: "2025-01-15T07:00:00Z",
},
},
},
arrival: {
scheduled: {
airport: "John F. Kennedy International Airport",
airportCode: "JFK",
city: "New York",
cityCode: "NYC",
countryCode: "US",
},
times: {
scheduledArrival: {
dayChange: { value: 0, title: "" },
local: "2025-01-15T14:30:00",
localTime: "14:30",
tzOffset: -5,
utc: "2025-01-15T19:30:00Z",
},
},
},
},
};
it("uses FLIGHT-DETAILS translation keys", () => {
const result = buildFlightDetailsSeo(stubT, flight, "ru", CANONICAL);
expect(result.title).toContain("SEO.BOARD.FLIGHT-DETAILS.TITLE");
expect(result.title).toContain("flightNumber=SU 0100");
});
it("sets canonical to the details URL", () => {
const result = buildFlightDetailsSeo(stubT, flight, "ru", CANONICAL);
expect(result.canonical).toBe(
"https://www.aeroflot.ru/ru/onlineboard/SU0100-20250115",
);
});
it("includes hreflang entries", () => {
const result = buildFlightDetailsSeo(stubT, flight, "ru", CANONICAL);
expect(result.hreflang).toHaveLength(10);
});
it("sets og.type to article for details pages", () => {
const result = buildFlightDetailsSeo(stubT, flight, "ru", CANONICAL);
expect(result.og.type).toBe("article");
});
});