@perspective-ai/sdk-react 1.0.0-alpha.2 → 1.0.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +139 -166
- package/dist/index.cjs +317 -270
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +121 -54
- package/dist/index.d.ts +121 -54
- package/dist/index.js +317 -271
- package/dist/index.js.map +1 -1
- package/package.json +9 -2
- package/src/FloatBubble.test.tsx +0 -39
- package/src/FloatBubble.tsx +22 -48
- package/src/hooks/useFloatBubble.test.ts +91 -0
- package/src/hooks/useFloatBubble.ts +180 -0
- package/src/hooks/usePopup.test.ts +219 -0
- package/src/hooks/usePopup.ts +190 -0
- package/src/hooks/useSlider.test.ts +150 -0
- package/src/hooks/useSlider.ts +181 -0
- package/src/index.ts +26 -29
- package/src/PopupButton.test.tsx +0 -273
- package/src/PopupButton.tsx +0 -208
- package/src/SliderButton.test.tsx +0 -279
- package/src/SliderButton.tsx +0 -208
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { renderHook, act, cleanup } from "@testing-library/react";
|
|
3
|
+
import { usePopup } from "./usePopup";
|
|
4
|
+
|
|
5
|
+
const mockDestroy = vi.fn();
|
|
6
|
+
const mockUnmount = vi.fn();
|
|
7
|
+
const mockUpdate = vi.fn();
|
|
8
|
+
|
|
9
|
+
vi.mock("@perspective-ai/sdk", () => ({
|
|
10
|
+
openPopup: vi.fn(() => ({
|
|
11
|
+
unmount: mockUnmount,
|
|
12
|
+
update: mockUpdate,
|
|
13
|
+
destroy: mockDestroy,
|
|
14
|
+
researchId: "test-research-id",
|
|
15
|
+
type: "popup",
|
|
16
|
+
iframe: null,
|
|
17
|
+
container: null,
|
|
18
|
+
})),
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
import { openPopup } from "@perspective-ai/sdk";
|
|
22
|
+
const mockOpenPopup = vi.mocked(openPopup);
|
|
23
|
+
|
|
24
|
+
describe("usePopup", () => {
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
vi.clearAllMocks();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
afterEach(() => {
|
|
30
|
+
cleanup();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("returns open, close, toggle functions and isOpen state", () => {
|
|
34
|
+
const { result } = renderHook(() =>
|
|
35
|
+
usePopup({ researchId: "test-research-id" })
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
expect(result.current.open).toBeInstanceOf(Function);
|
|
39
|
+
expect(result.current.close).toBeInstanceOf(Function);
|
|
40
|
+
expect(result.current.toggle).toBeInstanceOf(Function);
|
|
41
|
+
expect(result.current.isOpen).toBe(false);
|
|
42
|
+
expect(result.current.handle).toBeNull();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("does not call openPopup on mount", () => {
|
|
46
|
+
renderHook(() => usePopup({ researchId: "test-research-id" }));
|
|
47
|
+
|
|
48
|
+
expect(mockOpenPopup).not.toHaveBeenCalled();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("calls openPopup when open() is called", () => {
|
|
52
|
+
const { result } = renderHook(() =>
|
|
53
|
+
usePopup({ researchId: "test-research-id" })
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
act(() => {
|
|
57
|
+
result.current.open();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
expect(mockOpenPopup).toHaveBeenCalledTimes(1);
|
|
61
|
+
expect(mockOpenPopup).toHaveBeenCalledWith(
|
|
62
|
+
expect.objectContaining({
|
|
63
|
+
researchId: "test-research-id",
|
|
64
|
+
})
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("sets isOpen to true when open() is called", () => {
|
|
69
|
+
const { result } = renderHook(() =>
|
|
70
|
+
usePopup({ researchId: "test-research-id" })
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
expect(result.current.isOpen).toBe(false);
|
|
74
|
+
|
|
75
|
+
act(() => {
|
|
76
|
+
result.current.open();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
expect(result.current.isOpen).toBe(true);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("calls destroy and sets isOpen to false when close() is called", () => {
|
|
83
|
+
const { result } = renderHook(() =>
|
|
84
|
+
usePopup({ researchId: "test-research-id" })
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
act(() => {
|
|
88
|
+
result.current.open();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
expect(result.current.isOpen).toBe(true);
|
|
92
|
+
|
|
93
|
+
act(() => {
|
|
94
|
+
result.current.close();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
expect(mockDestroy).toHaveBeenCalledTimes(1);
|
|
98
|
+
expect(result.current.isOpen).toBe(false);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("toggles open state", () => {
|
|
102
|
+
const { result } = renderHook(() =>
|
|
103
|
+
usePopup({ researchId: "test-research-id" })
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
expect(result.current.isOpen).toBe(false);
|
|
107
|
+
|
|
108
|
+
act(() => {
|
|
109
|
+
result.current.toggle();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
expect(result.current.isOpen).toBe(true);
|
|
113
|
+
expect(mockOpenPopup).toHaveBeenCalledTimes(1);
|
|
114
|
+
|
|
115
|
+
act(() => {
|
|
116
|
+
result.current.toggle();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
expect(result.current.isOpen).toBe(false);
|
|
120
|
+
expect(mockDestroy).toHaveBeenCalledTimes(1);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("passes config to openPopup", () => {
|
|
124
|
+
const onReady = vi.fn();
|
|
125
|
+
const onSubmit = vi.fn();
|
|
126
|
+
|
|
127
|
+
const { result } = renderHook(() =>
|
|
128
|
+
usePopup({
|
|
129
|
+
researchId: "test-research-id",
|
|
130
|
+
params: { source: "test" },
|
|
131
|
+
theme: "dark",
|
|
132
|
+
host: "https://custom.example.com",
|
|
133
|
+
onReady,
|
|
134
|
+
onSubmit,
|
|
135
|
+
})
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
act(() => {
|
|
139
|
+
result.current.open();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
expect(mockOpenPopup).toHaveBeenCalledTimes(1);
|
|
143
|
+
const config = mockOpenPopup.mock.calls[0]![0];
|
|
144
|
+
expect(config.researchId).toBe("test-research-id");
|
|
145
|
+
expect(config.params).toEqual({ source: "test" });
|
|
146
|
+
expect(config.theme).toBe("dark");
|
|
147
|
+
expect(config.host).toBe("https://custom.example.com");
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("supports controlled mode with open prop", () => {
|
|
151
|
+
const onOpenChange = vi.fn();
|
|
152
|
+
|
|
153
|
+
const { result, rerender } = renderHook(
|
|
154
|
+
({ open }) =>
|
|
155
|
+
usePopup({
|
|
156
|
+
researchId: "test-research-id",
|
|
157
|
+
open,
|
|
158
|
+
onOpenChange,
|
|
159
|
+
}),
|
|
160
|
+
{ initialProps: { open: false } }
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
expect(result.current.isOpen).toBe(false);
|
|
164
|
+
|
|
165
|
+
rerender({ open: true });
|
|
166
|
+
|
|
167
|
+
expect(mockOpenPopup).toHaveBeenCalledTimes(1);
|
|
168
|
+
|
|
169
|
+
rerender({ open: false });
|
|
170
|
+
|
|
171
|
+
expect(mockDestroy).toHaveBeenCalledTimes(1);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("calls onOpenChange in controlled mode when open() is called", () => {
|
|
175
|
+
const onOpenChange = vi.fn();
|
|
176
|
+
|
|
177
|
+
const { result } = renderHook(() =>
|
|
178
|
+
usePopup({
|
|
179
|
+
researchId: "test-research-id",
|
|
180
|
+
open: false,
|
|
181
|
+
onOpenChange,
|
|
182
|
+
})
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
act(() => {
|
|
186
|
+
result.current.open();
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
expect(onOpenChange).toHaveBeenCalledWith(true);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("cleans up on unmount", () => {
|
|
193
|
+
const { result, unmount } = renderHook(() =>
|
|
194
|
+
usePopup({ researchId: "test-research-id" })
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
act(() => {
|
|
198
|
+
result.current.open();
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
unmount();
|
|
202
|
+
|
|
203
|
+
expect(mockDestroy).toHaveBeenCalled();
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("makes handle reactive - handle is available after open", () => {
|
|
207
|
+
const { result } = renderHook(() =>
|
|
208
|
+
usePopup({ researchId: "test-research-id" })
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
expect(result.current.handle).toBeNull();
|
|
212
|
+
|
|
213
|
+
act(() => {
|
|
214
|
+
result.current.open();
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
expect(result.current.handle).not.toBeNull();
|
|
218
|
+
});
|
|
219
|
+
});
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { useCallback, useState, useEffect, useRef } from "react";
|
|
2
|
+
import {
|
|
3
|
+
openPopup,
|
|
4
|
+
type EmbedConfig,
|
|
5
|
+
type EmbedHandle,
|
|
6
|
+
} from "@perspective-ai/sdk";
|
|
7
|
+
import { useStableCallback } from "./useStableCallback";
|
|
8
|
+
|
|
9
|
+
/** Options for usePopup hook */
|
|
10
|
+
export interface UsePopupOptions extends Omit<EmbedConfig, "type"> {
|
|
11
|
+
/** Controlled open state */
|
|
12
|
+
open?: boolean;
|
|
13
|
+
/** Callback when open state changes */
|
|
14
|
+
onOpenChange?: (open: boolean) => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Return type for usePopup hook */
|
|
18
|
+
export interface UsePopupReturn {
|
|
19
|
+
/** Open the popup */
|
|
20
|
+
open: () => void;
|
|
21
|
+
/** Close the popup */
|
|
22
|
+
close: () => void;
|
|
23
|
+
/** Toggle the popup */
|
|
24
|
+
toggle: () => void;
|
|
25
|
+
/** Whether the popup is currently open */
|
|
26
|
+
isOpen: boolean;
|
|
27
|
+
/** The underlying SDK handle (null when closed) */
|
|
28
|
+
handle: EmbedHandle | null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Headless hook for programmatic popup control.
|
|
33
|
+
* Use this when you need custom trigger elements or programmatic control.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```tsx
|
|
37
|
+
* // Basic usage with custom trigger
|
|
38
|
+
* const { open, isOpen } = usePopup({ researchId: "abc" });
|
|
39
|
+
* <MyCustomButton onClick={open}>Open Survey</MyCustomButton>
|
|
40
|
+
*
|
|
41
|
+
* // Controlled mode
|
|
42
|
+
* const [isOpen, setIsOpen] = useState(false);
|
|
43
|
+
* const popup = usePopup({
|
|
44
|
+
* researchId: "abc",
|
|
45
|
+
* open: isOpen,
|
|
46
|
+
* onOpenChange: setIsOpen
|
|
47
|
+
* });
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export function usePopup(options: UsePopupOptions): UsePopupReturn {
|
|
51
|
+
const {
|
|
52
|
+
researchId,
|
|
53
|
+
params,
|
|
54
|
+
brand,
|
|
55
|
+
theme,
|
|
56
|
+
host,
|
|
57
|
+
onReady,
|
|
58
|
+
onSubmit,
|
|
59
|
+
onNavigate,
|
|
60
|
+
onClose,
|
|
61
|
+
onError,
|
|
62
|
+
open: controlledOpen,
|
|
63
|
+
onOpenChange,
|
|
64
|
+
} = options;
|
|
65
|
+
|
|
66
|
+
const [handle, setHandle] = useState<EmbedHandle | null>(null);
|
|
67
|
+
const [internalOpen, setInternalOpen] = useState(false);
|
|
68
|
+
const handleRef = useRef<EmbedHandle | null>(null);
|
|
69
|
+
|
|
70
|
+
const isControlled = controlledOpen !== undefined;
|
|
71
|
+
const isOpen = isControlled ? controlledOpen : internalOpen;
|
|
72
|
+
|
|
73
|
+
const stableOnReady = useStableCallback(onReady);
|
|
74
|
+
const stableOnSubmit = useStableCallback(onSubmit);
|
|
75
|
+
const stableOnNavigate = useStableCallback(onNavigate);
|
|
76
|
+
const stableOnError = useStableCallback(onError);
|
|
77
|
+
|
|
78
|
+
const setOpen = useCallback(
|
|
79
|
+
(value: boolean) => {
|
|
80
|
+
if (isControlled) {
|
|
81
|
+
onOpenChange?.(value);
|
|
82
|
+
} else {
|
|
83
|
+
setInternalOpen(value);
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
[isControlled, onOpenChange]
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const handleClose = useCallback(() => {
|
|
90
|
+
handleRef.current = null;
|
|
91
|
+
setHandle(null);
|
|
92
|
+
setOpen(false);
|
|
93
|
+
onClose?.();
|
|
94
|
+
}, [setOpen, onClose]);
|
|
95
|
+
|
|
96
|
+
const stableOnClose = useStableCallback(handleClose);
|
|
97
|
+
|
|
98
|
+
const createPopup = useCallback(() => {
|
|
99
|
+
if (handleRef.current) return handleRef.current;
|
|
100
|
+
|
|
101
|
+
const newHandle = openPopup({
|
|
102
|
+
researchId,
|
|
103
|
+
params,
|
|
104
|
+
brand,
|
|
105
|
+
theme,
|
|
106
|
+
host,
|
|
107
|
+
onReady: stableOnReady,
|
|
108
|
+
onSubmit: stableOnSubmit,
|
|
109
|
+
onNavigate: stableOnNavigate,
|
|
110
|
+
onClose: stableOnClose,
|
|
111
|
+
onError: stableOnError,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
handleRef.current = newHandle;
|
|
115
|
+
setHandle(newHandle);
|
|
116
|
+
return newHandle;
|
|
117
|
+
}, [
|
|
118
|
+
researchId,
|
|
119
|
+
params,
|
|
120
|
+
brand,
|
|
121
|
+
theme,
|
|
122
|
+
host,
|
|
123
|
+
stableOnReady,
|
|
124
|
+
stableOnSubmit,
|
|
125
|
+
stableOnNavigate,
|
|
126
|
+
stableOnClose,
|
|
127
|
+
stableOnError,
|
|
128
|
+
]);
|
|
129
|
+
|
|
130
|
+
const destroyPopup = useCallback(() => {
|
|
131
|
+
if (handleRef.current) {
|
|
132
|
+
handleRef.current.destroy();
|
|
133
|
+
handleRef.current = null;
|
|
134
|
+
setHandle(null);
|
|
135
|
+
}
|
|
136
|
+
}, []);
|
|
137
|
+
|
|
138
|
+
const openFn = useCallback(() => {
|
|
139
|
+
if (isControlled) {
|
|
140
|
+
onOpenChange?.(true);
|
|
141
|
+
} else {
|
|
142
|
+
createPopup();
|
|
143
|
+
setInternalOpen(true);
|
|
144
|
+
}
|
|
145
|
+
}, [isControlled, onOpenChange, createPopup]);
|
|
146
|
+
|
|
147
|
+
const closeFn = useCallback(() => {
|
|
148
|
+
if (isControlled) {
|
|
149
|
+
onOpenChange?.(false);
|
|
150
|
+
} else {
|
|
151
|
+
destroyPopup();
|
|
152
|
+
setInternalOpen(false);
|
|
153
|
+
}
|
|
154
|
+
}, [isControlled, onOpenChange, destroyPopup]);
|
|
155
|
+
|
|
156
|
+
const toggleFn = useCallback(() => {
|
|
157
|
+
if (isOpen) {
|
|
158
|
+
closeFn();
|
|
159
|
+
} else {
|
|
160
|
+
openFn();
|
|
161
|
+
}
|
|
162
|
+
}, [isOpen, openFn, closeFn]);
|
|
163
|
+
|
|
164
|
+
useEffect(() => {
|
|
165
|
+
if (!isControlled) return;
|
|
166
|
+
|
|
167
|
+
if (controlledOpen && !handleRef.current) {
|
|
168
|
+
createPopup();
|
|
169
|
+
} else if (!controlledOpen && handleRef.current) {
|
|
170
|
+
destroyPopup();
|
|
171
|
+
}
|
|
172
|
+
}, [controlledOpen, isControlled, createPopup, destroyPopup]);
|
|
173
|
+
|
|
174
|
+
useEffect(() => {
|
|
175
|
+
return () => {
|
|
176
|
+
if (handleRef.current) {
|
|
177
|
+
handleRef.current.destroy();
|
|
178
|
+
handleRef.current = null;
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
}, []);
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
open: openFn,
|
|
185
|
+
close: closeFn,
|
|
186
|
+
toggle: toggleFn,
|
|
187
|
+
isOpen,
|
|
188
|
+
handle,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { renderHook, act, cleanup } from "@testing-library/react";
|
|
3
|
+
import { useSlider } from "./useSlider";
|
|
4
|
+
|
|
5
|
+
const mockDestroy = vi.fn();
|
|
6
|
+
const mockUnmount = vi.fn();
|
|
7
|
+
const mockUpdate = vi.fn();
|
|
8
|
+
|
|
9
|
+
vi.mock("@perspective-ai/sdk", () => ({
|
|
10
|
+
openSlider: vi.fn(() => ({
|
|
11
|
+
unmount: mockUnmount,
|
|
12
|
+
update: mockUpdate,
|
|
13
|
+
destroy: mockDestroy,
|
|
14
|
+
researchId: "test-research-id",
|
|
15
|
+
type: "slider",
|
|
16
|
+
iframe: null,
|
|
17
|
+
container: null,
|
|
18
|
+
})),
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
import { openSlider } from "@perspective-ai/sdk";
|
|
22
|
+
const mockOpenSlider = vi.mocked(openSlider);
|
|
23
|
+
|
|
24
|
+
describe("useSlider", () => {
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
vi.clearAllMocks();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
afterEach(() => {
|
|
30
|
+
cleanup();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("returns open, close, toggle functions and isOpen state", () => {
|
|
34
|
+
const { result } = renderHook(() =>
|
|
35
|
+
useSlider({ researchId: "test-research-id" })
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
expect(result.current.open).toBeInstanceOf(Function);
|
|
39
|
+
expect(result.current.close).toBeInstanceOf(Function);
|
|
40
|
+
expect(result.current.toggle).toBeInstanceOf(Function);
|
|
41
|
+
expect(result.current.isOpen).toBe(false);
|
|
42
|
+
expect(result.current.handle).toBeNull();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("does not call openSlider on mount", () => {
|
|
46
|
+
renderHook(() => useSlider({ researchId: "test-research-id" }));
|
|
47
|
+
|
|
48
|
+
expect(mockOpenSlider).not.toHaveBeenCalled();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("calls openSlider when open() is called", () => {
|
|
52
|
+
const { result } = renderHook(() =>
|
|
53
|
+
useSlider({ researchId: "test-research-id" })
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
act(() => {
|
|
57
|
+
result.current.open();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
expect(mockOpenSlider).toHaveBeenCalledTimes(1);
|
|
61
|
+
expect(mockOpenSlider).toHaveBeenCalledWith(
|
|
62
|
+
expect.objectContaining({
|
|
63
|
+
researchId: "test-research-id",
|
|
64
|
+
})
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("sets isOpen to true when open() is called", () => {
|
|
69
|
+
const { result } = renderHook(() =>
|
|
70
|
+
useSlider({ researchId: "test-research-id" })
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
expect(result.current.isOpen).toBe(false);
|
|
74
|
+
|
|
75
|
+
act(() => {
|
|
76
|
+
result.current.open();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
expect(result.current.isOpen).toBe(true);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("calls destroy and sets isOpen to false when close() is called", () => {
|
|
83
|
+
const { result } = renderHook(() =>
|
|
84
|
+
useSlider({ researchId: "test-research-id" })
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
act(() => {
|
|
88
|
+
result.current.open();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
expect(result.current.isOpen).toBe(true);
|
|
92
|
+
|
|
93
|
+
act(() => {
|
|
94
|
+
result.current.close();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
expect(mockDestroy).toHaveBeenCalledTimes(1);
|
|
98
|
+
expect(result.current.isOpen).toBe(false);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("toggles open state", () => {
|
|
102
|
+
const { result } = renderHook(() =>
|
|
103
|
+
useSlider({ researchId: "test-research-id" })
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
expect(result.current.isOpen).toBe(false);
|
|
107
|
+
|
|
108
|
+
act(() => {
|
|
109
|
+
result.current.toggle();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
expect(result.current.isOpen).toBe(true);
|
|
113
|
+
expect(mockOpenSlider).toHaveBeenCalledTimes(1);
|
|
114
|
+
|
|
115
|
+
act(() => {
|
|
116
|
+
result.current.toggle();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
expect(result.current.isOpen).toBe(false);
|
|
120
|
+
expect(mockDestroy).toHaveBeenCalledTimes(1);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("cleans up on unmount", () => {
|
|
124
|
+
const { result, unmount } = renderHook(() =>
|
|
125
|
+
useSlider({ researchId: "test-research-id" })
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
act(() => {
|
|
129
|
+
result.current.open();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
unmount();
|
|
133
|
+
|
|
134
|
+
expect(mockDestroy).toHaveBeenCalled();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("makes handle reactive - handle is available after open", () => {
|
|
138
|
+
const { result } = renderHook(() =>
|
|
139
|
+
useSlider({ researchId: "test-research-id" })
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
expect(result.current.handle).toBeNull();
|
|
143
|
+
|
|
144
|
+
act(() => {
|
|
145
|
+
result.current.open();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
expect(result.current.handle).not.toBeNull();
|
|
149
|
+
});
|
|
150
|
+
});
|