@excom/provider-geolocation 0.1.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.
- package/.rush/temp/chunked-rush-logs/provider-geolocation.apply-exports.chunks.jsonl +1 -0
- package/.rush/temp/chunked-rush-logs/provider-geolocation.build_docs.chunks.jsonl +1 -0
- package/.rush/temp/chunked-rush-logs/provider-geolocation.build_package-metas.chunks.jsonl +1 -0
- package/.rush/temp/operation/apply-exports/all.log +1 -0
- package/.rush/temp/operation/apply-exports/log-chunks.jsonl +1 -0
- package/.rush/temp/operation/apply-exports/state.json +3 -0
- package/.rush/temp/operation/build_docs/all.log +1 -0
- package/.rush/temp/operation/build_docs/log-chunks.jsonl +1 -0
- package/.rush/temp/operation/build_docs/state.json +3 -0
- package/.rush/temp/operation/build_package-metas/all.log +1 -0
- package/.rush/temp/operation/build_package-metas/log-chunks.jsonl +1 -0
- package/.rush/temp/operation/build_package-metas/state.json +3 -0
- package/.rush/temp/shrinkwrap-deps.json +3 -0
- package/config/rig.json +5 -0
- package/index.ts +18 -0
- package/package.json +42 -0
- package/provider-geolocation.ts +222 -0
- package/rush-logs/provider-geolocation.apply-exports.cache.log +1 -0
- package/rush-logs/provider-geolocation.apply-exports.log +1 -0
- package/rush-logs/provider-geolocation.build_docs.cache.log +1 -0
- package/rush-logs/provider-geolocation.build_docs.log +1 -0
- package/rush-logs/provider-geolocation.build_package-metas.cache.log +1 -0
- package/rush-logs/provider-geolocation.build_package-metas.log +1 -0
- package/support/custom-elements.json +271 -0
- package/support/demos/request.html +12 -0
- package/support/dist-docs/provider-geolocation.md +108 -0
- package/support/docs/README.md +34 -0
- package/support/package-meta.json +148 -0
- package/support/tests/provider-geolocation.test.ts +563 -0
- package/support/tests/request.view.test.ts +38 -0
- package/tsconfig.json +5 -0
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
import { invokeCommand } from "@excom/neutron";
|
|
2
|
+
import {
|
|
3
|
+
afterEach,
|
|
4
|
+
beforeEach,
|
|
5
|
+
describe,
|
|
6
|
+
expect,
|
|
7
|
+
fixture,
|
|
8
|
+
it,
|
|
9
|
+
vi,
|
|
10
|
+
wait,
|
|
11
|
+
waitForEvent,
|
|
12
|
+
} from "@excom/heft-rig/profiles/default/config/test-utils";
|
|
13
|
+
import "../../index";
|
|
14
|
+
|
|
15
|
+
const mockPosition = {
|
|
16
|
+
coords: {
|
|
17
|
+
latitude: 37.7749,
|
|
18
|
+
longitude: -122.4194,
|
|
19
|
+
accuracy: 100,
|
|
20
|
+
altitude: 50,
|
|
21
|
+
altitudeAccuracy: 10,
|
|
22
|
+
heading: 90,
|
|
23
|
+
speed: 5,
|
|
24
|
+
},
|
|
25
|
+
timestamp: 1700000000000,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const expectedData = {
|
|
29
|
+
coords: {
|
|
30
|
+
latitude: 37.7749,
|
|
31
|
+
longitude: -122.4194,
|
|
32
|
+
accuracy: 100,
|
|
33
|
+
altitude: 50,
|
|
34
|
+
altitudeAccuracy: 10,
|
|
35
|
+
heading: 90,
|
|
36
|
+
speed: 5,
|
|
37
|
+
timestamp: 1700000000000,
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const mockGeoError = {
|
|
42
|
+
code: 1,
|
|
43
|
+
message: "User denied Geolocation",
|
|
44
|
+
PERMISSION_DENIED: 1,
|
|
45
|
+
POSITION_UNAVAILABLE: 2,
|
|
46
|
+
TIMEOUT: 3,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
describe("provider-geolocation", () => {
|
|
50
|
+
let geo: {
|
|
51
|
+
getCurrentPosition: ReturnType<typeof vi.fn>;
|
|
52
|
+
watchPosition: ReturnType<typeof vi.fn>;
|
|
53
|
+
clearWatch: ReturnType<typeof vi.fn>;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
beforeEach(() => {
|
|
57
|
+
geo = {
|
|
58
|
+
getCurrentPosition: vi.fn((success) => {
|
|
59
|
+
setTimeout(() => success(mockPosition), 0);
|
|
60
|
+
}),
|
|
61
|
+
watchPosition: vi.fn((success) => {
|
|
62
|
+
setTimeout(() => success(mockPosition), 0);
|
|
63
|
+
return 42;
|
|
64
|
+
}),
|
|
65
|
+
clearWatch: vi.fn(),
|
|
66
|
+
};
|
|
67
|
+
Object.defineProperty(navigator, "geolocation", {
|
|
68
|
+
value: geo,
|
|
69
|
+
writable: true,
|
|
70
|
+
configurable: true,
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
afterEach(() => {
|
|
75
|
+
document.body.innerHTML = "";
|
|
76
|
+
vi.restoreAllMocks();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("does not request when paused", async () => {
|
|
80
|
+
const el = fixture<HTMLProviderGeolocationElement>(
|
|
81
|
+
`<provider-geolocation is-paused></provider-geolocation>`,
|
|
82
|
+
);
|
|
83
|
+
expect(el).dom.to.equalTag(
|
|
84
|
+
`<provider-geolocation is-paused></provider-geolocation>`,
|
|
85
|
+
);
|
|
86
|
+
expect(el.provision).toBe(null);
|
|
87
|
+
expect(geo.getCurrentPosition).not.toHaveBeenCalled();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("gets coordinates on connect", async () => {
|
|
91
|
+
const el = fixture<HTMLProviderGeolocationElement>(
|
|
92
|
+
`<provider-geolocation></provider-geolocation>`,
|
|
93
|
+
);
|
|
94
|
+
expect(el.isRequesting).toBe(true);
|
|
95
|
+
expect(geo.getCurrentPosition).toHaveBeenCalledWith(
|
|
96
|
+
expect.any(Function),
|
|
97
|
+
expect.any(Function),
|
|
98
|
+
{ maximumAge: 60000, timeout: Infinity, enableHighAccuracy: false },
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
await waitForEvent(el, "provider-geolocation-success");
|
|
102
|
+
|
|
103
|
+
expect(el).dom.to.equalTag(
|
|
104
|
+
`<provider-geolocation is-success></provider-geolocation>`,
|
|
105
|
+
);
|
|
106
|
+
expect(el.provision).toEqual(expectedData);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("requests when isPaused is unset", async () => {
|
|
110
|
+
const el = fixture<HTMLProviderGeolocationElement>(
|
|
111
|
+
`<provider-geolocation is-paused></provider-geolocation>`,
|
|
112
|
+
);
|
|
113
|
+
expect(geo.getCurrentPosition).not.toHaveBeenCalled();
|
|
114
|
+
|
|
115
|
+
el.isPaused = false;
|
|
116
|
+
expect(el.isRequesting).toBe(true);
|
|
117
|
+
expect(geo.getCurrentPosition).toHaveBeenCalled();
|
|
118
|
+
|
|
119
|
+
await waitForEvent(el, "provider-geolocation-success");
|
|
120
|
+
expect(el.isSuccess).toBe(true);
|
|
121
|
+
expect(el.provision).toEqual(expectedData);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("handles geolocation error", async () => {
|
|
125
|
+
geo.getCurrentPosition.mockImplementation((_success, error) => {
|
|
126
|
+
setTimeout(() => error(mockGeoError), 0);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const el = fixture<HTMLProviderGeolocationElement>(
|
|
130
|
+
`<provider-geolocation></provider-geolocation>`,
|
|
131
|
+
);
|
|
132
|
+
expect(el.isRequesting).toBe(true);
|
|
133
|
+
|
|
134
|
+
await waitForEvent(el, "provider-geolocation-error");
|
|
135
|
+
|
|
136
|
+
expect(el).dom.to.equalTag(
|
|
137
|
+
`<provider-geolocation is-error></provider-geolocation>`,
|
|
138
|
+
);
|
|
139
|
+
expect(el.provision).toEqual(mockGeoError);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("uses watchPosition when configured", async () => {
|
|
143
|
+
const el = fixture<HTMLProviderGeolocationElement>(
|
|
144
|
+
`<provider-geolocation watch-position></provider-geolocation>`,
|
|
145
|
+
);
|
|
146
|
+
expect(geo.watchPosition).toHaveBeenCalled();
|
|
147
|
+
expect(geo.getCurrentPosition).not.toHaveBeenCalled();
|
|
148
|
+
|
|
149
|
+
await waitForEvent(el, "provider-geolocation-success");
|
|
150
|
+
expect(el.isSuccess).toBe(true);
|
|
151
|
+
expect(el.provision).toEqual(expectedData);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("sets maximumAge to 0 when using watchPosition", async () => {
|
|
155
|
+
fixture<HTMLProviderGeolocationElement>(
|
|
156
|
+
`<provider-geolocation watch-position></provider-geolocation>`,
|
|
157
|
+
);
|
|
158
|
+
expect(geo.watchPosition).toHaveBeenCalledWith(
|
|
159
|
+
expect.any(Function),
|
|
160
|
+
expect.any(Function),
|
|
161
|
+
expect.objectContaining({ maximumAge: 0 }),
|
|
162
|
+
);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("clears watch on disconnect", async () => {
|
|
166
|
+
const el = fixture<HTMLProviderGeolocationElement>(
|
|
167
|
+
`<provider-geolocation watch-position></provider-geolocation>`,
|
|
168
|
+
);
|
|
169
|
+
await waitForEvent(el, "provider-geolocation-success");
|
|
170
|
+
|
|
171
|
+
el.remove();
|
|
172
|
+
await wait(0);
|
|
173
|
+
expect(geo.clearWatch).toHaveBeenCalledWith(42);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("keeps the watch when moved between parents", async () => {
|
|
177
|
+
let nextWatchId = 42;
|
|
178
|
+
geo.watchPosition.mockImplementation((success) => {
|
|
179
|
+
setTimeout(() => success(mockPosition), 0);
|
|
180
|
+
return nextWatchId++;
|
|
181
|
+
});
|
|
182
|
+
const el = fixture<HTMLProviderGeolocationElement>(
|
|
183
|
+
`<provider-geolocation watch-position></provider-geolocation>`,
|
|
184
|
+
);
|
|
185
|
+
await waitForEvent(el, "provider-geolocation-success");
|
|
186
|
+
expect(geo.watchPosition).toHaveBeenCalledTimes(1);
|
|
187
|
+
|
|
188
|
+
const other = document.createElement("div");
|
|
189
|
+
document.body.append(other);
|
|
190
|
+
other.append(el);
|
|
191
|
+
await wait(0);
|
|
192
|
+
|
|
193
|
+
// a synchronous re-parent is a move: the watch is neither cleared nor
|
|
194
|
+
// re-requested (a re-request would re-prompt for permission)
|
|
195
|
+
expect(geo.clearWatch).not.toHaveBeenCalled();
|
|
196
|
+
expect(geo.watchPosition).toHaveBeenCalledTimes(1);
|
|
197
|
+
expect(el.existingWatchId).toBe(42);
|
|
198
|
+
expect(el.isSuccess).toBe(true);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it("restarts the watch after a genuine disconnect and reconnect", async () => {
|
|
202
|
+
let nextWatchId = 42;
|
|
203
|
+
geo.watchPosition.mockImplementation((success) => {
|
|
204
|
+
setTimeout(() => success(mockPosition), 0);
|
|
205
|
+
return nextWatchId++;
|
|
206
|
+
});
|
|
207
|
+
const el = fixture<HTMLProviderGeolocationElement>(
|
|
208
|
+
`<provider-geolocation watch-position></provider-geolocation>`,
|
|
209
|
+
);
|
|
210
|
+
await waitForEvent(el, "provider-geolocation-success");
|
|
211
|
+
const parent = el.parentElement!;
|
|
212
|
+
|
|
213
|
+
el.remove();
|
|
214
|
+
await wait(0);
|
|
215
|
+
expect(geo.clearWatch).toHaveBeenCalledWith(42);
|
|
216
|
+
|
|
217
|
+
await waitForEvent(el, "provider-geolocation-success", () => {
|
|
218
|
+
parent.append(el);
|
|
219
|
+
});
|
|
220
|
+
expect(geo.watchPosition).toHaveBeenCalledTimes(2);
|
|
221
|
+
expect(el.existingWatchId).toBe(43);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("drops a position that resolves after the element was removed", async () => {
|
|
225
|
+
let resolvePosition: (position: typeof mockPosition) => void;
|
|
226
|
+
geo.getCurrentPosition.mockImplementation((success) => {
|
|
227
|
+
resolvePosition = success;
|
|
228
|
+
});
|
|
229
|
+
const el = fixture<HTMLProviderGeolocationElement>(
|
|
230
|
+
`<provider-geolocation></provider-geolocation>`,
|
|
231
|
+
);
|
|
232
|
+
expect(el.isRequesting).toBe(true);
|
|
233
|
+
const spy = vi.fn();
|
|
234
|
+
el.addEventListener("provider-geolocation-success", spy);
|
|
235
|
+
|
|
236
|
+
el.remove();
|
|
237
|
+
await wait(0);
|
|
238
|
+
resolvePosition!(mockPosition);
|
|
239
|
+
await wait(0);
|
|
240
|
+
|
|
241
|
+
expect(spy).not.toHaveBeenCalled();
|
|
242
|
+
expect(el.isSuccess).toBeFalsy();
|
|
243
|
+
expect(el.provision).toBeFalsy();
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it("drops an error that resolves after the element was removed", async () => {
|
|
247
|
+
let rejectPosition: (error: typeof mockGeoError) => void;
|
|
248
|
+
geo.getCurrentPosition.mockImplementation((_success, error) => {
|
|
249
|
+
rejectPosition = error;
|
|
250
|
+
});
|
|
251
|
+
const el = fixture<HTMLProviderGeolocationElement>(
|
|
252
|
+
`<provider-geolocation></provider-geolocation>`,
|
|
253
|
+
);
|
|
254
|
+
const spy = vi.fn();
|
|
255
|
+
el.addEventListener("provider-geolocation-error", spy);
|
|
256
|
+
|
|
257
|
+
el.remove();
|
|
258
|
+
await wait(0);
|
|
259
|
+
rejectPosition!(mockGeoError);
|
|
260
|
+
await wait(0);
|
|
261
|
+
|
|
262
|
+
expect(spy).not.toHaveBeenCalled();
|
|
263
|
+
expect(el.isError).toBeFalsy();
|
|
264
|
+
expect(el.provision).toBeFalsy();
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it("re-requests on the --request command", async () => {
|
|
268
|
+
const el = fixture<HTMLProviderGeolocationElement>(
|
|
269
|
+
`<provider-geolocation></provider-geolocation>`,
|
|
270
|
+
);
|
|
271
|
+
await waitForEvent(el, "provider-geolocation-success");
|
|
272
|
+
expect(geo.getCurrentPosition).toHaveBeenCalledTimes(1);
|
|
273
|
+
|
|
274
|
+
invokeCommand(el, "--request");
|
|
275
|
+
await wait(0);
|
|
276
|
+
expect(geo.getCurrentPosition).toHaveBeenCalledTimes(2);
|
|
277
|
+
|
|
278
|
+
await waitForEvent(el, "provider-geolocation-success");
|
|
279
|
+
expect(el.isSuccess).toBe(true);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it("passes custom options to the geolocation API", async () => {
|
|
283
|
+
fixture<HTMLProviderGeolocationElement>(
|
|
284
|
+
`<provider-geolocation high-accuracy geo-timeout="5000" maximum-age="30000"></provider-geolocation>`,
|
|
285
|
+
);
|
|
286
|
+
expect(geo.getCurrentPosition).toHaveBeenCalledWith(
|
|
287
|
+
expect.any(Function),
|
|
288
|
+
expect.any(Function),
|
|
289
|
+
{ maximumAge: 30000, timeout: 5000, enableHighAccuracy: true },
|
|
290
|
+
);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it("defaults enableHighAccuracy to false when high-accuracy is unset", async () => {
|
|
294
|
+
const el = fixture<HTMLProviderGeolocationElement>(
|
|
295
|
+
`<provider-geolocation is-paused></provider-geolocation>`,
|
|
296
|
+
);
|
|
297
|
+
// a nullish prop value still resolves to the API default
|
|
298
|
+
(el as any).highAccuracy = null;
|
|
299
|
+
invokeCommand(el, "--request");
|
|
300
|
+
await wait(0);
|
|
301
|
+
|
|
302
|
+
expect(geo.getCurrentPosition).toHaveBeenCalledWith(
|
|
303
|
+
expect.any(Function),
|
|
304
|
+
expect.any(Function),
|
|
305
|
+
expect.objectContaining({ enableHighAccuracy: false }),
|
|
306
|
+
);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
describe("success payload", () => {
|
|
310
|
+
it("normalizes null optional coordinate fields to undefined", async () => {
|
|
311
|
+
geo.getCurrentPosition.mockImplementation((success) => {
|
|
312
|
+
setTimeout(
|
|
313
|
+
() =>
|
|
314
|
+
success({
|
|
315
|
+
coords: {
|
|
316
|
+
latitude: 1,
|
|
317
|
+
longitude: 2,
|
|
318
|
+
accuracy: null,
|
|
319
|
+
altitude: null,
|
|
320
|
+
altitudeAccuracy: null,
|
|
321
|
+
heading: null,
|
|
322
|
+
speed: null,
|
|
323
|
+
},
|
|
324
|
+
timestamp: 5,
|
|
325
|
+
}),
|
|
326
|
+
0,
|
|
327
|
+
);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
const el = fixture<HTMLProviderGeolocationElement>(
|
|
331
|
+
`<provider-geolocation></provider-geolocation>`,
|
|
332
|
+
);
|
|
333
|
+
await waitForEvent(el, "provider-geolocation-success");
|
|
334
|
+
|
|
335
|
+
const { coords } = el.provision as any;
|
|
336
|
+
expect(coords).toEqual({ latitude: 1, longitude: 2, timestamp: 5 });
|
|
337
|
+
for (const key of [
|
|
338
|
+
"accuracy",
|
|
339
|
+
"altitude",
|
|
340
|
+
"altitudeAccuracy",
|
|
341
|
+
"heading",
|
|
342
|
+
"speed",
|
|
343
|
+
]) {
|
|
344
|
+
expect(key in coords).toBe(true);
|
|
345
|
+
expect(coords[key]).toBeUndefined();
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it("exposes the success detail as the event detail", async () => {
|
|
350
|
+
const el = fixture<HTMLProviderGeolocationElement>(
|
|
351
|
+
`<provider-geolocation></provider-geolocation>`,
|
|
352
|
+
);
|
|
353
|
+
const spy = vi.fn();
|
|
354
|
+
el.addEventListener("provider-geolocation-success", spy);
|
|
355
|
+
await waitForEvent(el, "provider-geolocation-success");
|
|
356
|
+
|
|
357
|
+
expect(spy.mock.calls[0][0].detail).toEqual(expectedData);
|
|
358
|
+
expect(el.provision).toBe(spy.mock.calls[0][0].detail);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it("clears is-requesting and is-error on success", async () => {
|
|
362
|
+
geo.getCurrentPosition.mockImplementationOnce((_success, error) => {
|
|
363
|
+
setTimeout(() => error(mockGeoError), 0);
|
|
364
|
+
});
|
|
365
|
+
const el = fixture<HTMLProviderGeolocationElement>(
|
|
366
|
+
`<provider-geolocation></provider-geolocation>`,
|
|
367
|
+
);
|
|
368
|
+
await waitForEvent(el, "provider-geolocation-error");
|
|
369
|
+
expect(el.isError).toBe(true);
|
|
370
|
+
|
|
371
|
+
await waitForEvent(el, "provider-geolocation-success", () => {
|
|
372
|
+
invokeCommand(el, "--request");
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
expect(el).dom.to.equalTag(
|
|
376
|
+
`<provider-geolocation is-success></provider-geolocation>`,
|
|
377
|
+
);
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it("publishes provision with neutron-provision", async () => {
|
|
381
|
+
const el = fixture<HTMLProviderGeolocationElement>(
|
|
382
|
+
`<provider-geolocation></provider-geolocation>`,
|
|
383
|
+
);
|
|
384
|
+
const spy = vi.fn();
|
|
385
|
+
el.addEventListener("neutron-provision", spy);
|
|
386
|
+
await waitForEvent(el, "provider-geolocation-success");
|
|
387
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
388
|
+
});
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
describe("error paths", () => {
|
|
392
|
+
it("reports permission denied", async () => {
|
|
393
|
+
geo.getCurrentPosition.mockImplementation((_success, error) => {
|
|
394
|
+
setTimeout(() => error({ ...mockGeoError, code: 1 }), 0);
|
|
395
|
+
});
|
|
396
|
+
const el = fixture<HTMLProviderGeolocationElement>(
|
|
397
|
+
`<provider-geolocation></provider-geolocation>`,
|
|
398
|
+
);
|
|
399
|
+
await waitForEvent(el, "provider-geolocation-error");
|
|
400
|
+
expect((el.provision as GeolocationPositionError).code).toBe(1);
|
|
401
|
+
expect(el.isRequesting).toBe(false);
|
|
402
|
+
expect(el.isSuccess).toBe(false);
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
it("reports a timeout", async () => {
|
|
406
|
+
geo.getCurrentPosition.mockImplementation((_success, error, options) => {
|
|
407
|
+
setTimeout(
|
|
408
|
+
() =>
|
|
409
|
+
error({
|
|
410
|
+
...mockGeoError,
|
|
411
|
+
code: 3,
|
|
412
|
+
message: `Timeout expired after ${options.timeout}ms`,
|
|
413
|
+
}),
|
|
414
|
+
0,
|
|
415
|
+
);
|
|
416
|
+
});
|
|
417
|
+
const el = fixture<HTMLProviderGeolocationElement>(
|
|
418
|
+
`<provider-geolocation geo-timeout="10"></provider-geolocation>`,
|
|
419
|
+
);
|
|
420
|
+
await waitForEvent(el, "provider-geolocation-error");
|
|
421
|
+
expect(el).dom.to.equalTag(
|
|
422
|
+
`<provider-geolocation geo-timeout="10" is-error></provider-geolocation>`,
|
|
423
|
+
);
|
|
424
|
+
expect((el.provision as GeolocationPositionError).code).toBe(3);
|
|
425
|
+
expect((el.provision as GeolocationPositionError).message).toBe(
|
|
426
|
+
"Timeout expired after 10ms",
|
|
427
|
+
);
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
it("reports position unavailable", async () => {
|
|
431
|
+
geo.getCurrentPosition.mockImplementation((_success, error) => {
|
|
432
|
+
setTimeout(() => error({ ...mockGeoError, code: 2 }), 0);
|
|
433
|
+
});
|
|
434
|
+
const el = fixture<HTMLProviderGeolocationElement>(
|
|
435
|
+
`<provider-geolocation></provider-geolocation>`,
|
|
436
|
+
);
|
|
437
|
+
await waitForEvent(el, "provider-geolocation-error");
|
|
438
|
+
expect((el.provision as GeolocationPositionError).code).toBe(2);
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
it("emits error when the Geolocation API throws synchronously", async () => {
|
|
442
|
+
const thrown = new TypeError("boom");
|
|
443
|
+
geo.getCurrentPosition.mockImplementation(() => {
|
|
444
|
+
throw thrown;
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
// the throw happens during connect, so listen before connecting
|
|
448
|
+
const el = document.createElement("provider-geolocation");
|
|
449
|
+
const spy = vi.fn();
|
|
450
|
+
el.addEventListener("provider-geolocation-error", spy);
|
|
451
|
+
document.body.append(el);
|
|
452
|
+
await wait(0);
|
|
453
|
+
|
|
454
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
455
|
+
expect(spy.mock.calls[0][0].detail).toBe(thrown);
|
|
456
|
+
expect(el).dom.to.equalTag(
|
|
457
|
+
`<provider-geolocation is-error></provider-geolocation>`,
|
|
458
|
+
);
|
|
459
|
+
expect(el.provision).toBe(thrown);
|
|
460
|
+
expect(el.existingWatchId).toBe(null);
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
it("emits error when navigator.geolocation is unavailable", async () => {
|
|
464
|
+
Object.defineProperty(navigator, "geolocation", {
|
|
465
|
+
value: undefined,
|
|
466
|
+
writable: true,
|
|
467
|
+
configurable: true,
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
const el = document.createElement("provider-geolocation");
|
|
471
|
+
const spy = vi.fn();
|
|
472
|
+
el.addEventListener("provider-geolocation-error", spy);
|
|
473
|
+
document.body.append(el);
|
|
474
|
+
await wait(0);
|
|
475
|
+
|
|
476
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
477
|
+
expect(el.isError).toBe(true);
|
|
478
|
+
expect(el.provision).toBeInstanceOf(Error);
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
it("emits error as event detail", async () => {
|
|
482
|
+
geo.getCurrentPosition.mockImplementation((_success, error) => {
|
|
483
|
+
setTimeout(() => error(mockGeoError), 0);
|
|
484
|
+
});
|
|
485
|
+
const el = fixture<HTMLProviderGeolocationElement>(
|
|
486
|
+
`<provider-geolocation></provider-geolocation>`,
|
|
487
|
+
);
|
|
488
|
+
const spy = vi.fn();
|
|
489
|
+
el.addEventListener("provider-geolocation-error", spy);
|
|
490
|
+
await waitForEvent(el, "provider-geolocation-error");
|
|
491
|
+
expect(spy.mock.calls[0][0].detail).toBe(mockGeoError);
|
|
492
|
+
});
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
describe("watch-position", () => {
|
|
496
|
+
it("fires success on every update and stays is-success", async () => {
|
|
497
|
+
let update: (p: typeof mockPosition) => void = () => {};
|
|
498
|
+
geo.watchPosition.mockImplementation((success) => {
|
|
499
|
+
update = success;
|
|
500
|
+
return 7;
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
const el = fixture<HTMLProviderGeolocationElement>(
|
|
504
|
+
`<provider-geolocation watch-position></provider-geolocation>`,
|
|
505
|
+
);
|
|
506
|
+
const spy = vi.fn();
|
|
507
|
+
el.addEventListener("provider-geolocation-success", spy);
|
|
508
|
+
expect(el.existingWatchId).toBe(7);
|
|
509
|
+
|
|
510
|
+
update(mockPosition);
|
|
511
|
+
update({ ...mockPosition, coords: { ...mockPosition.coords, speed: 9 } });
|
|
512
|
+
|
|
513
|
+
// events are synchronous; the default action (state) lands a tick later
|
|
514
|
+
expect(spy).toHaveBeenCalledTimes(2);
|
|
515
|
+
await wait(0);
|
|
516
|
+
expect(el).dom.to.equalTag(
|
|
517
|
+
`<provider-geolocation is-success watch-position></provider-geolocation>`,
|
|
518
|
+
);
|
|
519
|
+
expect((el.provision as any).coords.speed).toBe(9);
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
it("clears the previous watch when a new request is made", async () => {
|
|
523
|
+
let id = 40;
|
|
524
|
+
geo.watchPosition.mockImplementation(() => ++id);
|
|
525
|
+
|
|
526
|
+
const el = fixture<HTMLProviderGeolocationElement>(
|
|
527
|
+
`<provider-geolocation watch-position></provider-geolocation>`,
|
|
528
|
+
);
|
|
529
|
+
expect(el.existingWatchId).toBe(41);
|
|
530
|
+
|
|
531
|
+
invokeCommand(el, "--request");
|
|
532
|
+
await wait(0);
|
|
533
|
+
|
|
534
|
+
expect(geo.clearWatch).toHaveBeenCalledWith(41);
|
|
535
|
+
expect(el.existingWatchId).toBe(42);
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
it("does not call clearWatch on disconnect for a one-shot request", async () => {
|
|
539
|
+
const el = fixture<HTMLProviderGeolocationElement>(
|
|
540
|
+
`<provider-geolocation></provider-geolocation>`,
|
|
541
|
+
);
|
|
542
|
+
await waitForEvent(el, "provider-geolocation-success");
|
|
543
|
+
expect(el.existingWatchId).toBe(null);
|
|
544
|
+
|
|
545
|
+
el.remove();
|
|
546
|
+
await wait(0);
|
|
547
|
+
expect(geo.clearWatch).not.toHaveBeenCalled();
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
it("clears the watch while a request is still pending", async () => {
|
|
551
|
+
geo.watchPosition.mockImplementation(() => 99);
|
|
552
|
+
const el = fixture<HTMLProviderGeolocationElement>(
|
|
553
|
+
`<provider-geolocation watch-position></provider-geolocation>`,
|
|
554
|
+
);
|
|
555
|
+
expect(el.isRequesting).toBe(true);
|
|
556
|
+
|
|
557
|
+
el.remove();
|
|
558
|
+
await wait(0);
|
|
559
|
+
expect(geo.clearWatch).toHaveBeenCalledWith(99);
|
|
560
|
+
expect(el.existingWatchId).toBe(null);
|
|
561
|
+
});
|
|
562
|
+
});
|
|
563
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { invokeCommand } from "@excom/neutron";
|
|
2
|
+
import "@excom/event-handler";
|
|
3
|
+
import "@excom/quark-sheet";
|
|
4
|
+
import "../../index";
|
|
5
|
+
import {
|
|
6
|
+
afterEach,
|
|
7
|
+
beforeEach,
|
|
8
|
+
describe,
|
|
9
|
+
expect,
|
|
10
|
+
it,
|
|
11
|
+
} from "@excom/heft-rig/profiles/default/config/test-utils";
|
|
12
|
+
import {
|
|
13
|
+
flush,
|
|
14
|
+
installDemoModules,
|
|
15
|
+
mountView,
|
|
16
|
+
readDemo,
|
|
17
|
+
restoreDemoModules,
|
|
18
|
+
} from "@excom/quark/support/tests/view-helpers";
|
|
19
|
+
|
|
20
|
+
describe("request view", () => {
|
|
21
|
+
beforeEach(() => installDemoModules());
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
document.body.innerHTML = "";
|
|
24
|
+
restoreDemoModules();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("starts paused and accepts a request click", async () => {
|
|
28
|
+
const { root } = await mountView(readDemo(import.meta.url, "request"));
|
|
29
|
+
const provider = root.querySelector("provider-geolocation")!;
|
|
30
|
+
expect(provider.hasAttribute("is-paused")).toBe(true);
|
|
31
|
+
const button = root.querySelector<HTMLButtonElement>("button[command]")!;
|
|
32
|
+
expect(button.getAttribute("command")).toBe("--request");
|
|
33
|
+
// happy-dom has no Command API: dispatch what the button would have
|
|
34
|
+
invokeCommand(root.querySelector("provider-geolocation")!, "--request", button);
|
|
35
|
+
await flush();
|
|
36
|
+
expect(root.querySelector("output")).toBeTruthy();
|
|
37
|
+
});
|
|
38
|
+
});
|