Skip to content

Commit 5f40a4c

Browse files
test(web): add tests for hooks [VIZ-1559] (#1589)
1 parent 7c1e45b commit 5f40a4c

File tree

4 files changed

+330
-1
lines changed

4 files changed

+330
-1
lines changed
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import { renderHook } from "@reearth/test/utils";
2+
import { describe, it, expect, beforeEach, vi, Mock } from "vitest";
3+
4+
import { useEditorNavigation, useSettingsNavigation } from "./navigationHooks";
5+
6+
const useNavigateMock: Mock = vi.fn();
7+
8+
vi.mock(`react-router-dom`, async (): Promise<unknown> => {
9+
const actual: Record<string, unknown> =
10+
await vi.importActual(`react-router-dom`);
11+
12+
return {
13+
...actual,
14+
useNavigate: (): Mock => useNavigateMock
15+
};
16+
});
17+
18+
describe("useEditorNavigation", () => {
19+
beforeEach(() => {
20+
vi.resetAllMocks();
21+
});
22+
23+
it("should return undefined if sceneId is not provided", () => {
24+
const { result } = renderHook(() =>
25+
useEditorNavigation({ sceneId: undefined })
26+
);
27+
expect(result.current).toBeUndefined();
28+
});
29+
30+
it("should return a navigation function if sceneId is provided", () => {
31+
const { result } = renderHook(() =>
32+
useEditorNavigation({ sceneId: "scene-123" })
33+
);
34+
expect(typeof result.current).toBe("function");
35+
});
36+
37+
it("should navigate to the correct URL when the function is called", () => {
38+
const { result } = renderHook(() =>
39+
useEditorNavigation({ sceneId: "scene-123" })
40+
);
41+
const navigateFunction = result.current;
42+
43+
expect(navigateFunction).toBeDefined();
44+
if (navigateFunction) {
45+
navigateFunction("story");
46+
expect(useNavigateMock).toHaveBeenCalledWith("/scene/scene-123/story");
47+
}
48+
});
49+
50+
it("should not navigate if sceneId is not provided", () => {
51+
const { result } = renderHook(() =>
52+
useEditorNavigation({ sceneId: undefined })
53+
);
54+
expect(result.current).toBeUndefined();
55+
});
56+
});
57+
58+
describe("useSettingsNavigation", () => {
59+
beforeEach(() => {
60+
vi.resetAllMocks();
61+
});
62+
63+
it("should return undefined if projectId is not provided", () => {
64+
const { result } = renderHook(() =>
65+
useSettingsNavigation({ projectId: undefined })
66+
);
67+
expect(result.current).toBeUndefined();
68+
});
69+
70+
it("should return a navigation function if projectId is provided", () => {
71+
const { result } = renderHook(() =>
72+
useSettingsNavigation({ projectId: "project-123" })
73+
);
74+
expect(typeof result.current).toBe("function");
75+
});
76+
77+
it("should navigate to the correct URL when the function is called with just the page", () => {
78+
const { result } = renderHook(() =>
79+
useSettingsNavigation({ projectId: "project-123" })
80+
);
81+
const navigateFunction = result.current;
82+
83+
expect(navigateFunction).toBeDefined();
84+
if (navigateFunction) {
85+
navigateFunction("public");
86+
expect(useNavigateMock).toHaveBeenCalledWith(
87+
"/settings/projects/project-123/public"
88+
);
89+
}
90+
});
91+
92+
it("should navigate to the correct URL when the function is called with page and subId", () => {
93+
const { result } = renderHook(() =>
94+
useSettingsNavigation({ projectId: "project-123" })
95+
);
96+
const navigateFunction = result.current;
97+
98+
expect(navigateFunction).toBeDefined();
99+
if (navigateFunction) {
100+
navigateFunction("plugin", "plugin-456");
101+
expect(useNavigateMock).toHaveBeenCalledWith(
102+
"/settings/projects/project-123/plugin/plugin-456"
103+
);
104+
}
105+
});
106+
107+
it("should not navigate if page is not provided", () => {
108+
const { result } = renderHook(() =>
109+
useSettingsNavigation({ projectId: "project-123" })
110+
);
111+
const navigateFunction = result.current;
112+
113+
expect(navigateFunction).toBeDefined();
114+
if (navigateFunction) {
115+
navigateFunction(undefined);
116+
expect(useNavigateMock).not.toHaveBeenCalled();
117+
}
118+
});
119+
120+
it("should not navigate if projectId is not provided", () => {
121+
const { result } = renderHook(() =>
122+
useSettingsNavigation({ projectId: undefined })
123+
);
124+
expect(result.current).toBeUndefined();
125+
});
126+
});
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { renderHook } from "@reearth/test/utils";
2+
import { describe, expect, it } from "vitest";
3+
4+
import useAccountSettingsTabs, {
5+
accountSettingTabs
6+
} from "./useAccountSettingsTabs";
7+
8+
describe("useAccountSettingsTabs", () => {
9+
it("should return tabs with workspace ID injected into paths", () => {
10+
const workspaceId = "test-workspace-123";
11+
const { result } = renderHook(() =>
12+
useAccountSettingsTabs({ workspaceId })
13+
);
14+
15+
expect(result.current.tabs).toHaveLength(accountSettingTabs.length);
16+
17+
const workspaceTab = result.current.tabs.find(
18+
(tab) => tab.id === "workspace"
19+
);
20+
expect(workspaceTab).toBeDefined();
21+
expect(workspaceTab?.path).toBe("/settings/workspaces/test-workspace-123");
22+
});
23+
24+
it("should preserve other tab properties like icon and disabled", () => {
25+
const workspaceId = "any-id";
26+
const { result } = renderHook(() =>
27+
useAccountSettingsTabs({ workspaceId })
28+
);
29+
30+
result.current.tabs.forEach((tab, index) => {
31+
expect(tab.icon).toBe(accountSettingTabs[index].icon);
32+
expect(tab.disabled).toBe(accountSettingTabs[index].disabled);
33+
});
34+
});
35+
36+
it("should re-render when workspaceId changes", () => {
37+
const { result, rerender } = renderHook(
38+
({ workspaceId }) => useAccountSettingsTabs({ workspaceId }),
39+
{ initialProps: { workspaceId: "workspace-1" } }
40+
);
41+
42+
const initialWorkspaceTabPath = result.current.tabs.find(
43+
(tab) => tab.id === "workspace"
44+
)?.path;
45+
46+
rerender({ workspaceId: "workspace-2" });
47+
48+
const updatedWorkspaceTabPath = result.current.tabs.find(
49+
(tab) => tab.id === "workspace"
50+
)?.path;
51+
52+
expect(initialWorkspaceTabPath).toBe("/settings/workspaces/workspace-1");
53+
expect(updatedWorkspaceTabPath).toBe("/settings/workspaces/workspace-2");
54+
});
55+
});

web/src/beta/hooks/useAccountSettingsTabs.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ export const accountSettingTabs: {
1818
}
1919
] as const;
2020

