diff --git a/apps/mobile/src/state/use-selected-thread-git-state.test.tsx b/apps/mobile/src/state/use-selected-thread-git-state.test.tsx new file mode 100644 index 0000000000000..73c3b22d02abe --- /dev/null +++ b/apps/mobile/src/state/use-selected-thread-git-state.test.tsx @@ -0,0 +1,106 @@ +import * as NodeChildProcess from "node:child_process"; +import * as NodeFS from "node:fs"; +import * as NodeOS from "node:os"; +import * as NodePath from "node:path"; +import { act, useLayoutEffect } from "react"; +import { createRoot } from "react-dom/client"; +import { expect, it, vi } from "vite-plus/test"; + +const harness = vi.hoisted(() => ({ + root: "", + cwd: "", +})); + +vi.mock("./use-thread-selection", () => ({ + useThreadSelection: () => ({ + selectedThread: { environmentId: "environment-1" }, + selectedThreadProject: { workspaceRoot: harness.root }, + }), +})); +vi.mock("./use-selected-thread-worktree", () => ({ + useSelectedThreadWorktree: () => ({ selectedThreadCwd: harness.cwd }), +})); +vi.mock("./use-vcs-action-state", () => ({ + useVcsActionState: () => ({ currentLabel: null }), +})); +vi.mock("./sourceControl", () => ({ + sourceControlEnvironment: { discovery: () => null }, +})); +vi.mock("./query", () => ({ useEnvironmentQuery: () => ({ data: null }) })); +vi.mock("./queries", () => ({ + useBranches: ({ cwd }: { cwd: string }) => ({ + data: { + refs: NodeChildProcess.execFileSync( + "git", + ["for-each-ref", "--format=%(refname:short) %(HEAD)", "refs/heads"], + { + cwd, + encoding: "utf8", + }, + ) + .trim() + .split("\n") + .map((line) => ({ + name: line.split(" ")[0], + current: line.endsWith("*"), + isRemote: false, + })), + }, + isPending: false, + }), +})); + +import { useSelectedThreadGitState } from "./use-selected-thread-git-state"; + +it("marks the selected checkout's branch current when switching threads in one project", () => { + const directory = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "mobile-branches-")); + const git = (...args: string[]) => + NodeChildProcess.execFileSync("git", ["-c", "commit.gpgsign=false", ...args], { + cwd: directory, + encoding: "utf8", + stdio: "pipe", + }); + const root = createRoot(document.createElement("div")); + (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true; + let currentBranches: string[] = []; + function Consumer() { + const { selectedThreadBranches } = useSelectedThreadGitState(); + useLayoutEffect(() => { + currentBranches = selectedThreadBranches + .filter((branch) => branch.current) + .map((branch) => branch.name); + }, [selectedThreadBranches]); + return null; + } + + try { + git("init", "--initial-branch=main"); + git( + "-c", + "user.name=Test", + "-c", + "user.email=test@example.com", + "commit", + "--allow-empty", + "-m", + "initial", + ); + const worktree = NodePath.join(directory, "linked"); + git("worktree", "add", "-b", "feature/mobile", worktree); + harness.root = directory; + harness.cwd = directory; + act(() => root.render()); + expect(currentBranches).toEqual(["main"]); + + harness.cwd = worktree; + act(() => root.render()); + expect(currentBranches).toEqual(["feature/mobile"]); + + harness.cwd = directory; + act(() => root.render()); + expect(currentBranches).toEqual(["main"]); + } finally { + act(() => root.unmount()); + NodeFS.rmSync(directory, { recursive: true, force: true }); + } +}); diff --git a/apps/mobile/src/state/use-selected-thread-git-state.ts b/apps/mobile/src/state/use-selected-thread-git-state.ts index e8b3339c79493..e9a961b152fed 100644 --- a/apps/mobile/src/state/use-selected-thread-git-state.ts +++ b/apps/mobile/src/state/use-selected-thread-git-state.ts @@ -10,7 +10,7 @@ import { useThreadSelection } from "./use-thread-selection"; import { useSelectedThreadWorktree } from "./use-selected-thread-worktree"; export function useSelectedThreadGitState() { - const { selectedThread, selectedThreadProject } = useThreadSelection(); + const { selectedThread } = useThreadSelection(); const { selectedThreadCwd } = useSelectedThreadWorktree(); const selectedThreadGitTarget = { @@ -29,7 +29,7 @@ export function useSelectedThreadGitState() { const selectedThreadBranchTarget = { environmentId: selectedThread?.environmentId ?? null, - cwd: selectedThreadProject?.workspaceRoot ?? null, + cwd: selectedThreadCwd, query: null, }; const selectedThreadBranchState = useBranches(selectedThreadBranchTarget); diff --git a/apps/mobile/vite.config.ts b/apps/mobile/vite.config.ts index 9ddb84efbc832..d64a6cb8ca27e 100644 --- a/apps/mobile/vite.config.ts +++ b/apps/mobile/vite.config.ts @@ -28,6 +28,7 @@ const reactTests = [ "src/native/useRetryingImageSource.test.tsx", "src/state/queries.debounce.test.tsx", "src/state/use-selected-thread-git-actions.test.tsx", + "src/state/use-selected-thread-git-state.test.tsx", "src/features/terminal/useTerminalExitNavigation.test.tsx", "src/features/threads/use-thread-list-now.test.tsx", "src/features/home/home-list-options.test.tsx",