Skip to content

Commit 322f15f

Browse files
authored
fix: persist navigation state (#362)
1 parent 7544ce0 commit 322f15f

File tree

3 files changed

+265
-96
lines changed

3 files changed

+265
-96
lines changed

apps/array/src/renderer/components/MainLayout.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,27 +8,35 @@ import { SettingsView } from "@features/settings/components/SettingsView";
88
import { MainSidebar } from "@features/sidebar/components/MainSidebar";
99
import { TaskDetail } from "@features/task-detail/components/TaskDetail";
1010
import { TaskInput } from "@features/task-detail/components/TaskInput";
11+
import { useTasks } from "@features/tasks/hooks/useTasks";
1112
import { useIntegrations } from "@hooks/useIntegrations";
1213
import { Box, Flex } from "@radix-ui/themes";
1314
import { useNavigationStore } from "@stores/navigationStore";
1415
import { useShortcutsSheetStore } from "@stores/shortcutsSheetStore";
15-
import { useCallback, useState } from "react";
16+
import { useCallback, useEffect, useState } from "react";
1617
import { Toaster } from "sonner";
1718
import { useTaskDeepLink } from "../hooks/useTaskDeepLink";
1819
import { GlobalEventHandlers } from "./GlobalEventHandlers";
1920

2021
export function MainLayout() {
21-
const { view } = useNavigationStore();
22+
const { view, hydrateTask } = useNavigationStore();
2223
const [commandMenuOpen, setCommandMenuOpen] = useState(false);
2324
const {
2425
isOpen: shortcutsSheetOpen,
2526
toggle: toggleShortcutsSheet,
2627
close: closeShortcutsSheet,
2728
} = useShortcutsSheetStore();
29+
const { data: tasks } = useTasks();
2830

2931
useIntegrations();
3032
useTaskDeepLink();
3133

34+
useEffect(() => {
35+
if (tasks) {
36+
hydrateTask(tasks);
37+
}
38+
}, [tasks, hydrateTask]);
39+
3240
const handleToggleCommandMenu = useCallback(() => {
3341
setCommandMenuOpen((prev) => !prev);
3442
}, []);
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import type { Task } from "@shared/types";
2+
import { beforeEach, describe, expect, it, vi } from "vitest";
3+
import { useNavigationStore } from "./navigationStore";
4+
5+
vi.mock("@renderer/lib/analytics", () => ({ track: vi.fn() }));
6+
vi.mock("@renderer/lib/logger", () => ({
7+
logger: { scope: () => ({ info: vi.fn(), error: vi.fn(), debug: vi.fn() }) },
8+
}));
9+
vi.mock("@features/task-detail/stores/taskExecutionStore", () => ({
10+
useTaskExecutionStore: {
11+
getState: () => ({ getTaskState: () => ({ workspaceMode: "local" }) }),
12+
},
13+
}));
14+
vi.mock("@features/workspace/stores/workspaceStore", () => ({
15+
useWorkspaceStore: { getState: () => ({ ensureWorkspace: vi.fn() }) },
16+
}));
17+
vi.mock("@stores/registeredFoldersStore", () => ({
18+
useRegisteredFoldersStore: { getState: () => ({ addFolder: vi.fn() }) },
19+
}));
20+
vi.mock("@stores/taskDirectoryStore", () => ({
21+
useTaskDirectoryStore: { getState: () => ({ getTaskDirectory: () => null }) },
22+
}));
23+
24+
const mockTask: Task = {
25+
id: "task-123",
26+
task_number: 1,
27+
slug: "test-task",
28+
title: "Test task",
29+
description: "Test task description",
30+
origin_product: "array",
31+
created_at: "2024-01-01T00:00:00Z",
32+
updated_at: "2024-01-01T00:00:00Z",
33+
};
34+
35+
const getStore = () => useNavigationStore.getState();
36+
const getView = () => getStore().view;
37+
const getPersistedState = () => {
38+
const data = localStorage.getItem("navigation-storage");
39+
return data ? JSON.parse(data).state : null;
40+
};
41+
42+
describe("navigationStore", () => {
43+
beforeEach(() => {
44+
localStorage.clear();
45+
useNavigationStore.setState({
46+
view: { type: "task-input" },
47+
history: [{ type: "task-input" }],
48+
historyIndex: 0,
49+
});
50+
});
51+
52+
it("starts with task-input view", () => {
53+
expect(getView().type).toBe("task-input");
54+
});
55+
56+
describe("navigation", () => {
57+
it("navigates to task detail with taskId", async () => {
58+
await getStore().navigateToTask(mockTask);
59+
expect(getView()).toMatchObject({
60+
type: "task-detail",
61+
data: mockTask,
62+
taskId: "task-123",
63+
});
64+
});
65+
66+
it("navigates to settings and back via toggle", () => {
67+
getStore().toggleSettings();
68+
expect(getView().type).toBe("settings");
69+
70+
getStore().toggleSettings();
71+
expect(getView().type).toBe("task-input");
72+
});
73+
74+
it("navigates to task input with folderId", () => {
75+
getStore().navigateToTaskInput("folder-123");
76+
expect(getView()).toMatchObject({
77+
type: "task-input",
78+
folderId: "folder-123",
79+
});
80+
});
81+
});
82+
83+
describe("history", () => {
84+
it("tracks history and supports back/forward", async () => {
85+
await getStore().navigateToTask(mockTask);
86+
getStore().navigateToSettings();
87+
88+
expect(getStore().history).toHaveLength(3);
89+
expect(getStore().canGoBack()).toBe(true);
90+
91+
getStore().goBack();
92+
expect(getView().type).toBe("task-detail");
93+
94+
expect(getStore().canGoForward()).toBe(true);
95+
getStore().goForward();
96+
expect(getView().type).toBe("settings");
97+
});
98+
});
99+
100+
describe("persistence", () => {
101+
it("persists view type and taskId but not full task data", async () => {
102+
await getStore().navigateToTask(mockTask);
103+
104+
const persisted = getPersistedState();
105+
expect(persisted.view).toEqual({
106+
type: "task-detail",
107+
taskId: "task-123",
108+
folderId: undefined,
109+
});
110+
});
111+
112+
it("restores view from localStorage without task data", async () => {
113+
await getStore().navigateToTask(mockTask);
114+
const storedData = localStorage.getItem("navigation-storage");
115+
116+
useNavigationStore.setState({
117+
view: { type: "task-input" },
118+
history: [{ type: "task-input" }],
119+
historyIndex: 0,
120+
});
121+
122+
localStorage.setItem("navigation-storage", storedData!);
123+
useNavigationStore.persist.rehydrate();
124+
125+
expect(getView()).toMatchObject({
126+
type: "task-detail",
127+
taskId: "task-123",
128+
});
129+
expect(getView().data).toBeUndefined();
130+
});
131+
});
132+
});

0 commit comments

Comments
 (0)