File
Blob: test/tab-strip.test.ts
| 1 | // @vitest-environment jsdom |
| 2 | |
| 3 | import { act, createElement } from "react"; |
| 4 | import { createRoot, type Root } from "react-dom/client"; |
| 5 | import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; |
| 6 | import { TabStrip } from "../src/client/components/tab-strip"; |
| 7 | import { useWorkspaceStore } from "../src/client/workspace/store"; |
| 8 | |
| 9 | let container: HTMLDivElement | null = null; |
| 10 | let root: Root | null = null; |
| 11 | const reactActEnvironment = globalThis as typeof globalThis & { |
| 12 | IS_REACT_ACT_ENVIRONMENT?: boolean; |
| 13 | }; |
| 14 | |
| 15 | function resetStore(): void { |
| 16 | sessionStorage.clear(); |
| 17 | useWorkspaceStore.setState(useWorkspaceStore.getInitialState()); |
| 18 | } |
| 19 | |
| 20 | function renderTabStrip(): void { |
| 21 | if (!root) { |
| 22 | throw new Error("Root was not created"); |
| 23 | } |
| 24 | |
| 25 | act(() => { |
| 26 | root!.render(createElement(TabStrip)); |
| 27 | }); |
| 28 | } |
| 29 | |
| 30 | beforeEach(() => { |
| 31 | reactActEnvironment.IS_REACT_ACT_ENVIRONMENT = true; |
| 32 | resetStore(); |
| 33 | container = document.createElement("div"); |
| 34 | document.body.appendChild(container); |
| 35 | root = createRoot(container); |
| 36 | }); |
| 37 | |
| 38 | afterEach(() => { |
| 39 | if (root) { |
| 40 | act(() => { |
| 41 | root!.unmount(); |
| 42 | }); |
| 43 | } |
| 44 | root = null; |
| 45 | container?.remove(); |
| 46 | container = null; |
| 47 | document.body.innerHTML = ""; |
| 48 | resetStore(); |
| 49 | delete reactActEnvironment.IS_REACT_ACT_ENVIRONMENT; |
| 50 | vi.restoreAllMocks(); |
| 51 | }); |
| 52 | |
| 53 | describe("TabStrip", () => { |
| 54 | it("renders the close control outside the tab button without nesting warnings", () => { |
| 55 | useWorkspaceStore.getState().newTab(); |
| 56 | const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); |
| 57 | |
| 58 | renderTabStrip(); |
| 59 | |
| 60 | const closeButton = container?.querySelector('button[aria-label="Close Terminal 1"]'); |
| 61 | expect(closeButton).toBeInstanceOf(HTMLButtonElement); |
| 62 | expect(closeButton?.closest('button[role="tab"]')).toBeNull(); |
| 63 | |
| 64 | const nestedButtonWarning = errorSpy.mock.calls.some((args) => |
| 65 | args.some((arg) => String(arg).includes("<button> cannot be a descendant of <button>")), |
| 66 | ); |
| 67 | expect(nestedButtonWarning).toBe(false); |
| 68 | }); |
| 69 | |
| 70 | it("closes an inactive tab without switching the active tab first", () => { |
| 71 | const store = useWorkspaceStore.getState(); |
| 72 | store.newTab(); |
| 73 | store.newTab(); |
| 74 | const activeTabIdBeforeClose = useWorkspaceStore.getState().activeTabId; |
| 75 | |
| 76 | renderTabStrip(); |
| 77 | |
| 78 | const closeButton = container?.querySelector('button[aria-label="Close Terminal 1"]'); |
| 79 | expect(closeButton).toBeInstanceOf(HTMLButtonElement); |
| 80 | |
| 81 | act(() => { |
| 82 | closeButton!.dispatchEvent(new MouseEvent("click", { bubbles: true })); |
| 83 | }); |
| 84 | |
| 85 | const { activeTabId, tabs } = useWorkspaceStore.getState(); |
| 86 | expect(tabs).toHaveLength(2); |
| 87 | expect(tabs.some((tab) => tab.title === "Terminal 1")).toBe(false); |
| 88 | expect(activeTabId).toBe(activeTabIdBeforeClose); |
| 89 | }); |
| 90 | }); |