openvibers/openvibe.work Commits

Structured data names list items as the page shows them (the release browser check failed both lists) (#69)

OP OpenVibers committed 21 days ago · 2a86f88700 · 2 files, +13 −2

Changes

ddiff --git a/server/http/pages.js b/server/http/pages.js
iindex be0a499..9805916 100644
---- a/server/http/pages.js
++++ b/server/http/pages.js
@@@ -143,7 +143,7 @@ ${facets.type.map((t) => link({ type: t.type }, t.type, filters.type === t.type,
description: `Search ${total ? `${n(total)} ` : ''}jobs from open job boards, each with its source and date, plus saved searches, alerts through OpenVibe.Watch and an application tracker.`,
jsonLd: [
...homeJsonLd(config),
-- latest.length ? seo.jsonLd.itemList('Latest remote jobs on OpenVibe.Work', latest.map((row) => ({ name: `${row.title} at ${row.company}`, url: `${base}/jobs/${row.id}` })), { url: `${base}/jobs?remote=true` }) : null,
++ latest.length ? seo.jsonLd.itemList('Latest remote jobs on OpenVibe.Work', latest.map((row) => ({ name: row.title, url: `${base}/jobs/${row.id}` })), { url: `${base}/jobs?remote=true` }) : null,
FAQ.length ? seo.jsonLd.faq(FAQ.map((f) => ({ q: f.question, a: f.answer }))) : null,
],
styles: [showcase.STYLESHEET], localStyles: ['css/home.css'],
@@@ -343,7 +343,7 @@ ${l.posted_at ? html`<dt>Posted</dt><dd>${time(l.posted_at)}</dd>` : ''}
title: q ? `Companies matching “${q}”` : 'Companies hiring',
description: `${n(all.length)} companies with open listings on OpenVibe.Work, most openings first, each with its jobs, where they are and which boards list them.`,
crumbs: [{ label: 'Companies' }], styles: [showcase.STYLESHEET],
-- jsonLd: q ? [] : [seo.jsonLd.itemList('Companies hiring on OpenVibe.Work', shown.slice(0, 50).map((c) => ({ name: `${c.company}: ${c.count} open listing${c.count === 1 ? '' : 's'}`, url: `${base}/companies/${c.slug}` })), { url: `${base}/companies` })],
++ jsonLd: q ? [] : [seo.jsonLd.itemList('Companies hiring on OpenVibe.Work', shown.slice(0, 50).map((c) => ({ name: c.company, url: `${base}/companies/${c.slug}` })), { url: `${base}/companies` })],
body: html`<h1>Companies hiring</h1>
<p class="lede">${n(all.length)} companies have open listings on the boards OpenVibe.Work reads, most openings first.</p>
<form class="ov-form filter-form" method="get" action="/companies" role="search"><label for="cq">Company</label><input id="cq" name="q" type="search" value="${q}" maxlength="80" placeholder="A company's name"><button class="sc-btn" type="submit">Find</button>${q ? html` <a href="/companies">All companies</a>` : ''}</form>
ddiff --git a/test/jobs.test.js b/test/jobs.test.js
iindex eb3cb5b..bf10d64 100644
---- a/test/jobs.test.js
++++ b/test/jobs.test.js
@@@ -231,6 +231,17 @@ const first = async (t, path) => (await t.get(path)).json();
for (const h of ['Browse by field', 'Jobs by city', 'Companies with the most openings', 'Questions people ask']) assert.ok(r.text.includes(h), `the home has "${h}"`);
assert.match(r.text, /<li><a href="\/companies\/[a-z0-9-]+"><b>[^<]+<\/b><span>\d+ open listings?<\/span>/, 'a company card opens its company page, with its count');
assert.match(r.text, /"@type":"ItemList"/, 'the latest remote jobs are an ItemList');
++ // Structured data states only what the page shows: each list item's name is visible text (the release
++ // browser check fails a list whose names are not, 2026-10-23).
++ const visible = (html) => html.replace(/<script[\s\S]*?<\/script>/g, ' ').replace(/<[^>]+>/g, ' ').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&#39;/g, "'").replace(/&quot;/g, '"').replace(/&amp;/g, '&');
++ const itemNames = (html) => [...html.matchAll(/<script type="application\/ld\+json">([\s\S]*?)<\/script>/g)].map((m) => JSON.parse(m[1]))
++ .filter((d) => d['@type'] === 'ItemList').flatMap((d) => d.itemListElement.map((x) => x.name || (x.item && x.item.name)));
++ for (const page of ['/', '/companies']) {
++ const h = (await t.get(page)).text;
++ const names = itemNames(h);
++ assert.ok(names.length, `${page} has a list`);
++ for (const nm of names) assert.ok(visible(h).includes(nm), `${page}: list item "${nm}" is not on the page`);
++ }
});
await check('search is limited per caller, and past it the API says so with Retry-After', async () => {