661 lines
20 KiB
TypeScript
661 lines
20 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
buildOrgSelectionOptions,
|
|
buildUsersMap,
|
|
clampScale,
|
|
filterSystemGlobalTenants,
|
|
getMemberGridMetrics,
|
|
getOrgNodeHeaderFill,
|
|
getSemanticZoomMode,
|
|
layoutForest,
|
|
resolveOrgChartFamilyRoot,
|
|
type OrgNode,
|
|
} from "./OrgChartPage";
|
|
|
|
function orgNode(id: string, children: OrgNode[] = [], level = 0): OrgNode {
|
|
return {
|
|
id,
|
|
name: id,
|
|
level,
|
|
members: [],
|
|
children,
|
|
totalCount: 0,
|
|
totalMemberIds: new Set<string>(),
|
|
companyCode: id,
|
|
type: level === 0 ? "COMPANY" : "USER_GROUP",
|
|
};
|
|
}
|
|
|
|
function member(id: string) {
|
|
return {
|
|
id,
|
|
email: `${id}@example.com`,
|
|
name: id,
|
|
role: "user",
|
|
status: "active",
|
|
companyCode: "root",
|
|
grade: "사원",
|
|
createdAt: "2026-05-11T00:00:00.000Z",
|
|
updatedAt: "2026-05-11T00:00:00.000Z",
|
|
};
|
|
}
|
|
|
|
function tenantNode(
|
|
id: string,
|
|
type: string,
|
|
name: string,
|
|
slug: string,
|
|
children = [],
|
|
) {
|
|
return {
|
|
id,
|
|
type,
|
|
name,
|
|
slug,
|
|
children,
|
|
description: "",
|
|
status: "active",
|
|
memberCount: 0,
|
|
recursiveMemberCount: 0,
|
|
createdAt: "2026-05-11T00:00:00.000Z",
|
|
updatedAt: "2026-05-11T00:00:00.000Z",
|
|
};
|
|
}
|
|
|
|
function getNodeBoundsAspectRatio(
|
|
nodes: ReturnType<typeof layoutForest>["nodes"],
|
|
) {
|
|
const minX = Math.min(...nodes.map((node) => node.x));
|
|
const maxX = Math.max(...nodes.map((node) => node.x + node.width));
|
|
const minY = Math.min(...nodes.map((node) => node.y));
|
|
const maxY = Math.max(...nodes.map((node) => node.y + node.height));
|
|
|
|
return (maxX - minX) / (maxY - minY);
|
|
}
|
|
|
|
describe("org chart layout", () => {
|
|
it("keeps small sibling groups horizontal in automatic mode", () => {
|
|
const children = Array.from({ length: 4 }, (_, index) =>
|
|
orgNode(`child-${index + 1}`, [], 1),
|
|
);
|
|
const layout = layoutForest([orgNode("root", children)], new Set());
|
|
const childNodes = layout.nodes.filter((node) =>
|
|
node.node.id.startsWith("child-"),
|
|
);
|
|
|
|
expect(new Set(childNodes.map((node) => node.y)).size).toBe(1);
|
|
});
|
|
|
|
it("uses member columns in node bounds when the rendered node aspect ratio needs them", () => {
|
|
const compactMembers = Array.from({ length: 10 }, (_, index) =>
|
|
member(`member-${index + 1}`),
|
|
);
|
|
const node = {
|
|
...orgNode("root"),
|
|
members: compactMembers,
|
|
totalCount: compactMembers.length,
|
|
totalMemberIds: new Set(compactMembers.map((item) => item.id)),
|
|
};
|
|
const layout = layoutForest([node], new Set());
|
|
const rootNode = layout.nodes.find((item) => item.node.id === "root");
|
|
|
|
expect(rootNode).toBeDefined();
|
|
expect(rootNode?.width).toBeGreaterThan(240);
|
|
expect(rootNode?.height).toBeLessThan(42 + 24 + 10 * 24);
|
|
expect(layout.width).toBeGreaterThan((rootNode?.width ?? 0) + 72 * 2 - 1);
|
|
});
|
|
|
|
it("sizes member cards from an eight-character baseline and expands for long display names", () => {
|
|
const shortMembers = Array.from({ length: 6 }, (_, index) => ({
|
|
...member(`short-${index + 1}`),
|
|
name: `홍길${index + 1}`,
|
|
grade: "책임",
|
|
}));
|
|
const longMembers = shortMembers.map((item, index) => ({
|
|
...item,
|
|
id: `long-${index + 1}`,
|
|
name: `매우긴사용자이름${index + 1}`,
|
|
}));
|
|
const shortLayout = layoutForest(
|
|
[
|
|
{
|
|
...orgNode("short"),
|
|
members: shortMembers,
|
|
totalCount: shortMembers.length,
|
|
totalMemberIds: new Set(shortMembers.map((item) => item.id)),
|
|
},
|
|
],
|
|
new Set(),
|
|
);
|
|
const longLayout = layoutForest(
|
|
[
|
|
{
|
|
...orgNode("long"),
|
|
members: longMembers,
|
|
totalCount: longMembers.length,
|
|
totalMemberIds: new Set(longMembers.map((item) => item.id)),
|
|
},
|
|
],
|
|
new Set(),
|
|
);
|
|
const shortNode = shortLayout.nodes.find(
|
|
(item) => item.node.id === "short",
|
|
);
|
|
const longNode = longLayout.nodes.find((item) => item.node.id === "long");
|
|
|
|
expect(shortNode?.width).toBeLessThan(320);
|
|
expect(longNode?.width).toBeGreaterThan(shortNode?.width ?? 0);
|
|
});
|
|
|
|
it("uses compact member columns when another column improves the rendered ratio", () => {
|
|
const tenMembers = Array.from({ length: 10 }, (_, index) =>
|
|
member(`member-${index + 1}`),
|
|
);
|
|
const sixMembers = tenMembers.slice(0, 6);
|
|
const sixLayout = layoutForest(
|
|
[
|
|
{
|
|
...orgNode("six"),
|
|
members: sixMembers,
|
|
totalCount: sixMembers.length,
|
|
totalMemberIds: new Set(sixMembers.map((item) => item.id)),
|
|
},
|
|
],
|
|
new Set(),
|
|
);
|
|
const tenLayout = layoutForest(
|
|
[
|
|
{
|
|
...orgNode("ten"),
|
|
members: tenMembers,
|
|
totalCount: tenMembers.length,
|
|
totalMemberIds: new Set(tenMembers.map((item) => item.id)),
|
|
},
|
|
],
|
|
new Set(),
|
|
);
|
|
const sixNode = sixLayout.nodes.find((item) => item.node.id === "six");
|
|
const tenNode = tenLayout.nodes.find((item) => item.node.id === "ten");
|
|
|
|
expect(sixNode?.width).toBeGreaterThan(240);
|
|
expect(tenNode?.width).toBe(sixNode?.width);
|
|
expect(sixNode?.height).toBeLessThan(42 + 24 + 6 * 24);
|
|
expect(tenNode?.height).toBeLessThan(42 + 24 + 10 * 24);
|
|
});
|
|
|
|
it("chooses member columns from the rendered node aspect ratio instead of fixed five-member buckets", () => {
|
|
expect(getMemberGridMetrics(6)).toEqual({ columnCount: 2, rowCount: 3 });
|
|
expect(getMemberGridMetrics(10)).toEqual({ columnCount: 2, rowCount: 5 });
|
|
expect(getMemberGridMetrics(25)).toEqual({ columnCount: 4, rowCount: 7 });
|
|
});
|
|
|
|
it("sorts members by normalized rank inside the same organization", () => {
|
|
const members = [
|
|
{ ...member("staff"), name: "사원", grade: "사원" },
|
|
{ ...member("principal"), name: "수석", grade: "수석연구원" },
|
|
{ ...member("director"), name: "전무", grade: "전무이사" },
|
|
{ ...member("lead"), name: "책임", grade: "책임" },
|
|
];
|
|
const layout = layoutForest(
|
|
[
|
|
{
|
|
...orgNode("root"),
|
|
members,
|
|
totalCount: members.length,
|
|
totalMemberIds: new Set(members.map((item) => item.id)),
|
|
},
|
|
],
|
|
new Set(),
|
|
);
|
|
const rootNode = layout.nodes.find((item) => item.node.id === "root");
|
|
|
|
expect(rootNode?.members.map((item) => item.id)).toEqual([
|
|
"director",
|
|
"principal",
|
|
"lead",
|
|
"staff",
|
|
]);
|
|
});
|
|
|
|
it("uses multi-column layout by default when sibling width crosses the threshold", () => {
|
|
const children = Array.from({ length: 13 }, (_, index) =>
|
|
orgNode(`child-${index + 1}`, [], 1),
|
|
);
|
|
const layout = layoutForest([orgNode("root", children)], new Set());
|
|
const childNodes = layout.nodes.filter((node) =>
|
|
node.node.id.startsWith("child-"),
|
|
);
|
|
const uniqueChildRows = new Set(childNodes.map((node) => node.y));
|
|
const childSpan =
|
|
Math.max(...childNodes.map((node) => node.x + node.width)) -
|
|
Math.min(...childNodes.map((node) => node.x));
|
|
const aspectRatio = getNodeBoundsAspectRatio(layout.nodes);
|
|
|
|
expect(childNodes).toHaveLength(13);
|
|
expect(uniqueChildRows.size).toBeGreaterThan(1);
|
|
expect(aspectRatio).toBeGreaterThanOrEqual(1.41);
|
|
expect(aspectRatio).toBeLessThanOrEqual(1.61);
|
|
expect(childSpan).toBeLessThan(13 * 240 + 12 * 80);
|
|
expect(
|
|
layout.edges.filter((edge) => edge.key.startsWith("root->")),
|
|
).toHaveLength(13);
|
|
expect(
|
|
layout.edges.filter(
|
|
(edge) => edge.key.startsWith("root->") && edge.visibleByDefault,
|
|
),
|
|
).toHaveLength(new Set(childNodes.map((node) => node.x)).size);
|
|
});
|
|
|
|
it("tunes column and row gaps after column selection to keep auto layout near the target aspect ratio", () => {
|
|
const children = Array.from({ length: 5 }, (_, index) =>
|
|
orgNode(`child-${index + 1}`, [], 1),
|
|
);
|
|
const layout = layoutForest([orgNode("root", children)], new Set());
|
|
const childNodes = layout.nodes.filter((node) =>
|
|
node.node.id.startsWith("child-"),
|
|
);
|
|
const aspectRatio = getNodeBoundsAspectRatio(layout.nodes);
|
|
|
|
expect(new Set(childNodes.map((node) => node.x)).size).toBe(4);
|
|
expect(aspectRatio).toBeGreaterThanOrEqual(1.41);
|
|
expect(aspectRatio).toBeLessThanOrEqual(1.61);
|
|
});
|
|
|
|
it("keeps direct siblings on one level in top-down mode", () => {
|
|
const children = Array.from({ length: 13 }, (_, index) =>
|
|
orgNode(`child-${index + 1}`, [], 1),
|
|
);
|
|
const layout = layoutForest([orgNode("root", children)], new Set(), {
|
|
childLayoutMode: "topDown",
|
|
});
|
|
const childNodes = layout.nodes.filter((node) =>
|
|
node.node.id.startsWith("child-"),
|
|
);
|
|
const uniqueChildRows = new Set(childNodes.map((node) => node.y));
|
|
|
|
expect(childNodes).toHaveLength(13);
|
|
expect(uniqueChildRows.size).toBe(1);
|
|
});
|
|
|
|
it("places children in three fixed columns with centered parent edges", () => {
|
|
const children = Array.from({ length: 10 }, (_, index) =>
|
|
orgNode(`child-${index + 1}`, [], 1),
|
|
);
|
|
const layout = layoutForest([orgNode("root", children)], new Set(), {
|
|
childLayoutMode: "threeColumn",
|
|
});
|
|
const childNodes = layout.nodes.filter((node) =>
|
|
node.node.id.startsWith("child-"),
|
|
);
|
|
const uniqueChildColumns = new Set(childNodes.map((node) => node.x));
|
|
const uniqueChildRows = new Set(childNodes.map((node) => node.y));
|
|
const rootEdges = layout.edges.filter((edge) =>
|
|
edge.key.startsWith("root->"),
|
|
);
|
|
|
|
expect(uniqueChildColumns.size).toBe(3);
|
|
expect(uniqueChildRows.size).toBe(4);
|
|
expect(rootEdges).toHaveLength(10);
|
|
expect(rootEdges.filter((edge) => edge.visibleByDefault)).toHaveLength(3);
|
|
});
|
|
|
|
it("places the deepest child subtree in the first multi-column section", () => {
|
|
const children = [
|
|
orgNode("shallow-1", [], 1),
|
|
orgNode("shallow-2", [], 1),
|
|
orgNode("shallow-3", [], 1),
|
|
orgNode(
|
|
"deep",
|
|
[
|
|
orgNode(
|
|
"deep-branch",
|
|
[orgNode("deep-leaf", [orgNode("deep-tail", [], 4)], 3)],
|
|
2,
|
|
),
|
|
],
|
|
1,
|
|
),
|
|
orgNode("shallow-4", [], 1),
|
|
orgNode("shallow-5", [], 1),
|
|
];
|
|
const layout = layoutForest([orgNode("root", children)], new Set(), {
|
|
childLayoutMode: "threeColumn",
|
|
});
|
|
const rootEdges = layout.edges.filter((edge) =>
|
|
edge.key.startsWith("root->"),
|
|
);
|
|
|
|
expect(rootEdges.map((edge) => edge.key)).toContain("root->deep");
|
|
});
|
|
|
|
it("centers a parent over the full child span in multi-column mode", () => {
|
|
const children = [
|
|
orgNode(
|
|
"deep",
|
|
[
|
|
orgNode(
|
|
"deep-branch",
|
|
[orgNode("deep-leaf", [orgNode("deep-tail", [], 4)], 3)],
|
|
2,
|
|
),
|
|
],
|
|
1,
|
|
),
|
|
...Array.from({ length: 9 }, (_, index) =>
|
|
orgNode(`shallow-${index + 1}`, [], 1),
|
|
),
|
|
];
|
|
const layout = layoutForest([orgNode("root", children)], new Set(), {
|
|
childLayoutMode: "threeColumn",
|
|
});
|
|
const rootNode = layout.nodes.find((node) => node.node.id === "root");
|
|
const directChildren = layout.nodes.filter((node) => node.node.level === 1);
|
|
const childSpanCenter =
|
|
(Math.min(...directChildren.map((node) => node.x + node.width / 2)) +
|
|
Math.max(...directChildren.map((node) => node.x + node.width / 2))) /
|
|
2;
|
|
const rootCenter = rootNode ? rootNode.x + rootNode.width / 2 : 0;
|
|
|
|
expect(rootNode).toBeDefined();
|
|
expect(rootCenter).toBeCloseTo(childSpanCenter, 5);
|
|
});
|
|
|
|
it("centers parents above the tidy child span", () => {
|
|
const children = [
|
|
orgNode("left", [orgNode("left-a", [], 2), orgNode("left-b", [], 2)], 1),
|
|
orgNode("middle", [], 1),
|
|
orgNode(
|
|
"right",
|
|
[orgNode("right-a", [], 2), orgNode("right-b", [], 2)],
|
|
1,
|
|
),
|
|
];
|
|
const layout = layoutForest([orgNode("root", children)], new Set(), {
|
|
childLayoutMode: "topDown",
|
|
});
|
|
const rootNode = layout.nodes.find((node) => node.node.id === "root");
|
|
const directChildren = layout.nodes.filter((node) =>
|
|
["left", "middle", "right"].includes(node.node.id),
|
|
);
|
|
const childSpanCenter =
|
|
(Math.min(...directChildren.map((node) => node.x + node.width / 2)) +
|
|
Math.max(...directChildren.map((node) => node.x + node.width / 2))) /
|
|
2;
|
|
const rootCenter = rootNode ? rootNode.x + rootNode.width / 2 : 0;
|
|
|
|
expect(rootNode).toBeDefined();
|
|
expect(rootCenter).toBeCloseTo(childSpanCenter, 5);
|
|
});
|
|
|
|
it("keeps compressed subtrees from overlapping on shared vertical bands", () => {
|
|
const layout = layoutForest(
|
|
[
|
|
orgNode("root", [
|
|
orgNode(
|
|
"left",
|
|
[orgNode("left-a", [], 2), orgNode("left-b", [], 2)],
|
|
1,
|
|
),
|
|
orgNode(
|
|
"right",
|
|
[orgNode("right-a", [], 2), orgNode("right-b", [], 2)],
|
|
1,
|
|
),
|
|
]),
|
|
],
|
|
new Set(),
|
|
);
|
|
|
|
for (const node of layout.nodes) {
|
|
for (const other of layout.nodes) {
|
|
if (node.node.id >= other.node.id) continue;
|
|
const verticalOverlap =
|
|
node.y < other.y + other.height && other.y < node.y + node.height;
|
|
const horizontalOverlap =
|
|
node.x < other.x + other.width && other.x < node.x + node.width;
|
|
|
|
expect(
|
|
verticalOverlap && horizontalOverlap,
|
|
`${node.node.id} overlaps ${other.node.id}`,
|
|
).toBe(false);
|
|
}
|
|
}
|
|
});
|
|
|
|
it("keeps zoom limits wide enough for large SVG organization charts", () => {
|
|
expect(clampScale(0.08)).toBe(0.08);
|
|
expect(clampScale(32)).toBe(32);
|
|
expect(clampScale(64)).toBe(32);
|
|
});
|
|
|
|
it("switches semantic zoom modes from overview to detail", () => {
|
|
expect(getSemanticZoomMode(0.12)).toBe("overview");
|
|
expect(getSemanticZoomMode(0.4)).toBe("compact");
|
|
expect(getSemanticZoomMode(0.8)).toBe("detail");
|
|
});
|
|
|
|
it("uses distinct header fills by organization depth", () => {
|
|
expect(getOrgNodeHeaderFill(0, "family")).toBe("#000000");
|
|
expect(getOrgNodeHeaderFill(0, "saman")).toBe("#f58220");
|
|
expect(getOrgNodeHeaderFill(0, "hanmac")).toBe("#1e489d");
|
|
expect(getOrgNodeHeaderFill(0, "gpdtdc")).toBe("#4b746d");
|
|
expect(getOrgNodeHeaderFill(0, "baron")).toBe("#004cbf");
|
|
expect(getOrgNodeHeaderFill(1, "saman")).not.toBe(
|
|
getOrgNodeHeaderFill(0, "saman"),
|
|
);
|
|
expect(getOrgNodeHeaderFill(2, "saman")).not.toBe(
|
|
getOrgNodeHeaderFill(1, "saman"),
|
|
);
|
|
});
|
|
|
|
it("orders top organization choices by the hanmac family policy", () => {
|
|
const familyRoot = tenantNode(
|
|
"family",
|
|
"COMPANY_GROUP",
|
|
"한맥가족",
|
|
"hanmac-family",
|
|
[
|
|
tenantNode("saman", "COMPANY", "삼안", "saman"),
|
|
tenantNode("baron", "COMPANY_GROUP", "바론그룹", "baron-group"),
|
|
tenantNode("hanmac", "COMPANY", "한맥기술", "hanmac"),
|
|
tenantNode("gpdtdc", "ORGANIZATION", "총괄기획&기술개발센터", "gpdtdc"),
|
|
],
|
|
);
|
|
|
|
expect(
|
|
buildOrgSelectionOptions(familyRoot).map((option) => option.label),
|
|
).toEqual(["총괄기획&기술개발센터", "삼안", "한맥기술", "바론그룹"]);
|
|
});
|
|
|
|
it("selects hanmac family as the default root even when public sector group is listed first", () => {
|
|
const publicSector = tenantNode(
|
|
"public-sector",
|
|
"COMPANY_GROUP",
|
|
"공공기관",
|
|
"public-sector",
|
|
);
|
|
const familyRoot = tenantNode(
|
|
"family",
|
|
"COMPANY_GROUP",
|
|
"한맥가족",
|
|
"hanmac-family",
|
|
[tenantNode("saman", "COMPANY", "삼안", "saman")],
|
|
);
|
|
|
|
expect(resolveOrgChartFamilyRoot([publicSector, familyRoot])?.id).toBe(
|
|
"family",
|
|
);
|
|
});
|
|
|
|
it("hides internal organizations by default and includes them for internal mode", () => {
|
|
const visibleParent = tenantNode(
|
|
"visible-parent",
|
|
"COMPANY",
|
|
"공개 회사",
|
|
"visible-parent",
|
|
);
|
|
const internalOrg = {
|
|
...tenantNode(
|
|
"internal-org",
|
|
"ORGANIZATION",
|
|
"내부 조직",
|
|
"internal-org",
|
|
),
|
|
parentId: "visible-parent",
|
|
config: { visibility: "internal" },
|
|
};
|
|
const internalChild = {
|
|
...tenantNode(
|
|
"internal-child",
|
|
"ORGANIZATION",
|
|
"내부 하위",
|
|
"internal-child",
|
|
),
|
|
parentId: "internal-org",
|
|
};
|
|
const privateOrg = {
|
|
...tenantNode(
|
|
"private-org",
|
|
"ORGANIZATION",
|
|
"비공개 조직",
|
|
"private-org",
|
|
),
|
|
parentId: "visible-parent",
|
|
config: { visibility: "private" },
|
|
};
|
|
const publicOrg = {
|
|
...tenantNode("public-org", "ORGANIZATION", "공개 조직", "public-org"),
|
|
parentId: "visible-parent",
|
|
};
|
|
|
|
const tenants = [
|
|
visibleParent,
|
|
internalOrg,
|
|
internalChild,
|
|
privateOrg,
|
|
publicOrg,
|
|
];
|
|
|
|
expect(
|
|
filterSystemGlobalTenants(tenants, "public").map((tenant) => tenant.id),
|
|
).toEqual(["visible-parent", "public-org"]);
|
|
expect(
|
|
filterSystemGlobalTenants(tenants, "internal").map((tenant) => tenant.id),
|
|
).toEqual([
|
|
"visible-parent",
|
|
"internal-org",
|
|
"internal-child",
|
|
"public-org",
|
|
]);
|
|
});
|
|
|
|
it("maps legacy companyCode users to matching tenant slugs", () => {
|
|
const usersMap = buildUsersMap(
|
|
[
|
|
{
|
|
...member("engineering-user"),
|
|
companyCode: "engineering",
|
|
tenantSlug: undefined,
|
|
tenant: undefined,
|
|
joinedTenants: undefined,
|
|
},
|
|
],
|
|
[tenantNode("engineering", "ORGANIZATION", "Engineering", "engineering")],
|
|
{ activeOnly: true },
|
|
);
|
|
|
|
expect(usersMap.get("engineering")?.map((user) => user.id)).toEqual([
|
|
"engineering-user",
|
|
]);
|
|
});
|
|
|
|
it("maps GPDTDC representative users to their visible leaf appointment", () => {
|
|
const gpdtdc = tenantNode("gpdtdc", "COMPANY", "GPDTDC", "gpdtdc");
|
|
const tdc = {
|
|
...tenantNode("tdc", "ORGANIZATION", "기술개발센터", "tdc"),
|
|
parentId: "gpdtdc",
|
|
};
|
|
const leaf = {
|
|
...tenantNode("tdc-leaf", "USER_GROUP", "기술개발센터 1팀", "tdc-leaf"),
|
|
parentId: "tdc",
|
|
};
|
|
const rootNode = {
|
|
...gpdtdc,
|
|
children: [{ ...tdc, children: [leaf] }],
|
|
};
|
|
|
|
const usersMap = buildUsersMap(
|
|
[
|
|
{
|
|
...member("gpdtdc-user"),
|
|
companyCode: undefined,
|
|
tenantSlug: "gpdtdc",
|
|
metadata: {
|
|
additionalAppointments: [
|
|
{
|
|
tenantSlug: "internal-planning",
|
|
isPrimary: true,
|
|
},
|
|
{
|
|
tenantSlug: "tdc-leaf",
|
|
isPrimary: false,
|
|
grade: "책임",
|
|
position: "팀장",
|
|
},
|
|
],
|
|
},
|
|
joinedTenants: undefined,
|
|
},
|
|
],
|
|
[rootNode],
|
|
{ activeOnly: true },
|
|
);
|
|
|
|
expect(usersMap.get("gpdtdc")).toBeUndefined();
|
|
expect(usersMap.get("internal-planning")).toBeUndefined();
|
|
expect(usersMap.get("tdc-leaf")?.map((user) => user.id)).toEqual([
|
|
"gpdtdc-user",
|
|
]);
|
|
});
|
|
|
|
it("does not fall back to a visible parent for hidden leaf memberships", () => {
|
|
const gpdtdc = tenantNode("gpdtdc", "COMPANY", "GPDTDC", "gpdtdc");
|
|
const internalLeaf = {
|
|
...tenantNode(
|
|
"internal-leaf",
|
|
"USER_GROUP",
|
|
"내부 구성 조직",
|
|
"internal-leaf",
|
|
),
|
|
parentId: "gpdtdc",
|
|
};
|
|
|
|
const usersMap = buildUsersMap(
|
|
[
|
|
{
|
|
...member("hidden-only-user"),
|
|
companyCode: undefined,
|
|
tenantSlug: "gpdtdc",
|
|
metadata: {
|
|
additionalAppointments: [
|
|
{
|
|
tenantSlug: "internal-leaf",
|
|
isPrimary: true,
|
|
},
|
|
],
|
|
},
|
|
joinedTenants: undefined,
|
|
},
|
|
],
|
|
[gpdtdc],
|
|
{
|
|
activeOnly: true,
|
|
membershipRootNodes: [{ ...gpdtdc, children: [internalLeaf] }],
|
|
},
|
|
);
|
|
|
|
expect(usersMap.get("gpdtdc")).toBeUndefined();
|
|
expect(usersMap.get("internal-leaf")).toBeUndefined();
|
|
});
|
|
});
|