Files
trackgo-fe/.agents/skills/archify/test/route-share-card.test.mjs
T
shancheas 166e0d40ac feat: introduce archify skill for generating architecture diagrams
- Added a new Archify skill, enabling users to create polished architecture, workflow, sequence, data-flow, and lifecycle diagrams.
- Implemented comprehensive functionality including rendering, validation, and delivery of diagrams in various formats.
- Integrated a user-friendly command-line interface for generating and previewing diagrams.
- Developed supporting files including package.json, LICENSE, and SKILL.md for documentation and licensing.
- Added unit tests to ensure reliability and functionality of the new skill.

These changes enhance the application by providing a structured approach to visualizing system architecture and workflows, improving user experience and data representation.
2026-08-31 11:31:29 +07:00

164 lines
9.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const repoRoot = path.resolve(skillRoot, '..');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-route-share-card-'));
const CASES = {
architecture: 'web-app.architecture.json',
workflow: 'agent-tool-call.workflow.json',
sequence: 'cache-miss-request.sequence.json',
dataflow: 'product-analytics.dataflow.json',
lifecycle: 'agent-run.lifecycle.json',
};
function render(mode, example) {
const output = path.join(tmp, `${mode}.html`);
execFileSync(process.execPath, [
path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`),
path.join(skillRoot, 'examples', example),
output,
]);
return fs.readFileSync(output, 'utf8');
}
function canonicalSvg(html) {
return html.match(/<svg\b[\s\S]*?<\/svg>/)?.[0] || '';
}
test('all five renderers inherit one resolved-only Route Share Card Export item', () => {
for (const [mode, example] of Object.entries(CASES)) {
const html = render(mode, example);
assert.match(html, /data-action="route-share-card"[^>]*hidden disabled[^>]*>[\s\S]*?Route Share Card[\s\S]*?1200(?:&times;|×)630 PNG/, mode);
assert.match(html, /function syncRouteShareItem\(\)/, mode);
assert.match(html, /routeShareItem\.hidden = !snapshot;/, mode);
assert.match(html, /routeShareItem\.disabled = !snapshot;/, mode);
assert.match(html, /\.toolbar \.export-menu button\[hidden\] \{ display: none; \}/, mode);
assert.doesNotMatch(html, /id="route-probe-share"|class="route-probe-share"/, mode);
assert.doesNotMatch(canonicalSvg(html), /data-share-route(?:-|=)/, mode);
}
});
test('Route Share Card menu lifecycle excludes hidden state from keyboard navigation', () => {
const html = render('workflow', CASES.workflow);
assert.match(html, /function open\(focusLast\)[\s\S]*?syncRouteShareItem\(\);/);
assert.match(html, /items\(\)\.filter\(function \(i\) \{ return !i\.hidden && !i\.disabled; \}\)/);
assert.match(html, /function clear\(options\)[\s\S]*?Archify\.exportMenu\.syncRouteShare\(\)/);
assert.match(html, /function showResult\(result, options\)[\s\S]*?Archify\.exportMenu\.syncRouteShare\(\)/);
assert.match(html, /html\[data-embed="true"\] \.toolbar/);
assert.match(html, /@media print[\s\S]*?\.toolbar/);
});
test('Route Share Card snapshots copy exact resolved node and relationship identity without rerouting', () => {
const html = render('architecture', CASES.architecture);
const snapshotBlock = html.match(/function exportSnapshot\(\) \{[\s\S]*?\n \}/)?.[0] || '';
const geometryBlock = html.match(/function hasDrawableGeometry\(element\) \{[\s\S]*?\n \}/)?.[0] || '';
assert.match(snapshotBlock, /nodeIds: activeNodeIds\.slice\(\)/);
assert.match(snapshotBlock, /hops: activeEdges\.length/);
assert.match(snapshotBlock, /edges: activeEdges\.map\(function \(edge\) \{/);
assert.match(snapshotBlock, /key: edge\.getAttribute\('data-edge-key'\)/);
assert.match(snapshotBlock, /seenNodeIds = Object\.create\(null\)/);
assert.match(snapshotBlock, /seenEdgeKeys = Object\.create\(null\)/);
assert.match(snapshotBlock, /fragment\.getAttribute\('data-edge-from'\) === activeNodeIds\[index\]/);
assert.match(snapshotBlock, /drawableFragments = fragments\.filter\(hasDrawableGeometry\)/);
assert.match(snapshotBlock, /drawableFragments\.length !== 1/);
assert.match(snapshotBlock, /drawableFragments\[0\] !== edge/);
assert.match(geometryBlock, /geometry\.getTotalLength/);
assert.match(geometryBlock, /Number\.isFinite\(length\) && length > 0/);
assert.match(geometryBlock, /nan\|infinity/i);
assert.match(html, /exportSnapshot: exportSnapshot/);
assert.doesNotMatch(snapshotBlock, /shortestDirectedPath|outgoingByNode|reachableFrom|labelAt|nearest/i);
});
test('Route Share Card snapshot fails closed when the resolved DOM becomes stale or conflicting', () => {
const html = render('workflow', CASES.workflow);
const snapshotBlock = html.match(/function exportSnapshot\(\) \{[\s\S]*?\n \}/)?.[0] || '';
assert.match(snapshotBlock, /activeNodeIds\.length < 2/);
assert.match(snapshotBlock, /activeEdges\.length !== activeNodeIds\.length - 1/);
assert.match(snapshotBlock, /allNodes\.filter\(function \(node\)/);
assert.match(snapshotBlock, /!edge \|\| !svg\.contains\(edge\)/);
assert.match(snapshotBlock, /!edgeKey \|\| seenEdgeKeys\[edgeKey\]/);
assert.match(snapshotBlock, /!fragments\.every/);
assert.match(snapshotBlock, /return null/);
});
test('Route variant decorates only a finite canonical clone with dedicated static attributes', () => {
const html = render('architecture', CASES.architecture);
const applyBlock = html.match(/function applyRouteSnapshot\(clone, snapshot\) \{[\s\S]*?\n \}/)?.[0] || '';
assert.match(applyBlock, /snapshot\.edges\.length !== snapshot\.nodeIds\.length - 1/);
assert.match(applyBlock, /snapshot\.hops !== snapshot\.edges\.length/);
assert.match(applyBlock, /matchedNodes\.length !== 1/);
assert.match(applyBlock, /matchedEdges\.every/);
assert.match(applyBlock, /drawableMatches = matchedEdges\.filter\(hasDrawableGeometry\)/);
assert.match(applyBlock, /drawableMatches\.length !== 1/);
assert.match(applyBlock, /data-share-route-match/);
assert.match(applyBlock, /data-share-route-start/);
assert.match(applyBlock, /data-share-route-middle/);
assert.match(applyBlock, /data-share-route-end/);
assert.match(applyBlock, /clone\.removeAttribute\('data-animation'\)/);
assert.doesNotMatch(applyBlock, /setAttribute\('data-route-(?:match|step|start|end|active|journey)/);
assert.match(html, /canonicalStateClean && finiteSvgDimensions && applyRouteSnapshot\(clone, opts\.routeSnapshot\)/);
assert.match(html, /Number\.isFinite\(vb\.width\)[\s\S]*?vb\.width > 0 && vb\.height > 0/);
assert.ok(html.indexOf('var canonicalStateClean =') < html.indexOf('applyRouteSnapshot(clone, opts.routeSnapshot)'), 'canonical cleanup must precede route decoration');
});
test('clone-only Route styling retains context and distinguishes start, middle, and end without motion', () => {
const html = render('dataflow', CASES.dataflow);
assert.match(html, /svg\[data-share-route\] \[data-node-id\], svg\[data-share-route\] \[data-edge-from\] \{ opacity: 0\.18; \}/);
assert.match(html, /svg\[data-share-route\] \[data-share-route-match\] \{ opacity: 1; \}/);
assert.match(html, /data-share-route-start[\s\S]*?stroke-dasharray: 5 3/);
assert.match(html, /data-share-route-middle[\s\S]*?stroke-width: 2\.2/);
assert.match(html, /data-share-route-end[\s\S]*?stroke-width: 3\.4/);
assert.doesNotMatch(html.match(/if \(opts\.routeSnapshot\) \{[\s\S]*?\n \}/)?.[0] || '', /display:\s*none|animation:|filter:|transform:/);
});
test('Route Share Card reuses one 1200x630 variant seam and publishes a truthful receipt', () => {
const html = render('architecture', CASES.architecture);
assert.match(html, /function rasterizeShareCard\(options\)/);
assert.match(html, /options\.variant !== 'route'/);
assert.match(html, /var snapshot = Archify\.routeProbe && Archify\.routeProbe\.exportSnapshot\(\)/);
assert.match(html, /renderShareCard\(\{ routeSnapshot: snapshot \}\)/);
assert.doesNotMatch(html, /function rasterizeRouteShareCard|routeShareCard:/);
assert.match(html, /var title = titleNode \? titleNode\.textContent : document\.title;/);
assert.match(html, /viewerCount\('viewer\.export\.card\.routeSummary', routeSnapshot\.hops/);
assert.match(html, /source: routeSnapshot\.source\.label/);
assert.match(html, /target: routeSnapshot\.target\.label/);
assert.match(html, /recordExportReceipt\('share-card', blob, false, \{ width: SHARE_CARD_WIDTH, height: SHARE_CARD_HEIGHT \}, 'route', true\)/);
assert.match(html, /diagramFilename\(\) \+ '-route-share-card\.png'/);
assert.match(html, /data-last-export-variant/);
assert.match(html, /data-last-export-route-state-clean/);
assert.match(html, /clearExportReceipt\(\);[\s\S]*?var snapshot = Archify\.routeProbe/);
assert.match(html, /function runExport\(format\)[\s\S]*?clearExportReceipt\(\);/);
assert.match(html, /var ctx = canvas2dOrThrow\(canvas, viewerText\('viewer\.export\.shareCard'\)\)/);
});
test('skill and READMEs describe the optional Export variant and show one real card without changing the hero', () => {
const viewer = fs.readFileSync(path.join(skillRoot, 'references', 'viewer-runtime.md'), 'utf8');
assert.match(viewer, /Export → Route Share Card/);
assert.match(viewer, /format=share-card/);
assert.match(viewer, /variant=route/);
assert.match(viewer, /data-share-route-\*/);
assert.match(viewer, /download-only/i);
for (const readme of ['README.md', 'README_EN.md', 'README_ZH.md']) {
const text = fs.readFileSync(path.join(repoRoot, readme), 'utf8');
assert.match(text, /Export → Route Share Card/, readme);
assert.match(text, /docs\/assets\/archify-route-share-card\.png/, readme);
}
const png = fs.readFileSync(path.join(repoRoot, 'docs/assets/archify-route-share-card.png'));
assert.equal(png.subarray(0, 8).toString('hex'), '89504e470d0a1a0a');
assert.equal(png.readUInt32BE(16), 1200);
assert.equal(png.readUInt32BE(20), 630);
});
process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));