|
1 | | -import { describe, expect, it } from "vite-plus/test"; |
| 1 | +import { describe, expect, it, vi } from "vite-plus/test"; |
| 2 | +import { act, createElement, createContext, useState, type ReactNode } from "react"; |
| 3 | +import { createRoot } from "react-dom/client"; |
| 4 | +import { WorkspaceRootScreen } from "./WorkspaceRootScreen.android"; |
2 | 5 | import { |
3 | 6 | initialWorkspaceTabsState, |
4 | 7 | workspaceTabsReducer, |
@@ -161,3 +164,91 @@ describe("workspace root navigation", () => { |
161 | 164 | expect(workspaceTabForNativeSlot(order, "removed")).toBeNull(); |
162 | 165 | }); |
163 | 166 | }); |
| 167 | + |
| 168 | +const mocks = vi.hoisted(() => ({ split: false, floating: false })); |
| 169 | + |
| 170 | +vi.mock("../settings/appearance/AppearancePreferencesProvider", () => ({ |
| 171 | + useAndroidFloatingTabs: () => mocks.floating, |
| 172 | +})); |
| 173 | +vi.mock("react-native", () => ({ |
| 174 | + useWindowDimensions: () => ({ width: 400, height: 800 }), |
| 175 | + View: ({ children }: { children: ReactNode }) => createElement("div", null, children), |
| 176 | +})); |
| 177 | +vi.mock("react-native-safe-area-context", () => ({ |
| 178 | + SafeAreaInsetsContext: createContext({ top: 0, right: 0, bottom: 24, left: 0 }), |
| 179 | + useSafeAreaInsets: () => ({ top: 0, right: 0, bottom: 24, left: 0 }), |
| 180 | +})); |
| 181 | +vi.mock("../layout/AdaptiveWorkspaceLayout", () => ({ |
| 182 | + useAdaptiveWorkspaceLayout: () => ({ |
| 183 | + layout: { usesSplitView: mocks.split }, |
| 184 | + startNewTask: () => undefined, |
| 185 | + }), |
| 186 | +})); |
| 187 | +vi.mock("./navigation-tabs", () => ({ |
| 188 | + useEnabledNavigationTabs: () => ["threads", "pullRequests", "usage"], |
| 189 | +})); |
| 190 | +vi.mock("./WorkspaceTabsContext", () => ({ |
| 191 | + WorkspaceTabsContext: createContext(null), |
| 192 | + useWorkspaceTabs: () => ({ state: { selectedTab: "usage" } }), |
| 193 | +})); |
| 194 | +vi.mock("./WorkspaceExtraTab", () => ({ |
| 195 | + WorkspaceExtraTab: function UsageProbe() { |
| 196 | + const [tab, setTab] = useState("Limits"); |
| 197 | + return createElement("button", { onClick: () => setTab("Usage") }, tab); |
| 198 | + }, |
| 199 | +})); |
| 200 | +vi.mock("../../native/StackHeader", () => ({ NativeStackScreenOptions: () => null })); |
| 201 | +vi.mock("../../components/ui/AndroidScreenHeader", () => ({ AndroidScreenHeader: () => null })); |
| 202 | +vi.mock("../../components/ui/EmptyState", () => ({ EmptyState: () => null })); |
| 203 | +vi.mock("../../components/ui/ScreenContent", () => ({ |
| 204 | + ScreenContent: ({ children }: { children: ReactNode }) => createElement("div", null, children), |
| 205 | +})); |
| 206 | +vi.mock("../../components/ui/useAndroidControlSizing", () => ({ |
| 207 | + useAndroidControlSizing: () => ({ fabClearance: 64, scale: 1, fabSize: 56 }), |
| 208 | +})); |
| 209 | +vi.mock("../home/HomeRouteScreen", () => ({ HomeRouteScreen: () => null })); |
| 210 | +vi.mock("../pull-requests/PullRequestFilterMenuContext", () => ({ |
| 211 | + PullRequestFilterMenuProvider: ({ children }: { children: ReactNode }) => |
| 212 | + createElement("div", null, children), |
| 213 | +})); |
| 214 | +vi.mock("../pull-requests/PullRequestsScreen", () => ({ PullRequestsScreen: () => null })); |
| 215 | +vi.mock("../pull-requests/PullRequestToolbar.android", () => ({ PullRequestToolbar: () => null })); |
| 216 | +vi.mock("./AndroidWorkspaceNavigation", () => ({ |
| 217 | + AndroidWorkspaceNavigation: () => null, |
| 218 | + FloatingWorkspaceTabs: () => null, |
| 219 | +})); |
| 220 | +vi.mock("./WorkspaceNoTabs", () => ({ WorkspaceNoTabs: () => null })); |
| 221 | + |
| 222 | +function setup(split: boolean, floating: boolean) { |
| 223 | + mocks.split = split; |
| 224 | + mocks.floating = floating; |
| 225 | + vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true); |
| 226 | + return { [Symbol.dispose]: () => vi.unstubAllGlobals() }; |
| 227 | +} |
| 228 | + |
| 229 | +it.each([ |
| 230 | + [false, false], |
| 231 | + [true, false], |
| 232 | + [false, true], |
| 233 | + [true, true], |
| 234 | +])( |
| 235 | + "keeps the Usage page mounted through layout transitions (split: %s, floating: %s)", |
| 236 | + async (split, floating) => { |
| 237 | + using _actEnvironment = setup(split, floating); |
| 238 | + const container = document.createElement("div"); |
| 239 | + const root = createRoot(container); |
| 240 | + using _root = { [Symbol.dispose]: () => act(() => root.unmount()) }; |
| 241 | + await act(async () => root.render(createElement(WorkspaceRootScreen))); |
| 242 | + |
| 243 | + const button = container.querySelector("button")!; |
| 244 | + await act(async () => button.click()); |
| 245 | + expect(button.textContent).toBe("Usage"); |
| 246 | + |
| 247 | + for (const nextSplit of [!split, split, !split, split]) { |
| 248 | + mocks.split = nextSplit; |
| 249 | + await act(async () => root.render(createElement(WorkspaceRootScreen))); |
| 250 | + expect(container.querySelector("button")?.textContent).toBe("Usage"); |
| 251 | + expect(container.querySelector("button")).toBe(button); |
| 252 | + } |
| 253 | + }, |
| 254 | +); |
0 commit comments