21-
export default ({ workspaceId }: { workspaceId: string }) => {
21+
type Tabs = typeof accountSettingTabs;
22+
23+
export default ({ workspaceId }: { workspaceId: string }): { tabs: Tabs } => {
2224
const t = useT();
2325

2426
const tabs = useMemo(
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import { renderHook } from "@reearth/test/utils";
2+
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
3+
4+
import useDoubleClick from "./useDoubleClick";
5+
6+
describe("useDoubleClick", () => {
7+
beforeEach(() => {
8+
vi.useFakeTimers();
9+
});
10+
11+
afterEach(() => {
12+
vi.useRealTimers();
13+
});
14+
15+
test("should call onClick after delay when clicked once", () => {
16+
const onClick = vi.fn();
17+
const onDoubleClick = vi.fn();
18+
19+
const { result } = renderHook(() =>
20+
useDoubleClick(onClick, onDoubleClick, 200)
21+
);
22+
23+
const [singleClickHandler] = result.current;
24+
25+
singleClickHandler();
26+
27+
expect(onClick).not.toHaveBeenCalled();
28+
29+
vi.advanceTimersByTime(200);
30+
31+
expect(onClick).toHaveBeenCalledTimes(1);
32+
expect(onDoubleClick).not.toHaveBeenCalled();
33+
});
34+
35+
test("should call onDoubleClick when clicked twice rapidly", () => {
36+
const onClick = vi.fn();
37+
const onDoubleClick = vi.fn();
38+
39+
const { result } = renderHook(() =>
40+
useDoubleClick(onClick, onDoubleClick, 200)
41+
);
42+
43+
const [singleClickHandler, doubleClickHandler] = result.current;
44+
45+
singleClickHandler();
46+
47+
expect(onClick).not.toHaveBeenCalled();
48+
49+
doubleClickHandler();
50+
51+
expect(onClick).not.toHaveBeenCalled();
52+
expect(onDoubleClick).toHaveBeenCalledTimes(1);
53+
});
54+
55+
test("should not call onClick if onDoubleClick is triggered", () => {
56+
const onClick = vi.fn();
57+
const onDoubleClick = vi.fn();
58+
59+
const { result } = renderHook(() =>
60+
useDoubleClick(onClick, onDoubleClick, 200)
61+
);
62+
63+
const [singleClickHandler, doubleClickHandler] = result.current;
64+
65+
singleClickHandler();
66+
doubleClickHandler();
67+
68+
vi.advanceTimersByTime(300);
69+
70+
expect(onClick).not.toHaveBeenCalled();
71+
expect(onDoubleClick).toHaveBeenCalledTimes(1);
72+
});
73+
74+
test("should handle undefined callbacks gracefully", () => {
75+
const { result } = renderHook(() =>
76+
useDoubleClick(undefined, undefined, 200)
77+
);
78+
79+
const [singleClickHandler, doubleClickHandler] = result.current;
80+
81+
expect(() => {
82+
singleClickHandler();
83+
doubleClickHandler();
84+
vi.advanceTimersByTime(300);
85+
}).not.toThrow();
86+
});
87+
88+
test("should use custom delay value", () => {
89+
const onClick = vi.fn();
90+
const customDelay = 500;
91+
92+
const { result } = renderHook(() =>
93+
useDoubleClick(onClick, undefined, customDelay)
94+
);
95+
96+
const [singleClickHandler] = result.current;
97+
98+
singleClickHandler();
99+
vi.advanceTimersByTime(400);
100+
expect(onClick).not.toHaveBeenCalled();
101+
vi.advanceTimersByTime(100);
102+
expect(onClick).toHaveBeenCalledTimes(1);
103+
});
104+
105+
test("should handle rapid double clicking (two clicks in succession)", () => {
106+
const onClick = vi.fn();
107+
const onDoubleClick = vi.fn();
108+
109+
const { result } = renderHook(() =>
110+
useDoubleClick(onClick, onDoubleClick, 200)
111+
);
112+
113+
const [singleClickHandler, doubleClickHandler] = result.current;
114+
singleClickHandler();
115+
vi.advanceTimersByTime(50);
116+
doubleClickHandler();
117+
expect(onDoubleClick).toHaveBeenCalledTimes(1);
118+
vi.advanceTimersByTime(200);
119+
expect(onClick).not.toHaveBeenCalled();
120+
});
121+
122+
test("should handle double click with varying timing between clicks", () => {
123+
const onClick = vi.fn();
124+
const onDoubleClick = vi.fn();
125+
const delay = 200;
126+
127+
const { result } = renderHook(() =>
128+
useDoubleClick(onClick, onDoubleClick, delay)
129+
);
130+
131+
const [singleClickHandler, doubleClickHandler] = result.current;
132+
133+
const timings = [10, 50, 100, 199];
134+
135+
timings.forEach((timing) => {
136+
onClick.mockReset();
137+
onDoubleClick.mockReset();
138+
singleClickHandler();
139+
vi.advanceTimersByTime(timing);
140+
doubleClickHandler();
141+
expect(onDoubleClick).toHaveBeenCalledTimes(1);
142+
expect(onClick).not.toHaveBeenCalled();
143+
vi.advanceTimersByTime(delay);
144+
});
145+
});
146+
});

0 commit comments

Comments
 (0)