diff --git a/ui/src/components/assistant/PromoteTransition.test.tsx b/ui/src/components/assistant/PromoteTransition.test.tsx new file mode 100644 index 00000000..df6e5cd1 --- /dev/null +++ b/ui/src/components/assistant/PromoteTransition.test.tsx @@ -0,0 +1,154 @@ +// @vitest-environment jsdom + +import { act } from "react"; +import { createRoot } from "react-dom/client"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { PromoteTransition } from "./PromoteTransition"; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +(globalThis as any).IS_REACT_ACT_ENVIRONMENT = true; + +describe("", () => { + let container: HTMLDivElement; + let root: ReturnType | null = null; + + beforeEach(() => { + container = document.createElement("div"); + document.body.appendChild(container); + root = null; + }); + + afterEach(() => { + if (root) { + act(() => { + root!.unmount(); + }); + root = null; + } + if (container.parentNode) container.remove(); + }); + + function render(node: React.ReactNode) { + root = createRoot(container); + act(() => { + root!.render(node); + }); + } + + it("renders nothing in idle state", () => { + render( + panel} + > +
ribbon
+
, + ); + expect(container.querySelector('[data-testid="promote-transition"]')).toBeNull(); + }); + + it("renders nothing in done state", () => { + render( + panel} + > +
ribbon
+
, + ); + expect(container.querySelector('[data-testid="promote-transition"]')).toBeNull(); + }); + + it("renders nothing in error state", () => { + render( + panel} + > +
ribbon
+
, + ); + expect(container.querySelector('[data-testid="promote-transition"]')).toBeNull(); + }); + + it("renders the overlay, ribbon, panel and label in prompting state", () => { + render( + panel} + > +
ribbon
+
, + ); + const overlay = container.querySelector('[data-testid="promote-transition"]') as HTMLElement; + expect(overlay).not.toBeNull(); + expect(overlay.getAttribute("data-state")).toBe("prompting"); + expect(overlay.getAttribute("aria-live")).toBe("polite"); + expect(overlay.getAttribute("role")).toBe("region"); + + const ribbon = container.querySelector('[data-testid="promote-chat-ribbon"]') as HTMLElement; + const panelContainer = container.querySelector('[data-testid="promote-panel-container"]') as HTMLElement; + const label = container.querySelector('[data-testid="promote-source-label"]') as HTMLElement; + + expect(ribbon).not.toBeNull(); + expect(panelContainer).not.toBeNull(); + expect(label).not.toBeNull(); + + // State data attribute is threaded to the animated children so CSS can + // select them. + expect(ribbon.getAttribute("data-pstate")).toBe("prompting"); + expect(panelContainer.getAttribute("data-pstate")).toBe("prompting"); + expect(label.getAttribute("data-pstate")).toBe("prompting"); + + // Children render inside the right slots. + expect(ribbon.querySelector('[data-testid="ribbon"]')).not.toBeNull(); + expect(panelContainer.querySelector('[data-testid="panel"]')).not.toBeNull(); + + // Source conversation label text. + expect(label.textContent?.trim().toLowerCase()).toBe("source conversation"); + }); + + it("updates data-state to creating while the API call is in flight", () => { + render( + panel} + > +
ribbon
+
, + ); + const overlay = container.querySelector('[data-testid="promote-transition"]') as HTMLElement; + expect(overlay.getAttribute("data-state")).toBe("creating"); + const ribbon = container.querySelector('[data-testid="promote-chat-ribbon"]') as HTMLElement; + expect(ribbon.getAttribute("data-pstate")).toBe("creating"); + }); + + it("inlines a + + {/* SOURCE CONVERSATION label */} +
+ Source Conversation +
+ + {/* Compressed chat ribbon */} +
+ {children} +
+ + {/* Rising brainstormer panel */} +
+ {panelChildren} +
+ + ); +}