03c46b37c3
WeChat link cards were pointing og:image at mindspace-cover paths that never landed on disk; serve-time injection now uses thumbnail.png instead. Co-authored-by: Cursor <cursoragent@cursor.com>
166 lines
8.0 KiB
JavaScript
166 lines
8.0 KiB
JavaScript
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import {
|
|
extractSharePreviewMeta,
|
|
injectOgTags,
|
|
injectWechatShareBridge,
|
|
PLATFORM_SITE_NAME,
|
|
renderWechatSharePreviewHtml,
|
|
} from './mindspace-og-tags.mjs';
|
|
|
|
const ctx = {
|
|
origin: 'https://m.tkmind.cn',
|
|
pageUrl: 'https://m.tkmind.cn/MindSpace/john/public/space.html',
|
|
pageDirUrl: 'https://m.tkmind.cn/MindSpace/john/public/',
|
|
};
|
|
|
|
test('injects og/twitter tags with absolute image from a relative cover', () => {
|
|
const html = `<html><head><title>🚀 太空小勇士 - 闯关大冒险</title>` +
|
|
`<meta name="description" content="一起闯关大冒险">` +
|
|
`<meta name="mindspace-cover" content='{"tag":"游戏","cover":"assets/hero.jpg"}'></head><body></body></html>`;
|
|
const out = injectOgTags(html, ctx);
|
|
assert.match(out, /<meta property="og:title" content="🚀 太空小勇士 - 闯关大冒险">/);
|
|
assert.match(out, /<meta property="og:description" content="一起闯关大冒险">/);
|
|
assert.match(out, /<meta property="og:image" content="https:\/\/m\.tkmind\.cn\/MindSpace\/john\/public\/assets\/hero\.jpg">/);
|
|
assert.match(out, /<meta property="og:site_name" content="TKMind 智趣">/);
|
|
assert.match(out, /<link rel="icon" type="image\/png" href="https:\/\/m\.tkmind\.cn\/brand\/tkmind-icon\.png">/);
|
|
assert.match(out, /<meta name="twitter:card" content="summary_large_image">/);
|
|
assert.ok(out.indexOf('og:image') < out.indexOf('</head>'));
|
|
});
|
|
|
|
test('falls back to the thumbnail png when the page has no cover of its own', () => {
|
|
const html = `<head><title>纯文字页面</title></head>`;
|
|
const out = injectOgTags(html, {
|
|
...ctx,
|
|
fallbackImageUrl: 'https://m.tkmind.cn/MindSpace/john/public/space.thumbnail.png',
|
|
});
|
|
assert.match(out, /<meta property="og:image" content="https:\/\/m\.tkmind\.cn\/MindSpace\/john\/public\/space\.thumbnail\.png">/);
|
|
assert.match(out, /<meta property="og:site_name" content="TKMind 智趣">/);
|
|
assert.match(out, /<meta name="twitter:card" content="summary_large_image">/);
|
|
});
|
|
|
|
test('prefers the page cover over the thumbnail fallback when the cover file exists', () => {
|
|
const html = `<head><title>X</title><meta name="mindspace-cover" content='{"cover":"assets/hero.jpg"}'></head>`;
|
|
const out = injectOgTags(html, {
|
|
...ctx,
|
|
fallbackImageUrl: 'https://m.tkmind.cn/x.thumbnail.png',
|
|
htmlFilePath: '/tmp/public/page.html',
|
|
fileExists: (target) => String(target).endsWith('assets/hero.jpg'),
|
|
});
|
|
assert.match(out, /og:image" content="https:\/\/m\.tkmind\.cn\/MindSpace\/john\/public\/assets\/hero\.jpg"/);
|
|
assert.doesNotMatch(out, /thumbnail\.png/);
|
|
});
|
|
|
|
test('falls back to thumbnail png when the declared cover file is missing on disk', () => {
|
|
const html = `<head><title>X</title><meta name="mindspace-cover" content='{"cover":"assets/hero.jpg"}'></head>`;
|
|
const out = injectOgTags(html, {
|
|
...ctx,
|
|
fallbackImageUrl: 'https://m.tkmind.cn/x.thumbnail.png',
|
|
htmlFilePath: '/tmp/public/page.html',
|
|
fileExists: () => false,
|
|
});
|
|
assert.match(out, /<meta property="og:image" content="https:\/\/m\.tkmind\.cn\/x\.thumbnail\.png">/);
|
|
assert.doesNotMatch(out, /<meta property="og:image" content="[^"]*assets\/hero\.jpg"/);
|
|
});
|
|
|
|
test('keeps an author-provided og:image and still backfills site_name', () => {
|
|
const html = `<head><title>T</title><meta property="og:image" content="https://x/y.png"></head>`;
|
|
const out = injectOgTags(html, ctx);
|
|
assert.equal((out.match(/og:image/g) ?? []).length, 1);
|
|
assert.match(out, /<meta property="og:site_name" content="TKMind 智趣">/);
|
|
});
|
|
|
|
test('skips og:image for svg-only cover, falls back to summary card', () => {
|
|
const html = `<head><title>仅SVG</title><meta name="mindspace-cover" content='{"cover":"foo.svg"}'></head>`;
|
|
const out = injectOgTags(html, ctx);
|
|
assert.doesNotMatch(out, /og:image/);
|
|
assert.match(out, /<meta name="twitter:card" content="summary">/);
|
|
assert.match(out, /<meta property="og:site_name" content="TKMind 智趣">/);
|
|
});
|
|
|
|
test('passes through a full https cover url unchanged', () => {
|
|
const html = `<head><title>X</title><meta name="mindspace-cover" content='{"cover":"https://cdn.example.com/a.png"}'></head>`;
|
|
const out = injectOgTags(html, ctx);
|
|
assert.match(out, /<meta property="og:image" content="https:\/\/cdn\.example\.com\/a\.png">/);
|
|
});
|
|
|
|
test('resolves a root-absolute cover against the origin', () => {
|
|
const html = `<head><title>X</title><meta name="mindspace-cover" content='{"cover":"/shared/cover.jpg"}'></head>`;
|
|
const out = injectOgTags(html, ctx);
|
|
assert.match(out, /<meta property="og:image" content="https:\/\/m\.tkmind\.cn\/shared\/cover\.jpg">/);
|
|
});
|
|
|
|
test('escapes quotes and ampersands in title to keep the meta tag well-formed', () => {
|
|
const html = `<head><title>say "hi" & bye</title></head>`;
|
|
const out = injectOgTags(html, ctx);
|
|
assert.match(out, /<meta property="og:title" content="say "hi" & bye">/);
|
|
});
|
|
|
|
test('uses meta override for shell pages that only expose a title', () => {
|
|
const shell = `<head><title>连云港三天两夜攻略 🌊</title></head>`;
|
|
const out = injectOgTags(shell, {
|
|
...ctx,
|
|
fallbackImageUrl: 'https://m.tkmind.cn/MindSpace/john/public/demo.thumbnail.png',
|
|
meta: { subtitle: '三天两夜·沙滩酒店·必吃美食' },
|
|
});
|
|
assert.match(out, /<meta property="og:description" content="三天两夜·沙滩酒店·必吃美食">/);
|
|
assert.match(out, /<meta property="og:site_name" content="TKMind 智趣">/);
|
|
});
|
|
|
|
test('extractSharePreviewMeta returns the card fields used by preview tooling', () => {
|
|
const html = injectOgTags(
|
|
`<head><title>标题</title><meta name="description" content="描述"></head>`,
|
|
ctx,
|
|
);
|
|
const preview = extractSharePreviewMeta(html, ctx);
|
|
assert.equal(preview.title, '标题');
|
|
assert.equal(preview.description, '描述');
|
|
assert.equal(preview.siteName, PLATFORM_SITE_NAME);
|
|
assert.match(preview.iconUrl, /\/brand\/tkmind-icon\.png$/);
|
|
});
|
|
|
|
test('renderWechatSharePreviewHtml includes title, description and site footer', () => {
|
|
const html = renderWechatSharePreviewHtml({
|
|
title: '连云港三天两夜攻略 🌊',
|
|
description: '三天两夜·沙滩酒店·必吃美食',
|
|
siteName: PLATFORM_SITE_NAME,
|
|
imageUrl: 'https://example.com/cover.png',
|
|
iconUrl: '/brand/tkmind-icon.png',
|
|
pageUrl: ctx.pageUrl,
|
|
});
|
|
assert.match(html, /连云港三天两夜攻略/);
|
|
assert.match(html, /三天两夜·沙滩酒店·必吃美食/);
|
|
assert.match(html, /TKMind 智趣/);
|
|
assert.match(html, /example\.com\/cover\.png/);
|
|
});
|
|
|
|
test('injectWechatShareBridge adds WeChat share bootstrap with og-derived metadata', () => {
|
|
const html = `<html><head><title>页面标题</title>` +
|
|
`<meta property="og:title" content="分享标题">` +
|
|
`<meta property="og:description" content="分享描述">` +
|
|
`<meta property="og:site_name" content="TKMind 智趣">` +
|
|
`<meta property="og:image" content="https://g2.tkmind.cn/hero.png"></head><body></body></html>`;
|
|
const out = injectWechatShareBridge(html, {
|
|
pageUrl: 'https://m.tkmind.cn/u/john/pages/demo',
|
|
});
|
|
assert.match(out, /data-tkmind-wechat-share="1"/);
|
|
assert.match(out, /fetch\(endpoint \+ '\?url='/);
|
|
assert.match(out, /updateAppMessageShareData/);
|
|
assert.match(out, /https:\/\/g2\.tkmind\.cn\/hero\.png/);
|
|
assert.match(out, /分享标题/);
|
|
assert.match(out, /分享描述/);
|
|
});
|
|
|
|
test('injectWechatShareBridge falls back description when og tags are missing', () => {
|
|
const html = `<head><title>页面标题</title></head>`;
|
|
const out = injectWechatShareBridge(html, { pageUrl: 'https://m.tkmind.cn/u/john/pages/demo' });
|
|
assert.match(out, /TKMind 智趣 · 精选作品/);
|
|
});
|
|
|
|
test('injectWechatShareBridge is idempotent', () => {
|
|
const html = `<head><title>X</title></head>`;
|
|
const first = injectWechatShareBridge(html, { pageUrl: 'https://m.tkmind.cn/u/john/pages/demo' });
|
|
const second = injectWechatShareBridge(first, { pageUrl: 'https://m.tkmind.cn/u/john/pages/demo' });
|
|
assert.equal((second.match(/data-tkmind-wechat-share="1"/g) ?? []).length, 1);
|
|
});
|