diff --git a/frontend/scripts/generate-seo-pages.mjs b/frontend/scripts/generate-seo-pages.mjs index a8d30dcd01..e4a17a2b62 100644 --- a/frontend/scripts/generate-seo-pages.mjs +++ b/frontend/scripts/generate-seo-pages.mjs @@ -355,7 +355,7 @@ const renderGuide = (guide) => { ${(guide.intro || []).map((paragraph) => `

${escapeHtml(paragraph)}

`).join('')} ${sections} -

Frequently asked questions

${faq}
+ ${guide.faq.length ? `

Frequently asked questions

${faq}
` : ''}

${escapeHtml(guide.cta.title)}

${escapeHtml(guide.cta.body)}

${escapeHtml(guide.cta.primary.label)}${escapeHtml(guide.cta.secondary.label)}

${relatedLinks}

diff --git a/frontend/scripts/generate-seo-pages.test.mjs b/frontend/scripts/generate-seo-pages.test.mjs index b87efd0dd1..3a5b8b8c69 100644 --- a/frontend/scripts/generate-seo-pages.test.mjs +++ b/frontend/scripts/generate-seo-pages.test.mjs @@ -20,7 +20,7 @@ test('emits a canonical crawlable page for every public route', async () => { const guides = JSON.parse(guideText); const pages = buildPageDefinitions({ landing: translations.landing, compare: translations.compare, useCases, guides }); - assert.equal(pages.length, 100); + assert.equal(pages.length, 101); assert.deepEqual(pages.map((page) => page.path), [ '/', '/compare/', @@ -122,6 +122,7 @@ test('emits a canonical crawlable page for every public route', async () => { '/guides/control-what-coding-agents-can-access/', '/guides/github-agent-hq-alternative/', '/guides/run-claude-code-and-codex-together/', + '/guides/claude-code-team-chat-mentions/', ]); assert.deepEqual(pages[0].schema['@graph'].map((item) => item['@type']), [ 'Organization', @@ -159,7 +160,7 @@ test('emits a canonical crawlable page for every public route', async () => { '/guides/ai-agent-task-management/', '/guides/connect-claude-codex-shared-workspace/', ]); - assert.equal(guidePages.length, 90); + assert.equal(guidePages.length, 91); for (const guide of guidePages) { assert.equal(guide.ogType, 'article'); const article = guide.schema['@graph'].find((item) => item['@type'] === 'Article'); @@ -2190,6 +2191,21 @@ test('the compare page is unlisted: noindex, out of the sitemap, and linked from assert.match(sitemap, /https:\/\/commonly\.me\/use-cases\/team-chat\/<\/loc>/); }); +test('a guide with no FAQ entries renders no FAQ heading', async () => { + const outputDir = await mkdtemp(join(tmpdir(), 'seo-nofaq-')); + try { + const source = await readFile(resolve(frontendDir, 'index.html'), 'utf8'); + await writeFile(join(outputDir, 'index.html'), source.replace('src="/src/index.tsx"', 'src="/assets/index-test.js"')); + await generateSeoPages({ outputDir }); + const empty = await readFile(join(outputDir, 'guides/claude-code-team-chat-mentions/index.html'), 'utf8'); + assert.doesNotMatch(empty, /

Frequently asked questions<\/h2>/); + const withFaq = await readFile(join(outputDir, 'guides/ai-agent-collaboration-platforms/index.html'), 'utf8'); + assert.match(withFaq, /

Frequently asked questions<\/h2>/); + } finally { + await rm(outputDir, { recursive: true, force: true }); + } +}); + // The guide restyle scopes its CSS on the header's use-cases link, so every guide // page and the guides index must carry it, and no other page may. (The restyle's CSS-only // proof, every page's markup hashed against main with