forked from baron/baron-sso
조직현황 구조변경. 총괄센터삼안 실 조직 삽입확인
This commit is contained in:
@@ -8,6 +8,7 @@ type TenantFixture = {
|
||||
description: string;
|
||||
status: string;
|
||||
parentId?: string;
|
||||
config?: Record<string, unknown>;
|
||||
memberCount: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
@@ -41,7 +42,7 @@ function user(id: string, name: string, companyCode: string) {
|
||||
role: "user",
|
||||
status: "active",
|
||||
companyCode,
|
||||
position: "사원",
|
||||
grade: "사원",
|
||||
createdAt: "2026-04-01T00:00:00.000Z",
|
||||
updatedAt: "2026-04-01T00:00:00.000Z",
|
||||
};
|
||||
@@ -125,3 +126,554 @@ test("org chart viewport pans with drag and zooms with the mouse wheel", async (
|
||||
);
|
||||
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();
|
||||
});
|
||||
|
||||
@@ -56,7 +56,7 @@ function user(
|
||||
status: "active",
|
||||
tenantSlug,
|
||||
companyCode: tenantSlug,
|
||||
position: "사원",
|
||||
grade: "사원",
|
||||
createdAt: "2026-04-01T00:00:00.000Z",
|
||||
updatedAt: "2026-04-01T00:00:00.000Z",
|
||||
...overrides,
|
||||
@@ -173,7 +173,8 @@ async function installOrgPickerApiMock(
|
||||
user("user-platform", "Platform User", "platform", {
|
||||
metadata: { employeeNumber: "EMP-9001", skill: "Kubernetes" },
|
||||
jobTitle: "Platform Engineer",
|
||||
position: "책임",
|
||||
grade: "책임",
|
||||
position: "팀장",
|
||||
}),
|
||||
user("user-sales", "Sales User", "sales"),
|
||||
];
|
||||
@@ -252,14 +253,64 @@ test("picker menu lets developers switch selection mode and selectable type", as
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test("picker displays user names with job title and position", async ({
|
||||
test("picker defaults to the hanmac-family company-group when no tenant id is supplied", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.unroute("**/api/v1/admin/tenants**");
|
||||
await page.unroute("**/api/v1/admin/users**");
|
||||
|
||||
const tenants = [
|
||||
tenant("wrong-group", "COMPANY_GROUP", "Wrong Group", "wrong-group"),
|
||||
tenant(
|
||||
"wrong-company",
|
||||
"COMPANY",
|
||||
"Wrong Company",
|
||||
"wrong-company",
|
||||
"wrong-group",
|
||||
),
|
||||
tenant("hanmac-family-id", "COMPANY_GROUP", "한맥가족", "hanmac-family"),
|
||||
tenant("saman-id", "COMPANY", "삼안", "saman", "hanmac-family-id"),
|
||||
];
|
||||
|
||||
await page.route("**/api/v1/admin/tenants**", async (route) => {
|
||||
await route.fulfill({
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({
|
||||
items: tenants,
|
||||
total: tenants.length,
|
||||
limit: 10000,
|
||||
offset: 0,
|
||||
}),
|
||||
});
|
||||
});
|
||||
await page.route("**/api/v1/admin/users**", async (route) => {
|
||||
await route.fulfill({
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({
|
||||
items: [],
|
||||
total: 0,
|
||||
limit: 5000,
|
||||
offset: 0,
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto(withShareToken("/picker"));
|
||||
|
||||
const picker = page.frameLocator("iframe");
|
||||
await expect(picker.getByText("한맥가족", { exact: true })).toBeVisible();
|
||||
await expect(picker.getByText("삼안", { exact: true })).toBeVisible();
|
||||
await expect(picker.getByText("Wrong Group", { exact: true })).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("picker displays user names with grade and optional position", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto(withShareToken("/embed/picker?mode=single&select=user"));
|
||||
|
||||
await expect(
|
||||
page.getByRole("button", {
|
||||
name: "Platform User(Platform Engineer) 책임",
|
||||
name: "Platform User 책임(팀장)",
|
||||
}),
|
||||
).toBeVisible();
|
||||
});
|
||||
@@ -319,17 +370,17 @@ test("embed preview menu updates the iframe picker source", async ({
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test("embed preview passes tenant id and custom dimensions through the picker url", async ({
|
||||
test("embed preview passes tenant slug and custom dimensions through the picker url", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto(withShareToken("/embed-preview"));
|
||||
|
||||
await page.getByLabel("tenant ID").fill("company-baron");
|
||||
await page.getByLabel("tenant slug").fill("baron");
|
||||
await page.getByLabel("임베딩 너비").fill("520");
|
||||
await page.getByLabel("임베딩 높이").fill("480");
|
||||
|
||||
await expect(page.getByTestId("embed-preview-src")).toContainText(
|
||||
"tenantId=company-baron",
|
||||
"tenantSlug=baron",
|
||||
);
|
||||
await expect(page.getByTestId("embed-preview-src")).toContainText(
|
||||
"width=520",
|
||||
@@ -347,16 +398,16 @@ test("embed preview passes tenant id and custom dimensions through the picker ur
|
||||
await expect(picker.getByText("Sales User")).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("embed picker scopes the tree by tenant id, hides users for tenant selection, and keeps direct members before child tenants", async ({
|
||||
test("embed picker scopes the tree by tenant slug, hides users for tenant selection, and keeps direct members before child tenants", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto(
|
||||
withShareToken("/embed-preview?tenantId=company-baron&select=tenant"),
|
||||
withShareToken("/embed-preview?tenantSlug=baron&select=tenant"),
|
||||
);
|
||||
|
||||
await expect(page.getByLabel("tenant ID")).toHaveValue("company-baron");
|
||||
await expect(page.getByLabel("tenant slug")).toHaveValue("baron");
|
||||
await expect(page.getByTestId("embed-preview-src")).toContainText(
|
||||
"tenantId=company-baron",
|
||||
"tenantSlug=baron",
|
||||
);
|
||||
|
||||
const picker = page.frameLocator("iframe");
|
||||
@@ -599,7 +650,7 @@ test("embed picker includes descendants by default and can disable descendant in
|
||||
await picker.getByLabel("Engineering 선택").check();
|
||||
await expect(picker.getByLabel("Platform 선택")).toBeChecked();
|
||||
await expect(
|
||||
picker.getByLabel("Platform User(Platform Engineer) 책임 선택"),
|
||||
picker.getByLabel("Platform User 책임(팀장) 선택"),
|
||||
).toBeChecked();
|
||||
await picker.getByRole("button", { name: "선택 완료" }).click();
|
||||
|
||||
@@ -617,7 +668,7 @@ test("embed picker includes descendants by default and can disable descendant in
|
||||
await picker.getByLabel("Engineering 선택").check();
|
||||
await expect(picker.getByLabel("Platform 선택")).not.toBeChecked();
|
||||
await expect(
|
||||
picker.getByLabel("Platform User(Platform Engineer) 책임 선택"),
|
||||
picker.getByLabel("Platform User 책임(팀장) 선택"),
|
||||
).not.toBeChecked();
|
||||
await picker.getByRole("button", { name: "선택 완료" }).click();
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ function user(id: string, name: string, companyCode: string) {
|
||||
role: "user",
|
||||
status: "active",
|
||||
companyCode,
|
||||
position: "사원",
|
||||
grade: "사원",
|
||||
createdAt: "2026-04-01T00:00:00.000Z",
|
||||
updatedAt: "2026-04-01T00:00:00.000Z",
|
||||
};
|
||||
@@ -84,9 +84,7 @@ test("org chart uses svg viewBox zoom for sharp vector rendering", async ({
|
||||
const viewport = page.locator('[data-testid="orgchart-viewport"]');
|
||||
const svg = page.locator('[data-testid="orgchart-vector-svg"]');
|
||||
await expect(svg).toBeVisible();
|
||||
await expect(
|
||||
svg.locator("text", { hasText: "Engineering User" }),
|
||||
).toBeVisible();
|
||||
await expect(svg.getByText("Engineering User 사원")).toBeVisible();
|
||||
|
||||
const initialViewBox = await svg.getAttribute("viewBox");
|
||||
const transform = await page
|
||||
@@ -142,24 +140,18 @@ test("org chart filters by Hanmac family and company while excluding hanmac.kr a
|
||||
await expect(page.getByText("총 4명")).toBeVisible();
|
||||
|
||||
const svg = page.locator('[data-testid="orgchart-vector-svg"]');
|
||||
await expect(
|
||||
svg.locator("text", { hasText: "Hidden Hanmac User" }),
|
||||
).toHaveCount(0);
|
||||
await expect(
|
||||
svg.locator("text", { hasText: "Engineering User" }),
|
||||
).toBeVisible();
|
||||
await expect(svg.locator("text", { hasText: "Sales User" })).toBeVisible();
|
||||
await expect(svg.getByText(/Hidden Hanmac User/)).toHaveCount(0);
|
||||
await expect(svg.getByText("Engineering User 사원")).toBeVisible();
|
||||
await expect(svg.getByText("Sales User 사원")).toBeVisible();
|
||||
|
||||
await page.getByRole("button", { name: "Baron" }).click();
|
||||
await expect(page.getByText("총 2명")).toBeVisible();
|
||||
await expect(page.getByText("총 4명")).toHaveCount(0);
|
||||
await expect(
|
||||
svg.locator("text", { hasText: "Engineering User" }),
|
||||
).toBeVisible();
|
||||
await expect(svg.locator("text", { hasText: "Sales User" })).toHaveCount(0);
|
||||
await expect(svg.getByText("Engineering User 사원")).toBeVisible();
|
||||
await expect(svg.getByText(/Sales User/)).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("org chart displays user names with job title and position", async ({
|
||||
test("org chart displays user names with grade and optional position", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.route("**/api/v1/public/orgchart**", async (route) => {
|
||||
@@ -176,7 +168,8 @@ test("org chart displays user names with job title and position", async ({
|
||||
{
|
||||
...user("u-eng", "Engineering User", "engineering"),
|
||||
jobTitle: "Platform Engineer",
|
||||
position: "책임",
|
||||
grade: "책임",
|
||||
position: "팀장",
|
||||
},
|
||||
],
|
||||
}),
|
||||
@@ -186,11 +179,7 @@ test("org chart displays user names with job title and position", async ({
|
||||
await page.goto("/chart?token=display-name");
|
||||
|
||||
const svg = page.locator('[data-testid="orgchart-vector-svg"]');
|
||||
await expect(
|
||||
svg.locator("text", {
|
||||
hasText: "Engineering User(Platform Engineer) 책임",
|
||||
}),
|
||||
).toBeVisible();
|
||||
await expect(svg.getByText("Engineering User 책임(팀장)")).toBeVisible();
|
||||
});
|
||||
|
||||
test("org chart places multi-tenant users only on leaf memberships without duplicate rendering", async ({
|
||||
@@ -313,8 +302,8 @@ test("org chart places multi-tenant users only on leaf memberships without dupli
|
||||
await expect(page.getByText("총 1명")).toBeVisible();
|
||||
const svg = page.locator('[data-testid="orgchart-vector-svg"]');
|
||||
await expect(svg).toBeVisible();
|
||||
await expect(svg.locator("text", { hasText: "Shared User" })).toHaveCount(1);
|
||||
await expect(svg.locator("text").filter({ hasText: /^1$/ })).toHaveCount(4);
|
||||
await expect(svg.getByText(/Shared User/)).toHaveCount(1);
|
||||
await expect(svg.getByText(/^1$/)).toHaveCount(4);
|
||||
});
|
||||
|
||||
test("org chart counts multi-leaf tenant users once in ancestor totals", async ({
|
||||
@@ -355,8 +344,8 @@ test("org chart counts multi-leaf tenant users once in ancestor totals", async (
|
||||
await expect(page.getByText("총 1명")).toBeVisible();
|
||||
const svg = page.locator('[data-testid="orgchart-vector-svg"]');
|
||||
await expect(svg).toBeVisible();
|
||||
await expect(svg.locator("text", { hasText: "Shared User" })).toHaveCount(2);
|
||||
await expect(svg.locator("text").filter({ hasText: /^1$/ })).toHaveCount(5);
|
||||
await expect(svg.getByText(/Shared User/)).toHaveCount(2);
|
||||
await expect(svg.getByText(/^1$/)).toHaveCount(5);
|
||||
});
|
||||
|
||||
test("org chart hides system global tenant members", async ({ page }) => {
|
||||
@@ -389,8 +378,8 @@ test("org chart hides system global tenant members", async ({ page }) => {
|
||||
await expect(page.getByText("총 1명")).toBeVisible();
|
||||
const svg = page.locator('[data-testid="orgchart-vector-svg"]');
|
||||
await expect(svg).toBeVisible();
|
||||
await expect(svg.locator("text", { hasText: "시스템 전역" })).toHaveCount(0);
|
||||
await expect(svg.locator("text", { hasText: "Global Admin" })).toHaveCount(0);
|
||||
await expect(svg.locator("text", { hasText: "System Admin" })).toHaveCount(0);
|
||||
await expect(svg.locator("text", { hasText: "Baron User" })).toBeVisible();
|
||||
await expect(svg.getByText(/시스템 전역/)).toHaveCount(0);
|
||||
await expect(svg.getByText(/Global Admin/)).toHaveCount(0);
|
||||
await expect(svg.getByText(/System Admin/)).toHaveCount(0);
|
||||
await expect(svg.getByText("Baron User 사원")).toBeVisible();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user