feat(org-chart): support external CSS themes and align documentation
This commit is contained in:
1 parent
dcfa4e0895
commit
346c648c09
27 files changed
+1189
-75
No files matched your search
@@ -0,0 +1,2 @@
|
||||
@import './external-theme.css';
|
||||
:host { --hm-orgchart-group-title-background: #613e8b; }
|
||||
@@ -0,0 +1,44 @@
|
||||
/* Served to stylesheetUrls.core: imports the complete default package stylesheet.
|
||||
Vite resolves this public export; plain servers use their copied /assets/style.css.
|
||||
To change a theme, edit only values in this file. No !important or inline colors. */
|
||||
@import '@hm-design/org-chart/style.css';
|
||||
:host {
|
||||
--hm-orgchart-bg-surface: #edf4ff;
|
||||
--hm-orgchart-bg-content: #dce8f8;
|
||||
--hm-orgchart-fg-primary: #172b4d;
|
||||
--hm-orgchart-stroke-default: #8b9bb0;
|
||||
--hm-orgchart-company-hanmac: #2457a7;
|
||||
--hm-orgchart-company-saman: #385ca8;
|
||||
--hm-orgchart-company-jangheon-industry: #14786b;
|
||||
--hm-orgchart-company-jangheon: #503a91;
|
||||
--hm-orgchart-company-ptc: #755024;
|
||||
--hm-orgchart-company-halla: #207879;
|
||||
--hm-orgchart-company-baron: #4d6295;
|
||||
--hm-orgchart-group-title-color: #fff4d6;
|
||||
--hm-orgchart-card-background: #f4f8ff;
|
||||
--hm-orgchart-leader-card-background: #e3eeff;
|
||||
--hm-orgchart-top-leader-card-background: #d2e3ff;
|
||||
--hm-orgchart-card-border: #527aa9;
|
||||
--hm-orgchart-card-hover-background: #c1d8f8;
|
||||
--hm-orgchart-card-focus-color: #7650a5;
|
||||
--hm-orgchart-expanded-line-color: #249a79;
|
||||
--hm-orgchart-fixed-line-color: #297ab0;
|
||||
--hm-orgchart-business-management-gradient: linear-gradient(180deg, #2457a7, #123568);
|
||||
--hm-orgchart-business-management-border: #2457a7;
|
||||
--hm-orgchart-business-planning-gradient: linear-gradient(180deg, #385ca8, #23417f);
|
||||
--hm-orgchart-business-planning-border: #385ca8;
|
||||
--hm-orgchart-dispatched-card-gradient: linear-gradient(180deg, #e9f9f0, #a9d8bd);
|
||||
--hm-orgchart-dispatched-card-border: #278857;
|
||||
--hm-orgchart-dispatched-card-color: #145d39;
|
||||
--hm-orgchart-dispatched-title-background: linear-gradient(180deg, #328c61, #1e5b3d);
|
||||
--hm-orgchart-on-leave-card-gradient: linear-gradient(180deg, #f7efff, #d6bbeb);
|
||||
--hm-orgchart-on-leave-card-border: #8050a1;
|
||||
--hm-orgchart-on-leave-card-color: #603080;
|
||||
--hm-orgchart-on-leave-title-background: linear-gradient(180deg, #8d66b3, #573771);
|
||||
--hm-orgchart-dialog-background: #eef2ff;
|
||||
--hm-orgchart-dialog-color: #283568;
|
||||
--hm-orgchart-dialog-border: #6879ba;
|
||||
--hm-orgchart-dialog-close-color: #36478a;
|
||||
--hm-orgchart-dialog-close-hover-background: #dce2f9;
|
||||
--hm-orgchart-dialog-focus-color: #8958ba;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
/* Loaded by the host document. Colors inherit; Shadow selectors stay private. */
|
||||
.orgchart-custom-theme {
|
||||
--hm-orgchart-company-hanmac: #2457a7;
|
||||
--hm-orgchart-company-saman: #385ca8;
|
||||
--hm-orgchart-expanded-line-color: #249a79;
|
||||
--hm-orgchart-bg-surface: #edf4ff;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
<!doctype html>
|
||||
<html lang="ko"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="data:,"><title>OrgChart P11 theme verification</title><body><main id="root"></main><script type="module" src="./p11.tsx"></script></body></html>
|
||||
@@ -0,0 +1,34 @@
|
||||
// Isolated consumer fixture; no Storybook registration or production controls.
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { OrgChart } from '@hm-design/org-chart-react';
|
||||
import { createOrgChart, JsonOrgChartDataSource } from '@hm-design/org-chart';
|
||||
import coreUrl from '@hm-design/org-chart/style.css?url';
|
||||
import engineUrl from '@hm-design/org-chart/engine.css?url';
|
||||
import externalThemeUrl from './external-theme.css?url';
|
||||
import hostThemeUrl from './host-theme.css?url';
|
||||
import groupThemeUrl from './external-group-theme.css?url';
|
||||
import autoData from '../org-chart/data-expanded-small.json';
|
||||
import allCompanies from '../org-chart/data-expanded-large.json';
|
||||
import fixedData from '../org-chart/data-p1.json';
|
||||
|
||||
const query = new URLSearchParams(location.search);
|
||||
const mode = query.get('mode') ?? 'auto';
|
||||
const data = structuredClone(mode === 'auto' ? query.has('all') ? allCompanies : autoData : fixedData);
|
||||
if (mode === 'auto' && query.has('statuses')) {
|
||||
data.employees.filter(employee => !data.organizations.some(org => org.leaderId === employee.id)).slice(0, 2).forEach((employee, index) => { employee.workStatus = index ? 'onLeave' : 'dispatched'; });
|
||||
}
|
||||
const options = {
|
||||
dataSource: new JsonOrgChartDataSource(data),
|
||||
dragDropEnabled: query.has('drag'),
|
||||
stylesheetUrls: { core: query.has('groups') ? groupThemeUrl : query.has('theme') ? externalThemeUrl : coreUrl, engine: engineUrl },
|
||||
...(mode === 'auto' ? { layoutMode: 'auto' as const, presentationMode: 'chart-only' as const, initialView: 'fit' as const, wheelZoom: 'direct' as const } : { displayMode: mode === 'fixed' ? 'readOnly' as const : 'interactive' as const }),
|
||||
};
|
||||
document.body.style.margin = '0';
|
||||
Object.assign(window, { p11HostThemeUrl: hostThemeUrl });
|
||||
const container = document.getElementById('root')!;
|
||||
container.style.height = '850px';
|
||||
if (query.get('runtime') === 'vanilla') {
|
||||
const chart = createOrgChart({ container, ...options });
|
||||
void chart.mount();
|
||||
Object.assign(window, { p11Chart: chart });
|
||||
} else createRoot(container).render(<OrgChart {...options} />);
|
||||
@@ -0,0 +1,35 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import assert from 'node:assert/strict';
|
||||
import { launchChromium } from '../../scripts/orgchart-browser-runtime.mjs';
|
||||
const output = path.resolve('test-results/org-chart-p11');
|
||||
const browser = await launchChromium({ headless: true });
|
||||
const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
|
||||
const errors = [], checks = [];
|
||||
page.on('pageerror', error => errors.push(error.message));
|
||||
page.on('console', message => { if (message.type() === 'error') errors.push(message.text()); });
|
||||
try {
|
||||
for (const [id, selector] of [
|
||||
['full-composition', '.hm-orgchart-composition[data-display-mode="readOnly"]'],
|
||||
['expanded-org-chart', '.hm-orgchart-expanded'],
|
||||
['department-colors', '.hm-orgchart-department-colors'],
|
||||
['logo-personnel-summary', '.hm-orgchart-affiliation-mark'],
|
||||
]) {
|
||||
await page.goto(`http://127.0.0.1:6034/iframe.html?id=orgchart--${id}&viewMode=story`);
|
||||
await page.locator(selector).first().waitFor();
|
||||
await page.evaluate(async () => { await document.fonts.ready; await new Promise(resolve => setTimeout(resolve, 800)); });
|
||||
if (id === 'department-colors') {
|
||||
const gradients = await page.locator('.hm-orgchart-color-preview .hm-orgchart-color-department-name').evaluateAll(headings => headings.map(heading => getComputedStyle(heading).backgroundImage));
|
||||
assert.equal(gradients.length, 6);
|
||||
assert.ok(gradients.every(gradient => gradient !== 'none'));
|
||||
assert.equal(new Set(gradients).size, 6);
|
||||
}
|
||||
await page.screenshot({ path: path.join(output, `storybook-${id}.png`) });
|
||||
checks.push({ name: id, status: 'PASS' });
|
||||
}
|
||||
assert.deepEqual(errors, []);
|
||||
} finally {
|
||||
await fs.writeFile(path.join(output, 'storybook-result.json'), JSON.stringify({ checks, errors }, null, 2));
|
||||
await browser.close();
|
||||
}
|
||||
console.log(`Storybook: ${checks.length} checks, ${errors.length} errors`);
|
||||
@@ -0,0 +1,184 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import assert from 'node:assert/strict';
|
||||
import path from 'node:path';
|
||||
import { launchChromium } from '../../scripts/orgchart-browser-runtime.mjs';
|
||||
|
||||
const output = path.resolve('test-results/org-chart-p11');
|
||||
await fs.mkdir(output, { recursive: true });
|
||||
const browser = await launchChromium({ headless: true });
|
||||
const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
|
||||
const errors = [], checks = [], measurements = {};
|
||||
page.on('pageerror', error => errors.push(error.message));
|
||||
page.on('console', message => { if (message.type() === 'error') errors.push(message.text()); });
|
||||
const baseline = process.argv.includes('--baseline');
|
||||
const origin = process.env.ORGCHART_P11_ORIGIN ?? 'http://127.0.0.1:6026';
|
||||
async function open(runtime, mode, theme = false, extra = '') {
|
||||
await page.goto(`${origin}/p11.html?runtime=${runtime}&mode=${mode}${theme ? '&theme=1' : ''}${extra}`);
|
||||
await page.locator('.hm-orgchart-employee').first().waitFor();
|
||||
await page.waitForFunction(() => [...document.querySelector('.hm-orgchart-host').shadowRoot.querySelectorAll('link')].every(link => link.sheet));
|
||||
await page.evaluate(async () => { await document.fonts.ready; await new Promise(resolve => setTimeout(resolve, 600)); });
|
||||
}
|
||||
async function styles() {
|
||||
return page.evaluate(() => {
|
||||
const shadow = document.querySelector('.hm-orgchart-host').shadowRoot;
|
||||
const selectors = '.hm-orgchart-shell,.hm-orgchart-viewport,.hm-orgchart-organization-name,.hm-orgchart-employee,.hm-orgchart-status-group > .hm-orgchart-summary-title,.hm-orgchart-employee-dialog,.hm-orgchart-employee-dialog-close';
|
||||
const fields = ['backgroundColor', 'backgroundImage', 'color', 'borderTopColor', 'borderRightColor', 'borderTopWidth', 'boxShadow', 'width', 'height', 'fontSize', 'padding', 'borderRadius'];
|
||||
return [...shadow.querySelectorAll(selectors)].map(el => ({ tag: el.tagName, class: el.className, company: el.dataset.companyId ?? el.closest('[data-company-id]')?.dataset.companyId, kind: el.dataset.cardKind, status: el.dataset.workStatus, css: Object.fromEntries(fields.map(field => [field, getComputedStyle(el)[field]])) }));
|
||||
});
|
||||
}
|
||||
async function check(name, run) { await run(); checks.push({ name, status: 'PASS' }); }
|
||||
try {
|
||||
for (const runtime of ['vanilla', 'react']) for (const mode of ['auto', 'fixed', 'interactive']) {
|
||||
const key = `${runtime}-${mode}`;
|
||||
await open(runtime, mode);
|
||||
measurements[key] = await styles();
|
||||
await page.screenshot({ path: path.join(output, `${baseline ? 'before' : 'after'}-${key}.png`) });
|
||||
if (!baseline) {
|
||||
const before = JSON.parse(await fs.readFile(path.join(output, 'baseline.json'), 'utf8'));
|
||||
await check(`${key}: default colors and geometry identical`, () => assert.equal(JSON.stringify(measurements[key]), JSON.stringify(before[key])));
|
||||
}
|
||||
// Popup is captured separately so it cannot obscure the chart screenshot.
|
||||
await page.locator('.hm-orgchart-employee').first().click();
|
||||
await page.locator('.hm-orgchart-employee-dialog[open]').waitFor();
|
||||
measurements[`${key}-popup`] = (await styles()).filter(row => row.class.includes('dialog'));
|
||||
if (!baseline) {
|
||||
const before = JSON.parse(await fs.readFile(path.join(output, 'baseline.json'), 'utf8'));
|
||||
await check(`${key}: default popup identical`, () => assert.equal(JSON.stringify(measurements[`${key}-popup`]), JSON.stringify(before[`${key}-popup`])));
|
||||
}
|
||||
}
|
||||
if (baseline) await fs.writeFile(path.join(output, 'baseline.json'), JSON.stringify(measurements, null, 2));
|
||||
else {
|
||||
const expectedCompanies = { hanmac: 'rgb(36, 87, 167)', saman: 'rgb(56, 92, 168)', 'jangheon-industry': 'rgb(20, 120, 107)' };
|
||||
const value = (selector, property, pseudo = null) => page.locator(selector).first().evaluate((el, { property, pseudo }) => getComputedStyle(el, pseudo)[property], { property, pseudo });
|
||||
for (const runtime of ['vanilla', 'react']) {
|
||||
await open(runtime, 'auto', true);
|
||||
for (const [id, color] of Object.entries(expectedCompanies)) {
|
||||
await check(`${runtime}: external CSS ${id} title`, async () => assert.equal(await value(`.node[data-company-id="${id}"] .hm-orgchart-organization-name`, 'backgroundColor'), color));
|
||||
await check(`${runtime}: external CSS ${id} card accent`, async () => assert.equal(await value(`.hm-orgchart-employee[data-company-id="${id}"]`, 'borderTopColor'), color));
|
||||
}
|
||||
await check(`${runtime}: canvas and title text`, async () => {
|
||||
assert.equal(await value('.hm-orgchart-viewport', 'backgroundColor'), 'rgb(237, 244, 255)');
|
||||
assert.equal(await value('.hm-orgchart-organization-name', 'color'), 'rgb(255, 244, 214)');
|
||||
});
|
||||
await check(`${runtime}: independent card roles and border`, async () => {
|
||||
assert.equal(await value('.hm-orgchart-employee[data-card-kind="employee"]', 'backgroundColor'), 'rgb(244, 248, 255)');
|
||||
assert.equal(await value('.hm-orgchart-employee[data-card-kind="topLeader"]', 'backgroundColor'), 'rgb(210, 227, 255)');
|
||||
assert.equal(await value('.hm-orgchart-employee', 'borderRightColor'), 'rgb(82, 122, 169)');
|
||||
});
|
||||
await check(`${runtime}: employee and organization connector theme`, async () => {
|
||||
assert.equal(await value('.hm-orgchart-employee-connectors path', 'stroke'), 'rgb(36, 154, 121)');
|
||||
assert.equal(await value('.hm-orgchart-engine > ul > li > ul li > .node', 'backgroundColor', '::before'), 'rgb(36, 154, 121)');
|
||||
});
|
||||
const employee = page.locator('.hm-orgchart-employee[data-card-kind="employee"]').first();
|
||||
await employee.hover();
|
||||
await check(`${runtime}: actual card hover`, async () => assert.equal(await value('.hm-orgchart-employee[data-card-kind="employee"]', 'backgroundColor'), 'rgb(193, 216, 248)'));
|
||||
await page.mouse.move(0, 0); await employee.focus();
|
||||
await check(`${runtime}: actual card focus`, async () => assert.equal(await value('.hm-orgchart-employee[data-card-kind="employee"]', 'outlineColor'), 'rgb(118, 80, 165)'));
|
||||
await employee.press('Enter');
|
||||
await page.locator('.hm-orgchart-employee-dialog[open]').waitFor();
|
||||
await check(`${runtime}: Core popup background, text and border`, async () => {
|
||||
assert.equal(await value('.hm-orgchart-employee-dialog', 'backgroundColor'), 'rgb(238, 242, 255)');
|
||||
assert.equal(await value('.hm-orgchart-employee-dialog', 'color'), 'rgb(40, 53, 104)');
|
||||
assert.equal(await value('.hm-orgchart-employee-dialog', 'borderTopColor'), 'rgb(104, 121, 186)');
|
||||
});
|
||||
const close = page.locator('.hm-orgchart-employee-dialog-close');
|
||||
await close.hover(); await page.waitForTimeout(200);
|
||||
await check(`${runtime}: popup hover`, async () => assert.equal(await value('.hm-orgchart-employee-dialog-close', 'backgroundColor'), 'rgb(220, 226, 249)'));
|
||||
await page.mouse.move(0, 0); await close.focus(); await page.waitForTimeout(200);
|
||||
await check(`${runtime}: popup focus`, async () => assert.equal(await value('.hm-orgchart-employee-dialog-close', 'borderTopColor'), 'rgb(137, 88, 186)'));
|
||||
await page.keyboard.press('Escape');
|
||||
await check(`${runtime}: popup Escape and focus restoration`, async () => assert.equal(await employee.evaluate(el => el.getRootNode().activeElement === el), true));
|
||||
await page.screenshot({ path: path.join(output, `custom-${runtime}-auto.png`) });
|
||||
|
||||
await open(runtime, 'fixed', true);
|
||||
await check(`${runtime}: Fixed headers and background`, async () => {
|
||||
assert.match(await value('[data-group-variant="0"] .hm-orgchart-organization-name', 'backgroundImage'), /rgb\(36, 87, 167\)/);
|
||||
assert.equal(await value('.hm-orgchart-composition', 'backgroundColor'), 'rgb(220, 232, 248)');
|
||||
assert.equal(await value('.hm-orgchart-workdesk-overlay path[data-connector]', 'stroke'), 'rgb(41, 122, 176)');
|
||||
});
|
||||
for (const [status, color] of [['dispatched', 'rgb(39, 136, 87)'], ['onLeave', 'rgb(128, 80, 161)']]) {
|
||||
await check(`${runtime}: ${status} external gradient and border`, async () => {
|
||||
const selector = `.hm-orgchart-status-group[data-work-status="${status}"] .hm-orgchart-employee`;
|
||||
assert.equal(await value(selector, 'borderTopColor'), color);
|
||||
assert.notEqual(await value(selector, 'backgroundImage'), 'none');
|
||||
});
|
||||
}
|
||||
await page.screenshot({ path: path.join(output, `custom-${runtime}-fixed.png`) });
|
||||
|
||||
await open(runtime, 'auto');
|
||||
const unthemed = await styles();
|
||||
await page.evaluate(() => {
|
||||
const link = document.createElement('link'); link.rel = 'stylesheet'; link.href = window.p11HostThemeUrl; link.id = 'host-theme'; document.head.append(link);
|
||||
document.querySelector('#root').classList.add('orgchart-custom-theme');
|
||||
});
|
||||
await page.waitForFunction(() => document.querySelector('#host-theme').sheet);
|
||||
await check(`${runtime}: host CSS crosses Shadow boundary without inline conflict`, async () => {
|
||||
assert.equal(await value('.node[data-company-id="hanmac"] .hm-orgchart-organization-name', 'backgroundColor'), expectedCompanies.hanmac);
|
||||
assert.equal(await value('.node[data-company-id="saman"] .hm-orgchart-organization-name', 'backgroundColor'), expectedCompanies.saman);
|
||||
assert.equal(await value('.hm-orgchart-viewport', 'backgroundColor'), 'rgb(237, 244, 255)');
|
||||
});
|
||||
await page.evaluate(() => document.querySelector('#root').classList.remove('orgchart-custom-theme'));
|
||||
await check(`${runtime}: removing host theme restores defaults immediately`, async () => assert.equal(JSON.stringify(await styles()), JSON.stringify(unthemed)));
|
||||
|
||||
await open(runtime, 'auto', false, '&all=1');
|
||||
await check(`${runtime}: all seven approved default company colors`, async () => {
|
||||
for (const [id, color] of Object.entries({ hanmac: 'rgb(203, 46, 40)', saman: 'rgb(240, 131, 0)', 'jangheon-industry': 'rgb(69, 128, 166)', jangheon: 'rgb(29, 65, 141)', ptc: 'rgb(132, 51, 142)', halla: 'rgb(0, 104, 183)', baron: 'rgb(12, 54, 127)' })) assert.equal(await value(`.node[data-company-id="${id}"] .hm-orgchart-organization-name`, 'backgroundColor'), color);
|
||||
});
|
||||
await open(runtime, 'auto', true, '&all=1&statuses=1&drag=1');
|
||||
for (const [id, color] of Object.entries({ ...expectedCompanies, jangheon: 'rgb(80, 58, 145)', ptc: 'rgb(117, 80, 36)', halla: 'rgb(32, 120, 121)', baron: 'rgb(77, 98, 149)' })) {
|
||||
await check(`${runtime}: seven-company theme ${id}`, async () => assert.equal(await value(`.node[data-company-id="${id}"] .hm-orgchart-organization-name`, 'backgroundColor'), color));
|
||||
}
|
||||
await check(`${runtime}: Auto status cards use external gradients and borders`, async () => {
|
||||
assert.equal(await value('.hm-orgchart-employee[data-work-status="dispatched"]', 'borderRightColor'), 'rgb(39, 136, 87)');
|
||||
assert.match(await value('.hm-orgchart-employee[data-work-status="onLeave"]', 'backgroundImage'), /rgb\(214, 187, 235\)/);
|
||||
});
|
||||
await check(`${runtime}: group leader has an independent background`, async () => assert.equal(await value('.hm-orgchart-employee[data-card-kind="leader"]', 'backgroundColor'), 'rgb(227, 238, 255)'));
|
||||
await page.evaluate(() => {
|
||||
const shadow = document.querySelector('.hm-orgchart-host').shadowRoot;
|
||||
const removeHover = rules => { for (const rule of rules) { rule.style?.removeProperty('--hm-orgchart-card-hover-background'); if (rule.styleSheet) removeHover(rule.styleSheet.cssRules); if (rule.cssRules) removeHover(rule.cssRules); } };
|
||||
for (const link of shadow.querySelectorAll('link')) removeHover(link.sheet.cssRules);
|
||||
});
|
||||
const statusCard = page.locator('.hm-orgchart-employee[data-work-status="dispatched"]').first();
|
||||
const statusGradient = await value('.hm-orgchart-employee[data-work-status="dispatched"]', 'backgroundImage');
|
||||
await statusCard.hover();
|
||||
await check(`${runtime}: unspecified hover preserves status gradient`, async () => assert.equal(await value('.hm-orgchart-employee[data-work-status="dispatched"]', 'backgroundImage'), statusGradient));
|
||||
await check(`${runtime}: Drag & Drop ON`, async () => assert.equal(await page.locator('.hm-orgchart-viewport').getAttribute('data-drag-drop-enabled'), 'true'));
|
||||
await open(runtime, 'auto', true);
|
||||
await check(`${runtime}: Drag & Drop OFF`, async () => assert.equal(await page.locator('.hm-orgchart-viewport').getAttribute('data-drag-drop-enabled'), 'false'));
|
||||
await open(runtime, 'auto', true, '&groups=1');
|
||||
await check(`${runtime}: independent group background via external CSS file`, async () => assert.equal(await value('.hm-orgchart-organization-name', 'backgroundColor'), 'rgb(97, 62, 139)'));
|
||||
await open(runtime, 'auto', true);
|
||||
const forest = page.locator('.hm-orgchart-forest');
|
||||
const transform = await forest.evaluate(el => el.style.transform);
|
||||
const viewport = await page.locator('.hm-orgchart-viewport').boundingBox();
|
||||
const mouseX = viewport.x + viewport.width - 12, mouseY = viewport.y + viewport.height - 12;
|
||||
await page.mouse.move(mouseX, mouseY); await page.mouse.wheel(0, -150); await page.waitForTimeout(150);
|
||||
await check(`${runtime}: direct wheel zoom`, async () => assert.notEqual(await forest.evaluate(el => el.style.transform), transform));
|
||||
const zoomed = await forest.evaluate(el => el.style.transform);
|
||||
await page.mouse.move(mouseX, mouseY); await page.mouse.down(); await page.mouse.move(mouseX - 50, mouseY - 50, { steps: 5 }); await page.mouse.up();
|
||||
await check(`${runtime}: pan`, async () => assert.notEqual(await forest.evaluate(el => el.style.transform), zoomed));
|
||||
await open(runtime, 'auto', true);
|
||||
const collapse = page.locator('.hm-orgchart-expanded-toggle').first();
|
||||
await collapse.click();
|
||||
await check(`${runtime}: collapse`, async () => assert.equal(await page.locator('.hm-orgchart-expanded-toggle').first().getAttribute('aria-expanded'), 'false'));
|
||||
await page.locator('.hm-orgchart-expanded-toggle').first().click();
|
||||
await check(`${runtime}: expand`, async () => assert.equal(await page.locator('.hm-orgchart-expanded-toggle').first().getAttribute('aria-expanded'), 'true'));
|
||||
for (const width of [390, 768, 1280, 1440]) {
|
||||
await page.setViewportSize({ width, height: 900 });
|
||||
await open(runtime, 'auto', true);
|
||||
await check(`${runtime}: ${width}px canvas fits and document does not overflow`, async () => {
|
||||
const box = await page.locator('.hm-orgchart-viewport').boundingBox();
|
||||
assert.equal(box.width, width);
|
||||
assert.equal(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth), true);
|
||||
});
|
||||
await open(runtime, 'fixed', true);
|
||||
await check(`${runtime}: ${width}px Fixed document does not overflow`, async () => assert.equal(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth), true));
|
||||
}
|
||||
}
|
||||
}
|
||||
assert.deepEqual(errors, [], 'No browser errors');
|
||||
} finally {
|
||||
await fs.writeFile(path.join(output, baseline ? 'baseline-result.json' : 'browser-result.json'), JSON.stringify({ checks, errors, measurements }, null, 2));
|
||||
await browser.close();
|
||||
}
|
||||
console.log(`${baseline ? 'Baseline' : 'P11'}: ${checks.length} checks, ${errors.length} errors`);
|
||||
@@ -7,5 +7,5 @@ export default defineConfig({
|
||||
base: './',
|
||||
plugins: [react()],
|
||||
server: { host: '127.0.0.1', port: 6026, strictPort: true },
|
||||
build: { outDir: 'dist', emptyOutDir: true, rolldownOptions: { input: { index: path.resolve(import.meta.dirname, 'index.html'), p1: path.resolve(import.meta.dirname, 'p1.html') } } },
|
||||
build: { outDir: 'dist', emptyOutDir: true, rolldownOptions: { input: { index: path.resolve(import.meta.dirname, 'index.html'), p1: path.resolve(import.meta.dirname, 'p1.html'), p11: path.resolve(import.meta.dirname, 'p11.html') } } },
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
// Plain HTTP consumer: only the published Core ESM and CSS, no React runtime.
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import assert from 'node:assert/strict';
|
||||
import { launchChromium } from '../../scripts/orgchart-browser-runtime.mjs';
|
||||
const output = path.resolve('test-results/org-chart-p11');
|
||||
const fixture = path.resolve('examples/org-chart-react/dist/plain-p11');
|
||||
await fs.mkdir(fixture, { recursive: true });
|
||||
for (const file of ['index.js', 'style.css', 'engine.css']) await fs.copyFile(`packages/org-chart/dist/${file}`, path.join(fixture, file));
|
||||
await fs.copyFile('examples/org-chart/data-expanded-small.json', path.join(fixture, 'data.json'));
|
||||
const theme = (await fs.readFile('examples/org-chart-react/external-theme.css', 'utf8')).replace("@import '@hm-design/org-chart/style.css';", "@import './style.css';");
|
||||
await fs.writeFile(path.join(fixture, 'external-theme.css'), theme);
|
||||
await fs.writeFile(path.join(fixture, 'index.html'), '<!doctype html><html lang="ko"><meta charset="utf-8"><link rel="icon" href="data:,"><title>Plain Core P11</title><main id="root" style="height:850px"></main><script type="module" src="./app.js"></script></html>');
|
||||
await fs.writeFile(path.join(fixture, 'app.js'), `import { createOrgChart, JsonOrgChartDataSource } from './index.js';
|
||||
const data = await (await fetch('./data.json')).json();
|
||||
window.chart = createOrgChart({ container: document.querySelector('#root'), dataSource: new JsonOrgChartDataSource(data), layoutMode: 'auto', presentationMode: 'chart-only', initialView: 'fit', wheelZoom: 'direct', stylesheetUrls: { engine: './engine.css', core: location.search ? './external-theme.css' : './style.css' } });
|
||||
await window.chart.mount();`);
|
||||
const browser = await launchChromium({ headless: true });
|
||||
const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
|
||||
const requests = [], errors = [], checks = [];
|
||||
page.on('request', request => requests.push(request.url()));
|
||||
page.on('pageerror', error => errors.push(error.message));
|
||||
page.on('console', message => { if (message.type() === 'error') errors.push(message.text()); });
|
||||
try {
|
||||
for (const custom of [false, true]) {
|
||||
await page.goto(`http://127.0.0.1:6032/plain-p11/index.html${custom ? '?theme=1' : ''}`);
|
||||
await page.locator('.hm-orgchart-employee').first().waitFor();
|
||||
await page.waitForFunction(() => [...document.querySelector('.hm-orgchart-host').shadowRoot.querySelectorAll('link')].every(link => link.sheet));
|
||||
for (const [id, color] of Object.entries(custom ? { hanmac: 'rgb(36, 87, 167)', saman: 'rgb(56, 92, 168)' } : { hanmac: 'rgb(203, 46, 40)', saman: 'rgb(240, 131, 0)' })) {
|
||||
assert.equal(await page.locator(`.node[data-company-id="${id}"] .hm-orgchart-organization-name`).first().evaluate(el => getComputedStyle(el).backgroundColor), color);
|
||||
checks.push({ name: `${custom ? 'external' : 'default'} ${id}`, status: 'PASS' });
|
||||
}
|
||||
if (custom) {
|
||||
await page.locator('.hm-orgchart-employee').first().click();
|
||||
await page.locator('.hm-orgchart-employee-dialog[open]').waitFor();
|
||||
assert.equal(await page.locator('.hm-orgchart-employee-dialog').evaluate(el => getComputedStyle(el).backgroundColor), 'rgb(238, 242, 255)');
|
||||
checks.push({ name: 'plain HTTP @import and native popup', status: 'PASS' });
|
||||
}
|
||||
}
|
||||
const viewport = await page.evaluate(() => {
|
||||
window.chart.fitToView(); const fit = window.chart.getViewportState();
|
||||
window.chart.zoomTo(Math.min(1, fit.scale * 1.3)); const zoom = window.chart.getViewportState();
|
||||
window.chart.panBy(30, 20); const pan = window.chart.getViewportState();
|
||||
window.chart.fitToView(); const restored = window.chart.getViewportState();
|
||||
return { fit, zoom, pan, restored };
|
||||
});
|
||||
assert.notEqual(viewport.fit.scale, viewport.zoom.scale);
|
||||
assert.equal(viewport.pan.x - viewport.zoom.x, 30);
|
||||
assert.equal(viewport.pan.y - viewport.zoom.y, 20);
|
||||
assert.ok(['scale', 'x', 'y'].every(key => Math.abs(viewport.restored[key] - viewport.fit[key]) < 0.01));
|
||||
checks.push({ name: 'public zoomTo / panBy / fitToView', status: 'PASS' });
|
||||
await page.evaluate(() => window.chart.destroy());
|
||||
assert.equal(await page.locator('.hm-orgchart-host').count(), 0);
|
||||
checks.push({ name: 'public destroy cleanup', status: 'PASS' });
|
||||
assert.ok(requests.every(url => !/react|node_modules/.test(new URL(url).pathname)));
|
||||
assert.deepEqual(errors, []);
|
||||
checks.push({ name: 'Core ESM runs without React or bundler requests', status: 'PASS' });
|
||||
} finally {
|
||||
await fs.writeFile(path.join(output, 'standalone-vanilla-result.json'), JSON.stringify({ checks, requests, errors }, null, 2));
|
||||
await browser.close();
|
||||
}
|
||||
console.log(`Standalone Vanilla: ${checks.length} checks, ${errors.length} errors`);
|
||||
Reference in new issue
Block a user