Unit Tests / test (pull_request) Successful in 37s
Crawlers were reading one shared shell, so titles, descriptions, links, and markup were missing or the same on every URL. HeyCatch and Google Analytics load only on the production site and the iOS and Android apps. Co-authored-by: Cursor <cursoragent@cursor.com>
135 lines
7.7 KiB
JavaScript
135 lines
7.7 KiB
JavaScript
const { test } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('fs');
|
|
const os = require('os');
|
|
const path = require('path');
|
|
const { PAGE_PATHS, h1ForPath, titleForPath, descriptionForPath } = require('../src/llm-fe/utils/canonicals');
|
|
const { htmlForPath, stampCanonicals } = require('./stampCanonicals');
|
|
|
|
const HOME = `<!DOCTYPE html><html><head>
|
|
<link rel="canonical" href="https://hesychia.ai/" />
|
|
<meta property="og:title" content="Hesychia — quiet, focused AI chat" />
|
|
<meta property="og:description" content="Hesychia is a $10/month AI chat product from AI ML Operations, LLC. It stores your documents so answers can use your files." />
|
|
<meta property="og:url" content="https://hesychia.ai/" />
|
|
<meta property="og:image" content="https://hesychia.ai/og-image.png" />
|
|
<meta name="twitter:card" content="summary_large_image" />
|
|
<meta name="twitter:title" content="Hesychia — quiet, focused AI chat" />
|
|
<meta name="twitter:description" content="Hesychia is a $10/month AI chat product from AI ML Operations, LLC. It stores your documents so answers can use your files." />
|
|
<meta name="twitter:image" content="https://hesychia.ai/og-image.png" />
|
|
<meta name="description" content="Hesychia is a $10/month AI chat product from AI ML Operations, LLC. It stores your documents so answers can use your files." />
|
|
<title>Hesychia — quiet, focused AI chat</title>
|
|
</head><body><div id="root"><h1>Hesychia is a calm AI assistant.</h1></div></body></html>`;
|
|
|
|
test('each page canonical is that page, and other head tags stay', () => {
|
|
const html = htmlForPath(HOME, '/pricing/');
|
|
assert.match(html, /<link rel="canonical" href="https:\/\/hesychia\.ai\/pricing\/" \/>/);
|
|
assert.equal(html.match(/rel="canonical"/g).length, 1);
|
|
assert.match(html, /property="og:url" content="https:\/\/hesychia\.ai\/pricing\/"/);
|
|
assert.match(html, /property="og:title" content="Hesychia pricing — \$10\/month, no ads"/);
|
|
assert.match(html, /property="og:description" content="\$10\/month, flat\. No ads, no upsells, one price\./);
|
|
assert.match(html, /property="og:image" content="https:\/\/hesychia\.ai\/og-image\.png"/);
|
|
assert.match(html, /name="twitter:card" content="summary_large_image"/);
|
|
assert.match(html, /name="twitter:title" content="Hesychia pricing — \$10\/month, no ads"/);
|
|
const nav = html.match(/<nav aria-label="Related pages">([\s\S]*?)<\/nav>/);
|
|
assert.ok(nav);
|
|
const anchors = nav[1].match(/<a href=/g);
|
|
assert.ok(anchors && anchors.length >= 2 && anchors.length <= 5);
|
|
assert.match(nav[1], /href="\/">What Hesychia is</);
|
|
assert.doesNotMatch(nav[1], /href="\/pricing\/"/);
|
|
assert.doesNotMatch(nav[1], />here</);
|
|
assert.match(html, /id="crawler-prose"/);
|
|
assert.equal(html.match(/application\/ld\+json/g).length, 1);
|
|
const pricingLd = JSON.parse(html.match(/<script type="application\/ld\+json">([\s\S]*?)<\/script>/)[1]);
|
|
const pricingPage = pricingLd['@graph'].find((node) => node['@type'] === 'WebPage');
|
|
assert.equal(pricingPage.headline, 'Hesychia pricing — $10/month, no ads');
|
|
assert.equal(pricingPage.url, 'https://hesychia.ai/pricing/');
|
|
assert.match(html, /\$10\/month, flat, with no ads, no upsells, and one price/);
|
|
assert.doesNotMatch(
|
|
html.match(/<div id="crawler-prose">([\s\S]*?)<\/div>/)[1],
|
|
/Quiet, focused AI chat/,
|
|
);
|
|
assert.match(html, /<title>Hesychia pricing — \$10\/month, no ads<\/title>/);
|
|
assert.equal(html.match(/<title\b/g).length, 1);
|
|
assert.match(html, /name="description" content="\$10\/month, flat\. No ads, no upsells, one price\./);
|
|
assert.equal(html.match(/name="description"/g).length, 1);
|
|
assert.equal(html.match(/<h1\b/g).length, 1);
|
|
assert.match(html, /<h1>Pricing<\/h1>/);
|
|
});
|
|
|
|
test('stamp writes a shell per route and leaves the homepage self-referencing', () => {
|
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'canon-'));
|
|
fs.writeFileSync(path.join(dir, 'index.html'), HOME);
|
|
stampCanonicals(dir);
|
|
|
|
const home = fs.readFileSync(path.join(dir, 'index.html'), 'utf8');
|
|
assert.match(home, /href="https:\/\/hesychia\.ai\/"/);
|
|
const homeLd = JSON.parse(home.match(/<script type="application\/ld\+json">([\s\S]*?)<\/script>/)[1]);
|
|
const homeOrg = homeLd['@graph'].find((node) => node['@type'] === 'Organization' && node.name === 'Hesychia');
|
|
assert.equal(homeOrg.url, 'https://hesychia.ai');
|
|
assert.equal(homeOrg.logo, 'https://hesychia.ai/apple-touch-icon.png');
|
|
assert.doesNotMatch(home, /href="https:\/\/hesychia\.ai\/pricing\/"/);
|
|
assert.equal(home.match(/<h1\b/g).length, 1);
|
|
assert.match(home, /<h1>Hesychia is a calm AI assistant.<\/h1>/);
|
|
|
|
const titles = PAGE_PATHS.map((pathname) => titleForPath(pathname));
|
|
assert.equal(new Set(titles).size, titles.length);
|
|
const descriptions = PAGE_PATHS.map((pathname) => descriptionForPath(pathname));
|
|
assert.equal(new Set(descriptions).size, descriptions.length);
|
|
|
|
for (const pathname of PAGE_PATHS) {
|
|
if (pathname === '/') continue;
|
|
const rel = pathname.endsWith('/')
|
|
? path.join(pathname.slice(1), 'index.html')
|
|
: pathname.slice(1);
|
|
const html = fs.readFileSync(path.join(dir, rel), 'utf8');
|
|
const title = titleForPath(pathname);
|
|
assert.match(html, new RegExp(`href="https://hesychia\\.ai${pathname.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}"`));
|
|
assert.match(html, /name="twitter:card" content="summary_large_image"/);
|
|
const related = html.match(/<nav aria-label="Related pages">([\s\S]*?)<\/nav>/);
|
|
assert.ok(related, `${pathname} missing related nav`);
|
|
const relatedAnchors = related[1].match(/<a href=/g);
|
|
assert.ok(
|
|
relatedAnchors && relatedAnchors.length >= 2 && relatedAnchors.length <= 5,
|
|
`${pathname} related link count`,
|
|
);
|
|
assert.doesNotMatch(related[1], new RegExp(`href="${pathname.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}"`));
|
|
assert.match(html, /property="og:image" content="https:\/\/hesychia\.ai\/og-image\.png"/);
|
|
assert.match(
|
|
html,
|
|
new RegExp(`property="og:title" content="${title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}"`),
|
|
);
|
|
assert.equal(html.match(/rel="canonical"/g).length, 1);
|
|
assert.equal(html.match(/<h1\b/g).length, 1);
|
|
assert.match(html, new RegExp(`<h1>${h1ForPath(pathname).replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}</h1>`));
|
|
assert.ok(title.length >= 30 && title.length <= 60, `${pathname} title length ${title.length}`);
|
|
assert.match(html, new RegExp(`<title>${title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}</title>`));
|
|
assert.equal(html.match(/application\/ld\+json/g).length, 1);
|
|
const ld = JSON.parse(html.match(/<script type="application\/ld\+json">([\s\S]*?)<\/script>/)[1]);
|
|
const webPage = ld['@graph'].find((node) => node['@type'] === 'WebPage');
|
|
assert.equal(webPage.headline, title, `${pathname} JSON-LD headline`);
|
|
const org = ld['@graph'].find((node) => node['@type'] === 'Organization' && node.name === 'Hesychia');
|
|
assert.ok(org, `${pathname} missing Organization`);
|
|
assert.equal(org.url, 'https://hesychia.ai');
|
|
assert.equal(org.logo, 'https://hesychia.ai/apple-touch-icon.png');
|
|
if (pathname === '/changelog/') {
|
|
const posts = ld['@graph'].filter((node) => node['@type'] === 'BlogPosting');
|
|
assert.ok(posts.length >= 2);
|
|
for (const post of posts) {
|
|
assert.ok(post.headline);
|
|
assert.equal(post.author.name, 'AI ML Operations, LLC');
|
|
assert.match(post.datePublished, /^\d{4}-\d{2}-\d{2}$/);
|
|
assert.equal(post.dateModified, post.datePublished);
|
|
}
|
|
}
|
|
const description = descriptionForPath(pathname);
|
|
assert.ok(
|
|
description.length >= 110 && description.length <= 160,
|
|
`${pathname} description length ${description.length}`,
|
|
);
|
|
assert.match(
|
|
html,
|
|
new RegExp(`content="${description.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}"`),
|
|
);
|
|
}
|
|
});
|