openvibers/openvibe.tips Commits
Render every Tips page through openvibe-shared/shell v2.6.0 (#5)
Changes
@@@ -16,7 +16,7 @@"jsonwebtoken": "^9.0.2","openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.79.0","openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.26.0",-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.5.0",++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.6.0","pg": "^8.23.0"},"devDependencies": {@@@ -1162,9 +1162,9 @@}},"node_modules/openvibe-shared": {-- "version": "2.5.0",-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.5.0",-- "integrity": "sha512-fHuUS6SXM7ezatfAyqLm1V8B6puV9YXSnz6aG6XQbWx28qghRCCHeUF3lr4nxiCGdn9SyikYtIyLGL5r1ycp9Q==",++ "version": "2.6.0",++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.6.0",++ "integrity": "sha512-IKKvWTM/9UonQ4msrzM4yir0QcTUNbhyqeN1r5ywWavU4VTsL1sI5QdHQlxge7BAsMrQ4OvMEPlHjuAfUJM3rg==","license": "MIT","dependencies": {"jsonwebtoken": "^9.0.0"@@@ -28,7 +28,7 @@"jsonwebtoken": "^9.0.2","openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.79.0","openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.26.0",-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.5.0",++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.6.0","pg": "^8.23.0"},"engines": {@@@ -1,10 +1,10 @@'use strict';/**-- * Page shell for every server-rendered page: full <head> SEO (title, description, canonical,-- * robots, Open Graph), the shared OpenVibe Frame (inline critical canvas + app icon from-- * openvibe-shared, navbar.js from the Network, the SSR footer and a <noscript> navigation), this-- * site's stylesheet and its small progressive script. Everything is useful without JavaScript.++ * Page shell for every server-rendered page, composed by openvibe-shared/shell: full <head> SEO++ * (title, description, canonical, robots, Open Graph, JSON-LD), the shared OpenVibe Frame (navbar.js++ * from the Network, the SSR footer and a <noscript> navigation), plus this site's app icon,++ * stylesheet, boost marker and its small progressive script. Everything is useful without JavaScript.*/const crypto = require('crypto');const ovServe = require('openvibe-shared/serve');@@@ -12,6 +12,7 @@ const fs = require('fs');const path = require('path');const appIcon = require('openvibe-shared/app-icon');const frame = require('openvibe-shared/frame');++const shell = require('openvibe-shared/shell');const SITE_NAME = 'OpenVibe.Tips';const NETWORK_URL = 'https://openvibe.network';@@@ -49,7 +50,6 @@ function createLayout({ config, release }) {* active ('home'|'dashboard'|'receipts'|'creator'), ogImage, jsonLd (array), viewer*/function page(o) {-- const title = o.title ? `${o.title} · ${SITE_NAME}` : `${SITE_NAME} — support the creators you watch`;const description = (o.description || DEFAULT_DESCRIPTION).replace(/\s+/g, ' ').trim().slice(0, 300);const canonical = abs(o.canonicalPath || '/');const robots = o.robots || 'index,follow';@@@ -64,56 +64,42 @@ function createLayout({ config, release }) {notificationsRealtime: true, // the bell hears new notifications over OpenVibe.Events (Shared 1.22.0)};const footer = { service: 'tips', variant: 'full', mount: '#ov-footer', brandName: SITE_NAME, updates: '/updates' };-- const jsonLd = (o.jsonLd || []).map((x) => `<script type="application/ld+json">${JSON.stringify(x).replace(/</g, '\\u003c')}</script>`).join('\n');const who = o.viewer? `<span class="who">Signed in as <b>${esc(o.viewer.name || o.viewer.username || 'you')}</b> · <a href="/auth/logout?next=/">Sign out</a></span>`: `<a class="who" href="/auth/login?next=${encodeURIComponent(o.canonicalPath || '/')}">Sign in</a>`;-- return `<!DOCTYPE html>--<html lang="en" data-page="${esc(o.active || 'page')}">--<head>--<meta charset="utf-8">--<meta name="viewport" content="width=device-width, initial-scale=1">--<title>${esc(title)}</title>--<meta name="description" content="${esc(description)}">--<link rel="canonical" href="${esc(canonical)}">--<meta name="robots" content="${esc(robots)}">--<meta name="referrer" content="strict-origin-when-cross-origin">--<meta property="og:site_name" content="${SITE_NAME}">--<meta property="og:type" content="${o.ogType || 'website'}">--<meta property="og:title" content="${esc(o.title || SITE_NAME)}">--<meta property="og:description" content="${esc(description)}">--<meta property="og:url" content="${esc(canonical)}">--${o.ogImage ? `<meta property="og:image" content="${esc(o.ogImage)}">` : ''}--<meta name="twitter:card" content="summary">--${appIcon.headTags({ site: 'tips' })}--${release ? release.metaTag() : ''}--<script src="${ovServe.url('theme-loader.js')}" defer></script>--<link rel="stylesheet" href="${asset('css/tips.css')}">--${jsonLd}--<script src="${ovServe.url('navbar.js')}" defer></script>--<script src="${ovServe.url('footer.js')}" defer></script>--<script src="${asset('js/tips.js')}" defer></script>--<meta name="ov-boost" content="tips@${esc(RELEASE)}">--<script src="${ovServe.url('boost.js')}" data-main="#main" defer></script>--</head>--<body>--<div id="navbar-mount"></div>--${frame.noscriptNav({ name: SITE_NAME, home: '/', links: NAV_LINKS })}++ // shell.page writes the document, the SEO head, the theme-loader, navbar.js/footer.js with the++ // navbar init, the noscript nav and the SSR footer; the rest of the head is this site's own.++ return shell.page({++ name: SITE_NAME, service: 'tips', lang: 'en',++ title: o.title || `${SITE_NAME} — support the creators you watch`,++ titleSuffix: o.title ? ` · ${SITE_NAME}` : undefined,++ siteName: SITE_NAME, description, canonical, robots,++ type: o.ogType || 'website', image: o.ogImage, jsonLd: o.jsonLd,++ navbar: nav, footer, home: '/', navLinks: NAV_LINKS,++ head: [++ '<meta name="referrer" content="strict-origin-when-cross-origin">',++ appIcon.headTags({ site: 'tips' }),++ release ? release.metaTag() : '',++ `<link rel="stylesheet" href="${asset('css/tips.css')}">`,++ `<meta name="ov-boost" content="tips@${esc(RELEASE)}">`,++ `<script src="${ovServe.url('boost.js')}" data-main="#main" defer></script>`,++ `<script src="${asset('js/tips.js')}" defer></script>`,++ ].filter(Boolean).join('\n'),++ body: `<div id="navbar-mount"></div><header class="site-head"><a class="brand" href="/">${SITE_NAME}</a><nav>${NAV_LINKS.map((l) => `<a href="${l.href}"${o.active === l.label.toLowerCase() ? ' aria-current="page"' : ''}>${l.label}</a>`).join('')}</nav>${who}</header><main id="main" class="page">${o.body || ''}${o.canonicalPath === '/' && o.active === 'home' ? frame.shipped({ service: 'tips', title: `Recently shipped on ${SITE_NAME}` }) : ''}</main>--${frame.footer(footer)}<script>window.__OV_PAGE = ${JSON.stringify({ navbar: nav, footer }).replace(/</g, '\\u003c')};--document.addEventListener('DOMContentLoaded', function () {-- try { if (window.OpenVibeNavbar) { OpenVibeNavbar.init(window.__OV_PAGE.navbar); document.documentElement.classList.add('ov-has-navbar'); } } catch (e) { /* the SSR header stays */ }-- try { if (window.OpenVibeFooter) OpenVibeFooter.init(window.__OV_PAGE.footer); } catch (e) { /* */ }++window.addEventListener('DOMContentLoaded', function () {++ if (window.OpenVibeNavbar) document.documentElement.classList.add('ov-has-navbar');++ try { if (window.OpenVibeFooter) OpenVibeFooter.init(window.__OV_PAGE.footer); } catch (e) { /* the SSR footer stays */ }});--</script>--</body>--</html>`;++</script>`,++ bodyAttributes: { 'data-page': o.active || 'page' },++ });}return { page, abs };@@@ -56,6 +56,33 @@ const { boot, check, done } = require('./helpers/app');assert.match((await get('/@alex')).headers.get('location') || '', /\/alex$/);});++ await check('the head comes from openvibe-shared/shell and keeps every tag and script of the site', async () => {++ const home = await get('/');++ const head = home.text.slice(0, home.text.indexOf('</head>'));++ assert.strictEqual(head.match(/<title>/g).length, 1, 'exactly one <title>');++ assert.match(head, /<title>OpenVibe\.Tips — support the creators you watch<\/title>/);++ assert.match(head, /<link rel="canonical" href="http:\/\/tips\.test\/">/);++ assert.match(head, /<meta name="robots" content="index,follow">/);++ assert.match(head, /<meta property="og:title" content="OpenVibe\.Tips — support the creators you watch">/);++ assert.match(head, /<meta name="referrer" content="strict-origin-when-cross-origin">/);++ assert.match(head, /<link rel="stylesheet" href="\/css\/tips\.css\?v=[^"]+">/);++ assert.match(head, /<link rel="icon"[^>]*data-ov-icon="tips"/, 'the app-icon tags');++ assert.match(head, /<meta name="ov-release"/);++ assert.match(head, /<meta name="ov-boost" content="tips@[^"]+">/);++ assert.match(head, /<script src="\/shared\/boost\.js\?v=[0-9a-f]{12}" data-main="#main" defer><\/script>/);++ assert.match(head, /<script src="\/js\/tips\.js\?v=[^"]+" defer><\/script>/);++ for (const s of ['theme-loader', 'navbar', 'footer']) assert.match(head, new RegExp(`/shared/${s}\\.js\\?v=[0-9a-f]{12}`), s);++ assert.match(home.text, /<body data-page="home">/);++ assert.match(home.text, /<header class="site-head">/);++ assert.match(home.text, /<main id="main" class="page">/);++ assert.match(home.text, /OpenVibeFooter\.init\(window\.__OV_PAGE\.footer\)/);++++ const creator = await get('/alex');++ const creatorHead = creator.text.slice(0, creator.text.indexOf('</head>'));++ assert.match(creatorHead, /<link rel="canonical" href="http:\/\/tips\.test\/alex">/);++ assert.match(creatorHead, /<script type="application\/ld\+json">\{[^<]*"@type":"ProfilePage"/);++ });++await check('a switched-off page: 404 + noindex for everyone else; a noindex preview for its creator', async () => {const anon = await get('/bob');assert.strictEqual(anon.status, 404);
@@ -16,7 +16,7 @@
@@ -16,7 +16,7 @@
"jsonwebtoken": "^9.0.2",
"jsonwebtoken": "^9.0.2",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.79.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.79.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.26.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.26.0",
- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.5.0",
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.6.0",
"pg": "^8.23.0"
"pg": "^8.23.0"
},
},
"devDependencies": {
"devDependencies": {
@@ -1162,9 +1162,9 @@
@@ -1162,9 +1162,9 @@
}
}
},
},
"node_modules/openvibe-shared": {
"node_modules/openvibe-shared": {
- "version": "2.5.0",
- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.5.0",
- "integrity": "sha512-fHuUS6SXM7ezatfAyqLm1V8B6puV9YXSnz6aG6XQbWx28qghRCCHeUF3lr4nxiCGdn9SyikYtIyLGL5r1ycp9Q==",
+ "version": "2.6.0",
+ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.6.0",
+ "integrity": "sha512-IKKvWTM/9UonQ4msrzM4yir0QcTUNbhyqeN1r5ywWavU4VTsL1sI5QdHQlxge7BAsMrQ4OvMEPlHjuAfUJM3rg==",
"license": "MIT",
"license": "MIT",
"dependencies": {
"dependencies": {
"jsonwebtoken": "^9.0.0"
"jsonwebtoken": "^9.0.0"
@@ -28,7 +28,7 @@
@@ -28,7 +28,7 @@
"jsonwebtoken": "^9.0.2",
"jsonwebtoken": "^9.0.2",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.79.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.79.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.26.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.26.0",
- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.5.0",
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.6.0",
"pg": "^8.23.0"
"pg": "^8.23.0"
},
},
"engines": {
"engines": {
@@ -1,10 +1,10 @@
@@ -1,10 +1,10 @@
'use strict';
'use strict';
/**
/**
- * Page shell for every server-rendered page: full <head> SEO (title, description, canonical,
- * robots, Open Graph), the shared OpenVibe Frame (inline critical canvas + app icon from
- * openvibe-shared, navbar.js from the Network, the SSR footer and a <noscript> navigation), this
- * site's stylesheet and its small progressive script. Everything is useful without JavaScript.
+ * Page shell for every server-rendered page, composed by openvibe-shared/shell: full <head> SEO
+ * (title, description, canonical, robots, Open Graph, JSON-LD), the shared OpenVibe Frame (navbar.js
+ * from the Network, the SSR footer and a <noscript> navigation), plus this site's app icon,
+ * stylesheet, boost marker and its small progressive script. Everything is useful without JavaScript.
*/
*/
const crypto = require('crypto');
const crypto = require('crypto');
const ovServe = require('openvibe-shared/serve');
const ovServe = require('openvibe-shared/serve');
@@ -12,6 +12,7 @@ const fs = require('fs');
@@ -12,6 +12,7 @@ const fs = require('fs');
const path = require('path');
const path = require('path');
const appIcon = require('openvibe-shared/app-icon');
const appIcon = require('openvibe-shared/app-icon');
const frame = require('openvibe-shared/frame');
const frame = require('openvibe-shared/frame');
+const shell = require('openvibe-shared/shell');
const SITE_NAME = 'OpenVibe.Tips';
const SITE_NAME = 'OpenVibe.Tips';
const NETWORK_URL = 'https://openvibe.network';
const NETWORK_URL = 'https://openvibe.network';
@@ -49,7 +50,6 @@ function createLayout({ config, release }) {
@@ -49,7 +50,6 @@ function createLayout({ config, release }) {
* active ('home'|'dashboard'|'receipts'|'creator'), ogImage, jsonLd (array), viewer
* active ('home'|'dashboard'|'receipts'|'creator'), ogImage, jsonLd (array), viewer
*/
*/
function page(o) {
function page(o) {
- const title = o.title ? `${o.title} · ${SITE_NAME}` : `${SITE_NAME} — support the creators you watch`;
const description = (o.description || DEFAULT_DESCRIPTION).replace(/\s+/g, ' ').trim().slice(0, 300);
const description = (o.description || DEFAULT_DESCRIPTION).replace(/\s+/g, ' ').trim().slice(0, 300);
const canonical = abs(o.canonicalPath || '/');
const canonical = abs(o.canonicalPath || '/');
const robots = o.robots || 'index,follow';
const robots = o.robots || 'index,follow';
@@ -64,56 +64,42 @@ function createLayout({ config, release }) {
@@ -64,56 +64,42 @@ function createLayout({ config, release }) {
notificationsRealtime: true, // the bell hears new notifications over OpenVibe.Events (Shared 1.22.0)
notificationsRealtime: true, // the bell hears new notifications over OpenVibe.Events (Shared 1.22.0)
};
};
const footer = { service: 'tips', variant: 'full', mount: '#ov-footer', brandName: SITE_NAME, updates: '/updates' };
const footer = { service: 'tips', variant: 'full', mount: '#ov-footer', brandName: SITE_NAME, updates: '/updates' };
- const jsonLd = (o.jsonLd || []).map((x) => `<script type="application/ld+json">${JSON.stringify(x).replace(/</g, '\\u003c')}</script>`).join('\n');
const who = o.viewer
const who = o.viewer
? `<span class="who">Signed in as <b>${esc(o.viewer.name || o.viewer.username || 'you')}</b> · <a href="/auth/logout?next=/">Sign out</a></span>`
? `<span class="who">Signed in as <b>${esc(o.viewer.name || o.viewer.username || 'you')}</b> · <a href="/auth/logout?next=/">Sign out</a></span>`
: `<a class="who" href="/auth/login?next=${encodeURIComponent(o.canonicalPath || '/')}">Sign in</a>`;
: `<a class="who" href="/auth/login?next=${encodeURIComponent(o.canonicalPath || '/')}">Sign in</a>`;
- return `<!DOCTYPE html>
-<html lang="en" data-page="${esc(o.active || 'page')}">
-<head>
-<meta charset="utf-8">
-<meta name="viewport" content="width=device-width, initial-scale=1">
-<title>${esc(title)}</title>
-<meta name="description" content="${esc(description)}">
-<link rel="canonical" href="${esc(canonical)}">
-<meta name="robots" content="${esc(robots)}">
-<meta name="referrer" content="strict-origin-when-cross-origin">
-<meta property="og:site_name" content="${SITE_NAME}">
-<meta property="og:type" content="${o.ogType || 'website'}">
-<meta property="og:title" content="${esc(o.title || SITE_NAME)}">
-<meta property="og:description" content="${esc(description)}">
-<meta property="og:url" content="${esc(canonical)}">
-${o.ogImage ? `<meta property="og:image" content="${esc(o.ogImage)}">` : ''}
-<meta name="twitter:card" content="summary">
-${appIcon.headTags({ site: 'tips' })}
-${release ? release.metaTag() : ''}
-<script src="${ovServe.url('theme-loader.js')}" defer></script>
-<link rel="stylesheet" href="${asset('css/tips.css')}">
-${jsonLd}
-<script src="${ovServe.url('navbar.js')}" defer></script>
-<script src="${ovServe.url('footer.js')}" defer></script>
-<script src="${asset('js/tips.js')}" defer></script>
-<meta name="ov-boost" content="tips@${esc(RELEASE)}">
-<script src="${ovServe.url('boost.js')}" data-main="#main" defer></script>
-</head>
-<body>
-<div id="navbar-mount"></div>
-${frame.noscriptNav({ name: SITE_NAME, home: '/', links: NAV_LINKS })}
+ // shell.page writes the document, the SEO head, the theme-loader, navbar.js/footer.js with the
+ // navbar init, the noscript nav and the SSR footer; the rest of the head is this site's own.
+ return shell.page({
+ name: SITE_NAME, service: 'tips', lang: 'en',
+ title: o.title || `${SITE_NAME} — support the creators you watch`,
+ titleSuffix: o.title ? ` · ${SITE_NAME}` : undefined,
+ siteName: SITE_NAME, description, canonical, robots,
+ type: o.ogType || 'website', image: o.ogImage, jsonLd: o.jsonLd,
+ navbar: nav, footer, home: '/', navLinks: NAV_LINKS,
+ head: [
+ '<meta name="referrer" content="strict-origin-when-cross-origin">',
+ appIcon.headTags({ site: 'tips' }),
+ release ? release.metaTag() : '',
+ `<link rel="stylesheet" href="${asset('css/tips.css')}">`,
+ `<meta name="ov-boost" content="tips@${esc(RELEASE)}">`,
+ `<script src="${ovServe.url('boost.js')}" data-main="#main" defer></script>`,
+ `<script src="${asset('js/tips.js')}" defer></script>`,
+ ].filter(Boolean).join('\n'),
+ body: `<div id="navbar-mount"></div>
<header class="site-head"><a class="brand" href="/">${SITE_NAME}</a><nav>${NAV_LINKS.map((l) => `<a href="${l.href}"${o.active === l.label.toLowerCase() ? ' aria-current="page"' : ''}>${l.label}</a>`).join('')}</nav>${who}</header>
<header class="site-head"><a class="brand" href="/">${SITE_NAME}</a><nav>${NAV_LINKS.map((l) => `<a href="${l.href}"${o.active === l.label.toLowerCase() ? ' aria-current="page"' : ''}>${l.label}</a>`).join('')}</nav>${who}</header>
<main id="main" class="page">
<main id="main" class="page">
${o.body || ''}
${o.body || ''}
${o.canonicalPath === '/' && o.active === 'home' ? frame.shipped({ service: 'tips', title: `Recently shipped on ${SITE_NAME}` }) : ''}
${o.canonicalPath === '/' && o.active === 'home' ? frame.shipped({ service: 'tips', title: `Recently shipped on ${SITE_NAME}` }) : ''}
</main>
</main>
-${frame.footer(footer)}
<script>
<script>
window.__OV_PAGE = ${JSON.stringify({ navbar: nav, footer }).replace(/</g, '\\u003c')};
window.__OV_PAGE = ${JSON.stringify({ navbar: nav, footer }).replace(/</g, '\\u003c')};
-document.addEventListener('DOMContentLoaded', function () {
- try { if (window.OpenVibeNavbar) { OpenVibeNavbar.init(window.__OV_PAGE.navbar); document.documentElement.classList.add('ov-has-navbar'); } } catch (e) { /* the SSR header stays */ }
- try { if (window.OpenVibeFooter) OpenVibeFooter.init(window.__OV_PAGE.footer); } catch (e) { /* */ }
+window.addEventListener('DOMContentLoaded', function () {
+ if (window.OpenVibeNavbar) document.documentElement.classList.add('ov-has-navbar');
+ try { if (window.OpenVibeFooter) OpenVibeFooter.init(window.__OV_PAGE.footer); } catch (e) { /* the SSR footer stays */ }
});
});
-</script>
-</body>
-</html>`;
+</script>`,
+ bodyAttributes: { 'data-page': o.active || 'page' },
+ });
}
}
return { page, abs };
return { page, abs };
@@ -56,6 +56,33 @@ const { boot, check, done } = require('./helpers/app');
@@ -56,6 +56,33 @@ const { boot, check, done } = require('./helpers/app');
assert.match((await get('/@alex')).headers.get('location') || '', /\/alex$/);
assert.match((await get('/@alex')).headers.get('location') || '', /\/alex$/);
});
});
+ await check('the head comes from openvibe-shared/shell and keeps every tag and script of the site', async () => {
+ const home = await get('/');
+ const head = home.text.slice(0, home.text.indexOf('</head>'));
+ assert.strictEqual(head.match(/<title>/g).length, 1, 'exactly one <title>');
+ assert.match(head, /<title>OpenVibe\.Tips — support the creators you watch<\/title>/);
+ assert.match(head, /<link rel="canonical" href="http:\/\/tips\.test\/">/);
+ assert.match(head, /<meta name="robots" content="index,follow">/);
+ assert.match(head, /<meta property="og:title" content="OpenVibe\.Tips — support the creators you watch">/);
+ assert.match(head, /<meta name="referrer" content="strict-origin-when-cross-origin">/);
+ assert.match(head, /<link rel="stylesheet" href="\/css\/tips\.css\?v=[^"]+">/);
+ assert.match(head, /<link rel="icon"[^>]*data-ov-icon="tips"/, 'the app-icon tags');
+ assert.match(head, /<meta name="ov-release"/);
+ assert.match(head, /<meta name="ov-boost" content="tips@[^"]+">/);
+ assert.match(head, /<script src="\/shared\/boost\.js\?v=[0-9a-f]{12}" data-main="#main" defer><\/script>/);
+ assert.match(head, /<script src="\/js\/tips\.js\?v=[^"]+" defer><\/script>/);
+ for (const s of ['theme-loader', 'navbar', 'footer']) assert.match(head, new RegExp(`/shared/${s}\\.js\\?v=[0-9a-f]{12}`), s);
+ assert.match(home.text, /<body data-page="home">/);
+ assert.match(home.text, /<header class="site-head">/);
+ assert.match(home.text, /<main id="main" class="page">/);
+ assert.match(home.text, /OpenVibeFooter\.init\(window\.__OV_PAGE\.footer\)/);
+
+ const creator = await get('/alex');
+ const creatorHead = creator.text.slice(0, creator.text.indexOf('</head>'));
+ assert.match(creatorHead, /<link rel="canonical" href="http:\/\/tips\.test\/alex">/);
+ assert.match(creatorHead, /<script type="application\/ld\+json">\{[^<]*"@type":"ProfilePage"/);
+ });
+
await check('a switched-off page: 404 + noindex for everyone else; a noindex preview for its creator', async () => {
await check('a switched-off page: 404 + noindex for everyone else; a noindex preview for its creator', async () => {
const anon = await get('/bob');
const anon = await get('/bob');
assert.strictEqual(anon.status, 404);
assert.strictEqual(anon.status, 404);