refactor(app): assert store state rather than mock call counts in store tests (#715)

Rewrites two store tests to verify observable state instead of implementation-level call counts.

navigation-active-workspace-store: the "persists" test now uses a functional in-memory AsyncStorage mock that retains written values across vi.resetModules(), then rehydrates a fresh store instance and asserts the resulting selection state matches what was written.

checkout-git-actions-store: removes toHaveBeenCalledWith/toHaveBeenCalledTimes assertions on client methods; replaces with getStatus() assertions that reflect the store's actual state after each action completes or fails.
This commit is contained in:
Mohamed Boudra
2026-05-05 19:58:55 +07:00
parent fd30eafafc
commit 90c7e591e8
2 changed files with 38 additions and 18 deletions
@@ -91,7 +91,6 @@ describe("checkout-git-actions-store", () => {
const first = store.commit({ serverId, cwd });
const second = store.commit({ serverId, cwd });
expect(client.checkoutCommit).toHaveBeenCalledTimes(1);
expect(store.getStatus({ serverId, cwd, actionId: "commit" })).toBe("pending");
deferred.resolve({});
@@ -126,8 +125,9 @@ describe("checkout-git-actions-store", () => {
await useCheckoutGitActionsStore.getState().pullAndPush({ serverId, cwd });
expect(order).toEqual(["pull", "push"]);
expect(client.checkoutPull).toHaveBeenCalledWith(cwd);
expect(client.checkoutPush).toHaveBeenCalledWith(cwd);
expect(
useCheckoutGitActionsStore.getState().getStatus({ serverId, cwd, actionId: "pull-and-push" }),
).toBe("success");
});
it("does not push when pull fails for pull-and-push", async () => {
@@ -146,7 +146,9 @@ describe("checkout-git-actions-store", () => {
await expect(
useCheckoutGitActionsStore.getState().pullAndPush({ serverId, cwd }),
).rejects.toThrow("pull conflict");
expect(client.checkoutPush).not.toHaveBeenCalled();
expect(
useCheckoutGitActionsStore.getState().getStatus({ serverId, cwd, actionId: "pull-and-push" }),
).toBe("idle");
});
it("surfaces push errors from pull-and-push after a successful pull", async () => {
@@ -165,8 +167,9 @@ describe("checkout-git-actions-store", () => {
await expect(
useCheckoutGitActionsStore.getState().pullAndPush({ serverId, cwd }),
).rejects.toThrow("push rejected");
expect(client.checkoutPull).toHaveBeenCalledTimes(1);
expect(client.checkoutPush).toHaveBeenCalledTimes(1);
expect(
useCheckoutGitActionsStore.getState().getStatus({ serverId, cwd, actionId: "pull-and-push" }),
).toBe("idle");
});
it("invalidates checkout PR status and every PR pane timeline for a checkout", async () => {
@@ -212,7 +215,6 @@ describe("checkout-git-actions-store", () => {
.getState()
.archiveWorktree({ serverId, cwd, worktreePath: cwd });
expect(client.archivePaseoWorktree).toHaveBeenCalledWith({ worktreePath: cwd });
expect(useSessionStore.getState().sessions[serverId]?.workspaces.has(cwd)).toBe(false);
expect(appQueryClient.getQueryData(["sidebarPaseoWorktreeList", serverId, "/tmp"])).toEqual([
{ worktreePath: "/tmp/other" },
@@ -1,9 +1,15 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
const asyncStorageMock = vi.hoisted(() => ({
getItem: vi.fn<(_: string) => Promise<string | null>>(),
setItem: vi.fn<(_: string, __: string) => Promise<void>>(),
}));
const asyncStorageMock = vi.hoisted(() => {
const storage = new Map<string, string>();
return {
storage,
getItem: vi.fn(async (key: string): Promise<string | null> => storage.get(key) ?? null),
setItem: vi.fn(async (key: string, value: string): Promise<void> => {
storage.set(key, value);
}),
};
});
vi.mock("@react-native-async-storage/async-storage", () => ({
default: asyncStorageMock,
@@ -94,10 +100,17 @@ function createNavigationPathWithParamsRef(path: string, serverId: string, works
describe("navigation active workspace store", () => {
beforeEach(() => {
vi.resetModules();
asyncStorageMock.storage.clear();
asyncStorageMock.getItem.mockReset();
asyncStorageMock.setItem.mockReset();
asyncStorageMock.getItem.mockResolvedValue(null);
asyncStorageMock.setItem.mockResolvedValue();
asyncStorageMock.getItem.mockImplementation(
async (key: string): Promise<string | null> => asyncStorageMock.storage.get(key) ?? null,
);
asyncStorageMock.setItem.mockImplementation(
async (key: string, value: string): Promise<void> => {
asyncStorageMock.storage.set(key, value);
},
);
});
afterEach(() => {
@@ -222,7 +235,6 @@ describe("navigation active workspace store", () => {
it("hydrates empty and corrupt workspace route storage as null", async () => {
installWindowStub("/open-project");
asyncStorageMock.getItem.mockResolvedValueOnce(null);
let store = await import("@/stores/navigation-active-workspace-store");
await store.hydrateLastNavigationWorkspaceRouteSelection();
@@ -244,10 +256,16 @@ describe("navigation active workspace store", () => {
await store.hydrateLastNavigationWorkspaceRouteSelection();
store.syncNavigationActiveWorkspace(createNavigationRef("server-1", "workspace-a"));
await Promise.resolve(); // flush the fire-and-forget setItem microtask
expect(asyncStorageMock.setItem).toHaveBeenCalledWith(
LAST_WORKSPACE_ROUTE_SELECTION_STORAGE_KEY,
JSON.stringify({ serverId: "server-1", workspaceId: "workspace-a" }),
);
vi.resetModules();
const freshStore = await import("@/stores/navigation-active-workspace-store");
await freshStore.hydrateLastNavigationWorkspaceRouteSelection();
expect(freshStore.getLastNavigationWorkspaceRouteSelection()).toEqual({
serverId: "server-1",
workspaceId: "workspace-a",
});
expect(freshStore.getIsLastNavigationWorkspaceRouteSelectionLoaded()).toBe(true);
});
});