import assert from 'node:assert/strict'; import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import test from 'node:test'; import { convertGenericPageHtmlToWechatArticle, convertPageHtmlToWechatArticle, convertRichMindSpacePageHtmlToWechatArticle, isNewsHotspotFormat, mindspaceWechatPageDraftInternals, resolvePagePublicUrl, resolveWechatDraftProfile, resolveWechatDraftThumbPath, validatePageForWechatDraft, } from './mindspace-wechat-page-draft.mjs'; import { buildDailyNewsWechatDraftArticle } from './wechat-news-morning-draft.mjs'; import { applyWechatDraftArticleLayout } from './wechat-draft-article-layout.mjs'; const SAMPLE_HTML = ` 测试页面

标题

正文内容

`; const POEM_HTML = `八月午后

八月午后

甜是慢的

壹

蝉声把正午拉得很长

蝉

声音的长度

蝉鸣没有起伏

八月的午后很难写。

可正是这种什么都没有
`; const PROSE_HTML = `秋庭小憩 · 散文

秋庭小憩

一篇百字散文 · 秋日慢时光

午后阳光斜洒庭院石阶,桂花香气随微风四处飘散。

墙外梧桐叶纷纷飘落,金黄铺满青石板路,沙沙作响。

🍂 秋日🌼 桂花
`; const BLOG_HTML = `博客文章

2026-09-11 · John

AI 与创作

背景

工具正在改变内容生产。

创作者需要新的工作流。

`; const STARTUP_HTML = `创业路演

让 AI 更懂业务

面向团队的知识助手

愿景

帮助每个团队拥有自己的 AI 大脑。

`; const DAILY_NEWS_HTML = ` 每日新闻早报 · 2026年9月10日

📰 每日新闻早报

2026年9月10日 · 星期三

示例热点

这是示例正文。

`; test('convertGenericPageHtmlToWechatArticle extracts title and body', () => { const article = convertGenericPageHtmlToWechatArticle(SAMPLE_HTML, { pageTitle: '测试页面', pageSummary: '页面摘要', }); assert.equal(article.title, '测试页面'); assert.equal(article.digest, '页面摘要'); assert.match(article.content, /正文内容/); }); test('resolvePagePublicUrl prefers publication slug url over empty workspace path', () => { const url = resolvePagePublicUrl( { publicationUrl: null, workspaceRelativePath: null }, { userId: 'user-1', publication: { publicUrl: 'https://m.tkmind.cn/u/john/pages/tkmind-upgrade-sep2026' }, publicBaseUrl: 'https://m.tkmind.cn', }, ); assert.equal(url, 'https://m.tkmind.cn/u/john/pages/tkmind-upgrade-sep2026'); }); test('daily-news draft includes read original when publicUrl is present', () => { const article = buildDailyNewsWechatDraftArticle({ html: DAILY_NEWS_HTML, publicUrl: 'https://m.tkmind.cn/u/john/pages/tkmind-upgrade-sep2026', qrcodeImageUrl: 'https://mmbiz.qpic.cn/qrcode.png', }); const readIdx = article.content.indexOf('阅读原文'); const followIdx = article.content.indexOf('欢迎关注 TKMind 智趣'); assert.ok(readIdx >= 0, '缺少阅读原文'); assert.ok(readIdx < followIdx, '阅读原文应在关注区之前'); assert.match(article.content, /完整网页版更精彩/); assert.match(article.content, /⬇ ⬇ ⬇/); assert.match(article.content, /width:120px;height:120px/); assert.match(article.content, /href="https:\/\/m\.tkmind\.cn\/u\/john\/pages\/tkmind-upgrade-sep2026"/); assert.equal(article.contentSourceUrl, 'https://m.tkmind.cn/u/john/pages/tkmind-upgrade-sep2026'); }); test('applyWechatDraftArticleLayout adds read original and follow footer', () => { const content = applyWechatDraftArticleLayout('

正文

', { publicUrl: 'https://m.tkmind.cn/u/demo/pages/test.html', qrcodeImageUrl: 'https://mmbiz.qpic.cn/qrcode.png', }); assert.match(content, /阅读原文/); assert.match(content, /欢迎关注 TKMind 智趣/); assert.match(content, /qrcode\.png/); assert.doesNotMatch(content, /点击阅读原文/); assert.ok(content.indexOf('正文') < content.indexOf('阅读原文')); assert.ok(content.indexOf('阅读原文') < content.indexOf('欢迎关注')); }); test('applyWechatDraftArticleLayout clips long body instead of required footer', () => { const content = applyWechatDraftArticleLayout(`

${'超长正文'.repeat(5000)}

`, { publicUrl: 'https://m.tkmind.cn/u/demo/pages/long.html', qrcodeImageUrl: 'https://mmbiz.qpic.cn/qrcode.png', }); assert.ok(content.length <= 20000); assert.match(content, /阅读原文/); assert.match(content, /欢迎关注 TKMind 智趣/); assert.ok(content.indexOf('阅读原文') < content.indexOf('欢迎关注')); }); test('convertPageHtmlToWechatArticle uses rich mindspace converter', () => { const article = convertPageHtmlToWechatArticle(POEM_HTML, { pageTitle: '测试页面', pageSummary: '页面摘要', }); assert.equal(article.title, '八月午后'); assert.match(article.content, /蝉声把正午拉得很长/); assert.match(article.content, /创作手记/); assert.match(article.content, /意象/); }); test('isNewsHotspotFormat ignores mindspace imagery cards', () => { assert.equal(isNewsHotspotFormat(POEM_HTML), false); assert.equal(resolveWechatDraftProfile(POEM_HTML), 'mindspace-page'); }); test('validatePageForWechatDraft warns for mindspace pages', () => { const result = validatePageForWechatDraft(SAMPLE_HTML, { pageTitle: '测试页面', pageSummary: '页面摘要', publicUrl: 'https://example.com/page.html', }); assert.equal(result.ok, true); assert.equal(result.profile, 'generic'); }); test('convertRichMindSpacePageHtmlToWechatArticle preserves poem sections', () => { const article = convertRichMindSpacePageHtmlToWechatArticle(POEM_HTML, { pageTitle: '八月午后', imageUrlMap: new Map([['hero.webp', 'https://mmbiz.qpic.cn/hero.png']]), }); assert.match(article.content, /蝉声把正午拉得很长/); assert.match(article.content, /声音的长度/); assert.match(article.content, /https:\/\/mmbiz\.qpic\.cn\/hero\.png/); }); test('convertRichMindSpacePageHtmlToWechatArticle preserves prose essay sections', () => { const article = convertRichMindSpacePageHtmlToWechatArticle(PROSE_HTML, { pageTitle: '秋庭小憩 · 散文', pageSummary: '午后庭院,桂花飘香', imageUrlMap: new Map([['qiuting-xiaoxi.thumbnail.svg', 'https://mmbiz.qpic.cn/thumb.png']]), }); assert.equal(article.title, '秋庭小憩'); assert.match(article.content, /一篇百字散文 · 秋日慢时光/); assert.match(article.content, /午后阳光斜洒庭院石阶/); assert.match(article.content, /墙外梧桐叶纷纷飘落/); assert.match(article.content, /🍂 秋日/); assert.match(article.content, /thumb\.png/); assert.doesNotMatch(article.content, /data-mindspace-public-share/); }); test('resolveWechatDraftProfile treats mindspace-cover pages as mindspace-page', () => { assert.equal(resolveWechatDraftProfile(BLOG_HTML), 'mindspace-page'); assert.equal(resolveWechatDraftProfile(STARTUP_HTML), 'mindspace-page'); }); test('convertRichMindSpacePageHtmlToWechatArticle extracts blog and startup templates via fallback', () => { const blog = convertRichMindSpacePageHtmlToWechatArticle(BLOG_HTML, { pageTitle: 'AI 与创作', pageSummary: '产品观察', }); assert.equal(blog.title, 'AI 与创作'); assert.match(blog.content, /工具正在改变内容生产/); assert.match(blog.content, /创作者需要新的工作流/); assert.doesNotMatch(blog.content, /data-mindspace-public-share/); const startup = convertRichMindSpacePageHtmlToWechatArticle(STARTUP_HTML, { pageTitle: '创业路演', }); assert.equal(startup.title, '让 AI 更懂业务'); assert.match(startup.content, /面向团队的知识助手/); assert.match(startup.content, /帮助每个团队拥有自己的 AI 大脑/); }); test('collectPageImageCandidates adds sidecar thumbnail when hero image missing', () => { const publishDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mindspace-wechat-draft-')); const htmlRelativePath = 'public/no-hero-page.html'; const htmlPath = path.join(publishDir, htmlRelativePath); fs.mkdirSync(path.dirname(htmlPath), { recursive: true }); fs.writeFileSync( htmlPath, '无封面页

无封面页

正文

', 'utf8', ); fs.writeFileSync(htmlPath.replace(/\.html$/i, '.thumbnail.png'), Buffer.from('png'), 'utf8'); const candidates = mindspaceWechatPageDraftInternals.collectPageImageCandidates( fs.readFileSync(htmlPath, 'utf8'), { publishDir, htmlRelativePath }, ); assert.ok(candidates.some((item) => /\.thumbnail\.(png|svg)$/i.test(item.ref ?? ''))); }); test('convertGenericPageHtmlToWechatArticle leaves length clipping to layout', () => { const longBody = `

${'很长'.repeat(12000)}

`; const article = mindspaceWechatPageDraftInternals.convertGenericPageHtmlToWechatArticle( `${longBody}`, { pageTitle: '长文', publicUrl: 'https://m.tkmind.cn/u/demo/pages/long.html' }, ); const laidOut = applyWechatDraftArticleLayout(article.content, { publicUrl: 'https://m.tkmind.cn/u/demo/pages/long.html', }); assert.ok(article.content.length > 20000); assert.ok(laidOut.length <= 20000); assert.match(laidOut, /阅读原文/); assert.match(laidOut, /欢迎关注 TKMind 智趣/); }); test('resolveWechatDraftThumbPath prefers hero image over thumbnail sidecar', () => { const userId = 'test-user-hero-pref'; const root = fs.mkdtempSync(path.join(os.tmpdir(), 'wechat-thumb-hero-')); const publishDir = path.join(root, 'MindSpace', userId); const publicDir = path.join(publishDir, 'public'); fs.mkdirSync(publicDir, { recursive: true }); const heroPath = path.join(publicDir, 'images', 'hero-ca5a45b835d8.webp'); fs.mkdirSync(path.dirname(heroPath), { recursive: true }); fs.writeFileSync(heroPath, Buffer.from('webp')); fs.writeFileSync(path.join(publicDir, 'august-afternoon.thumbnail.png'), Buffer.from('png')); const html = `八月午后

标题

`; fs.writeFileSync(path.join(publicDir, 'august-afternoon.html'), html, 'utf8'); const resolved = resolveWechatDraftThumbPath(html, { h5Root: root, userId, workspaceRelativePath: 'public/august-afternoon.html', publishDir, }); assert.equal(resolved.source, 'hero'); assert.match(resolved.path ?? '', /hero-ca5a45b835d8\.webp$/); }); test('resolveWechatDraftThumbPath falls back to raster sidecar when hero missing', () => { const userId = 'test-user-sidecar-fallback'; const root = fs.mkdtempSync(path.join(os.tmpdir(), 'wechat-thumb-sidecar-')); const publishDir = path.join(root, 'MindSpace', userId); const publicDir = path.join(publishDir, 'public'); fs.mkdirSync(publicDir, { recursive: true }); fs.writeFileSync(path.join(publicDir, 'august-afternoon.thumbnail.png'), Buffer.from('png')); const html = `无 hero

标题

`; fs.writeFileSync(path.join(publicDir, 'august-afternoon.html'), html, 'utf8'); const resolved = resolveWechatDraftThumbPath(html, { h5Root: root, userId, workspaceRelativePath: 'public/august-afternoon.html', publishDir, }); assert.equal(resolved.source, 'thumbnail-sidecar'); assert.match(resolved.path ?? '', /august-afternoon\.thumbnail\.png$/); });