Update SocMed Image
This commit is contained in:
+22
-2
@@ -1,8 +1,12 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import sharp from 'sharp';
|
||||
import { feedJson, manifest, renderIndex, sitemapXml } from '../src/seo.js';
|
||||
|
||||
const socialPreviewPath = fileURLToPath(new URL('../public/assets/social-preview-v2.78d008df9860.jpg', import.meta.url));
|
||||
|
||||
const id = 'a'.repeat(64);
|
||||
const meme = {
|
||||
id,
|
||||
@@ -17,6 +21,8 @@ const template = [
|
||||
'<meta name="description" content="__SEO_DESCRIPTION__">',
|
||||
'<link rel="canonical" href="__SEO_CANONICAL__">',
|
||||
'<meta property="og:image" content="__SEO_IMAGE__">',
|
||||
'<meta property="og:image:type" content="__SEO_IMAGE_TYPE__">',
|
||||
'<meta name="twitter:image" content="__SEO_IMAGE__">',
|
||||
'<script nonce="__CSP_NONCE__">__SEO_JSON_LD__</script>'
|
||||
].join('');
|
||||
|
||||
@@ -44,10 +50,24 @@ test('renders the homepage with the dedicated social preview image', () => {
|
||||
approvedCount: 12
|
||||
});
|
||||
|
||||
assert.match(html, /og:image" content="https:\/\/memes\.example\/assets\/social-preview-v1\.png"/);
|
||||
assert.match(html, /og:image" content="https:\/\/memes\.example\/assets\/social-preview-v2\.78d008df9860\.jpg"/);
|
||||
assert.match(html, /og:image:type" content="image\/jpeg"/);
|
||||
assert.match(html, /twitter:image" content="https:\/\/memes\.example\/assets\/social-preview-v2\.78d008df9860\.jpg"/);
|
||||
assert.doesNotMatch(html, /yoonect-logo\.png/);
|
||||
});
|
||||
|
||||
test('keeps the social preview small and broadly compatible', async () => {
|
||||
const metadata = await sharp(socialPreviewPath).metadata();
|
||||
const stats = fs.statSync(socialPreviewPath);
|
||||
|
||||
assert.equal(metadata.format, 'jpeg');
|
||||
assert.deepEqual([metadata.width, metadata.height], [1200, 630]);
|
||||
assert.equal(metadata.hasAlpha, false);
|
||||
assert.equal(metadata.exif, undefined);
|
||||
assert.equal(metadata.xmp, undefined);
|
||||
assert.ok(stats.size < 300_000, `expected social preview under 300 KB, got ${stats.size}`);
|
||||
});
|
||||
|
||||
test('uses the supplied favicon suite in the page and web app manifest', () => {
|
||||
const index = fs.readFileSync(new URL('../public/index.html', import.meta.url), 'utf8');
|
||||
const appManifest = manifest('https://memes.example');
|
||||
@@ -76,7 +96,7 @@ test('renders a shareable lore page with text-file metadata', () => {
|
||||
|
||||
assert.match(html, /<title>Lore \| The Meme Protocol<\/title>/);
|
||||
assert.match(html, /canonical" href="https:\/\/memes\.example\/lore"/);
|
||||
assert.match(html, /og:image" content="https:\/\/memes\.example\/assets\/social-preview-v1\.png"/);
|
||||
assert.match(html, /og:image" content="https:\/\/memes\.example\/assets\/social-preview-v2\.78d008df9860\.jpg"/);
|
||||
assert.match(html, /"@type":"CreativeWork"/);
|
||||
assert.match(html, /https:\/\/memes\.example\/assets\/meme-protocol-lore\.txt/);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { spawn } from 'node:child_process';
|
||||
import { once } from 'node:events';
|
||||
import fs from 'node:fs/promises';
|
||||
import net from 'node:net';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const projectRoot = fileURLToPath(new URL('../', import.meta.url));
|
||||
const socialPreviewPath = '/assets/social-preview-v2.78d008df9860.jpg';
|
||||
|
||||
test('serves crawler-safe HEAD responses for the homepage and social preview', { timeout: 15_000 }, async (t) => {
|
||||
let port;
|
||||
try {
|
||||
port = await availablePort();
|
||||
} catch (error) {
|
||||
if (error.code === 'EPERM') {
|
||||
t.skip('loopback listeners are disabled in this sandbox');
|
||||
return;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
const dataDir = await fs.mkdtemp(path.join(os.tmpdir(), 'meme-protocol-head-'));
|
||||
const child = spawn(process.execPath, ['server.js'], {
|
||||
cwd: projectRoot,
|
||||
env: {
|
||||
...process.env,
|
||||
ADMIN_TOKEN: 'test-admin-token-000000000000',
|
||||
DATA_DIR: dataDir,
|
||||
HOST: '127.0.0.1',
|
||||
PORT: String(port),
|
||||
SEED_DEMO_MEMES: 'false',
|
||||
SITE_URL: `http://127.0.0.1:${port}`
|
||||
},
|
||||
stdio: ['ignore', 'pipe', 'pipe']
|
||||
});
|
||||
|
||||
t.after(async () => {
|
||||
if (child.exitCode === null && child.signalCode === null) {
|
||||
child.kill('SIGTERM');
|
||||
await once(child, 'exit');
|
||||
}
|
||||
await fs.rm(dataDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
await waitForStart(child);
|
||||
|
||||
const headers = { 'user-agent': 'Twitterbot/1.0' };
|
||||
const page = await fetch(`http://127.0.0.1:${port}/`, { method: 'HEAD', headers });
|
||||
assert.equal(page.status, 200);
|
||||
assert.match(page.headers.get('content-type') || '', /^text\/html/);
|
||||
assert.ok(Number(page.headers.get('content-length')) > 0);
|
||||
assert.equal((await page.arrayBuffer()).byteLength, 0);
|
||||
|
||||
const image = await fetch(`http://127.0.0.1:${port}${socialPreviewPath}`, { method: 'HEAD', headers });
|
||||
assert.equal(image.status, 200);
|
||||
assert.equal(image.headers.get('content-type'), 'image/jpeg');
|
||||
assert.match(image.headers.get('cache-control') || '', /immutable/);
|
||||
assert.ok(Number(image.headers.get('content-length')) < 300_000);
|
||||
assert.equal((await image.arrayBuffer()).byteLength, 0);
|
||||
});
|
||||
|
||||
async function availablePort() {
|
||||
const server = net.createServer();
|
||||
await new Promise((resolve, reject) => {
|
||||
server.once('error', reject);
|
||||
server.listen(0, '127.0.0.1', resolve);
|
||||
});
|
||||
const address = server.address();
|
||||
const port = typeof address === 'object' && address ? address.port : 0;
|
||||
await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
|
||||
return port;
|
||||
}
|
||||
|
||||
async function waitForStart(child) {
|
||||
await new Promise((resolve, reject) => {
|
||||
let output = '';
|
||||
const timeout = setTimeout(() => finish(new Error(`server did not start:\n${output}`)), 10_000);
|
||||
const onData = (chunk) => {
|
||||
output += chunk.toString();
|
||||
if (output.includes('The Meme Protocol listening')) finish();
|
||||
};
|
||||
const onExit = (code, signal) => finish(new Error(`server exited before startup (${code ?? signal}):\n${output}`));
|
||||
const finish = (error) => {
|
||||
clearTimeout(timeout);
|
||||
child.stdout.off('data', onData);
|
||||
child.stderr.off('data', onData);
|
||||
child.off('exit', onExit);
|
||||
error ? reject(error) : resolve();
|
||||
};
|
||||
|
||||
child.stdout.on('data', onData);
|
||||
child.stderr.on('data', onData);
|
||||
child.once('exit', onExit);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user