gistda-sphere-react 1.0.0

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.
Files changed (41) hide show
  1. package/README.md +827 -0
  2. package/dist/index.d.mts +1081 -0
  3. package/dist/index.d.ts +1081 -0
  4. package/dist/index.js +2057 -0
  5. package/dist/index.mjs +2013 -0
  6. package/package.json +70 -0
  7. package/src/__tests__/Layer.test.tsx +133 -0
  8. package/src/__tests__/Marker.test.tsx +183 -0
  9. package/src/__tests__/SphereContext.test.tsx +120 -0
  10. package/src/__tests__/SphereMap.test.tsx +240 -0
  11. package/src/__tests__/geometry.test.tsx +454 -0
  12. package/src/__tests__/hooks.test.tsx +173 -0
  13. package/src/__tests__/setup.ts +204 -0
  14. package/src/__tests__/useMapControls.test.tsx +168 -0
  15. package/src/__tests__/useOverlays.test.tsx +265 -0
  16. package/src/__tests__/useRoute.test.tsx +219 -0
  17. package/src/__tests__/useSearch.test.tsx +205 -0
  18. package/src/__tests__/useTags.test.tsx +179 -0
  19. package/src/components/Circle.tsx +189 -0
  20. package/src/components/Dot.tsx +150 -0
  21. package/src/components/Layer.tsx +177 -0
  22. package/src/components/Marker.tsx +204 -0
  23. package/src/components/Polygon.tsx +223 -0
  24. package/src/components/Polyline.tsx +211 -0
  25. package/src/components/Popup.tsx +130 -0
  26. package/src/components/Rectangle.tsx +194 -0
  27. package/src/components/SphereMap.tsx +315 -0
  28. package/src/components/index.ts +18 -0
  29. package/src/context/MapContext.tsx +41 -0
  30. package/src/context/SphereContext.tsx +348 -0
  31. package/src/context/index.ts +15 -0
  32. package/src/hooks/index.ts +42 -0
  33. package/src/hooks/useMapEvent.ts +66 -0
  34. package/src/hooks/useOverlays.ts +278 -0
  35. package/src/hooks/useRoute.ts +232 -0
  36. package/src/hooks/useSearch.ts +143 -0
  37. package/src/hooks/useSphere.ts +18 -0
  38. package/src/hooks/useTags.ts +129 -0
  39. package/src/index.ts +124 -0
  40. package/src/types/index.ts +1 -0
  41. package/src/types/sphere.ts +671 -0
