@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,181 @@
|
|
|
1
|
+
import { useCallback, useState, useEffect, useRef } from "react";
|
|
2
|
+
import {
|
|
3
|
+
openSlider,
|
|
4
|
+
type EmbedConfig,
|
|
5
|
+
type EmbedHandle,
|
|
6
|
+
} from "@perspective-ai/sdk";
|
|
7
|
+
import { useStableCallback } from "./useStableCallback";
|
|
8
|
+
|
|
9
|
+
/** Options for useSlider hook */
|
|
10
|
+
export interface UseSliderOptions 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 useSlider hook */
|
|
18
|
+
export interface UseSliderReturn {
|
|
19
|
+
/** Open the slider */
|
|
20
|
+
open: () => void;
|
|
21
|
+
/** Close the slider */
|
|
22
|
+
close: () => void;
|
|
23
|
+
/** Toggle the slider */
|
|
24
|
+
toggle: () => void;
|
|
25
|
+
/** Whether the slider 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 slider control.
|
|
33
|
+
* Use this when you need custom trigger elements or programmatic control.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```tsx
|
|
37
|
+
* const { open, isOpen } = useSlider({ researchId: "abc" });
|
|
38
|
+
* <MyCustomButton onClick={open}>Give Feedback</MyCustomButton>
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export function useSlider(options: UseSliderOptions): UseSliderReturn {
|
|
42
|
+
const {
|
|
43
|
+
researchId,
|
|
44
|
+
params,
|
|
45
|
+
brand,
|
|
46
|
+
theme,
|
|
47
|
+
host,
|
|
48
|
+
onReady,
|
|
49
|
+
onSubmit,
|
|
50
|
+
onNavigate,
|
|
51
|
+
onClose,
|
|
52
|
+
onError,
|
|
53
|
+
open: controlledOpen,
|
|
54
|
+
onOpenChange,
|
|
55
|
+
} = options;
|
|
56
|
+
|
|
57
|
+
const [handle, setHandle] = useState<EmbedHandle | null>(null);
|
|
58
|
+
const [internalOpen, setInternalOpen] = useState(false);
|
|
59
|
+
const handleRef = useRef<EmbedHandle | null>(null);
|
|
60
|
+
|
|
61
|
+
const isControlled = controlledOpen !== undefined;
|
|
62
|
+
const isOpen = isControlled ? controlledOpen : internalOpen;
|
|
63
|
+
|
|
64
|
+
const stableOnReady = useStableCallback(onReady);
|
|
65
|
+
const stableOnSubmit = useStableCallback(onSubmit);
|
|
66
|
+
const stableOnNavigate = useStableCallback(onNavigate);
|
|
67
|
+
const stableOnError = useStableCallback(onError);
|
|
68
|
+
|
|
69
|
+
const setOpen = useCallback(
|
|
70
|
+
(value: boolean) => {
|
|
71
|
+
if (isControlled) {
|
|
72
|
+
onOpenChange?.(value);
|
|
73
|
+
} else {
|
|
74
|
+
setInternalOpen(value);
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
[isControlled, onOpenChange]
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const handleClose = useCallback(() => {
|
|
81
|
+
handleRef.current = null;
|
|
82
|
+
setHandle(null);
|
|
83
|
+
setOpen(false);
|
|
84
|
+
onClose?.();
|
|
85
|
+
}, [setOpen, onClose]);
|
|
86
|
+
|
|
87
|
+
const stableOnClose = useStableCallback(handleClose);
|
|
88
|
+
|
|
89
|
+
const createSlider = useCallback(() => {
|
|
90
|
+
if (handleRef.current) return handleRef.current;
|
|
91
|
+
|
|
92
|
+
const newHandle = openSlider({
|
|
93
|
+
researchId,
|
|
94
|
+
params,
|
|
95
|
+
brand,
|
|
96
|
+
theme,
|
|
97
|
+
host,
|
|
98
|
+
onReady: stableOnReady,
|
|
99
|
+
onSubmit: stableOnSubmit,
|
|
100
|
+
onNavigate: stableOnNavigate,
|
|
101
|
+
onClose: stableOnClose,
|
|
102
|
+
onError: stableOnError,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
handleRef.current = newHandle;
|
|
106
|
+
setHandle(newHandle);
|
|
107
|
+
return newHandle;
|
|
108
|
+
}, [
|
|
109
|
+
researchId,
|
|
110
|
+
params,
|
|
111
|
+
brand,
|
|
112
|
+
theme,
|
|
113
|
+
host,
|
|
114
|
+
stableOnReady,
|
|
115
|
+
stableOnSubmit,
|
|
116
|
+
stableOnNavigate,
|
|
117
|
+
stableOnClose,
|
|
118
|
+
stableOnError,
|
|
119
|
+
]);
|
|
120
|
+
|
|
121
|
+
const destroySlider = useCallback(() => {
|
|
122
|
+
if (handleRef.current) {
|
|
123
|
+
handleRef.current.destroy();
|
|
124
|
+
handleRef.current = null;
|
|
125
|
+
setHandle(null);
|
|
126
|
+
}
|
|
127
|
+
}, []);
|
|
128
|
+
|
|
129
|
+
const openFn = useCallback(() => {
|
|
130
|
+
if (isControlled) {
|
|
131
|
+
onOpenChange?.(true);
|
|
132
|
+
} else {
|
|
133
|
+
createSlider();
|
|
134
|
+
setInternalOpen(true);
|
|
135
|
+
}
|
|
136
|
+
}, [isControlled, onOpenChange, createSlider]);
|
|
137
|
+
|
|
138
|
+
const closeFn = useCallback(() => {
|
|
139
|
+
if (isControlled) {
|
|
140
|
+
onOpenChange?.(false);
|
|
141
|
+
} else {
|
|
142
|
+
destroySlider();
|
|
143
|
+
setInternalOpen(false);
|
|
144
|
+
}
|
|
145
|
+
}, [isControlled, onOpenChange, destroySlider]);
|
|
146
|
+
|
|
147
|
+
const toggleFn = useCallback(() => {
|
|
148
|
+
if (isOpen) {
|
|
149
|
+
closeFn();
|
|
150
|
+
} else {
|
|
151
|
+
openFn();
|
|
152
|
+
}
|
|
153
|
+
}, [isOpen, openFn, closeFn]);
|
|
154
|
+
|
|
155
|
+
useEffect(() => {
|
|
156
|
+
if (!isControlled) return;
|
|
157
|
+
|
|
158
|
+
if (controlledOpen && !handleRef.current) {
|
|
159
|
+
createSlider();
|
|
160
|
+
} else if (!controlledOpen && handleRef.current) {
|
|
161
|
+
destroySlider();
|
|
162
|
+
}
|
|
163
|
+
}, [controlledOpen, isControlled, createSlider, destroySlider]);
|
|
164
|
+
|
|
165
|
+
useEffect(() => {
|
|
166
|
+
return () => {
|
|
167
|
+
if (handleRef.current) {
|
|
168
|
+
handleRef.current.destroy();
|
|
169
|
+
handleRef.current = null;
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
}, []);
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
open: openFn,
|
|
176
|
+
close: closeFn,
|
|
177
|
+
toggle: toggleFn,
|
|
178
|
+
isOpen,
|
|
179
|
+
handle,
|
|
180
|
+
};
|
|
181
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,47 +1,44 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Perspective Embed SDK - React
|
|
4
|
+
* Perspective Embed SDK - React
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
* import {
|
|
6
|
+
* Hooks (for overlays - popup, slider, float bubble):
|
|
7
|
+
* import { usePopup, useSlider, useFloatBubble } from '@perspective-ai/sdk-react';
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
* <
|
|
9
|
+
* const { open } = usePopup({ researchId: "xxx" });
|
|
10
|
+
* <button onClick={open}>Take Survey</button>
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
12
|
+
* Components (for embeds - widget, fullpage):
|
|
13
|
+
* import { Widget, Fullpage, FloatBubble } from '@perspective-ai/sdk-react';
|
|
14
14
|
*
|
|
15
|
-
*
|
|
16
|
-
* <SliderButton researchId="xxx">Open Interview</SliderButton>
|
|
17
|
-
*
|
|
18
|
-
* // Floating bubble
|
|
19
|
-
* <FloatBubble researchId="xxx" />
|
|
20
|
-
*
|
|
21
|
-
* // Full page
|
|
15
|
+
* <Widget researchId="xxx" />
|
|
22
16
|
* <Fullpage researchId="xxx" />
|
|
17
|
+
* <FloatBubble researchId="xxx" />
|
|
23
18
|
*/
|
|
24
19
|
|
|
25
|
-
// Components
|
|
26
|
-
export { Widget, type WidgetProps } from "./Widget";
|
|
27
20
|
export {
|
|
28
|
-
|
|
29
|
-
type
|
|
30
|
-
type
|
|
31
|
-
} from "./
|
|
21
|
+
usePopup,
|
|
22
|
+
type UsePopupOptions,
|
|
23
|
+
type UsePopupReturn,
|
|
24
|
+
} from "./hooks/usePopup";
|
|
32
25
|
export {
|
|
33
|
-
|
|
34
|
-
type
|
|
35
|
-
type
|
|
36
|
-
} from "./
|
|
37
|
-
export {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
26
|
+
useSlider,
|
|
27
|
+
type UseSliderOptions,
|
|
28
|
+
type UseSliderReturn,
|
|
29
|
+
} from "./hooks/useSlider";
|
|
30
|
+
export {
|
|
31
|
+
useFloatBubble,
|
|
32
|
+
type UseFloatBubbleOptions,
|
|
33
|
+
type UseFloatBubbleReturn,
|
|
34
|
+
} from "./hooks/useFloatBubble";
|
|
41
35
|
export { useThemeSync } from "./hooks/useThemeSync";
|
|
42
36
|
export { useStableCallback } from "./hooks/useStableCallback";
|
|
43
37
|
|
|
44
|
-
|
|
38
|
+
export { Widget, type WidgetProps } from "./Widget";
|
|
39
|
+
export { Fullpage, type FullpageProps } from "./Fullpage";
|
|
40
|
+
export { FloatBubble, type FloatBubbleProps } from "./FloatBubble";
|
|
41
|
+
|
|
45
42
|
export type {
|
|
46
43
|
EmbedConfig,
|
|
47
44
|
EmbedHandle,
|
package/src/PopupButton.test.tsx
DELETED
|
@@ -1,273 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
-
import { render, screen, cleanup, fireEvent } from "@testing-library/react";
|
|
3
|
-
import { createRef } from "react";
|
|
4
|
-
import { PopupButton, type PopupButtonHandle } from "./PopupButton";
|
|
5
|
-
|
|
6
|
-
// Mock the core embed package
|
|
7
|
-
const mockDestroy = vi.fn();
|
|
8
|
-
const mockUnmount = vi.fn();
|
|
9
|
-
|
|
10
|
-
vi.mock("@perspective-ai/sdk", () => ({
|
|
11
|
-
openPopup: vi.fn(() => ({
|
|
12
|
-
unmount: mockUnmount,
|
|
13
|
-
update: vi.fn(),
|
|
14
|
-
destroy: mockDestroy,
|
|
15
|
-
researchId: "test-research-id",
|
|
16
|
-
type: "popup",
|
|
17
|
-
iframe: null,
|
|
18
|
-
container: null,
|
|
19
|
-
})),
|
|
20
|
-
}));
|
|
21
|
-
|
|
22
|
-
import { openPopup } from "@perspective-ai/sdk";
|
|
23
|
-
const mockOpenPopup = vi.mocked(openPopup);
|
|
24
|
-
|
|
25
|
-
describe("PopupButton", () => {
|
|
26
|
-
beforeEach(() => {
|
|
27
|
-
vi.clearAllMocks();
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
afterEach(() => {
|
|
31
|
-
cleanup();
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it("renders a button with children", () => {
|
|
35
|
-
render(
|
|
36
|
-
<PopupButton researchId="test-research-id">Open Interview</PopupButton>
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
const button = screen.getByRole("button");
|
|
40
|
-
expect(button).toBeDefined();
|
|
41
|
-
expect(button.textContent).toBe("Open Interview");
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it("has correct test id", () => {
|
|
45
|
-
render(<PopupButton researchId="test-research-id">Open</PopupButton>);
|
|
46
|
-
|
|
47
|
-
const button = screen.getByTestId("perspective-popup-button");
|
|
48
|
-
expect(button).toBeDefined();
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it("opens popup on click", () => {
|
|
52
|
-
render(<PopupButton researchId="test-research-id">Open</PopupButton>);
|
|
53
|
-
|
|
54
|
-
const button = screen.getByRole("button");
|
|
55
|
-
fireEvent.click(button);
|
|
56
|
-
|
|
57
|
-
expect(mockOpenPopup).toHaveBeenCalledTimes(1);
|
|
58
|
-
expect(mockOpenPopup).toHaveBeenCalledWith(
|
|
59
|
-
expect.objectContaining({
|
|
60
|
-
researchId: "test-research-id",
|
|
61
|
-
})
|
|
62
|
-
);
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it("closes popup on second click", () => {
|
|
66
|
-
render(<PopupButton researchId="test-research-id">Open</PopupButton>);
|
|
67
|
-
|
|
68
|
-
const button = screen.getByRole("button");
|
|
69
|
-
|
|
70
|
-
// First click opens
|
|
71
|
-
fireEvent.click(button);
|
|
72
|
-
expect(mockOpenPopup).toHaveBeenCalledTimes(1);
|
|
73
|
-
|
|
74
|
-
// Second click closes
|
|
75
|
-
fireEvent.click(button);
|
|
76
|
-
expect(mockDestroy).toHaveBeenCalledTimes(1);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it("passes config to openPopup", () => {
|
|
80
|
-
const onReady = vi.fn();
|
|
81
|
-
const onSubmit = vi.fn();
|
|
82
|
-
|
|
83
|
-
render(
|
|
84
|
-
<PopupButton
|
|
85
|
-
researchId="test-research-id"
|
|
86
|
-
params={{ source: "test" }}
|
|
87
|
-
theme="dark"
|
|
88
|
-
host="https://custom.example.com"
|
|
89
|
-
onReady={onReady}
|
|
90
|
-
onSubmit={onSubmit}
|
|
91
|
-
>
|
|
92
|
-
Open
|
|
93
|
-
</PopupButton>
|
|
94
|
-
);
|
|
95
|
-
|
|
96
|
-
fireEvent.click(screen.getByRole("button"));
|
|
97
|
-
|
|
98
|
-
const config = mockOpenPopup.mock.calls[0]![0];
|
|
99
|
-
expect(config.researchId).toBe("test-research-id");
|
|
100
|
-
expect(config.params).toEqual({ source: "test" });
|
|
101
|
-
expect(config.theme).toBe("dark");
|
|
102
|
-
expect(config.host).toBe("https://custom.example.com");
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
it("calls custom onClick handler", () => {
|
|
106
|
-
const onClick = vi.fn();
|
|
107
|
-
|
|
108
|
-
render(
|
|
109
|
-
<PopupButton researchId="test-research-id" onClick={onClick}>
|
|
110
|
-
Open
|
|
111
|
-
</PopupButton>
|
|
112
|
-
);
|
|
113
|
-
|
|
114
|
-
fireEvent.click(screen.getByRole("button"));
|
|
115
|
-
|
|
116
|
-
expect(onClick).toHaveBeenCalledTimes(1);
|
|
117
|
-
expect(mockOpenPopup).toHaveBeenCalledTimes(1);
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it("does not open popup if onClick prevents default", () => {
|
|
121
|
-
const onClick = vi.fn((e: React.MouseEvent) => e.preventDefault());
|
|
122
|
-
|
|
123
|
-
render(
|
|
124
|
-
<PopupButton researchId="test-research-id" onClick={onClick}>
|
|
125
|
-
Open
|
|
126
|
-
</PopupButton>
|
|
127
|
-
);
|
|
128
|
-
|
|
129
|
-
fireEvent.click(screen.getByRole("button"));
|
|
130
|
-
|
|
131
|
-
expect(onClick).toHaveBeenCalledTimes(1);
|
|
132
|
-
expect(mockOpenPopup).not.toHaveBeenCalled();
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
it("supports controlled mode via open prop", () => {
|
|
136
|
-
const onOpenChange = vi.fn();
|
|
137
|
-
|
|
138
|
-
const { rerender } = render(
|
|
139
|
-
<PopupButton
|
|
140
|
-
researchId="test-research-id"
|
|
141
|
-
open={false}
|
|
142
|
-
onOpenChange={onOpenChange}
|
|
143
|
-
>
|
|
144
|
-
Open
|
|
145
|
-
</PopupButton>
|
|
146
|
-
);
|
|
147
|
-
|
|
148
|
-
// Should not auto-open
|
|
149
|
-
expect(mockOpenPopup).not.toHaveBeenCalled();
|
|
150
|
-
|
|
151
|
-
// When open becomes true, popup should open
|
|
152
|
-
rerender(
|
|
153
|
-
<PopupButton
|
|
154
|
-
researchId="test-research-id"
|
|
155
|
-
open={true}
|
|
156
|
-
onOpenChange={onOpenChange}
|
|
157
|
-
>
|
|
158
|
-
Open
|
|
159
|
-
</PopupButton>
|
|
160
|
-
);
|
|
161
|
-
|
|
162
|
-
expect(mockOpenPopup).toHaveBeenCalledTimes(1);
|
|
163
|
-
|
|
164
|
-
// When open becomes false, popup should close
|
|
165
|
-
rerender(
|
|
166
|
-
<PopupButton
|
|
167
|
-
researchId="test-research-id"
|
|
168
|
-
open={false}
|
|
169
|
-
onOpenChange={onOpenChange}
|
|
170
|
-
>
|
|
171
|
-
Open
|
|
172
|
-
</PopupButton>
|
|
173
|
-
);
|
|
174
|
-
|
|
175
|
-
expect(mockDestroy).toHaveBeenCalledTimes(1);
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
it("exposes handle via embedRef", () => {
|
|
179
|
-
const embedRef = createRef<PopupButtonHandle | null>();
|
|
180
|
-
|
|
181
|
-
render(
|
|
182
|
-
<PopupButton researchId="test-research-id" embedRef={embedRef}>
|
|
183
|
-
Open
|
|
184
|
-
</PopupButton>
|
|
185
|
-
);
|
|
186
|
-
|
|
187
|
-
expect(embedRef.current).not.toBeNull();
|
|
188
|
-
expect(typeof embedRef.current?.open).toBe("function");
|
|
189
|
-
expect(typeof embedRef.current?.close).toBe("function");
|
|
190
|
-
expect(typeof embedRef.current?.toggle).toBe("function");
|
|
191
|
-
expect(embedRef.current?.researchId).toBe("test-research-id");
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
it("embedRef.open() opens popup", () => {
|
|
195
|
-
const embedRef = createRef<PopupButtonHandle | null>();
|
|
196
|
-
|
|
197
|
-
render(
|
|
198
|
-
<PopupButton researchId="test-research-id" embedRef={embedRef}>
|
|
199
|
-
Open
|
|
200
|
-
</PopupButton>
|
|
201
|
-
);
|
|
202
|
-
|
|
203
|
-
embedRef.current?.open();
|
|
204
|
-
|
|
205
|
-
expect(mockOpenPopup).toHaveBeenCalledTimes(1);
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
it("embedRef.close() closes popup", () => {
|
|
209
|
-
const embedRef = createRef<PopupButtonHandle | null>();
|
|
210
|
-
|
|
211
|
-
render(
|
|
212
|
-
<PopupButton researchId="test-research-id" embedRef={embedRef}>
|
|
213
|
-
Open
|
|
214
|
-
</PopupButton>
|
|
215
|
-
);
|
|
216
|
-
|
|
217
|
-
// First open
|
|
218
|
-
embedRef.current?.open();
|
|
219
|
-
expect(mockOpenPopup).toHaveBeenCalledTimes(1);
|
|
220
|
-
|
|
221
|
-
// Then close
|
|
222
|
-
embedRef.current?.close();
|
|
223
|
-
expect(mockDestroy).toHaveBeenCalledTimes(1);
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
it("embedRef.toggle() toggles popup state", async () => {
|
|
227
|
-
const embedRef = createRef<PopupButtonHandle | null>();
|
|
228
|
-
const onOpenChange = vi.fn();
|
|
229
|
-
|
|
230
|
-
render(
|
|
231
|
-
<PopupButton
|
|
232
|
-
researchId="test-research-id"
|
|
233
|
-
embedRef={embedRef}
|
|
234
|
-
onOpenChange={onOpenChange}
|
|
235
|
-
>
|
|
236
|
-
Open
|
|
237
|
-
</PopupButton>
|
|
238
|
-
);
|
|
239
|
-
|
|
240
|
-
// Toggle on
|
|
241
|
-
embedRef.current?.toggle();
|
|
242
|
-
expect(mockOpenPopup).toHaveBeenCalledTimes(1);
|
|
243
|
-
|
|
244
|
-
// Toggle off
|
|
245
|
-
embedRef.current?.toggle();
|
|
246
|
-
expect(mockDestroy).toHaveBeenCalledTimes(1);
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
it("passes button props", () => {
|
|
250
|
-
render(
|
|
251
|
-
<PopupButton
|
|
252
|
-
researchId="test-research-id"
|
|
253
|
-
className="custom-button"
|
|
254
|
-
disabled
|
|
255
|
-
aria-label="Open interview popup"
|
|
256
|
-
>
|
|
257
|
-
Open
|
|
258
|
-
</PopupButton>
|
|
259
|
-
);
|
|
260
|
-
|
|
261
|
-
const button = screen.getByRole("button");
|
|
262
|
-
expect(button.classList.contains("custom-button")).toBe(true);
|
|
263
|
-
expect(button.hasAttribute("disabled")).toBe(true);
|
|
264
|
-
expect(button.getAttribute("aria-label")).toBe("Open interview popup");
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
it("has type button", () => {
|
|
268
|
-
render(<PopupButton researchId="test-research-id">Open</PopupButton>);
|
|
269
|
-
|
|
270
|
-
const button = screen.getByRole("button");
|
|
271
|
-
expect(button.getAttribute("type")).toBe("button");
|
|
272
|
-
});
|
|
273
|
-
});
|