Files
memind/page-template-enforcement.test.mjs
T
john fde6503bdf
Memind CI / Test, build, and release guards (push) Has been cancelled
feat(mindspace): add SEO/GEO delivery, page template catalog, and admin hooks
Enable optional SEO/GEO injection and discovery routes for confirmed public pages while keeping private pages noindex. Add premium page template skills, portal catalog API, template shop UI, and Baidu push gated by memind_adm config.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-10 08:06:12 +08:00

102 lines
4.1 KiB
JavaScript

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 {
buildPageTemplateMandatoryInstruction,
collectPageTemplateViolations,
extractStyleSelectors,
resolveSelectedPageTemplateSkill,
validatePublishedPageAgainstTemplate,
} from './page-template-enforcement.mjs';
test('resolveSelectedPageTemplateSkill reads latest user selectedChatSkill', () => {
const skill = resolveSelectedPageTemplateSkill([
{ role: 'user', metadata: { memindRun: { selectedChatSkill: 'page-template-travel' } } },
{ role: 'assistant', content: [{ type: 'text', text: 'done' }] },
]);
assert.equal(skill, 'page-template-travel');
});
test('validatePublishedPageAgainstTemplate rejects extra css selectors', () => {
const templatePath = path.join(process.cwd(), 'skills/page-template-travel/templates/travel-hero.html');
const templateHtml = fs.readFileSync(templatePath, 'utf8');
const driftedHtml = templateHtml
.replace('{{PAGE_TITLE}}', '标题')
.replace('{{PAGE_DESCRIPTION}}', '摘要')
.replace('{{MINDSPACE_COVER_JSON}}', '{}')
.replace('{{ACCENT}}', '#0ea5e9')
.replace('{{ACCENT2}}', '#0369a1')
.replace('{{HERO_EYEBROW}}', '标签')
.replace('{{HERO_HEADLINE}}', '主标题')
.replace('{{HERO_SUBTITLE}}', '副标题')
.replace('{{HERO_IMAGE}}', 'assets/hero.webp')
.replace('{{MAIN_CONTENT_HTML}}', '<p>正文</p>')
.replace(
'.content h2 { color: #fff; margin: 2.5rem 0 1rem; font-size: clamp(22px, 4vw, 28px); letter-spacing: -.01em; }',
'.content h2 { color: #fff; margin: 2.5rem 0 1rem; font-size: clamp(22px, 4vw, 28px); letter-spacing: -.01em; }\n .spot-card { padding: 12px; }',
);
const result = validatePublishedPageAgainstTemplate({
skillName: 'page-template-travel',
html: driftedHtml,
h5Root: process.cwd(),
});
assert.equal(result.ok, false);
assert.equal(result.reason, 'style_drift');
});
test('validatePublishedPageAgainstTemplate accepts filled travel template', () => {
const templatePath = path.join(process.cwd(), 'skills/page-template-travel/templates/travel-hero.html');
let html = fs.readFileSync(templatePath, 'utf8');
html = html
.replaceAll('{{PAGE_TITLE}}', '标题')
.replaceAll('{{PAGE_DESCRIPTION}}', '摘要')
.replaceAll('{{MINDSPACE_COVER_JSON}}', '{}')
.replaceAll('{{ACCENT}}', '#0ea5e9')
.replaceAll('{{ACCENT2}}', '#0369a1')
.replaceAll('{{HERO_EYEBROW}}', '标签')
.replaceAll('{{HERO_HEADLINE}}', '主标题')
.replaceAll('{{HERO_SUBTITLE}}', '副标题')
.replaceAll('{{HERO_IMAGE}}', 'assets/hero.webp')
.replaceAll('{{MAIN_CONTENT_HTML}}', '<h2>章节</h2><p>正文</p>');
const result = validatePublishedPageAgainstTemplate({
skillName: 'page-template-travel',
html,
h5Root: process.cwd(),
});
assert.equal(result.ok, true);
});
test('collectPageTemplateViolations scans touched public html files', () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'page-template-guard-'));
const publishDir = path.join(root, 'public');
fs.mkdirSync(publishDir, { recursive: true });
const xianjuPath = path.join(process.cwd(), 'MindSpace/5331128a-810e-42ef-b4f6-95a075d12787/public/xianju-water-guide.html');
if (fs.existsSync(xianjuPath)) {
fs.copyFileSync(xianjuPath, path.join(publishDir, 'xianju-water-guide.html'));
const violations = collectPageTemplateViolations({
messages: [
{
role: 'user',
metadata: { memindRun: { selectedChatSkill: 'page-template-travel' } },
},
],
publishDir: root,
syncResult: { publicHtmlRelativePaths: ['public/xianju-water-guide.html'] },
h5Root: process.cwd(),
});
assert.ok(violations.length >= 1);
assert.equal(violations[0].reason, 'style_drift');
} else {
assert.match(buildPageTemplateMandatoryInstruction('page-template-travel'), /禁止从零手写/);
}
});
test('extractStyleSelectors ignores at-rules', () => {
const selectors = extractStyleSelectors('@media (max-width: 600px) { .hero { color: red; } } .hero { color: blue; }');
assert.deepEqual([...selectors], ['.hero']);
});