diff --git a/apps/app/app/components/ActorTable.tsx b/apps/app/app/components/ActorTable.tsx
index 1cc5a5d0..ea3d5753 100644
--- a/apps/app/app/components/ActorTable.tsx
+++ b/apps/app/app/components/ActorTable.tsx
@@ -1,6 +1,6 @@
"use client";
-import { useState } from "react";
+import { Fragment, useState } from "react";
import { ChevronRight, CircleDot, ListOrdered, User } from "lucide-react";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { Badge } from "@/components/ui/badge";
@@ -148,41 +148,46 @@ export function ActorTable({
) : (
groups.map(([type, items]) => {
const isOpen = !collapsed.has(type);
+ const groupId = `actor-group-${type}`;
return (
-
-
- |
-
- |
-
- {isOpen &&
- items.map((actor) => (
+
+
+
+ |
+
+ |
+
+
+
+ {items.map((actor) => (
))}
-
+
+
);
})
)}
diff --git a/apps/app/app/components/UsecaseTable.tsx b/apps/app/app/components/UsecaseTable.tsx
index d8cf56a4..d54589e7 100644
--- a/apps/app/app/components/UsecaseTable.tsx
+++ b/apps/app/app/components/UsecaseTable.tsx
@@ -1,6 +1,6 @@
"use client";
-import { useState } from "react";
+import { Fragment, useState } from "react";
import Link from "next/link";
import {
ChevronRight,
@@ -193,39 +193,44 @@ export function UsecaseTable({
) : (
groups.map(([level, items]) => {
const isOpen = !collapsed.has(level);
+ const groupId = `usecase-group-${level}`;
return (
-
-
- |
-
- |
-
- {isOpen &&
- items.map((usecase) => (
+
+
+
+ |
+
+ |
+
+
+
+ {items.map((usecase) => (
))}
-
+
+
);
})
)}
diff --git a/apps/app/tests/e2e-web/tier1.spec.ts b/apps/app/tests/e2e-web/tier1.spec.ts
index 1aa71726..86927a30 100644
--- a/apps/app/tests/e2e-web/tier1.spec.ts
+++ b/apps/app/tests/e2e-web/tier1.spec.ts
@@ -52,6 +52,37 @@ test("actor list page groups actors by role with per-actor detail", async ({
await expect(page.getByText("결제 게이트웨이")).toBeVisible();
});
+test("collapsible table groups keep controlled row groups mounted", async ({
+ page
+}) => {
+ await page.goto("/projects/CHECKOUT");
+
+ const usecaseToggle = page.getByRole("button", { name: "사용자 목표" });
+ await expect(usecaseToggle).toHaveAttribute("aria-expanded", "true");
+ await expect(usecaseToggle).toHaveAttribute(
+ "aria-controls",
+ "usecase-group-USER_GOAL"
+ );
+
+ const usecaseRows = page.locator("tbody#usecase-group-USER_GOAL");
+ await expect(usecaseRows).toHaveCount(1);
+ await usecaseToggle.click();
+ await expect(usecaseToggle).toHaveAttribute("aria-expanded", "false");
+ await expect(usecaseRows).toHaveClass(/hidden/);
+
+ await page.goto("/projects/CHECKOUT/actors");
+
+ const actorToggle = page.getByRole("button", { name: /지원 액터/ });
+ await expect(actorToggle).toHaveAttribute("aria-expanded", "true");
+ await expect(actorToggle).toHaveAttribute("aria-controls", "actor-group-SUPPORTING");
+
+ const actorRows = page.locator("tbody#actor-group-SUPPORTING");
+ await expect(actorRows).toHaveCount(1);
+ await actorToggle.click();
+ await expect(actorToggle).toHaveAttribute("aria-expanded", "false");
+ await expect(actorRows).toHaveClass(/hidden/);
+});
+
test("use case detail renders Cockburn fields", async ({ page }) => {
await page.goto("/projects/CHECKOUT/usecases/CHECKOUT-001");
await expect(