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));
|
||||
|
||||
Reference in new issue
Block a user