openvibers/openvibe.host Commits

Host pages carry one JSON-LD script, every node in one @graph (#190)

OP OpenVibers committed 15 days ago · 7f244da4c8 · 2 files, +18 −2

Changes

ddiff --git a/server/render/layout.js b/server/render/layout.js
iindex 9ff2d8f..48d55ba 100644
---- a/server/render/layout.js
++++ b/server/render/layout.js
@@@ -91,6 +91,14 @@ function renderPage(o) {
// footer.js with the navbar init, the noscript nav and the SSR footer; the rest of the head and the body
// are the catalog's own pages.
const canonical = canonicalOf(o);
++ // One JSON-LD script per page, every node in one @graph (the estate's convention): a page that hands over a
++ // list of nodes gets the graph wrapper here, and the nodes keep their own fields with the graph carrying the
++ // one @context. A page that already passes a single object is left as it is.
++ const jsonLd = (() => {
++ if (!o.indexable || !Array.isArray(o.jsonLd)) return undefined;
++ const nodes = o.jsonLd.filter(Boolean).map((n) => { const c = { ...n }; delete c['@context']; return c; });
++ return [{ '@context': 'https://schema.org', '@graph': nodes }];
++ })();
return shell.page({
// A page with a Markdown twin (o.agent) advertises it in the head; the twin itself is server/agent-pages.js.
agentLayer: !!o.agent,
@@@ -102,7 +110,7 @@ function renderPage(o) {
description: o.description || DESCRIPTION,
canonical,
robots: o.indexable ? 'index, follow' : 'noindex, nofollow',
-- jsonLd: o.indexable && Array.isArray(o.jsonLd) ? o.jsonLd : undefined,
++ jsonLd,
navbar: nav, footer, home: '/', navLinks: NAV_LINKS(signedIn),
head: [
appIcon.headTags({ site: 'host' }),
ddiff --git a/test/catalog-public.test.js b/test/catalog-public.test.js
iindex 5f523b1..6acd28c 100644
---- a/test/catalog-public.test.js
++++ b/test/catalog-public.test.js
@@@ -51,12 +51,20 @@ const { boot, check, done } = require('./helpers/boot');
const bad = await t.api('GET', '/catalog?sort=affiliate'); assert.strictEqual(bad.status, 422);
// JSON-LD on the indexable catalog pages: WebSite and an ItemList on the front, an ItemList on /catalog,
// a Product with a monthly Offer on a plan; never on a filtered (noindex) view.
-- const ld = (html) => [...html.matchAll(/<script type="application\/ld\+json">([\s\S]*?)<\/script>/g)].map((m) => JSON.parse(m[1])).flat();
++ // One JSON-LD script per page, every node in one @graph (the estate's convention): the helper flattens
++ // the graph, and the script count is asserted for each page below.
++ const ld = (html) => [...html.matchAll(/<script type="application\/ld\+json">([\s\S]*?)<\/script>/g)]
++ .map((m) => JSON.parse(m[1]))
++ .flatMap((doc) => (Array.isArray(doc['@graph']) ? doc['@graph'] : [doc]));
++ const scripts = (html) => [...html.matchAll(/<script type="application\/ld\+json">([\s\S]*?)<\/script>/g)].length;
const types = (html) => ld(html).map((x) => x['@type']);
++ assert.strictEqual(scripts(front.text), 1, 'the front page carries exactly one JSON-LD script');
++ assert.strictEqual(scripts(plans.text), 1, 'the catalog carries exactly one JSON-LD script');
assert.ok(types(front.text).includes('WebSite') && types(front.text).includes('ItemList') && types(front.text).includes('FAQPage'), JSON.stringify(types(front.text)));
assert.ok(types(plans.text).includes('ItemList'));
const planPage = await t.api('GET', `/catalog/plans/${cheap.id}`);
const product = ld(planPage.text).find((x) => x['@type'] === 'Product');
++ assert.strictEqual(scripts(planPage.text), 1, 'a plan page carries exactly one JSON-LD script');
assert.match(planPage.text, /class="hc-visit"/, 'a plan page offers a way to the provider');
assert.match(planPage.text, /href="https:\/\/openvibe\.watch\/watches\/new\?template=json&amp;url=[^"]*%2Fapi%2Fv1%2Fcatalog%2Fplans%2F[^"]*value_path=plan\.price_monthly&amp;op=changed/, 'a plan page offers a prefilled price watch');
assert.ok(product && product.offers.price === 4 && product.offers.priceCurrency === 'USD' && product.offers.priceSpecification.unitText === 'MONTH');