summaryrefslogtreecommitdiffhomepage
path: root/src/features/layout/ui/TileRoot.test.ts
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-10-21 21:06:12 +0900
committerAdam Malczewski <[email protected]>2026-10-21 21:06:12 +0900
commitbbf9728721bd25fc5b0532c16088e9e025dadb40 (patch)
treea57e270a79d8fbd8b5454af60f35f62a4f70b8d0 /src/features/layout/ui/TileRoot.test.ts
parent98f0494e46d09aa7799fa3018705d39ae04ea4eb (diff)
downloaddispatch-web-bbf9728721bd25fc5b0532c16088e9e025dadb40.tar.gz
dispatch-web-bbf9728721bd25fc5b0532c16088e9e025dadb40.zip
feat(layout): GSAP height animation when sidebar views are added, closed or changed
Diffstat (limited to 'src/features/layout/ui/TileRoot.test.ts')
-rw-r--r--src/features/layout/ui/TileRoot.test.ts53
1 files changed, 52 insertions, 1 deletions
diff --git a/src/features/layout/ui/TileRoot.test.ts b/src/features/layout/ui/TileRoot.test.ts
index 2f95854..4b404a1 100644
--- a/src/features/layout/ui/TileRoot.test.ts
+++ b/src/features/layout/ui/TileRoot.test.ts
@@ -1,6 +1,7 @@
import type { LayoutTree, SplitNode } from "@dispatch/layout-contract";
import { render, screen } from "@testing-library/svelte";
import userEvent from "@testing-library/user-event";
+import { gsap } from "gsap";
import { describe, expect, it, vi } from "vitest";
import { createCollapseStore, registerView, unregisterView } from "../index";
import FixtureA from "./fixtures/FixtureA.svelte";
@@ -271,7 +272,7 @@ describe("TileRoot", () => {
const { container } = render(TileRoot, {
props: { tree: markedTestTree(), collapse: createCollapseStore({ viewportWidth: 1024 }) },
});
- const panels = container.querySelector("[data-testid='fixture-b']")?.closest(".gap-2");
+ const panels = container.querySelector("[data-testid='fixture-b']")?.closest("[data-panels]");
expect(panels?.getAttribute("style") ?? "").not.toContain("outline");
});
@@ -341,3 +342,53 @@ describe("TileRoot registry cleanup", () => {
}
});
});
+
+describe("TilePanels — panel identity + height animation", () => {
+ const wrappers = (): HTMLElement[] => [
+ ...document.querySelectorAll<HTMLElement>("[data-panels] > [data-panel-key]"),
+ ];
+
+ it("closing the MIDDLE panel removes that panel's DOM, keeping the others' nodes", async () => {
+ const { rerender } = render(TileRoot, {
+ props: { tree: panelsOnlyTree("fix-b", "fix-a", "fix-b"), collapse: createCollapseStore() },
+ });
+ const [first, , third] = wrappers();
+ await rerender({ tree: panelsOnlyTree("fix-b", "fix-b"), collapse: createCollapseStore() });
+ // Identity, not position: the third panel's node survived (index keying
+ // would have reused the middle node and dropped the last one).
+ expect(wrappers()).toEqual([first, third]);
+ expect(screen.queryByTestId("fixture-a")).toBeNull();
+ });
+
+ it("✕ on one of two IDENTICAL views removes exactly that panel", async () => {
+ const user = userEvent.setup();
+ const tree = panelsOnlyTree("fix-b", "fix-a", "fix-a");
+ const { rerender } = render(TileRoot, { props: { tree, collapse: createCollapseStore() } });
+ const [first, , third] = wrappers();
+ // Click the MIDDLE panel's ✕ (the first panel has none), then the owner
+ // applies the edit — the two fix-a panels are indistinguishable by data.
+ const [middleClose] = screen.getAllByRole("button", { name: "Remove view" });
+ await user.click(middleClose as HTMLElement);
+ await rerender({ tree: panelsOnlyTree("fix-b", "fix-a"), collapse: createCollapseStore() });
+ expect(wrappers()).toEqual([first, third]);
+ });
+
+ it("with motion allowed, a closed panel stays in flow while it shrinks out", async () => {
+ vi.stubGlobal(
+ "matchMedia",
+ vi.fn(() => ({ matches: false }) as unknown as MediaQueryList),
+ );
+ try {
+ const { rerender } = render(TileRoot, {
+ props: { tree: panelsOnlyTree("fix-b", "fix-a"), collapse: createCollapseStore() },
+ });
+ const closing = wrappers()[1];
+ await rerender({ tree: panelsOnlyTree("fix-b"), collapse: createCollapseStore() });
+ // jsdom's stubbed Element.animate never finishes, so the outro holds.
+ expect(closing?.isConnected).toBe(true);
+ expect(gsap.getTweensOf(closing as HTMLElement)).toHaveLength(1);
+ } finally {
+ vi.unstubAllGlobals();
+ }
+ });
+});