docs: redesign Roadmap as a native five-stage workflow
Build and publish Eldorado documentation / build (pull_request) Successful in 39s
Build and publish Eldorado documentation / build (pull_request) Successful in 39s
This commit is contained in:
1 parent
2d63313180
commit
e149131307
13 files changed
+476
-237
No files matched your search
@@ -8,6 +8,50 @@ const fs = require('node:fs');
|
||||
const browser = await chromium.launch({headless:true, executablePath:process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE, args:['--no-sandbox']});
|
||||
const results = [];
|
||||
try {
|
||||
// Intermediate widths must use the readable vertical Roadmap, not shrink five cards.
|
||||
for (const width of [768, 1024, 1280]) {
|
||||
const context = await browser.newContext({viewport:{width,height:1000}, colorScheme:'light'});
|
||||
try {
|
||||
const page = await context.newPage();
|
||||
const errors = [];
|
||||
page.on('pageerror', error => errors.push(error.message));
|
||||
const response = await page.goto(`${base}/eldorado-v1/documentation/strategy/roadmap/`, {waitUntil:'networkidle'});
|
||||
assert.equal(response.status(), 200);
|
||||
const panel = page.locator('.archify-panel--story[data-diagram="strategy-roadmap"]');
|
||||
const iframe = panel.locator('.story-mobile');
|
||||
assert(await iframe.isVisible(), `${width}: vertical Roadmap iframe must be visible`);
|
||||
assert(!await panel.locator('.story-desktop').isVisible(), `${width}: horizontal Roadmap must be hidden`);
|
||||
const frame = await (await iframe.elementHandle()).contentFrame();
|
||||
await frame.locator('svg[role="img"]').waitFor();
|
||||
const geometry = await frame.locator('svg[role="img"]').evaluate(svg => ({
|
||||
width:innerWidth, height:innerHeight,
|
||||
overflow:document.documentElement.scrollWidth > innerWidth || document.documentElement.scrollHeight > innerHeight,
|
||||
nodes:[...svg.querySelectorAll('[data-node-id]')].map(node => ({
|
||||
id:node.dataset.nodeId, box:node.querySelector('rect').getBoundingClientRect().toJSON(),
|
||||
text:[...node.querySelectorAll('text')].map(t => ({text:t.textContent, box:t.getBoundingClientRect().toJSON(), size:parseFloat(getComputedStyle(t).fontSize)*t.getScreenCTM().a}))
|
||||
})).sort((a,b) => a.box.y-b.box.y || a.box.x-b.box.x)
|
||||
}));
|
||||
assert.deepEqual(geometry.nodes.map(n => n.id), ['assessment','readiness','pilot','expansion','sustain']);
|
||||
for (const [index, card] of geometry.nodes.entries()) {
|
||||
assert(card.text.length > 0, `${width}: missing text in ${card.id}`);
|
||||
assert(card.box.left >= 0 && card.box.top >= 0 && card.box.right <= geometry.width + 1 && card.box.bottom <= geometry.height + 1, `${width}: card outside iframe: ${card.id}`);
|
||||
for (const t of card.text) {
|
||||
assert(t.box.left >= card.box.left && t.box.right <= card.box.right + 1 && t.box.top >= card.box.top && t.box.bottom <= card.box.bottom + 1, `${width}: clipped ${card.id} ${t.text}`);
|
||||
assert(t.size >= 11, `${width}: small ${card.id} ${t.text}: ${t.size}`);
|
||||
}
|
||||
if (index) {
|
||||
const previous = geometry.nodes[index - 1].box;
|
||||
assert.equal(previous.x, card.box.x);
|
||||
assert(previous.bottom < card.box.top, `${width}: stages must flow vertically`);
|
||||
}
|
||||
}
|
||||
assert(!geometry.overflow, `${width}: iframe overflow`);
|
||||
assert(!await page.evaluate(() => document.documentElement.scrollWidth > innerWidth), `${width}: page overflow`);
|
||||
assert.deepEqual(errors, []);
|
||||
results.push({width, geometry, responsiveReadability:true});
|
||||
fs.writeFileSync(`${output}/results.json`, JSON.stringify(results,null,2));
|
||||
} finally {await context.close();}
|
||||
}
|
||||
for (const width of [1440, 390, 320]) {
|
||||
const context = await browser.newContext({viewport:{width,height:1000}, colorScheme:'light'});
|
||||
const page = await context.newPage();
|
||||
@@ -31,28 +75,61 @@ const fs = require('node:fs');
|
||||
await iframe.scrollIntoViewIfNeeded();
|
||||
const frame = await (await iframe.elementHandle()).contentFrame();
|
||||
await frame.locator('svg[role="img"]').waitFor();
|
||||
const checkCards = async () => {
|
||||
const cards = await frame.locator('svg[role="img"] [data-node-id]').evaluateAll(nodes => nodes.map(node => {
|
||||
const box = node.querySelector('rect').getBoundingClientRect();
|
||||
return {id:node.dataset.nodeId, box:box.toJSON(), text:[...node.querySelectorAll('text')].map(t => ({text:t.textContent, box:t.getBoundingClientRect().toJSON(), size:parseFloat(getComputedStyle(t).fontSize)*t.getScreenCTM().a}))};
|
||||
}));
|
||||
for (const card of cards) for (const t of card.text) {
|
||||
assert(t.box.left >= card.box.left && t.box.right <= card.box.right + 1 && t.box.top >= card.box.top && t.box.bottom <= card.box.bottom + 1, `${width}: clipped ${card.id} ${t.text}`);
|
||||
assert(t.size >= 11, `${width}: small ${card.id} ${t.text}: ${t.size}`);
|
||||
}
|
||||
return cards;
|
||||
};
|
||||
await checkCards();
|
||||
const geometry = await frame.locator('svg[role="img"]').evaluate(svg => ({
|
||||
nodes:[...svg.querySelectorAll('[data-node-id]')].map(n => ({id:n.dataset.nodeId,...n.getBoundingClientRect().toJSON()})),
|
||||
nodes:[...svg.querySelectorAll('[data-node-id]')].map(n => ({id:n.dataset.nodeId,...n.getBoundingClientRect().toJSON()})).sort((a,b) => a.y-b.y || a.x-b.x),
|
||||
width:innerWidth, height:innerHeight,
|
||||
minLabel:Math.min(...[...svg.querySelectorAll('text[data-node-label]')].map(n => parseFloat(getComputedStyle(n).fontSize)*n.getScreenCTM().a))
|
||||
}));
|
||||
assert.deepEqual(geometry.nodes.map(n => n.id), ['assessment','readiness','pilot','expansion','sustain']);
|
||||
for (const n of geometry.nodes) assert(n.x >= 0 && n.y >= 0 && n.right <= geometry.width + 1 && n.bottom <= geometry.height + 1);
|
||||
assert(geometry.minLabel >= 10, `readable node labels: ${geometry.minLabel}`);
|
||||
for (let i=1; i<geometry.nodes.length; i++) {
|
||||
const a=geometry.nodes[i-1], b=geometry.nodes[i];
|
||||
if(width > 700) {assert.equal(a.y,b.y); assert(a.right < b.x);}
|
||||
else {assert.equal(a.x,b.x); assert(a.bottom < b.y);}
|
||||
}
|
||||
// Portal -> diagram and diagram -> portal are both part of the contract.
|
||||
if (width <= 700) await page.getByRole('button', {name:'Toggle navigation bar'}).click();
|
||||
for (let attempt=0; attempt<3; attempt++) {
|
||||
await page.getByRole('button', {name:/Switch between dark and light mode/}).filter({visible:true}).click();
|
||||
if (await page.locator('html').getAttribute('data-theme') === 'dark') break;
|
||||
}
|
||||
if (width <= 700) await page.getByRole('button', {name:'Close navigation bar'}).click();
|
||||
await frame.waitForFunction(() => document.documentElement.dataset.theme === 'dark');
|
||||
await frame.locator('#btn-theme').click();
|
||||
await page.waitForFunction(() => document.documentElement.dataset.theme === 'light');
|
||||
await frame.locator('#btn-theme').click();
|
||||
await page.waitForFunction(() => document.documentElement.dataset.theme === 'dark');
|
||||
await checkCards();
|
||||
await frame.locator('#btn-theme').click();
|
||||
await page.waitForFunction(() => document.documentElement.dataset.theme === 'light');
|
||||
for (const preset of ['blueprint','classic']) {
|
||||
await frame.locator('#btn-preset').click();
|
||||
await frame.locator(`[data-preset-value="${preset}"]`).click();
|
||||
assert.equal(await frame.locator('html').getAttribute('data-preset'), preset);
|
||||
await checkCards();
|
||||
}
|
||||
await frame.locator('#btn-export').click();
|
||||
const [download] = await Promise.all([page.waitForEvent('download'), frame.locator('[data-format="svg"]').click()]);
|
||||
assert.equal(await download.failure(), null);
|
||||
await download.saveAs(`${output}/${width}.svg`);
|
||||
assert(fs.readFileSync(`${output}/${width}.svg`, 'utf8').includes('Assess current state'));
|
||||
assert(fs.readFileSync(`${output}/${width}.svg`, 'utf8').includes('Understand today'));
|
||||
assert(fs.readFileSync(`${output}/${width}.svg`, 'utf8').includes('eldorado-roadmap-typography'));
|
||||
for (const id of ['assessment','readiness','pilot','expansion','sustain']) {
|
||||
await frame.locator(`svg[role="img"] [data-node-id="${id}"]`).screenshot({path:`${output}/${width}-${id}-card.png`});
|
||||
}
|
||||
const opener = panel.getByRole('button', {name:/^Expand /});
|
||||
await opener.click();
|
||||
const close = panel.getByRole('button', {name:'Close full-screen diagram', exact:true});
|
||||
@@ -67,8 +144,9 @@ const fs = require('node:fs');
|
||||
await headings.first().scrollIntoViewIfNeeded();
|
||||
await page.screenshot({path:`${output}/${width}-assessment.png`});
|
||||
await page.screenshot({path:`${output}/${width}-full.png`, fullPage:true});
|
||||
await page.locator('.strategy-content summary').click();
|
||||
assert(await page.locator('.strategy-content details').evaluate(n => n.open));
|
||||
await page.locator('.strategy-content summary').focus();
|
||||
await page.keyboard.press('Enter');
|
||||
await page.waitForFunction(() => document.querySelector('.strategy-content details').open);
|
||||
await page.locator('.strategy-content details p').last().waitFor({state:'visible'});
|
||||
assert((await page.locator('.strategy-content details').innerText()).includes('exact-artifact approvals'));
|
||||
const links = await page.locator('.strategy-content a').evaluateAll(nodes => nodes.filter(n => !n.classList.contains('hash-link')).map(n => n.href));
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import {readdirSync, readFileSync, writeFileSync, mkdirSync, copyFileSync, renameSync, rmSync} from 'node:fs';
|
||||
import {join} from 'node:path';
|
||||
import {execFileSync} from 'node:child_process';
|
||||
import {roadmapViewer} from './roadmap-viewer.mjs';
|
||||
import {createHash} from 'node:crypto';
|
||||
const cli = 'vendor/archify/bin/archify.mjs';
|
||||
const types = new Set(['architecture','workflow','sequence','dataflow','lifecycle']);
|
||||
const inlineStyles = readFileSync(new URL('./inline-viewer.css', import.meta.url), 'utf8');
|
||||
@@ -23,9 +25,18 @@ for (const file of readdirSync('diagrams').sort()) {
|
||||
mkdirSync(`static/diagrams/${name}`, {recursive: true});
|
||||
if (spec.diagram_type !== type || spec.meta.output !== output) throw new Error(`Diagram type/output mismatch: ${file}`);
|
||||
const run = args => execFileSync(process.execPath,[cli,...args],{stdio:'inherit',env:{...process.env,ARCHIFY_UPDATE_CHECK_DISABLED:'1'}});
|
||||
run(['validate',type,input,'--quality','showcase','--json']);
|
||||
run(['deliver',type,input,output,'--quality','showcase','--json']);
|
||||
run(['check',output,'--json','--require-provenance']);
|
||||
const roadmap = ['strategy-roadmap', 'strategy-roadmap-mobile'].includes(name);
|
||||
if (roadmap) {
|
||||
// Native renderer validates schema and compiles layout. Check readability
|
||||
// after multiline typography, not upstream's single-line preview.
|
||||
execFileSync(process.execPath, ['vendor/archify/renderers/workflow/render-workflow.mjs', input, output], {stdio:'inherit', env:{...process.env, ARCHIFY_UPDATE_CHECK_DISABLED:'1'}});
|
||||
writeFileSync(output, roadmapViewer(readFileSync(output, 'utf8'), spec));
|
||||
run(['check',output,'--json']);
|
||||
} else {
|
||||
run(['validate',type,input,'--quality','showcase','--json']);
|
||||
run(['deliver',type,input,output,'--quality','showcase','--json']);
|
||||
run(['check',output,'--json','--require-provenance']);
|
||||
}
|
||||
// Verify the upstream artifact first, then add the scoped portal integration.
|
||||
let viewer = readFileSync(output, 'utf8');
|
||||
// Upstream intentionally disables preset selection in gallery embeds. Enable
|
||||
@@ -48,7 +59,14 @@ for (const file of readdirSync('diagrams').sort()) {
|
||||
}
|
||||
writeFileSync(output, viewer.replace('</head>', `<style id="eldorado-inline-controls">${inlineStyles}</style><style id="eldorado-cycle-labels">${storyStyles}</style></head>`)
|
||||
.replace('</body>', `<script>${themeBridge}</script></body>`));
|
||||
renameSync(`static/diagrams/${name}/index.delivery.json`, `.cache/archify/${name}.delivery.json`);
|
||||
if (roadmap) {
|
||||
// This is a portal receipt, not upstream deliver provenance: formatting
|
||||
// happens between the native schema/layout compiler and artifact checker.
|
||||
const hash = path => createHash('sha256').update(readFileSync(path)).digest('hex');
|
||||
const checked = JSON.parse(execFileSync(process.execPath, [cli,'check',output,'--json'], {encoding:'utf8', env:{...process.env, ARCHIFY_UPDATE_CHECK_DISABLED:'1'}}));
|
||||
if (!checked.ok || checked.composition?.status !== 'pass') throw new Error('Roadmap final artifact failed');
|
||||
writeFileSync(`.cache/archify/${name}.portal.json`, JSON.stringify({source:input, sourceSha256:hash(input), adapterSha256:hash('scripts/roadmap-viewer.mjs'), artifactSha256:hash(output), checker:checked}, null, 2));
|
||||
} else renameSync(`static/diagrams/${name}/index.delivery.json`, `.cache/archify/${name}.delivery.json`);
|
||||
}
|
||||
copyFileSync('vendor/archify/LICENSE','static/diagrams/ARCHIFY-LICENSE.txt');
|
||||
copyFileSync('vendor/archify/THIRD_PARTY_NOTICES.md','static/diagrams/THIRD-PARTY-NOTICES.md');
|
||||
@@ -0,0 +1,65 @@
|
||||
// Native Workflow v2 owns node geometry, mainPath and routing. The portal adds
|
||||
// multiline typography: upstream's three single-line labels are too small for
|
||||
// a five-stage reading view. Do not change the pinned vendor or graph semantics.
|
||||
const escape = text => text.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"');
|
||||
const wrap = (text, limit = 21) => {
|
||||
const lines = [];
|
||||
for (const word of text.split(/\s+/)) {
|
||||
if (!lines.length || `${lines.at(-1)} ${word}`.length > limit) lines.push(word);
|
||||
else lines[lines.length - 1] += ` ${word}`;
|
||||
}
|
||||
return lines;
|
||||
};
|
||||
export function roadmapViewer(html, spec) {
|
||||
if (spec.diagram_type !== 'workflow' || spec.schema_version !== 2 || spec.nodes.length !== 5) throw new Error('Roadmap workflow contract drift');
|
||||
const boxes = [];
|
||||
for (const [index, node] of spec.nodes.entries()) {
|
||||
const start = html.indexOf(`<g id="node-${node.id}"`);
|
||||
const next = html.indexOf('<text data-node-label=', start);
|
||||
const end = html.indexOf('</g>', next) + 4;
|
||||
if (start < 0 || next < start || end < next) throw new Error(`Roadmap node drift: ${node.id}`);
|
||||
const original = html.slice(start, end);
|
||||
const rect = /<rect x="([\d.-]+)" y="([\d.-]+)" width="([\d.]+)" height="([\d.]+)"/.exec(original);
|
||||
if (!rect || (original.match(/<text /g) || []).length !== 3) throw new Error(`Roadmap text contract drift: ${node.id}`);
|
||||
const [x,y,w,h] = rect.slice(1).map(Number);
|
||||
boxes.push({x,y,w,h});
|
||||
const text = (value, top, size, cls, attrs = 'data-detail="context"') => {
|
||||
const lines = wrap(value, size === 30 ? 18 : 21);
|
||||
const maxLines = top === 166 || top === 310 ? 3 : 2;
|
||||
if (lines.length > maxLines || lines.some(line => line.length > 21)) throw new Error(`Roadmap copy exceeds card capacity: ${node.id}`);
|
||||
return `<text x="${x+24}" y="${y+top}" font-size="${size}" class="${cls}" ${attrs}>${lines.map((line,i) => `<tspan x="${x+24}" dy="${i ? 34 : 0}">${escape(line)}</tspan>`).join('')}</text>`;
|
||||
};
|
||||
const [action, output] = node.sublabel.split('\nOutput: ');
|
||||
if (!action || !output || !node.tag.startsWith('Exit: ')) throw new Error(`Roadmap content contract drift: ${node.id}`);
|
||||
const content = text(String(index+1).padStart(2,'0'), 40, 26, 't-muted')
|
||||
+ text(node.label, 88, 30, 't-primary', 'data-node-label="" font-weight="700"')
|
||||
+ text(action, 166, 26, 't-primary')
|
||||
+ text('OUTPUT', 274, 20, 't-muted', 'font-weight="700"')
|
||||
+ text(output, 310, 26, 't-primary')
|
||||
+ text(node.tag, 416, 24, 't-primary');
|
||||
// Preserve native node title/metadata and rectangles; replace sigil/text only.
|
||||
// Sigils are nested SVG groups. Anchor on the two native node rectangles.
|
||||
const rectangles = [...original.matchAll(/<rect\b[^>]*\/>/g)];
|
||||
if (rectangles.length !== 2) throw new Error('Roadmap shape contract drift');
|
||||
const prefixEnd = rectangles[1].index + rectangles[1][0].length;
|
||||
const replacement = original.slice(0,prefixEnd).replaceAll('rx="6"', 'rx="0"') + content + '</g>';
|
||||
html = html.slice(0,start) + replacement + html.slice(end);
|
||||
}
|
||||
const left = Math.min(...boxes.map(b => b.x)) - 12;
|
||||
const top = Math.min(...boxes.map(b => b.y)) - 12;
|
||||
const width = Math.max(...boxes.map(b => b.x+b.w)) - left + 12;
|
||||
const height = Math.max(...boxes.map(b => b.y+b.h)) - top + 12;
|
||||
// One editorial lane; its title/frame would repeat the page's framing.
|
||||
html = html.replace(/<rect\b[^>]*data-composition-frame-kind="lane"[^>]*\/>\s*<text\b[^>]*>[\s\S]*?<\/text>/g, '');
|
||||
const roots = [...html.matchAll(/<svg\b[^>]*role="img"[^>]*>/g)];
|
||||
if (roots.length !== 1) throw new Error('Roadmap SVG contract drift');
|
||||
const root = roots[0][0].replace(/viewBox="[^"]*"/, `viewBox="${left} ${top} ${width} ${height}"`);
|
||||
const styles = `<style id="eldorado-roadmap-typography">
|
||||
[data-node-id] text {font-family: Arial, sans-serif;}
|
||||
svg[data-preset="classic"] [data-node-id] > rect.c-backend,
|
||||
svg[data-preset="editorial"] [data-node-id] > rect.c-backend {fill:var(--panel); stroke:var(--border);}
|
||||
svg[data-preset="classic"] .c-grid {opacity:0;}
|
||||
</style>`;
|
||||
html = html.replace(roots[0][0], root + styles);
|
||||
return html;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import {test} from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import {readFileSync, writeFileSync, mkdtempSync, rmSync, mkdirSync} from 'node:fs';
|
||||
import {execFileSync} from 'node:child_process';
|
||||
import {roadmapViewer} from './roadmap-viewer.mjs';
|
||||
|
||||
for (const suffix of ['', '-mobile']) test(`native Workflow${suffix} retains graph and passes final composition`, () => {
|
||||
mkdirSync('.cache', {recursive:true});
|
||||
const dir = mkdtempSync('.cache/roadmap-test-');
|
||||
try {
|
||||
const source = `diagrams/strategy-roadmap${suffix}.workflow.json`;
|
||||
const spec = JSON.parse(readFileSync(source, 'utf8'));
|
||||
const output = `${dir}/index.html`;
|
||||
execFileSync(process.execPath, ['vendor/archify/renderers/workflow/render-workflow.mjs', source, output], {env:{...process.env, ARCHIFY_UPDATE_CHECK_DISABLED:'1'}});
|
||||
const native = readFileSync(output, 'utf8');
|
||||
const styled = roadmapViewer(native, spec);
|
||||
const graph = html => [...html.matchAll(/<path\b[^>]*data-edge-id="[^"]+"[^>]*>/g)].map(m => m[0]);
|
||||
assert.equal(graph(native).length, 4);
|
||||
assert.deepEqual(graph(styled), graph(native), 'native paths must not be rewritten');
|
||||
for (const node of spec.nodes) assert(styled.includes(`data-node-id="${node.id}"`));
|
||||
assert.equal((styled.match(/<text data-node-label=/g) || []).length, 0, 'single-line preview replaced');
|
||||
assert(styled.includes('eldorado-roadmap-typography'));
|
||||
writeFileSync(output, styled);
|
||||
const checked = JSON.parse(execFileSync(process.execPath, ['vendor/archify/bin/archify.mjs','check',output,'--json'], {encoding:'utf8', env:{...process.env, ARCHIFY_UPDATE_CHECK_DISABLED:'1'}}));
|
||||
assert(checked.ok);
|
||||
assert.equal(checked.composition.status, 'pass');
|
||||
assert.equal(checked.composition.metrics.properCrossings, 0);
|
||||
assert.equal(checked.composition.metrics.labelCanvasOverflowIssues, 0);
|
||||
assert.throws(() => roadmapViewer('', spec), /drift/);
|
||||
const oversized = structuredClone(spec);
|
||||
oversized.nodes[0].sublabel = 'Too much copy '.repeat(20) + '\nOutput: baseline';
|
||||
assert.throws(() => roadmapViewer(native, oversized), /capacity/);
|
||||
assert.throws(() => roadmapViewer(native, {...spec,diagram_type:'architecture'}), /contract drift/);
|
||||
} finally {rmSync(dir, {recursive:true, force:true});}
|
||||
});
|
||||
@@ -19,11 +19,18 @@ test('Roadmap visibly assesses code and real delivery before adoption', () => {
|
||||
test('both roadmap diagrams make assessment and readiness prerequisites to the pilot', () => {
|
||||
const ids = ['assessment', 'readiness', 'pilot', 'expansion', 'sustain'];
|
||||
for (const suffix of ['', '-mobile']) {
|
||||
const diagram = JSON.parse(read(`diagrams/strategy-roadmap${suffix}.architecture.json`));
|
||||
assert.deepEqual(diagram.components.map(n => n.id), ids);
|
||||
assert.deepEqual(diagram.connections.map(e => [e.from,e.to]), ids.slice(1).map((id, i) => [ids[i],id]));
|
||||
assert.match(diagram.components[0].sublabel, /Code quality \+ delivery flow/);
|
||||
assert.match(diagram.components[1].sublabel, /Critical gaps/);
|
||||
assert.match(diagram.components[4].label, /Sustain/);
|
||||
const diagram = JSON.parse(read(`diagrams/strategy-roadmap${suffix}.workflow.json`));
|
||||
assert.equal(diagram.schema_version, 2);
|
||||
assert.equal(diagram.diagram_type, 'workflow');
|
||||
assert.deepEqual(diagram.mainPath, ids);
|
||||
assert.deepEqual(diagram.nodes.map(n => n.id), ids);
|
||||
assert.deepEqual(diagram.nodes.map(n => n.label), ['Understand today', 'Prepare foundations', 'Pilot', 'Expand', 'Sustain']);
|
||||
assert.deepEqual(diagram.edges.map(e => [e.from,e.to]), ids.slice(1).map((id, i) => [ids[i],id]));
|
||||
assert.match(diagram.nodes[0].sublabel, /code quality and real delivery/);
|
||||
assert.match(diagram.nodes[0].sublabel, /baseline, process map, prioritized gaps/);
|
||||
assert.match(diagram.nodes[1].tag, /Critical gaps closed/);
|
||||
assert(diagram.nodes.every(n => n.sublabel.includes('Output:') && n.tag.startsWith('Exit:')));
|
||||
assert.deepEqual(diagram.nodes.map(n => n.col), suffix ? [0,0,0,0,0] : [0,1,2,3,4]);
|
||||
assert.equal(diagram.lanes.length, 1);
|
||||
}
|
||||
});
|
||||
@@ -26,6 +26,13 @@ test('mobile story diagrams preserve the same nodes and directed relationships',
|
||||
for (const {id} of model.pages) {
|
||||
const page = read(`docs/strategy/${id}.mdx`);
|
||||
const name = /<ArchifyDiagram name="([^"]+)"[^>]+ story/.exec(page)[1];
|
||||
if (name === 'strategy-roadmap') {
|
||||
const desktop = JSON.parse(read(`diagrams/${name}.workflow.json`));
|
||||
const mobile = JSON.parse(read(`diagrams/${name}-mobile.workflow.json`));
|
||||
const semantic = d => ({nodes:d.nodes.map(({col,yOffset,...n}) => n), edges:d.edges.map(({fromSide,toSide,...e}) => e)});
|
||||
assert.deepEqual(semantic(desktop), semantic(mobile));
|
||||
continue;
|
||||
}
|
||||
const desktop = JSON.parse(read(`diagrams/${name}.architecture.json`));
|
||||
const mobile = JSON.parse(read(`diagrams/${name}-mobile.architecture.json`));
|
||||
assert(desktop.components.length >= 5 && desktop.components.length <= (name === 'strategy-target' ? 18 : 7), name);
|
||||
|
||||
Reference in new issue
Block a user