dbb16f0678
Co-authored-by: Cursor <cursoragent@cursor.com>
278 lines
15 KiB
JavaScript
278 lines
15 KiB
JavaScript
import assert from 'node:assert/strict';
|
||
import test from 'node:test';
|
||
import {
|
||
convertDailyNewsHtmlToWechatInlineArticle,
|
||
wechatDailyNewsInlineInternals,
|
||
} from './wechat-daily-news-inline.mjs';
|
||
|
||
const WEATHER_HTML = `<!DOCTYPE html><html><body><div class="container">
|
||
<div class="hero"><h1>📰 每日新闻早报</h1></div>
|
||
<div class="quick-links"><a href="#weather">🌤️ 天气</a><a href="#world">🌍 国际</a></div>
|
||
<div class="section" id="headlines"><div class="section-title">要闻</div><div class="card"><h3>头条</h3><p>正文</p></div></div>
|
||
<div class="section" id="weather">
|
||
<div class="section-title">🌤️ 今日天气</div>
|
||
<div class="weather-today">
|
||
<div class="wt-row"><div><div class="wt-city">📍 北京</div><div class="wt-detail">☀️ 晴 · 温差大</div></div><div><div class="wt-temp-big">30°</div></div></div>
|
||
<div style="display:flex;gap:12px;"><span>🌧️ 华南有雨</span><span>☀️ 华北晴好</span></div>
|
||
<div style="background:rgba(255,255,255,0.5);padding:8px;"><strong>中央气象台:</strong>未来三天降水减弱。</div>
|
||
</div>
|
||
<div class="weather-grid">
|
||
<div class="weather-card w-hot"><div class="w-left"><span class="w-city">北京</span><span class="w-cond">☀️ 晴</span></div><div class="w-right"><span class="w-temp">30°</span></div></div>
|
||
<div class="weather-card w-rain"><div class="w-left"><span class="w-city">上海</span><span class="w-cond">🌧️ 小雨</span></div><div class="w-right"><span class="w-temp">26°</span></div></div>
|
||
</div>
|
||
<p style="font-size:12px;color:#a0aec0;">数据来源:中央气象台</p>
|
||
</div>
|
||
</div></body></html>`;
|
||
|
||
test('splitSections preserves section ids after opening tag split', () => {
|
||
const sections = wechatDailyNewsInlineInternals.splitSections(WEATHER_HTML);
|
||
assert.deepEqual(sections.map((item) => item.id), ['headlines', 'weather']);
|
||
});
|
||
|
||
test('renderWeatherSection uses tourist city title and two-column city cards', () => {
|
||
const sections = wechatDailyNewsInlineInternals.splitSections(WEATHER_HTML);
|
||
const weather = sections.find((item) => item.id === 'weather');
|
||
const rendered = wechatDailyNewsInlineInternals.renderWeatherSection(weather.html);
|
||
assert.match(rendered, /热门旅游城市天气/u);
|
||
assert.match(rendered, /📍 北京/u);
|
||
assert.match(rendered, /☀️ 晴 · 温差大/u);
|
||
assert.match(rendered, /width:50%;box-sizing:border-box/u);
|
||
assert.match(rendered, /display:inline-block/u);
|
||
assert.doesNotMatch(rendered, /display:flex/u);
|
||
assert.match(rendered, /30°/u);
|
||
assert.match(rendered, /26°/u);
|
||
assert.doesNotMatch(rendered, /中央气象台/u);
|
||
assert.doesNotMatch(rendered, /今日天气/u);
|
||
});
|
||
|
||
test('renderWeatherSection skips regional fallback cards and national summary', () => {
|
||
const regionalWeatherHtml = `<div class="section" id="weather">
|
||
<div class="section-title">🌤️ 今日天气 · 9月16日</div>
|
||
<div class="weather-today">
|
||
<div class="wt-row"><div><div class="wt-city">📍 全国概况 · 江汉西南阴雨开场</div><div class="wt-detail">🌧️ 湖北西部局地暴雨</div></div></div>
|
||
</div>
|
||
<div class="weather-grid">
|
||
<div class="weather-card"><div class="w-left"><span class="w-city">湖北西部</span><span class="w-cond">🌧️ 暴雨</span></div><div class="w-right"><span class="w-temp">雨</span></div></div>
|
||
<div class="weather-card"><div class="w-left"><span class="w-city">北京</span><span class="w-cond">☀️ 晴</span></div><div class="w-right"><span class="w-temp">30°</span></div></div>
|
||
<div class="weather-card"><div class="w-left"><span class="w-city">上海</span><span class="w-cond">🌧️ 小雨</span></div><div class="w-right"><span class="w-temp">26°</span></div></div>
|
||
</div>
|
||
</div>`;
|
||
const rendered = wechatDailyNewsInlineInternals.renderWeatherSection(regionalWeatherHtml);
|
||
assert.match(rendered, /热门旅游城市天气 · 9月16日/u);
|
||
assert.match(rendered, /北京/u);
|
||
assert.match(rendered, /上海/u);
|
||
assert.doesNotMatch(rendered, /湖北西部/u);
|
||
assert.doesNotMatch(rendered, /全国概况/u);
|
||
});
|
||
|
||
test('renderWeatherSection supports news002 wc/we/wt cards', () => {
|
||
const news002Weather = `<div class="section" id="weather">
|
||
<div class="section-title">🌤️ 今日天气 · 9月24日</div>
|
||
<div class="weather-today"><div><div class="c">全国重点城市 · 9月24日</div><div class="d">中央气象台预报,广东广西局地大到暴雨</div></div></div>
|
||
<div class="weather-grid">
|
||
<div class="weather-card"><div class="wc">北京</div><div class="we">🌤</div><div class="wt">14/26℃</div></div>
|
||
<div class="weather-card"><div class="wc">上海</div><div class="we">🌧</div><div class="wt">22/28℃</div></div>
|
||
</div>
|
||
</div>`;
|
||
const rendered = wechatDailyNewsInlineInternals.renderWeatherSection(news002Weather);
|
||
assert.match(rendered, /北京/u);
|
||
assert.match(rendered, /14\/26℃/u);
|
||
assert.match(rendered, /上海/u);
|
||
assert.match(rendered, /22\/28℃/u);
|
||
});
|
||
|
||
test('buildWechatInlineContent renders weather and optional quick links', () => {
|
||
const built = wechatDailyNewsInlineInternals.buildWechatInlineContent(WEATHER_HTML, {
|
||
includeFixedLayout: false,
|
||
trimProfile: { skipHistory: true, skipQuickLinks: false },
|
||
});
|
||
assert.match(built.content, /🌤️ 天气/u);
|
||
assert.match(built.content, /📍 北京/u);
|
||
assert.match(built.content, /30°/u);
|
||
});
|
||
|
||
const HISTORY_HTML = `<!DOCTYPE html><html><body><div class="container">
|
||
<div class="hero"><h1>📰 每日新闻早报</h1></div>
|
||
<div class="section" id="headlines"><div class="section-title">要闻</div><div class="card"><h3>头条</h3><p>正文</p></div></div>
|
||
<div class="section" id="history">
|
||
<div class="section-title">📜 历史上的今天 · 9月24日</div>
|
||
<ol class="history-list">
|
||
<li class="history-item"><span class="hy">1949年</span><span class="he">中国人民政治协商会议第一届全体会议开幕。</span></li>
|
||
<li class="history-item"><span class="hy">1957年</span><span class="he">美国小石城中央高中发生种族融合冲突。</span></li>
|
||
<li class="history-item"><span class="hy">1996年</span><span class="he">《全面禁止核试验条约》在联合国开放签署。</span></li>
|
||
<li class="history-item"><span class="hy">2008年</span><span class="he">神舟七号载人飞船成功发射。</span></li>
|
||
</ol>
|
||
</div>
|
||
</div></body></html>`;
|
||
|
||
test('renderHistorySection renders up to three compact history items', () => {
|
||
const sections = wechatDailyNewsInlineInternals.splitSections(HISTORY_HTML);
|
||
const history = sections.find((item) => item.id === 'history');
|
||
const rendered = wechatDailyNewsInlineInternals.renderHistorySection(history.html);
|
||
assert.match(rendered, /历史上的今天 · 9月24日/u);
|
||
assert.match(rendered, /1949年/u);
|
||
assert.match(rendered, /1957年/u);
|
||
assert.match(rendered, /1996年/u);
|
||
assert.doesNotMatch(rendered, /2008年/u);
|
||
assert.match(rendered, /中国人民政治协商会议/u);
|
||
});
|
||
|
||
test('buildWechatInlineContent includes history snippet by default', () => {
|
||
const built = wechatDailyNewsInlineInternals.buildWechatInlineContent(HISTORY_HTML, {
|
||
includeFixedLayout: false,
|
||
});
|
||
assert.match(built.content, /历史上的今天 · 9月24日/u);
|
||
assert.match(built.content, /1949年/u);
|
||
assert.doesNotMatch(built.content, /2008年/u);
|
||
});
|
||
|
||
test('buildWechatInlineContent can skip history when trimProfile requests it', () => {
|
||
const built = wechatDailyNewsInlineInternals.buildWechatInlineContent(HISTORY_HTML, {
|
||
includeFixedLayout: false,
|
||
trimProfile: { skipHistory: true },
|
||
});
|
||
assert.doesNotMatch(built.content, /历史上的今天/u);
|
||
});
|
||
|
||
test('convertDailyNewsHtmlToWechatInlineArticle keeps weather when trimming', () => {
|
||
const article = convertDailyNewsHtmlToWechatInlineArticle(WEATHER_HTML, {
|
||
includeFixedLayout: false,
|
||
fixedLayoutLength: 1200,
|
||
});
|
||
assert.match(article.content, /📍 北京/u);
|
||
assert.match(article.content, /30°/u);
|
||
});
|
||
|
||
test('trimWechatInlineBodyByRemovingCards drops tail cards instead of hard slicing', () => {
|
||
const html = `${WEATHER_HTML.slice(0, -24)}<div class="section" id="world"><div class="section-title">国际</div><div class="card"><h3>国际一</h3><p>正文一</p></div><div class="card"><h3>国际二</h3><p>正文二</p></div></div></div></body></html>`;
|
||
const built = wechatDailyNewsInlineInternals.buildWechatInlineContent(html, {
|
||
includeFixedLayout: false,
|
||
});
|
||
const beforeCards = wechatDailyNewsInlineInternals.countInlineCards(built.content);
|
||
const trimmed = wechatDailyNewsInlineInternals.trimWechatInlineBodyByRemovingCards(
|
||
built.content,
|
||
built.content.length - 260,
|
||
{ minCards: 1 },
|
||
);
|
||
const afterCards = wechatDailyNewsInlineInternals.countInlineCards(trimmed);
|
||
assert.ok(beforeCards >= 3);
|
||
assert.ok(afterCards < beforeCards);
|
||
assert.ok(trimmed.length < built.content.length);
|
||
assert.match(trimmed, /<\/section>\s*$/u);
|
||
});
|
||
|
||
test('section list numbering starts at 01 after feature cards', () => {
|
||
const html = `<!doctype html><html><body>
|
||
<div class="hero"><h1>每日新闻早报</h1><div class="subtitle">头条一 · 头条二</div></div>
|
||
<div class="section" id="headlines">
|
||
<div class="section-title">今日要闻</div>
|
||
<div class="card highlight-box"><h3>头条卡</h3><p>重点摘要</p></div>
|
||
<div class="card"><h3>普通要闻</h3><p>普通摘要</p></div>
|
||
</div>
|
||
<div class="section" id="world">
|
||
<div class="section-title">国际热点</div>
|
||
<div class="card"><h3>国际一</h3><p>国际摘要一</p></div>
|
||
</div>
|
||
</body></html>`;
|
||
const built = wechatDailyNewsInlineInternals.buildWechatInlineContent(html, {
|
||
includeFixedLayout: false,
|
||
});
|
||
|
||
assert.match(built.content, /头条卡[\s\S]*?01<\/span> <strong>普通要闻/u);
|
||
assert.match(built.content, /WORLD[\s\S]*?01<\/span> <strong>国际一/u);
|
||
assert.doesNotMatch(built.content, /02<\/span> <strong>普通要闻/u);
|
||
});
|
||
|
||
test('hero highlights render as a separate readable digest', () => {
|
||
const digest = wechatDailyNewsInlineInternals.renderTodayDigest(
|
||
'<div class="subtitle">平陆运河通航 · 海上火箭发射 · 全球市场聚焦议息</div>',
|
||
);
|
||
assert.match(digest, /今日导读/u);
|
||
assert.match(digest, /平陆运河通航<br>/u);
|
||
assert.match(digest, /海上火箭发射<br>/u);
|
||
});
|
||
|
||
test('renderHero does not insert the cover image as a large logo', () => {
|
||
const html = `<div class="hero"><h1>每日新闻早报</h1><div class="date-badge">9月16日</div></div>
|
||
<meta name="mindspace-cover" content='{"cover":"https://mmbiz.qpic.cn/logo.png"}'>`;
|
||
const rendered = wechatDailyNewsInlineInternals.renderHero(html, new Map([
|
||
['https://mmbiz.qpic.cn/logo.png', 'https://mmbiz.qpic.cn/logo.png'],
|
||
]));
|
||
assert.match(rendered, /每日新闻早报/u);
|
||
assert.doesNotMatch(rendered, /<img\b/u);
|
||
assert.doesNotMatch(rendered, /TKMIND · DAILY BRIEF/u);
|
||
});
|
||
|
||
const NEWS002_HTML = `<!DOCTYPE html><html><body>
|
||
<div class="masthead"><div class="kicker">TKMIND DAILY</div><h1>每日新闻早报</h1>
|
||
<div class="meta"><b>2026年9月21日</b><span>星期一</span><span>第 221 期</span></div></div>
|
||
<div class="lede">中美外长再次通话 · 东博会南宁开幕 · Kimi 发布金融方案</div>
|
||
<div class="container">
|
||
<div class="section" id="headlines"><div class="section-title"><h2>今日头条</h2></div>
|
||
<a class="lead-card" href="https://example.com/a" data-event-key="k1"><div class="media ph">📰</div>
|
||
<div class="body"><span class="tag">中美关系</span><h3>王毅同鲁比奥通电话</h3><p>头条正文摘要。</p>
|
||
<div class="source"><a href="https://example.com/a">新华社</a></div></div></a>
|
||
</div>
|
||
<div class="section" id="domestic"><div class="section-title"><h2>国内</h2></div>
|
||
<div class="card" data-event-key="k2"><div class="media ph">🇨🇳</div>
|
||
<div class="body"><span class="tag">时政</span><h3>沈阳举行九一八纪念活动</h3><p>国内正文摘要。</p>
|
||
<div class="why"><b>为什么重要</b>:影响当天交通管制。</div>
|
||
<div class="source"><a href="https://example.com/b">人民日报</a></div></div></div>
|
||
</div>
|
||
</div></body></html>`;
|
||
|
||
test('news002 lead-card renders as compact left-thumb card in the wechat draft', () => {
|
||
const rendered = wechatDailyNewsInlineInternals.renderGenericSection(
|
||
NEWS002_HTML.match(/<div class="section" id="headlines">[\s\S]*?<\/a>\n<\/div>/)[0],
|
||
{ sectionId: 'headlines', news002Mode: true },
|
||
);
|
||
assert.match(rendered, /王毅同鲁比奥通电话/u);
|
||
assert.match(rendered, /中美关系/u, 'lead-card 的 tag 必须保留');
|
||
assert.doesNotMatch(rendered, /01<\/span>/u, '头条卡不占列表序号');
|
||
assert.doesNotMatch(rendered, /calc\(100% \+ 28px\)/u, 'news002 头条不走全宽 feature 大图');
|
||
});
|
||
|
||
test('news002 card with media block still yields title and body', () => {
|
||
const rendered = wechatDailyNewsInlineInternals.renderGenericSection(
|
||
NEWS002_HTML.match(/<div class="section" id="domestic">[\s\S]*$/)[0],
|
||
{ sectionId: 'domestic', news002Mode: true },
|
||
);
|
||
assert.match(rendered, /沈阳举行九一八纪念活动/u);
|
||
assert.match(rendered, /国内正文摘要/u, '.media 在前不应挤掉正文 <p>');
|
||
});
|
||
|
||
test('news002 cards render uploaded source images in the wechat draft', () => {
|
||
const sourceImage = 'https://news.example/story.jpg';
|
||
const wechatImage = 'https://mmbiz.qpic.cn/wechat-story.jpg';
|
||
const html = NEWS002_HTML
|
||
.replace('<div class="media ph">📰</div>', `<div class="media"><img src="${sourceImage}" alt="头条"></div>`)
|
||
.replace('<div class="media ph">🇨🇳</div>', `<div class="media"><img src="${sourceImage}" alt="国内"></div>`);
|
||
const imageUrlMap = new Map([[sourceImage, wechatImage]]);
|
||
const lead = wechatDailyNewsInlineInternals.renderGenericSection(
|
||
html.match(/<div class="section" id="headlines">[\s\S]*?<\/a>\n<\/div>/)[0],
|
||
{ sectionId: 'headlines', imageUrlMap, news002Mode: true },
|
||
);
|
||
const domestic = wechatDailyNewsInlineInternals.renderGenericSection(
|
||
html.match(/<div class="section" id="domestic">[\s\S]*$/)[0],
|
||
{ sectionId: 'domestic', imageUrlMap, news002Mode: true },
|
||
);
|
||
assert.match(lead, /mmbiz\.qpic\.cn\/wechat-story\.jpg/u);
|
||
assert.match(domestic, /mmbiz\.qpic\.cn\/wechat-story\.jpg/u);
|
||
assert.doesNotMatch(lead, /news\.example\/story\.jpg/u);
|
||
assert.match(lead, /width:72px;height:54px/u, '配图必须是左上角小图');
|
||
assert.doesNotMatch(domestic, /width:100%;height:auto;margin:0 0 9px/u, '列表卡不再用顶部全宽图');
|
||
});
|
||
|
||
test('renderHero reads news002 masthead meta and lede', () => {
|
||
const rendered = wechatDailyNewsInlineInternals.renderHero(NEWS002_HTML);
|
||
assert.match(rendered, /每日新闻早报/u);
|
||
assert.match(rendered, /2026年9月21日/u, 'news002 用 .meta 取代 .date-badge');
|
||
assert.match(rendered, /东博会南宁开幕/u, 'news002 用 .lede 取代 .subtitle');
|
||
});
|
||
|
||
test('renderTodayDigest works off the news002 lede', () => {
|
||
const digest = wechatDailyNewsInlineInternals.renderTodayDigest(NEWS002_HTML);
|
||
assert.match(digest, /今日导读/u);
|
||
assert.match(digest, /中美外长再次通话<br>/u);
|
||
});
|