// Compare protected source hashes to the captured pre-P9.4 state and one Fixed // screen to existing P9.2 evidence. No full historical regression suite replay. import fs from 'node:fs/promises'; import path from 'node:path'; import { createHash } from 'node:crypto'; import { launchChromium } from '../../scripts/orgchart-browser-runtime.mjs'; const output = path.resolve('test-results/org-chart-p9-4'), origin = (process.env.ORGCHART_STORY_ORIGIN ?? 'http://127.0.0.1:6064').replace(/\/$/, ''); const results = [],errors = [],startedAt = new Date().toISOString(); const check = (pass,name,detail) => { results.push({ name,status:pass ? 'PASS':'FAIL', ...(detail === undefined ? {} : { detail }) }); if (!pass) console.log('FAIL',name,JSON.stringify(detail)); }; const fixedOnly = process.argv.includes('--fixed-only'); const baseline = fixedOnly ? { files:[] } : JSON.parse(await fs.readFile(path.join(output,'baseline.json'),'utf8')); const protectedPath = name => /^examples\/org-chart\/data.*\.json$/.test(name) || /^packages\/org-chart\/src\/assets\//.test(name) || ['packages/org-chart/src/company-color-presets.ts','packages/org-chart/src/core/style-presets.ts','packages/org-chart/src/core/employee-card.ts', 'packages/org-chart/src/styles/employee-card.css','packages/org-chart/src/core/workdesk-artwork.ts','packages/org-chart/src/core/workdesk-layout.ts', 'src/org-chart/center-composition-preview.ts','src/org-chart/org-chart-full-composition.stories.tsx','src/org-chart/org-chart-department-colors.stories.tsx', 'src/org-chart/org-chart-overview-internal.stories.tsx'].includes(name); const protectedFiles = baseline.files.filter(file => protectedPath(file.path)); const changed = []; for (const file of protectedFiles) { const actual = createHash('sha256').update(await fs.readFile(path.resolve(file.path))).digest('hex').toUpperCase(); if (actual !== file.sha256.toUpperCase()) changed.push({ path:file.path,before:file.sha256,after:actual }); } if (!fixedOnly) check(protectedFiles.length > 20 && changed.length === 0,'Pre-P9.4 fixtures, approved SVG/images, card renderer/styles, company/department presets and Fixed source hashes preserved',{ checked:protectedFiles.length,changed }); const browser = await launchChromium({ headless:true }), page = await browser.newPage({ viewport:{ width:1440,height:900 } }); page.on('pageerror',error => errors.push(error.message)); page.on('console',message => { if (message.type() === 'error') errors.push(message.text()); }); await page.goto(`${origin}/iframe.html?id=orgchart--full-composition&viewMode=story`); await page.waitForFunction(() => { const shadow = document.querySelector('.hm-orgchart-host')?.shadowRoot; return shadow?.querySelector('.hm-orgchart-stage[data-workdesk-artwork]') && !shadow.querySelector('[aria-busy="true"]') && [...shadow.querySelectorAll('link[rel="stylesheet"]')].every(link => link.sheet); }); await page.evaluate(async () => { await document.fonts.ready; await Promise.all([...document.querySelector('.hm-orgchart-host').shadowRoot.querySelectorAll('img')].map(async image => { image.loading = 'eager'; await image.decode(); })); for (let i = 0; i < 12; i++) await new Promise(requestAnimationFrame); }); const priorSource = await fs.readFile('examples/org-chart/verify-p9-2-composition.mjs','utf8'); const from = priorSource.indexOf('async function snapshot()'),to = priorSource.indexOf('async function stability()',from); const snapshot = await new Function('page',priorSource.slice(from,to) + '; return snapshot();')(page); const previous = JSON.parse(await fs.readFile('test-results/org-chart-p9-2/browser-composition.json','utf8')).snapshots['1440']; const same = (a,b) => JSON.stringify(a) === JSON.stringify(b); const normalizedEqual = snapshot.elements.every((element,index) => { const old = previous.elements[index]; return old?.key === element.key && old.hash === element.hash && same(old.style,element.style) && Object.keys(element.box).every(key => Math.abs(element.box[key] - old.box[key]) < .15); }); check(previous.htmlHash === snapshot.htmlHash && same(previous.assets,snapshot.assets) && normalizedEqual,'Fixed DOM, 139 asset hashes and 152 styles/geometry match preserved P9.2 evidence',{ previous:previous.htmlHash,current:snapshot.htmlHash,elements:snapshot.elements.length,assets:snapshot.assets.length }); check(Math.abs(snapshot.centerDifference) < 1 && snapshot.collisions.length === 0 && snapshot.counts.cards === 43 && snapshot.counts.paths === 5,'Fixed minimal center/cards/connectors regression',{ centerDifference:snapshot.centerDifference,counts:snapshot.counts,collisions:snapshot.collisions }); check(await page.evaluate(() => !document.querySelector('.hm-orgchart-host').shadowRoot.querySelector('[data-drag-drop-enabled],[data-edit-dragging],[data-drop-state],.hm-orgchart-move-feedback')),'Fixed has no ON/OFF or editing UI'); await page.screenshot({ path:path.join(output,'fixed-preserved-1440.png'),fullPage:true }); check(errors.length === 0,'Preservation browser console/page errors: zero',errors); const result = { startedAt,completedAt:new Date().toISOString(),origin,results,errors,protectedFiles:protectedFiles.map(file => file.path),fixedSnapshot:snapshot,pass:results.filter(item => item.status === 'PASS').length,fail:results.filter(item => item.status === 'FAIL').length }; await fs.writeFile(path.join(output,'fixed-preservation.json'),JSON.stringify(result,null,2)); await browser.close(); console.log(JSON.stringify({ pass:result.pass,fail:result.fail })); if (result.fail) process.exitCode = 1;