forked from baron/baron-sso
680 lines
22 KiB
TypeScript
680 lines
22 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
|
|
type TenantFixture = {
|
|
id: string;
|
|
type: string;
|
|
name: string;
|
|
slug: string;
|
|
description: string;
|
|
status: string;
|
|
parentId?: string;
|
|
config?: Record<string, unknown>;
|
|
memberCount: number;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
};
|
|
|
|
function tenant(
|
|
id: string,
|
|
name: string,
|
|
slug: string,
|
|
parentId?: string,
|
|
): TenantFixture {
|
|
return {
|
|
id,
|
|
type: parentId ? "USER_GROUP" : "COMPANY",
|
|
name,
|
|
slug,
|
|
description: "",
|
|
status: "active",
|
|
parentId,
|
|
memberCount: 1,
|
|
createdAt: "2026-04-01T00:00:00.000Z",
|
|
updatedAt: "2026-04-01T00:00:00.000Z",
|
|
};
|
|
}
|
|
|
|
function user(id: string, name: string, companyCode: string) {
|
|
return {
|
|
id,
|
|
email: `${id}@example.com`,
|
|
name,
|
|
role: "user",
|
|
status: "active",
|
|
companyCode,
|
|
grade: "사원",
|
|
createdAt: "2026-04-01T00:00:00.000Z",
|
|
updatedAt: "2026-04-01T00:00:00.000Z",
|
|
};
|
|
}
|
|
|
|
test("org chart viewport pans with drag and zooms with the mouse wheel", async ({
|
|
page,
|
|
}) => {
|
|
await page.route("**/api/v1/public/orgchart**", async (route) => {
|
|
await route.fulfill({
|
|
contentType: "application/json",
|
|
body: JSON.stringify({
|
|
sharedWith: "Playwright",
|
|
tenants: [
|
|
tenant("root", "Baron Group", "baron"),
|
|
tenant("engineering", "Engineering", "engineering", "root"),
|
|
tenant("platform", "Platform", "platform", "engineering"),
|
|
tenant("security", "Security", "security", "engineering"),
|
|
tenant("product", "Product", "product", "root"),
|
|
tenant("design", "Design", "design", "product"),
|
|
tenant("operations", "Operations", "operations", "root"),
|
|
],
|
|
users: [
|
|
user("u-root", "Root User", "baron"),
|
|
user("u-eng", "Engineering User", "engineering"),
|
|
user("u-platform", "Platform User", "platform"),
|
|
user("u-security", "Security User", "security"),
|
|
user("u-product", "Product User", "product"),
|
|
user("u-design", "Design User", "design"),
|
|
user("u-ops", "Operations User", "operations"),
|
|
],
|
|
}),
|
|
});
|
|
});
|
|
|
|
await page.goto("/chart?token=pan-zoom");
|
|
await expect(page.getByRole("heading", { name: "조직 현황" })).toBeVisible();
|
|
|
|
const viewport = page.locator('[data-testid="orgchart-viewport"]');
|
|
const canvas = page.locator('[data-testid="orgchart-canvas"]');
|
|
const svg = page.locator('[data-testid="orgchart-vector-svg"]');
|
|
await expect(viewport).toBeVisible();
|
|
await expect(canvas).toBeVisible();
|
|
await expect(svg).toBeVisible();
|
|
|
|
await expect
|
|
.poll(async () =>
|
|
viewport.evaluate((element) => {
|
|
const style = window.getComputedStyle(element);
|
|
return `${style.overflowX}/${style.overflowY}`;
|
|
}),
|
|
)
|
|
.toBe("hidden/hidden");
|
|
|
|
const initialViewBox = await svg.getAttribute("viewBox");
|
|
const box = await viewport.boundingBox();
|
|
expect(box).not.toBeNull();
|
|
|
|
if (!box) return;
|
|
|
|
await page.mouse.move(box.x + 24, box.y + box.height - 24);
|
|
await page.mouse.down();
|
|
await page.mouse.move(box.x + 164, box.y + box.height - 104);
|
|
await page.mouse.up();
|
|
|
|
await expect
|
|
.poll(async () => svg.getAttribute("viewBox"))
|
|
.not.toBe(initialViewBox);
|
|
|
|
const afterDragViewBox = await svg.getAttribute("viewBox");
|
|
|
|
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
|
|
await page.mouse.wheel(0, -500);
|
|
|
|
await expect
|
|
.poll(async () => svg.getAttribute("viewBox"))
|
|
.not.toBe(afterDragViewBox);
|
|
|
|
const scale = await svg.evaluate((element) =>
|
|
Number.parseFloat(element.getAttribute("data-scale") ?? "1"),
|
|
);
|
|
expect(scale).toBeGreaterThan(1);
|
|
});
|
|
|
|
test("org chart dashboard uses the full screen below the orgfront topbar", async ({
|
|
page,
|
|
}) => {
|
|
await page.route("**/api/v1/public/orgchart**", async (route) => {
|
|
await route.fulfill({
|
|
contentType: "application/json",
|
|
body: JSON.stringify({
|
|
sharedWith: "Playwright",
|
|
tenants: [
|
|
tenant("root", "Baron Group", "baron"),
|
|
tenant("engineering", "Engineering", "engineering", "root"),
|
|
],
|
|
users: [
|
|
user("u-root", "Root User", "baron"),
|
|
user("u-eng", "Engineering User", "engineering"),
|
|
],
|
|
}),
|
|
});
|
|
});
|
|
|
|
await page.goto("/chart?token=full-screen");
|
|
await expect(page.getByRole("heading", { name: "조직 현황" })).toBeVisible();
|
|
|
|
const metrics = await page.evaluate(() => {
|
|
const topbar = document
|
|
.querySelector('[data-testid="orgfront-topbar"]')
|
|
?.getBoundingClientRect();
|
|
const main = document
|
|
.querySelector('[data-testid="orgfront-main"]')
|
|
?.getBoundingClientRect();
|
|
const shell = document
|
|
.querySelector('[data-testid="orgchart-dashboard-shell"]')
|
|
?.getBoundingClientRect();
|
|
|
|
if (!topbar || !main || !shell) {
|
|
throw new Error("Missing org chart layout elements");
|
|
}
|
|
|
|
return {
|
|
innerHeight: window.innerHeight,
|
|
innerWidth: window.innerWidth,
|
|
mainTop: main.top,
|
|
shellBottom: shell.bottom,
|
|
shellLeft: shell.left,
|
|
shellRight: shell.right,
|
|
shellTop: shell.top,
|
|
topbarBottom: topbar.bottom,
|
|
};
|
|
});
|
|
|
|
expect(Math.abs(metrics.mainTop - metrics.topbarBottom)).toBeLessThanOrEqual(
|
|
1,
|
|
);
|
|
expect(metrics.shellTop).toBe(metrics.topbarBottom);
|
|
expect(metrics.shellLeft).toBeLessThanOrEqual(1);
|
|
expect(metrics.shellRight).toBeGreaterThanOrEqual(metrics.innerWidth - 1);
|
|
expect(metrics.shellBottom).toBeGreaterThanOrEqual(metrics.innerHeight - 1);
|
|
});
|
|
|
|
test("org chart non-shared title does not render the MH Dashboard eyebrow", async ({
|
|
page,
|
|
}) => {
|
|
await page.addInitScript(() => {
|
|
window.localStorage.setItem("playwright_auth_bypass", "1");
|
|
window.localStorage.setItem("dev_tenant_id", "group");
|
|
});
|
|
|
|
await page.route("**/api/v1/admin/tenants**", async (route) => {
|
|
await route.fulfill({
|
|
contentType: "application/json",
|
|
body: JSON.stringify({
|
|
items: [
|
|
{
|
|
...tenant("group", "Baron Group", "baron"),
|
|
type: "COMPANY_GROUP",
|
|
},
|
|
tenant("engineering", "Engineering", "engineering", "group"),
|
|
],
|
|
limit: 10000,
|
|
offset: 0,
|
|
total: 2,
|
|
}),
|
|
});
|
|
});
|
|
|
|
await page.route("**/api/v1/admin/users**", async (route) => {
|
|
await route.fulfill({
|
|
contentType: "application/json",
|
|
body: JSON.stringify({
|
|
items: [user("u-eng", "Engineering User", "engineering")],
|
|
limit: 5000,
|
|
offset: 0,
|
|
total: 1,
|
|
}),
|
|
});
|
|
});
|
|
|
|
await page.goto("/chart");
|
|
|
|
await expect(page.getByRole("heading", { name: "조직 현황" })).toBeVisible();
|
|
await expect(page.getByText("MH Dashboard", { exact: true })).toHaveCount(0);
|
|
});
|
|
|
|
test("org chart renders dense member nodes with calculated member columns", async ({
|
|
page,
|
|
}) => {
|
|
const denseUsers = Array.from({ length: 6 }, (_, index) =>
|
|
user(`u-dense-${index + 1}`, `Dense User ${index + 1}`, "baron"),
|
|
);
|
|
|
|
await page.route("**/api/v1/public/orgchart**", async (route) => {
|
|
await route.fulfill({
|
|
contentType: "application/json",
|
|
body: JSON.stringify({
|
|
sharedWith: "Playwright",
|
|
tenants: [tenant("root", "Baron Group", "baron")],
|
|
users: denseUsers,
|
|
}),
|
|
});
|
|
});
|
|
|
|
await page.goto("/chart?token=dense-members");
|
|
await expect(page.getByRole("heading", { name: "조직 현황" })).toBeVisible();
|
|
|
|
const rootNode = page.locator('[data-testid="orgchart-node-root"]');
|
|
await expect(rootNode).toHaveAttribute("width", /[4-9]\d{2,}/);
|
|
await expect(rootNode.locator('[data-member-columns="2"]')).toBeVisible();
|
|
await expect(rootNode.getByText("Dense User 6")).toBeVisible();
|
|
});
|
|
|
|
test("public org chart hides internal and private tenants and renders org unit type", async ({
|
|
page,
|
|
}) => {
|
|
await page.route("**/api/v1/public/orgchart**", async (route) => {
|
|
await route.fulfill({
|
|
contentType: "application/json",
|
|
body: JSON.stringify({
|
|
sharedWith: "Playwright",
|
|
tenants: [
|
|
{
|
|
...tenant("group", "한맥가족", "hanmac-family"),
|
|
type: "COMPANY_GROUP",
|
|
},
|
|
tenant("company", "삼안", "saman", "group"),
|
|
{
|
|
...tenant("open-team", "공개 팀", "open-team", "company"),
|
|
config: { orgUnitType: "팀", visibility: "public" },
|
|
},
|
|
{
|
|
...tenant("internal-team", "내부 팀", "internal-team", "company"),
|
|
config: { visibility: "internal" },
|
|
},
|
|
{
|
|
...tenant("private-team", "비공개 팀", "private-team", "company"),
|
|
config: { visibility: "private" },
|
|
},
|
|
tenant(
|
|
"private-child",
|
|
"비공개 하위",
|
|
"private-child",
|
|
"private-team",
|
|
),
|
|
],
|
|
users: [
|
|
user("u-open", "Open User", "open-team"),
|
|
user("u-internal", "Internal User", "internal-team"),
|
|
user("u-private", "Private User", "private-team"),
|
|
user("u-private-child", "Private Child User", "private-child"),
|
|
],
|
|
}),
|
|
});
|
|
});
|
|
|
|
await page.goto("/chart?token=tenant-visibility");
|
|
await expect(page.getByRole("heading", { name: "조직 현황" })).toBeVisible();
|
|
|
|
const svg = page.locator('[data-testid="orgchart-vector-svg"]');
|
|
await expect(svg.getByText("공개 팀", { exact: true })).toBeVisible();
|
|
await expect(svg.getByText("팀", { exact: true })).toBeVisible();
|
|
await expect(svg.getByText(/Open User/)).toBeVisible();
|
|
await expect(svg.getByText("내부 팀", { exact: true })).toHaveCount(0);
|
|
await expect(svg.getByText("Internal User", { exact: true })).toHaveCount(0);
|
|
await expect(svg.getByText("비공개 팀", { exact: true })).toHaveCount(0);
|
|
await expect(svg.getByText("Private User", { exact: true })).toHaveCount(0);
|
|
await expect(svg.getByText("비공개 하위", { exact: true })).toHaveCount(0);
|
|
await expect(
|
|
svg.getByText("Private Child User", { exact: true }),
|
|
).toHaveCount(0);
|
|
});
|
|
|
|
test("org chart colors hanmac family and nested baron company group separately", async ({
|
|
page,
|
|
}) => {
|
|
await page.route("**/api/v1/public/orgchart**", async (route) => {
|
|
await route.fulfill({
|
|
contentType: "application/json",
|
|
body: JSON.stringify({
|
|
sharedWith: "Playwright",
|
|
tenants: [
|
|
{
|
|
...tenant("family", "한맥가족", "hanmac-family"),
|
|
type: "COMPANY_GROUP",
|
|
},
|
|
{
|
|
...tenant("baron-group", "Baron Group", "baron-group", "family"),
|
|
type: "COMPANY_GROUP",
|
|
},
|
|
{
|
|
...tenant("baron-company", "Baron Company", "baron", "baron-group"),
|
|
type: "COMPANY",
|
|
},
|
|
],
|
|
users: [user("u-baron", "Baron User", "baron")],
|
|
}),
|
|
});
|
|
});
|
|
|
|
await page.goto("/chart?token=baron-group-color");
|
|
await expect(page.getByRole("heading", { name: "조직 현황" })).toBeVisible();
|
|
|
|
const svg = page.locator('[data-testid="orgchart-vector-svg"]');
|
|
await expect(svg.getByText("Baron Group", { exact: true })).toBeVisible();
|
|
|
|
const colors = await page.evaluate(() => {
|
|
function headerColor(nodeId: string) {
|
|
const node = document.querySelector(
|
|
`[data-testid="orgchart-node-${nodeId}"]`,
|
|
);
|
|
const header = node?.querySelector("div > div");
|
|
return header ? window.getComputedStyle(header).backgroundColor : "";
|
|
}
|
|
|
|
return {
|
|
baronCompany: headerColor("baron-company"),
|
|
baronGroup: headerColor("baron-group"),
|
|
family: headerColor("family"),
|
|
};
|
|
});
|
|
|
|
expect(colors.family).toBe("rgb(0, 0, 0)");
|
|
expect(colors.baronGroup).toBe("rgb(0, 76, 191)");
|
|
expect(colors.baronCompany).toBe("rgb(0, 76, 191)");
|
|
});
|
|
|
|
test("org chart orders top organization choices by the hanmac family policy", async ({
|
|
page,
|
|
}) => {
|
|
await page.route("**/api/v1/public/orgchart**", async (route) => {
|
|
await route.fulfill({
|
|
contentType: "application/json",
|
|
body: JSON.stringify({
|
|
sharedWith: "Playwright",
|
|
tenants: [
|
|
{
|
|
...tenant("family", "한맥가족", "hanmac-family"),
|
|
type: "COMPANY_GROUP",
|
|
},
|
|
{
|
|
...tenant("saman", "삼안", "saman", "family"),
|
|
type: "COMPANY",
|
|
},
|
|
{
|
|
...tenant("baron-group", "바론그룹", "baron-group", "family"),
|
|
type: "COMPANY_GROUP",
|
|
},
|
|
{
|
|
...tenant("hanmac", "한맥기술", "hanmac", "family"),
|
|
type: "COMPANY",
|
|
},
|
|
{
|
|
...tenant("gpdtdc", "총괄기획&기술개발센터", "gpdtdc", "family"),
|
|
type: "ORGANIZATION",
|
|
},
|
|
],
|
|
users: [],
|
|
}),
|
|
});
|
|
});
|
|
|
|
await page.goto("/chart?token=org-selection-order");
|
|
await expect(page.getByRole("heading", { name: "조직 현황" })).toBeVisible();
|
|
|
|
const labels = await page
|
|
.getByTestId("orgchart-org-selector")
|
|
.locator("button")
|
|
.evaluateAll((buttons) =>
|
|
buttons.map((button) => button.textContent?.trim() ?? ""),
|
|
);
|
|
|
|
expect(labels.slice(0, 5)).toEqual([
|
|
"한맥가족",
|
|
"총괄기획&기술개발센터",
|
|
"삼안",
|
|
"한맥기술",
|
|
"바론그룹",
|
|
]);
|
|
});
|
|
|
|
test("org chart compresses many sibling organizations and allows wide zoom out", async ({
|
|
page,
|
|
}) => {
|
|
const childTenants = Array.from({ length: 13 }, (_, index) =>
|
|
tenant(
|
|
`team-${index + 1}`,
|
|
`Team ${index + 1}`,
|
|
`team-${index + 1}`,
|
|
"root",
|
|
),
|
|
);
|
|
|
|
await page.route("**/api/v1/public/orgchart**", async (route) => {
|
|
await route.fulfill({
|
|
contentType: "application/json",
|
|
body: JSON.stringify({
|
|
sharedWith: "Playwright",
|
|
tenants: [tenant("root", "Baron Group", "baron"), ...childTenants],
|
|
users: childTenants.map((child, index) =>
|
|
user(`u-team-${index + 1}`, `Team ${index + 1} User`, child.slug),
|
|
),
|
|
}),
|
|
});
|
|
});
|
|
|
|
await page.goto("/chart?token=wide-siblings");
|
|
await expect(page.getByRole("heading", { name: "조직 현황" })).toBeVisible();
|
|
|
|
const viewport = page.locator('[data-testid="orgchart-viewport"]');
|
|
const svg = page.locator('[data-testid="orgchart-vector-svg"]');
|
|
await expect(svg).toBeVisible();
|
|
await expect(svg.getByText("Team 13", { exact: true })).toBeVisible();
|
|
await expect(svg.locator('foreignObject[data-node-id^="team-"]')).toHaveCount(
|
|
13,
|
|
);
|
|
await expect(
|
|
page.getByRole("button", { name: "조직: 한맥가족" }),
|
|
).toBeVisible();
|
|
await expect(page.getByText("배치", { exact: true })).toBeHidden();
|
|
await expect(page.getByRole("button", { name: "배치: 자동" })).toBeVisible();
|
|
await expect(page.getByText("연결", { exact: true })).toHaveCount(0);
|
|
await expect(page.getByText("상위연결", { exact: true })).toHaveCount(0);
|
|
|
|
const autoChildYPositions = await svg
|
|
.locator('foreignObject[data-node-id^="team-"]')
|
|
.evaluateAll((nodes) =>
|
|
nodes
|
|
.map((node) => node.getAttribute("y") ?? "")
|
|
.filter((value) => value.length > 0),
|
|
);
|
|
|
|
expect(new Set(autoChildYPositions).size).toBeGreaterThan(1);
|
|
await expect(svg.locator("path")).toHaveCount(13);
|
|
await expect(
|
|
svg.locator('path:not([data-hidden-default="true"])'),
|
|
).toHaveCount(4);
|
|
await expect(svg.locator('path[data-hidden-default="true"]')).toHaveCount(9);
|
|
|
|
await svg.locator('foreignObject[data-node-id="team-13"]').hover();
|
|
await expect(svg.locator('path[data-highlighted="true"]')).toHaveCount(1);
|
|
await expect(svg.locator('path[data-muted="true"]')).toHaveCount(4);
|
|
|
|
await page.getByTestId("orgchart-layout-mode-option").hover();
|
|
await expect(page.getByText("배치", { exact: true })).toBeVisible();
|
|
await expect(
|
|
page.getByRole("button", { exact: true, name: "자동" }),
|
|
).toHaveCount(0);
|
|
await page.getByRole("button", { name: "Top-down" }).click();
|
|
await expect
|
|
.poll(async () =>
|
|
svg
|
|
.locator('foreignObject[data-node-id^="team-"]')
|
|
.evaluateAll(
|
|
(nodes) =>
|
|
new Set(
|
|
nodes
|
|
.map((node) => node.getAttribute("y") ?? "")
|
|
.filter((value) => value.length > 0),
|
|
).size,
|
|
),
|
|
)
|
|
.toBe(1);
|
|
|
|
await page.getByTestId("orgchart-layout-mode-option").hover();
|
|
await page.getByRole("button", { name: "3열" }).click();
|
|
const threeColumnPositions = await svg
|
|
.locator('foreignObject[data-node-id^="team-"]')
|
|
.evaluateAll((nodes) =>
|
|
nodes.map((node) => ({
|
|
x: node.getAttribute("x") ?? "",
|
|
y: node.getAttribute("y") ?? "",
|
|
})),
|
|
);
|
|
|
|
expect(new Set(threeColumnPositions.map((position) => position.x)).size).toBe(
|
|
3,
|
|
);
|
|
expect(new Set(threeColumnPositions.map((position) => position.y)).size).toBe(
|
|
5,
|
|
);
|
|
await expect(svg.locator("path")).toHaveCount(13);
|
|
await expect(
|
|
svg.locator('path:not([data-hidden-default="true"])'),
|
|
).toHaveCount(3);
|
|
|
|
const box = await viewport.boundingBox();
|
|
expect(box).not.toBeNull();
|
|
if (!box) return;
|
|
|
|
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
|
|
await page.mouse.wheel(0, 2500);
|
|
|
|
await expect
|
|
.poll(async () =>
|
|
svg.evaluate((element) =>
|
|
Number.parseFloat(element.getAttribute("data-scale") ?? "1"),
|
|
),
|
|
)
|
|
.toBeLessThan(0.45);
|
|
});
|
|
|
|
test("org chart selects first and second depth organizations from company hover choices", async ({
|
|
page,
|
|
}) => {
|
|
await page.route("**/api/v1/public/orgchart**", async (route) => {
|
|
await route.fulfill({
|
|
contentType: "application/json",
|
|
body: JSON.stringify({
|
|
sharedWith: "Playwright",
|
|
tenants: [
|
|
{
|
|
...tenant("group", "Baron Group", "baron"),
|
|
type: "COMPANY_GROUP",
|
|
},
|
|
{
|
|
...tenant("company", "Company A", "company-a", "group"),
|
|
type: "COMPANY",
|
|
},
|
|
tenant("department", "Department A", "department-a", "company"),
|
|
tenant("squad", "Squad A", "squad-a", "department"),
|
|
tenant("team", "Team A", "team-a", "squad"),
|
|
],
|
|
users: [
|
|
user("u-company", "Company User", "company-a"),
|
|
user("u-department", "Department User", "department-a"),
|
|
user("u-squad", "Squad User", "squad-a"),
|
|
user("u-team", "Team User", "team-a"),
|
|
],
|
|
}),
|
|
});
|
|
});
|
|
|
|
await page.goto("/chart?token=company-depth-filter");
|
|
await expect(page.getByRole("heading", { name: "조직 현황" })).toBeVisible();
|
|
|
|
await expect(
|
|
page.getByRole("button", { name: "조직: 한맥가족" }),
|
|
).toBeVisible();
|
|
await expect(page.getByRole("button", { name: "Company A" })).toBeVisible();
|
|
await expect(page.getByText("하위범위", { exact: true })).toHaveCount(0);
|
|
await expect(page.getByText("조직", { exact: true })).toHaveCount(0);
|
|
await page.getByRole("button", { name: "Company A" }).click();
|
|
const svg = page.locator('[data-testid="orgchart-vector-svg"]');
|
|
await expect(svg.getByText("Team A", { exact: true })).toBeVisible();
|
|
await expect(
|
|
page.getByRole("button", { name: "조직: Company A" }),
|
|
).toBeVisible();
|
|
|
|
await expect(
|
|
page
|
|
.getByTestId("orgchart-company-option-company")
|
|
.getByRole("button", { name: "Company A" }),
|
|
).toBeVisible();
|
|
|
|
const orgButtonColor = await page
|
|
.getByRole("button", { name: "조직: Company A" })
|
|
.evaluate((element) => window.getComputedStyle(element).backgroundColor);
|
|
const layoutButtonColor = await page
|
|
.getByRole("button", { name: "배치: 자동" })
|
|
.evaluate((element) => window.getComputedStyle(element).backgroundColor);
|
|
expect(orgButtonColor).not.toBe(layoutButtonColor);
|
|
|
|
await page.getByTestId("orgchart-company-option-company").hover();
|
|
await expect(svg.getByText("Department A", { exact: true })).toBeVisible();
|
|
await page.getByRole("button", { name: "1뎁스 Department A" }).click();
|
|
await expect(
|
|
page.getByRole("button", { name: "조직: Department A" }),
|
|
).toBeVisible();
|
|
await expect(svg.getByText("Squad A", { exact: true })).toBeVisible();
|
|
|
|
await page.getByTestId("orgchart-company-option-company").hover();
|
|
await page.getByRole("button", { name: "2뎁스 Squad A" }).click();
|
|
await expect(
|
|
page.getByRole("button", { name: "조직: Squad A" }),
|
|
).toBeVisible();
|
|
await expect(svg.getByText("Squad A", { exact: true })).toBeVisible();
|
|
await expect(svg.getByText("Team A", { exact: true })).toBeVisible();
|
|
});
|
|
|
|
test("org chart uses semantic zoom to simplify deep nodes and restore labels on zoom in", async ({
|
|
page,
|
|
}) => {
|
|
await page.route("**/api/v1/public/orgchart**", async (route) => {
|
|
await route.fulfill({
|
|
contentType: "application/json",
|
|
body: JSON.stringify({
|
|
sharedWith: "Playwright",
|
|
tenants: [
|
|
tenant("root", "Baron Group", "baron"),
|
|
tenant("department", "Archive Department", "department", "root"),
|
|
tenant("division", "Archive Division", "division", "department"),
|
|
tenant("deep", "Archive Deep Team", "deep", "division"),
|
|
],
|
|
users: [
|
|
user("u-root", "Root User", "baron"),
|
|
user("u-department", "Department User", "department"),
|
|
user("u-division", "Division User", "division"),
|
|
user("u-deep", "Deep User", "deep"),
|
|
],
|
|
}),
|
|
});
|
|
});
|
|
|
|
await page.goto("/chart?token=semantic-zoom");
|
|
await expect(page.getByRole("heading", { name: "조직 현황" })).toBeVisible();
|
|
|
|
const viewport = page.locator('[data-testid="orgchart-viewport"]');
|
|
const svg = page.locator('[data-testid="orgchart-vector-svg"]');
|
|
const deepNode = svg.locator('foreignObject[data-node-id="deep"]');
|
|
|
|
await expect(svg).toHaveAttribute("data-semantic-zoom", "detail");
|
|
await expect(deepNode.getByText("Archive Deep Team")).toBeVisible();
|
|
|
|
const box = await viewport.boundingBox();
|
|
expect(box).not.toBeNull();
|
|
if (!box) return;
|
|
|
|
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
|
|
await page.mouse.wheel(0, 4000);
|
|
|
|
await expect
|
|
.poll(async () => svg.getAttribute("data-semantic-zoom"))
|
|
.toBe("overview");
|
|
await expect(deepNode.getByText("Archive Deep Team")).toHaveCount(0);
|
|
|
|
await page.mouse.wheel(0, -4000);
|
|
|
|
await expect
|
|
.poll(async () => svg.getAttribute("data-semantic-zoom"))
|
|
.toBe("detail");
|
|
await expect(deepNode.getByText("Archive Deep Team")).toBeVisible();
|
|
});
|