101 lines
12 KiB
JavaScript
101 lines
12 KiB
JavaScript
// Focused public Vanilla API and dynamic-option parity with React Story evidence.
|
|
import fs from 'node:fs/promises';
|
|
import path from 'node:path';
|
|
import { launchChromium } from '../../scripts/orgchart-browser-runtime.mjs';
|
|
const output = path.resolve('test-results/org-chart-p9-4');
|
|
const origin = (process.env.ORGCHART_CORE_ORIGIN ?? 'http://127.0.0.1:6074').replace(/\/$/, '');
|
|
const only = process.argv.slice(2), run = name => !only.length || only.includes(name), suffix = only.length ? `-${only.join('-')}` : '';
|
|
const results = [], errors = [], measurements = [], startedAt = new Date().toISOString();
|
|
await fs.mkdir(output, { recursive:true });
|
|
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 browser = await launchChromium({ headless:true }), page = await browser.newPage({ viewport:{ width:1440,height:1000 } });
|
|
page.setDefaultTimeout(15000);
|
|
page.on('pageerror', error => errors.push({ type:'pageerror', message:error.message }));
|
|
page.on('console', message => { if (message.type() === 'error') errors.push({ type:'console', message:message.text() }); });
|
|
const settle = () => page.evaluate(async () => { await document.fonts.ready; for (let i = 0; i < 8; i++) await new Promise(requestAnimationFrame); });
|
|
async function ready() { await page.waitForFunction(() => window.fixtureReady && window.chart?.getState().status === 'ready' && [...window.shadow.querySelectorAll('link[rel="stylesheet"]')].every(link => link.sheet)); await settle(); }
|
|
async function fresh(extra, mode = 'auto') {
|
|
await page.goto(`${origin}/test-results/org-chart-p9-4/vanilla.html`); await ready();
|
|
if (extra || mode !== 'auto') { await page.evaluate(({ extra, mode }) => window.mountChart(mode, extra ?? {}), { extra,mode }); await ready(); }
|
|
}
|
|
async function section(name, action) { if (!run(name)) return; try { await action(); } catch (error) { check(false, `${name}: execution completed`, { message:error.message,stack:error.stack }); await page.mouse.up().catch(() => {}); await page.screenshot({ path:path.join(output,`core-failure-${name}.png`) }).catch(() => {}); } }
|
|
const employee = id => page.locator(`.hm-orgchart-employee[data-employee-id="${id}"]`);
|
|
const requests = [
|
|
{ kind:'employee',sourceId:'virtual-022',targetOrganizationId:'hanmac-office-sibling' },
|
|
{ kind:'organization',sourceId:'hanmac-office-0',targetOrganizationId:'hanmac-office-1' },
|
|
{ kind:'employee',sourceId:'virtual-022',targetOrganizationId:'saman-office-0' },
|
|
{ kind:'organization',sourceId:'hanmac-office-0',targetOrganizationId:'saman-office-1' },
|
|
];
|
|
await section('api', async () => {
|
|
await fresh();
|
|
const before = await page.evaluate(() => ({ enabled:window.chart.getDragDropEnabled(), data:JSON.stringify(window.chart.getData()), loads:window.loads }));
|
|
check(before.enabled === false && before.loads === 1, 'Vanilla omitted option defaults OFF and loads source once', before.loads);
|
|
for (const request of requests) {
|
|
const rejection = await page.evaluate(request => window.chart.move(request), request);
|
|
check(!rejection.moved && rejection.code === 'EDITING_DISABLED' && await page.evaluate(data => JSON.stringify(window.chart.getData()) === data, before.data), `OFF public move rejects ${request.kind} to ${request.targetOrganizationId} without changing data`, rejection);
|
|
}
|
|
const pure = await page.evaluate(request => window.mod.applyOrgChartMove(window.fixture, request), requests[0]);
|
|
check(pure.moved, 'Pure data transformation remains compatible outside interactive OFF policy', pure.moved ? pure.request : pure);
|
|
const legacy = await page.evaluate(() => {
|
|
const original = JSON.stringify(window.template);
|
|
const total = window.mod.createOrgChartExampleDataset(window.template, { scope:'total',employeeCount:100 });
|
|
const perCompany = window.mod.createOrgChartExampleDataset(window.template, { scope:'per-company',employeeCount:20,companyIds:['hanmac','saman'] });
|
|
return { total:total.employees.length,totalValid:window.mod.validateOrgChartDataset(total).valid,perCompany:perCompany.employees.length,sourceUnchanged:JSON.stringify(window.template) === original };
|
|
});
|
|
check(legacy.total === 100 && legacy.totalValid && legacy.perCompany === 40 && legacy.sourceUnchanged, 'Public total scope remains compatible while Expanded uses company counts', legacy);
|
|
const toggle = await page.evaluate(() => {
|
|
const data = window.chart.getData(), forest = window.shadow.querySelector('.hm-orgchart-forest'), viewport = window.chart.getViewportState();
|
|
window.chart.setDragDropEnabled(true); const on = window.chart.getDragDropEnabled(); window.chart.setDragDropEnabled(false); const off = window.chart.getDragDropEnabled();
|
|
return { on,off,dataSame:data === window.chart.getData(),forestSame:forest === window.shadow.querySelector('.hm-orgchart-forest'),viewportSame:JSON.stringify(viewport) === JSON.stringify(window.chart.getViewportState()),loads:window.loads };
|
|
});
|
|
check(toggle.on && !toggle.off && toggle.dataSame && toggle.forestSame && toggle.viewportSame && toggle.loads === before.loads, 'Vanilla setter changes policy without data reload, DOM remount or viewport reset', toggle);
|
|
await page.evaluate(() => window.chart.setDragDropEnabled(true));
|
|
for (const request of requests) {
|
|
const moved = await page.evaluate(request => window.chart.move(request), request); await ready();
|
|
check(moved.moved, `ON public move permits ${request.kind} to ${request.targetOrganizationId}`, moved.moved ? moved.request : moved);
|
|
}
|
|
const after = await page.evaluate(() => {
|
|
const data = window.chart.getData(), before = JSON.stringify(data), forest = window.shadow.querySelector('.hm-orgchart-forest'); window.chart.setDragDropEnabled(false);
|
|
return { same:JSON.stringify(window.chart.getData()) === before,owned:data === window.chart.getData(),forestSame:forest === window.shadow.querySelector('.hm-orgchart-forest'),
|
|
unique:new Set(data.employees.map(employee => employee.id)).size === data.employees.length,total:data.employees.length,valid:window.mod.validateOrgChartDataset(data).valid,
|
|
employee:data.employees.find(employee => employee.id === 'virtual-022'),group:data.organizations.find(group => group.id === 'hanmac-office-0'),fixturePreserved:JSON.stringify(window.fixture) === window.fixtureOriginal,loads:window.loads };
|
|
});
|
|
check(after.same && after.owned && after.forestSame && after.unique && after.total === 40 && after.valid && after.fixturePreserved && after.loads === 1
|
|
&& after.employee.organizationId === 'saman-office-0' && after.group.parentId === 'saman-office-1', 'Vanilla OFF preserves completed cross-company moves, unique IDs and original fixture without reload', after);
|
|
await page.screenshot({ path:path.join(output,'vanilla-on-off-preserved.png'),fullPage:true }); measurements.push({ name:'public API',before,toggle,after });
|
|
});
|
|
await section('cancel', async () => {
|
|
await fresh({ dragDropEnabled:true }); await page.evaluate(() => { window.pointerId = undefined; window.shadow.querySelector('.hm-orgchart-viewport').addEventListener('pointerdown', event => { window.pointerId = event.pointerId; }, { capture:true }); });
|
|
const box = await employee('virtual-022').boundingBox();
|
|
await page.mouse.move(box.x + box.width/2,box.y + box.height/2); await page.mouse.down(); await page.mouse.move(box.x + box.width/2 + 12,box.y + box.height/2,{ steps:2 }); await settle();
|
|
const before = await page.evaluate(() => { const v = window.shadow.querySelector('.hm-orgchart-viewport'); return { dragging:v.dataset.editDragging,captured:v.hasPointerCapture(window.pointerId),data:JSON.stringify(window.chart.getData()) }; });
|
|
const after = await page.evaluate(() => { window.chart.setDragDropEnabled(false); const v = window.shadow.querySelector('.hm-orgchart-viewport'); return { dragging:v.dataset.editDragging,captured:v.hasPointerCapture(window.pointerId),highlight:window.shadow.querySelectorAll('[data-drop-state],[data-drop-hover],[data-edit-source]').length,data:JSON.stringify(window.chart.getData()) }; });
|
|
await page.mouse.up(); await settle();
|
|
check(before.dragging === 'true' && before.captured && !after.dragging && !after.captured && after.highlight === 0 && before.data === after.data, 'Vanilla live pointer OFF releases capture and clears all pending/highlight state', { before:{ ...before,data:undefined },after:{ ...after,data:undefined } });
|
|
check(await page.locator('.hm-orgchart-employee-dialog[open]').count() === 0 && await page.evaluate(data => JSON.stringify(window.chart.getData()) === data,before.data), 'Vanilla canceled pointer release neither commits nor opens Dialog');
|
|
await page.evaluate(() => window.chart.setDragDropEnabled(true)); await employee('virtual-022').focus(); await page.keyboard.press('Alt+m'); await settle();
|
|
const keyboardBefore = await page.evaluate(() => window.shadow.querySelector('.hm-orgchart-viewport').dataset.editDragging);
|
|
await page.evaluate(() => window.chart.setDragDropEnabled(false)); await page.keyboard.press('Enter'); await settle();
|
|
check(keyboardBefore === 'true' && await page.evaluate(data => !window.shadow.querySelector('[data-edit-dragging],[data-drop-state],[data-drop-hover]') && JSON.stringify(window.chart.getData()) === data,before.data), 'Vanilla active keyboard move is canceled and stale Enter cannot commit');
|
|
});
|
|
await section('default', async () => {
|
|
for (const [flag,expected] of [[undefined,false],[false,false],[true,true]]) {
|
|
await fresh(flag === undefined ? undefined : { dragDropEnabled:flag });
|
|
const actual = await page.evaluate(() => ({ value:window.chart.getDragDropEnabled(),attribute:window.shadow.querySelector('.hm-orgchart-viewport').dataset.dragDropEnabled,shortcuts:[...window.shadow.querySelectorAll('.hm-orgchart-employee,.hm-orgchart-expanded-toggle')].map(element => element.getAttribute('aria-keyshortcuts')) }));
|
|
check(actual.value === expected && actual.attribute === String(expected) && actual.shortcuts.every(value => expected ? value === 'Alt+M' : value === null), `Vanilla option ${String(flag)} matches React default/setter policy and keyboard affordances`, actual.value);
|
|
}
|
|
});
|
|
await section('fixed', async () => {
|
|
await fresh({ dragDropEnabled:true },'fixed');
|
|
const fixed = await page.evaluate(async () => {
|
|
let setterRejected = false; try { window.chart.setDragDropEnabled(true); } catch (error) { setterRejected = /Auto/.test(error.message); }
|
|
let moveRejected = false; try { await window.chart.move({ kind:'employee',sourceId:'emp-001',targetOrganizationId:'management-plan' }); } catch (error) { moveRejected = /Auto/.test(error.message); }
|
|
return { enabled:window.chart.getDragDropEnabled(),setterRejected,moveRejected,editingUI:window.shadow.querySelectorAll('[data-drag-drop-enabled],[data-edit-dragging],[data-drop-state],.hm-orgchart-move-feedback').length,loads:window.loads };
|
|
});
|
|
check(!fixed.enabled && fixed.setterRejected && fixed.moveRejected && fixed.editingUI === 0, 'Fixed ignores dragDropEnabled option, rejects Auto setter/move and adds no editing UI', fixed);
|
|
});
|
|
check(errors.length === 0,'Vanilla new console/page errors: zero',errors);
|
|
const result = { startedAt,completedAt:new Date().toISOString(),browser:browser.version(),origin,sections:only,results,measurements,errors,pass:results.filter(item => item.status === 'PASS').length,fail:results.filter(item => item.status === 'FAIL').length };
|
|
await fs.writeFile(path.join(output,`core${suffix}.json`),JSON.stringify(result,null,2)); await browser.close();
|
|
console.log(JSON.stringify({ pass:result.pass,fail:result.fail,output:`test-results/org-chart-p9-4/core${suffix}.json` })); if (result.fail) process.exitCode = 1;
|