@@ -0,0 +1,265 @@
1
+ import { act, renderHook } from "@testing-library/react";
2
+ import { describe, expect, it } from "vitest";
3
+ import {
4
+ useCircles,
5
+ useMarkers,
6
+ useOverlays,
7
+ usePolygons,
8
+ usePolylines,
9
+ } from "../hooks/useOverlays";
10
+
11
+ describe("useOverlays hooks", () => {
12
+ describe("useMarkers", () => {
13
+ it("starts with empty items", () => {
14
+ const { result } = renderHook(() => useMarkers());
15
+ expect(result.current.items).toEqual([]);
16
+ });
17
+
18
+ it("adds a marker and returns its id", () => {
19
+ const { result } = renderHook(() => useMarkers());
20
+
21
+ let id = "";
22
+ act(() => {
23
+ id = result.current.add({
24
+ position: { lon: 100.5, lat: 13.75 },
25
+ title: "Test Marker",
26
+ });
27
+ });
28
+
29
+ expect(result.current.items).toHaveLength(1);
30
+
31
+ const marker = result.current.items[0];
32
+ expect(marker).toBeDefined();
33
+ expect(marker?.id).toBe(id);
34
+ expect(marker?.position).toEqual({
35
+ lon: 100.5,
36
+ lat: 13.75,
37
+ });
38
+ expect(marker?.title).toBe("Test Marker");
39
+ });
40
+
41
+ it("allows custom id", () => {
42
+ const { result } = renderHook(() => useMarkers());
43
+
44
+ act(() => {
45
+ result.current.add({
46
+ id: "custom-id",
47
+ position: { lon: 100.5, lat: 13.75 },
48
+ });
49
+ });
50
+
51
+ const marker = result.current.items[0];
52
+ expect(marker).toBeDefined();
53
+ expect(marker?.id).toBe("custom-id");
54
+ });
55
+
56
+ it("updates a marker", () => {
57
+ const { result } = renderHook(() => useMarkers());
58
+
59
+ let id = "";
60
+ act(() => {
61
+ id = result.current.add({
62
+ position: { lon: 100.5, lat: 13.75 },
63
+ title: "Original",
64
+ });
65
+ });
66
+
67
+ act(() => {
68
+ result.current.update(id, { title: "Updated" });
69
+ });
70
+
71
+ const marker = result.current.items[0];
72
+ expect(marker).toBeDefined();
73
+ expect(marker?.title).toBe("Updated");
74
+ expect(marker?.position).toEqual({
75
+ lon: 100.5,
76
+ lat: 13.75,
77
+ });
78
+ });
79
+
80
+ it("removes a marker", () => {
81
+ const { result } = renderHook(() => useMarkers());
82
+
83
+ let id = "";
84
+ act(() => {
85
+ id = result.current.add({
86
+ position: { lon: 100.5, lat: 13.75 },
87
+ });
88
+ });
89
+
90
+ expect(result.current.items).toHaveLength(1);
91
+
92
+ act(() => {
93
+ result.current.remove(id);
94
+ });
95
+
96
+ expect(result.current.items).toHaveLength(0);
97
+ });
98
+
99
+ it("clears all markers", () => {
100
+ const { result } = renderHook(() => useMarkers());
101
+
102
+ act(() => {
103
+ result.current.add({ position: { lon: 100.5, lat: 13.75 } });
104
+ result.current.add({ position: { lon: 101.5, lat: 14.75 } });
105
+ result.current.add({ position: { lon: 102.5, lat: 15.75 } });
106
+ });
107
+
108
+ expect(result.current.items).toHaveLength(3);
109
+
110
+ act(() => {
111
+ result.current.clear();
112
+ });
113
+
114
+ expect(result.current.items).toHaveLength(0);
115
+ });
116
+
117
+ it("gets a marker by id", () => {
118
+ const { result } = renderHook(() => useMarkers());
119
+
120
+ let id = "";
121
+ act(() => {
122
+ id = result.current.add({
123
+ position: { lon: 100.5, lat: 13.75 },
124
+ title: "Find Me",
125
+ });
126
+ });
127
+
128
+ const found = result.current.get(id);
129
+ expect(found?.title).toBe("Find Me");
130
+
131
+ const notFound = result.current.get("non-existent");
132
+ expect(notFound).toBeUndefined();
133
+ });
134
+ });
135
+
136
+ describe("usePolygons", () => {
137
+ it("adds and removes polygons", () => {
138
+ const { result } = renderHook(() => usePolygons());
139
+
140
+ let id = "";
141
+ act(() => {
142
+ id = result.current.add({
143
+ positions: [
144
+ { lon: 100.4, lat: 13.8 },
145
+ { lon: 100.6, lat: 13.8 },
146
+ { lon: 100.5, lat: 13.6 },
147
+ ],
148
+ fillColor: "rgba(255, 0, 0, 0.3)",
149
+ });
150
+ });
151
+
152
+ expect(result.current.items).toHaveLength(1);
153
+
154
+ const polygon = result.current.items[0];
155
+ expect(polygon).toBeDefined();
156
+ expect(polygon?.positions).toHaveLength(3);
157
+
158
+ act(() => {
159
+ result.current.remove(id);
160
+ });
161
+
162
+ expect(result.current.items).toHaveLength(0);
163
+ });
164
+ });
165
+
166
+ describe("usePolylines", () => {
167
+ it("adds and updates polylines", () => {
168
+ const { result } = renderHook(() => usePolylines());
169
+
170
+ let id = "";
171
+ act(() => {
172
+ id = result.current.add({
173
+ positions: [
174
+ { lon: 100.4, lat: 13.8 },
175
+ { lon: 100.6, lat: 13.6 },
176
+ ],
177
+ lineWidth: 2,
178
+ });
179
+ });
180
+
181
+ act(() => {
182
+ result.current.update(id, { lineWidth: 5 });
183
+ });
184
+
185
+ const polyline = result.current.items[0];
186
+ expect(polyline).toBeDefined();
187
+ expect(polyline?.lineWidth).toBe(5);
188
+ });
189
+ });
190
+
191
+ describe("useCircles", () => {
192
+ it("adds circles with center and radius", () => {
193
+ const { result } = renderHook(() => useCircles());
194
+
195
+ act(() => {
196
+ result.current.add({
197
+ center: { lon: 100.5, lat: 13.75 },
198
+ radius: 0.01,
199
+ fillColor: "rgba(0, 0, 255, 0.3)",
200
+ });
201
+ });
202
+
203
+ expect(result.current.items).toHaveLength(1);
204
+
205
+ const circle = result.current.items[0];
206
+ expect(circle).toBeDefined();
207
+ expect(circle?.center).toEqual({
208
+ lon: 100.5,
209
+ lat: 13.75,
210
+ });
211
+ expect(circle?.radius).toBe(0.01);
212
+ });
213
+ });
214
+
215
+ describe("useOverlays", () => {
216
+ it("provides all overlay types", () => {
217
+ const { result } = renderHook(() => useOverlays());
218
+
219
+ expect(result.current.markers).toBeDefined();
220
+ expect(result.current.polygons).toBeDefined();
221
+ expect(result.current.polylines).toBeDefined();
222
+ expect(result.current.circles).toBeDefined();
223
+ expect(result.current.clearAll).toBeDefined();
224
+ });
225
+
226
+ it("clearAll removes all overlays", () => {
227
+ const { result } = renderHook(() => useOverlays());
228
+
229
+ act(() => {
230
+ result.current.markers.add({ position: { lon: 100, lat: 13 } });
231
+ result.current.polygons.add({
232
+ positions: [
233
+ { lon: 100, lat: 13 },
234
+ { lon: 101, lat: 14 },
235
+ { lon: 100, lat: 14 },
236
+ ],
237
+ });
238
+ result.current.polylines.add({
239
+ positions: [
240
+ { lon: 100, lat: 13 },
241
+ { lon: 101, lat: 14 },
242
+ ],
243
+ });
244
+ result.current.circles.add({
245
+ center: { lon: 100, lat: 13 },
246
+ radius: 0.01,
247
+ });
248
+ });
249
+
250
+ expect(result.current.markers.items).toHaveLength(1);
251
+ expect(result.current.polygons.items).toHaveLength(1);
252
+ expect(result.current.polylines.items).toHaveLength(1);
253
+ expect(result.current.circles.items).toHaveLength(1);
254
+
255
+ act(() => {
256
+ result.current.clearAll();
257
+ });
258
+
259
+ expect(result.current.markers.items).toHaveLength(0);
260
+ expect(result.current.polygons.items).toHaveLength(0);
261
+ expect(result.current.polylines.items).toHaveLength(0);
262
+ expect(result.current.circles.items).toHaveLength(0);
263
+ });
264
+ });
265
+ });
@@ -0,0 +1,219 @@
1
+ import { renderHook } from "@testing-library/react";
2
+ import type { ReactNode } from "react";
3
+ import { useEffect } from "react";
4
+ import { beforeEach, describe, expect, it, vi } from "vitest";
5
+ import { SphereProvider, useSphereContext } from "../context/SphereContext";
6
+ import { useRoute } from "../hooks/useRoute";
7
+ import { createMockSphereApi } from "./setup";
8
+
9
+ describe("useRoute", () => {
10
+ const { mockSphere, mockMap } = createMockSphereApi();
11
+ const map = mockMap as any;
12
+
13
+ beforeEach(() => {
14
+ // @ts-expect-error - mockSphere is a partial mock
15
+ window.sphere = mockSphere;
16
+ vi.clearAllMocks();
17
+
18
+ map.Route = {
19
+ add: vi.fn(),
20
+ insert: vi.fn(),
21
+ remove: vi.fn(),
22
+ removeAt: vi.fn(),
23
+ clearDestination: vi.fn(),
24
+ clearPath: vi.fn(),
25
+ clear: vi.fn(),
26
+ reverse: vi.fn(),
27
+ search: vi.fn(),
28
+ distance: vi.fn().mockReturnValue(450),
29
+ interval: vi.fn().mockReturnValue(19_800),
30
+ guide: vi.fn().mockReturnValue([]),
31
+ exportRouteLine: vi.fn().mockReturnValue(null),
32
+ list: vi.fn().mockReturnValue([]),
33
+ size: vi.fn().mockReturnValue(0),
34
+ mode: vi.fn(),
35
+ modeOf: vi.fn(),
36
+ enableRoute: vi.fn(),
37
+ label: vi.fn(),
38
+ auto: vi.fn(),
39
+ language: vi.fn(),
40
+ };
41
+ });
42
+
43
+ function MapRegistrar({ mapInstance }: { mapInstance: any }) {
44
+ const { registerMap } = useSphereContext();
45
+ useEffect(() => {
46
+ registerMap(mapInstance);
47
+ }, [mapInstance, registerMap]);
48
+ return null;
49
+ }
50
+
51
+ function createWrapper() {
52
+ return function Wrapper({ children }: { children: ReactNode }) {
53
+ return (
54
+ <SphereProvider apiKey="test-key">
55
+ <MapRegistrar mapInstance={map} />
56
+ {children}
57
+ </SphereProvider>
58
+ );
59
+ };
60
+ }
61
+
62
+ describe("destination management", () => {
63
+ it("delegates addDestination to Route API", () => {
64
+ const { result } = renderHook(() => useRoute(), {
65
+ wrapper: createWrapper(),
66
+ });
67
+
68
+ const location = { lon: 100.5, lat: 13.75 };
69
+ result.current.addDestination(location);
70
+
71
+ expect(map.Route.add).toHaveBeenCalledWith(location, undefined);
72
+ });
73
+
74
+ it("delegates insertDestination to Route API", () => {
75
+ const { result } = renderHook(() => useRoute(), {
76
+ wrapper: createWrapper(),
77
+ });
78
+
79
+ const location = { lon: 100.5, lat: 13.75 };
80
+ result.current.insertDestination(1, location, "Traffic");
81
+
82
+ expect(map.Route.insert).toHaveBeenCalledWith(1, location, "Traffic");
83
+ });
84
+
85
+ it("delegates removeDestinationAt to Route API", () => {
86
+ const { result } = renderHook(() => useRoute(), {
87
+ wrapper: createWrapper(),
88
+ });
89
+
90
+ result.current.removeDestinationAt(0);
91
+
92
+ expect(map.Route.removeAt).toHaveBeenCalledWith(0);
93
+ });
94
+
95
+ it("delegates clearDestinations to Route API", () => {
96
+ const { result } = renderHook(() => useRoute(), {
97
+ wrapper: createWrapper(),
98
+ });
99
+
100
+ result.current.clearDestinations();
101
+
102
+ expect(map.Route.clearDestination).toHaveBeenCalled();
103
+ });
104
+ });
105
+
106
+ describe("route operations", () => {
107
+ it("delegates search to Route API", () => {
108
+ const { result } = renderHook(() => useRoute(), {
109
+ wrapper: createWrapper(),
110
+ });
111
+
112
+ result.current.search();
113
+
114
+ expect(map.Route.search).toHaveBeenCalled();
115
+ });
116
+
117
+ it("delegates clear to Route API", () => {
118
+ const { result } = renderHook(() => useRoute(), {
119
+ wrapper: createWrapper(),
120
+ });
121
+
122
+ result.current.clear();
123
+
124
+ expect(map.Route.clear).toHaveBeenCalled();
125
+ });
126
+
127
+ it("delegates reverse to Route API", () => {
128
+ const { result } = renderHook(() => useRoute(), {
129
+ wrapper: createWrapper(),
130
+ });
131
+
132
+ result.current.reverse();
133
+
134
+ expect(map.Route.reverse).toHaveBeenCalled();
135
+ });
136
+ });
137
+
138
+ describe("route info", () => {
139
+ it("returns distance from Route API", () => {
140
+ const { result } = renderHook(() => useRoute(), {
141
+ wrapper: createWrapper(),
142
+ });
143
+
144
+ expect(result.current.getDistance()).toBe(450);
145
+ expect(map.Route.distance).toHaveBeenCalledWith(false);
146
+ });
147
+
148
+ it("returns formatted distance from Route API", () => {
149
+ map.Route.distance.mockReturnValue("450 km");
150
+
151
+ const { result } = renderHook(() => useRoute(), {
152
+ wrapper: createWrapper(),
153
+ });
154
+
155
+ expect(result.current.getDistance(true)).toBe("450 km");
156
+ expect(map.Route.distance).toHaveBeenCalledWith(true);
157
+ });
158
+
159
+ it("returns interval from Route API", () => {
160
+ const { result } = renderHook(() => useRoute(), {
161
+ wrapper: createWrapper(),
162
+ });
163
+
164
+ expect(result.current.getInterval()).toBe(19_800);
165
+ expect(map.Route.interval).toHaveBeenCalledWith(false);
166
+ });
167
+
168
+ it("returns guide from Route API", () => {
169
+ const { result } = renderHook(() => useRoute(), {
170
+ wrapper: createWrapper(),
171
+ });
172
+
173
+ expect(result.current.getGuide()).toEqual([]);
174
+ expect(map.Route.guide).toHaveBeenCalledWith(false);
175
+ });
176
+ });
177
+
178
+ describe("route settings", () => {
179
+ it("delegates setMode to Route API", () => {
180
+ const { result } = renderHook(() => useRoute(), {
181
+ wrapper: createWrapper(),
182
+ });
183
+
184
+ result.current.setMode("Traffic");
185
+
186
+ expect(map.Route.mode).toHaveBeenCalledWith("Traffic");
187
+ });
188
+
189
+ it("delegates setLabel to Route API", () => {
190
+ const { result } = renderHook(() => useRoute(), {
191
+ wrapper: createWrapper(),
192
+ });
193
+
194
+ result.current.setLabel("Distance");
195
+
196
+ expect(map.Route.label).toHaveBeenCalledWith("Distance");
197
+ });
198
+
199
+ it("delegates setLanguage to Route API", () => {
200
+ const { result } = renderHook(() => useRoute(), {
201
+ wrapper: createWrapper(),
202
+ });
203
+
204
+ result.current.setLanguage("en");
205
+
206
+ expect(map.Route.language).toHaveBeenCalledWith("en");
207
+ });
208
+
209
+ it("delegates setAuto to Route API", () => {
210
+ const { result } = renderHook(() => useRoute(), {
211
+ wrapper: createWrapper(),
212
+ });
213
+
214
+ result.current.setAuto(true);
215
+
216
+ expect(map.Route.auto).toHaveBeenCalledWith(true);
217
+ });
218
+ });
219
+ });
@@ -0,0 +1,205 @@
1
+ import { renderHook } from "@testing-library/react";
2
+ import type { ReactNode } from "react";
3
+ import { useEffect } from "react";
4
+ import { beforeEach, describe, expect, it, vi } from "vitest";
5
+ import { SphereProvider, useSphereContext } from "../context/SphereContext";
6
+ import { useSearch } from "../hooks/useSearch";
7
+ import { createMockSphereApi } from "./setup";
8
+
9
+ describe("useSearch", () => {
10
+ const { mockSphere, mockMap } = createMockSphereApi();
11
+ const map = mockMap as any;
12
+
13
+ beforeEach(() => {
14
+ // @ts-expect-error - mockSphere is a partial mock
15
+ window.sphere = mockSphere;
16
+ vi.clearAllMocks();
17
+ });
18
+
19
+ /**
20
+ * Registers the mock map in SphereContext so useMap() returns it.
21
+ * useSearch reads from SphereContext.useMap, not MapContext.
22
+ */
23
+ function MapRegistrar({ map: mapInstance }: { map: any }) {
24
+ const { registerMap } = useSphereContext();
25
+ useEffect(() => {
26
+ if (mapInstance) {
27
+ registerMap(mapInstance);
28
+ }
29
+ }, [mapInstance, registerMap]);
30
+ return null;
31
+ }
32
+
33
+ function createWrapper(mapReady = true) {
34
+ return function Wrapper({ children }: { children: ReactNode }) {
35
+ return (
36
+ <SphereProvider apiKey="test-key">
37
+ {mapReady && <MapRegistrar map={map} />}
38
+ {children}
39
+ </SphereProvider>
40
+ );
41
+ };
42
+ }
43
+
44
+ describe("when Search API is not available", () => {
45
+ it("throws when calling suggest without map", async () => {
46
+ const { result } = renderHook(() => useSearch(), {
47
+ wrapper: createWrapper(false),
48
+ });
49
+
50
+ await expect(result.current.suggest("coffee")).rejects.toThrow(
51
+ "Search API not available"
52
+ );
53
+ });
54
+
55
+ it("throws when calling search without map", async () => {
56
+ const { result } = renderHook(() => useSearch(), {
57
+ wrapper: createWrapper(false),
58
+ });
59
+
60
+ await expect(result.current.search("coffee")).rejects.toThrow(
61
+ "Search API not available"
62
+ );
63
+ });
64
+
65
+ it("throws when calling address without map", async () => {
66
+ const { result } = renderHook(() => useSearch(), {
67
+ wrapper: createWrapper(false),
68
+ });
69
+
70
+ await expect(
71
+ result.current.address({ lon: 100.5, lat: 13.75 })
72
+ ).rejects.toThrow("Search API not available");
73
+ });
74
+
75
+ it("throws when calling nearPoi without map", async () => {
76
+ const { result } = renderHook(() => useSearch(), {
77
+ wrapper: createWrapper(false),
78
+ });
79
+
80
+ await expect(
81
+ result.current.nearPoi({ lon: 100.5, lat: 13.75 })
82
+ ).rejects.toThrow("Search API not available");
83
+ });
84
+ });
85
+
86
+ describe("when Search API is available", () => {
87
+ beforeEach(() => {
88
+ map.Search = {
89
+ suggest: vi
90
+ .fn()
91
+ .mockResolvedValue({ data: [{ name: "Coffee A" }], total: 1 }),
92
+ search: vi
93
+ .fn()
94
+ .mockResolvedValue({ data: [{ name: "Coffee B" }], total: 1 }),
95
+ address: vi.fn().mockResolvedValue({
96
+ address: "123 Street",
97
+ province: "Bangkok",
98
+ }),
99
+ nearPoi: vi.fn().mockResolvedValue({
100
+ data: [
101
+ { id: "1", name: "Coffee C", location: { lon: 100.5, lat: 13.75 } },
102
+ ],
103
+ }),
104
+ clear: vi.fn(),
105
+ enablePopup: vi.fn(),
106
+ language: vi.fn(),
107
+ };
108
+ });
109
+
110
+ it("calls suggest with keyword and options", async () => {
111
+ const { result } = renderHook(() => useSearch(), {
112
+ wrapper: createWrapper(),
113
+ });
114
+
115
+ const searchResult = await result.current.suggest("coffee", { limit: 5 });
116
+
117
+ expect(map.Search.suggest).toHaveBeenCalledWith("coffee", {
118
+ limit: 5,
119
+ });
120
+ expect(searchResult).toEqual({ data: [{ name: "Coffee A" }], total: 1 });
121
+ });
122
+
123
+ it("calls search with keyword and options", async () => {
124
+ const { result } = renderHook(() => useSearch(), {
125
+ wrapper: createWrapper(),
126
+ });
127
+
128
+ const searchResult = await result.current.search("coffee", { limit: 10 });
129
+
130
+ expect(map.Search.search).toHaveBeenCalledWith("coffee", {
131
+ limit: 10,
132
+ });
133
+ expect(searchResult).toEqual({ data: [{ name: "Coffee B" }], total: 1 });
134
+ });
135
+
136
+ it("calls address with location", async () => {
137
+ const { result } = renderHook(() => useSearch(), {
138
+ wrapper: createWrapper(),
139
+ });
140
+
141
+ const addressResult = await result.current.address({
142
+ lon: 100.5,
143
+ lat: 13.75,
144
+ });
145
+
146
+ expect(map.Search.address).toHaveBeenCalledWith(
147
+ { lon: 100.5, lat: 13.75 },
148
+ undefined
149
+ );
150
+ expect(addressResult).toEqual({
151
+ address: "123 Street",
152
+ province: "Bangkok",
153
+ });
154
+ });
155
+
156
+ it("calls nearPoi with location and options", async () => {
157
+ const { result } = renderHook(() => useSearch(), {
158
+ wrapper: createWrapper(),
159
+ });
160
+
161
+ const pois = await result.current.nearPoi(
162
+ { lon: 100.5, lat: 13.75 },
163
+ { limit: 5 }
164
+ );
165
+
166
+ expect(map.Search.nearPoi).toHaveBeenCalledWith(
167
+ { lon: 100.5, lat: 13.75 },
168
+ { limit: 5 }
169
+ );
170
+ expect(pois).toEqual([
171
+ { id: "1", name: "Coffee C", location: { lon: 100.5, lat: 13.75 } },
172
+ ]);
173
+ });
174
+
175
+ it("delegates clear to Search API", () => {
176
+ const { result } = renderHook(() => useSearch(), {
177
+ wrapper: createWrapper(),
178
+ });
179
+
180
+ result.current.clear();
181
+
182
+ expect(map.Search.clear).toHaveBeenCalled();
183
+ });
184
+
185
+ it("delegates enablePopup to Search API", () => {
186
+ const { result } = renderHook(() => useSearch(), {
187
+ wrapper: createWrapper(),
188
+ });
189
+
190
+ result.current.enablePopup(true);
191
+
192
+ expect(map.Search.enablePopup).toHaveBeenCalledWith(true);
193
+ });
194
+
195
+ it("delegates setLanguage to Search API", () => {
196
+ const { result } = renderHook(() => useSearch(), {
197
+ wrapper: createWrapper(),
198
+ });
199
+
200
+ result.current.setLanguage("en");
201
+
202
+ expect(map.Search.language).toHaveBeenCalledWith("en");
203
+ });
204
+ });
205
+ });