첫 커밋: 로컬 프로젝트 업로드
This commit is contained in:
@@ -0,0 +1,660 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user