@excom/provider-orientation 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-orientation.apply-exports.chunks.jsonl +1 -0
- package/.rush/temp/chunked-rush-logs/provider-orientation.build_docs.chunks.jsonl +1 -0
- package/.rush/temp/chunked-rush-logs/provider-orientation.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-orientation.ts +225 -0
- package/rush-logs/provider-orientation.apply-exports.cache.log +1 -0
- package/rush-logs/provider-orientation.apply-exports.log +1 -0
- package/rush-logs/provider-orientation.build_docs.cache.log +1 -0
- package/rush-logs/provider-orientation.build_docs.log +1 -0
- package/rush-logs/provider-orientation.build_package-metas.cache.log +1 -0
- package/rush-logs/provider-orientation.build_package-metas.log +1 -0
- package/support/custom-elements.json +208 -0
- package/support/demos/request.html +14 -0
- package/support/dist-docs/provider-orientation.md +126 -0
- package/support/docs/README.md +52 -0
- package/support/package-meta.json +127 -0
- package/support/tests/provider-orientation.test.ts +624 -0
- package/support/tests/request.view.test.ts +39 -0
- package/tsconfig.json +5 -0
|
@@ -0,0 +1,624 @@
|
|
|
1
|
+
import { invokeCommand } from "@excom/neutron";
|
|
2
|
+
import {
|
|
3
|
+
afterEach,
|
|
4
|
+
describe,
|
|
5
|
+
expect,
|
|
6
|
+
fixture,
|
|
7
|
+
it,
|
|
8
|
+
vi,
|
|
9
|
+
wait,
|
|
10
|
+
waitForEvent,
|
|
11
|
+
} from "@excom/heft-rig/profiles/default/config/test-utils";
|
|
12
|
+
import "../../index";
|
|
13
|
+
|
|
14
|
+
// Window listener goes through Neutron's tracked registry, which passes
|
|
15
|
+
// `{ capture: true }`, not the bare `true` flag.
|
|
16
|
+
const CAPTURE = expect.objectContaining({ capture: true });
|
|
17
|
+
|
|
18
|
+
describe("provider-orientation", () => {
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
document.body.innerHTML = "";
|
|
21
|
+
vi.restoreAllMocks();
|
|
22
|
+
vi.unstubAllGlobals();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("defines the provider-orientation custom element", async () => {
|
|
26
|
+
expect(customElements.get("provider-orientation")).toBeTruthy();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe("makeRequest (Android / desktop — no requestPermission)", () => {
|
|
30
|
+
it("adds deviceorientationabsolute listener on connect when available", async () => {
|
|
31
|
+
const addSpy = vi.spyOn(window, "addEventListener");
|
|
32
|
+
(window as any).ondeviceorientationabsolute = null;
|
|
33
|
+
|
|
34
|
+
const el = fixture<HTMLProviderOrientationElement>(
|
|
35
|
+
`<provider-orientation></provider-orientation>`,
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
expect(addSpy).toHaveBeenCalledWith(
|
|
39
|
+
"deviceorientationabsolute",
|
|
40
|
+
expect.any(Function),
|
|
41
|
+
CAPTURE,
|
|
42
|
+
);
|
|
43
|
+
expect(el).dom.to.equalTag(
|
|
44
|
+
`<provider-orientation is-success></provider-orientation>`,
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
delete (window as any).ondeviceorientationabsolute;
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("falls back to deviceorientation when absolute is not available", async () => {
|
|
51
|
+
const addSpy = vi.spyOn(window, "addEventListener");
|
|
52
|
+
|
|
53
|
+
const el = fixture<HTMLProviderOrientationElement>(
|
|
54
|
+
`<provider-orientation></provider-orientation>`,
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
expect(addSpy).toHaveBeenCalledWith(
|
|
58
|
+
"deviceorientation",
|
|
59
|
+
expect.any(Function),
|
|
60
|
+
CAPTURE,
|
|
61
|
+
);
|
|
62
|
+
expect(el).dom.to.equalTag(
|
|
63
|
+
`<provider-orientation is-success></provider-orientation>`,
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("does not auto-request when isPaused is set", async () => {
|
|
68
|
+
const addSpy = vi.spyOn(window, "addEventListener");
|
|
69
|
+
|
|
70
|
+
const el = fixture<HTMLProviderOrientationElement>(
|
|
71
|
+
`<provider-orientation is-paused></provider-orientation>`,
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
expect(addSpy).not.toHaveBeenCalledWith(
|
|
75
|
+
"deviceorientation",
|
|
76
|
+
expect.any(Function),
|
|
77
|
+
CAPTURE,
|
|
78
|
+
);
|
|
79
|
+
expect(el).dom.to.equalTag(
|
|
80
|
+
`<provider-orientation is-paused></provider-orientation>`,
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("requests when isPaused is unset", async () => {
|
|
85
|
+
const addSpy = vi.spyOn(window, "addEventListener");
|
|
86
|
+
|
|
87
|
+
const el = fixture<HTMLProviderOrientationElement>(
|
|
88
|
+
`<provider-orientation is-paused></provider-orientation>`,
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
expect(addSpy).not.toHaveBeenCalledWith(
|
|
92
|
+
"deviceorientation",
|
|
93
|
+
expect.any(Function),
|
|
94
|
+
CAPTURE,
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
el.isPaused = false;
|
|
98
|
+
|
|
99
|
+
expect(addSpy).toHaveBeenCalledWith(
|
|
100
|
+
"deviceorientation",
|
|
101
|
+
expect.any(Function),
|
|
102
|
+
CAPTURE,
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("requests via the --request command", async () => {
|
|
107
|
+
const addSpy = vi.spyOn(window, "addEventListener");
|
|
108
|
+
|
|
109
|
+
const el = fixture<HTMLProviderOrientationElement>(
|
|
110
|
+
`<provider-orientation is-paused></provider-orientation>`,
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
invokeCommand(el, "--request");
|
|
114
|
+
await wait(0);
|
|
115
|
+
|
|
116
|
+
expect(addSpy).toHaveBeenCalledWith(
|
|
117
|
+
"deviceorientation",
|
|
118
|
+
expect.any(Function),
|
|
119
|
+
CAPTURE,
|
|
120
|
+
);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe("makeRequest (no DeviceOrientationEvent constructor at all)", () => {
|
|
125
|
+
it("falls back to the plain listener path", async () => {
|
|
126
|
+
vi.stubGlobal("DeviceOrientationEvent", undefined);
|
|
127
|
+
const addSpy = vi.spyOn(window, "addEventListener");
|
|
128
|
+
|
|
129
|
+
const el = fixture<HTMLProviderOrientationElement>(
|
|
130
|
+
`<provider-orientation></provider-orientation>`,
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
expect(addSpy).toHaveBeenCalledWith(
|
|
134
|
+
"deviceorientation",
|
|
135
|
+
expect.any(Function),
|
|
136
|
+
CAPTURE,
|
|
137
|
+
);
|
|
138
|
+
expect(el).dom.to.equalTag(
|
|
139
|
+
`<provider-orientation is-success></provider-orientation>`,
|
|
140
|
+
);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe("makeRequest (iOS — requestPermission)", () => {
|
|
145
|
+
it("adds the absolute listener after permission is granted when available", async () => {
|
|
146
|
+
const addSpy = vi.spyOn(window, "addEventListener");
|
|
147
|
+
(window as any).ondeviceorientationabsolute = null;
|
|
148
|
+
vi.stubGlobal("DeviceOrientationEvent", {
|
|
149
|
+
requestPermission: () => Promise.resolve("granted"),
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const el = fixture<HTMLProviderOrientationElement>(
|
|
153
|
+
`<provider-orientation></provider-orientation>`,
|
|
154
|
+
);
|
|
155
|
+
expect(el.isRequesting).toBe(true);
|
|
156
|
+
|
|
157
|
+
await wait(10);
|
|
158
|
+
|
|
159
|
+
expect(addSpy).toHaveBeenCalledWith(
|
|
160
|
+
"deviceorientationabsolute",
|
|
161
|
+
expect.any(Function),
|
|
162
|
+
CAPTURE,
|
|
163
|
+
);
|
|
164
|
+
expect(addSpy).not.toHaveBeenCalledWith(
|
|
165
|
+
"deviceorientation",
|
|
166
|
+
expect.any(Function),
|
|
167
|
+
CAPTURE,
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
delete (window as any).ondeviceorientationabsolute;
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("requests permission from a --request command while paused", async () => {
|
|
174
|
+
const requestPermission = vi.fn(() => Promise.resolve("granted"));
|
|
175
|
+
vi.stubGlobal("DeviceOrientationEvent", { requestPermission });
|
|
176
|
+
|
|
177
|
+
const el = fixture<HTMLProviderOrientationElement>(
|
|
178
|
+
`<provider-orientation is-paused></provider-orientation>`,
|
|
179
|
+
);
|
|
180
|
+
expect(requestPermission).not.toHaveBeenCalled();
|
|
181
|
+
|
|
182
|
+
invokeCommand(el, "--request");
|
|
183
|
+
await wait(0);
|
|
184
|
+
expect(requestPermission).toHaveBeenCalledTimes(1);
|
|
185
|
+
expect(el.isRequesting).toBe(true);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it("adds listener when permission is granted", async () => {
|
|
189
|
+
const addSpy = vi.spyOn(window, "addEventListener");
|
|
190
|
+
vi.stubGlobal("DeviceOrientationEvent", {
|
|
191
|
+
requestPermission: () => Promise.resolve("granted"),
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
const el = fixture<HTMLProviderOrientationElement>(
|
|
195
|
+
`<provider-orientation></provider-orientation>`,
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
expect(el.isRequesting).toBe(true);
|
|
199
|
+
|
|
200
|
+
await wait(10);
|
|
201
|
+
|
|
202
|
+
expect(addSpy).toHaveBeenCalledWith(
|
|
203
|
+
"deviceorientation",
|
|
204
|
+
expect.any(Function),
|
|
205
|
+
CAPTURE,
|
|
206
|
+
);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it("emits error when permission is denied", async () => {
|
|
210
|
+
vi.stubGlobal("DeviceOrientationEvent", {
|
|
211
|
+
requestPermission: () => Promise.resolve("denied"),
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
const el = fixture<HTMLProviderOrientationElement>(
|
|
215
|
+
`<provider-orientation></provider-orientation>`,
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
await waitForEvent(el, "provider-orientation-error");
|
|
219
|
+
|
|
220
|
+
expect(el).dom.to.equalTag(
|
|
221
|
+
`<provider-orientation is-error></provider-orientation>`,
|
|
222
|
+
);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it("emits error when permission promise rejects", async () => {
|
|
226
|
+
vi.stubGlobal("DeviceOrientationEvent", {
|
|
227
|
+
requestPermission: () => Promise.reject(new Error("fail")),
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
const el = fixture<HTMLProviderOrientationElement>(
|
|
231
|
+
`<provider-orientation></provider-orientation>`,
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
await waitForEvent(el, "provider-orientation-error");
|
|
235
|
+
|
|
236
|
+
expect(el).dom.to.equalTag(
|
|
237
|
+
`<provider-orientation is-error></provider-orientation>`,
|
|
238
|
+
);
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
describe("compassHandler", () => {
|
|
243
|
+
it("calculates bearing from webkitCompassHeading (iOS)", async () => {
|
|
244
|
+
let compassHandler: Function;
|
|
245
|
+
vi.spyOn(window, "addEventListener").mockImplementation(
|
|
246
|
+
(type: string, handler: any) => {
|
|
247
|
+
if (type === "deviceorientation") compassHandler = handler;
|
|
248
|
+
},
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
const el = fixture<HTMLProviderOrientationElement>(
|
|
252
|
+
`<provider-orientation></provider-orientation>`,
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
const successPromise = waitForEvent(el, "provider-orientation-success");
|
|
256
|
+
|
|
257
|
+
compassHandler!({ webkitCompassHeading: 180 });
|
|
258
|
+
|
|
259
|
+
await successPromise;
|
|
260
|
+
|
|
261
|
+
expect(el.provision).toEqual({ bearing: 180, alpha: undefined });
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it("calculates bearing from alpha with absolute (Android)", async () => {
|
|
265
|
+
let compassHandler: Function;
|
|
266
|
+
(window as any).ondeviceorientationabsolute = null;
|
|
267
|
+
vi.spyOn(window, "addEventListener").mockImplementation(
|
|
268
|
+
(type: string, handler: any) => {
|
|
269
|
+
if (type === "deviceorientationabsolute") compassHandler = handler;
|
|
270
|
+
},
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
const el = fixture<HTMLProviderOrientationElement>(
|
|
274
|
+
`<provider-orientation></provider-orientation>`,
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
const successPromise = waitForEvent(el, "provider-orientation-success");
|
|
278
|
+
|
|
279
|
+
compassHandler!({ alpha: 90, absolute: true });
|
|
280
|
+
|
|
281
|
+
await successPromise;
|
|
282
|
+
|
|
283
|
+
expect(el.provision).toEqual({ bearing: 270, alpha: 90 });
|
|
284
|
+
|
|
285
|
+
delete (window as any).ondeviceorientationabsolute;
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it("ignores events without valid heading data", async () => {
|
|
289
|
+
let compassHandler: Function;
|
|
290
|
+
vi.spyOn(window, "addEventListener").mockImplementation(
|
|
291
|
+
(type: string, handler: any) => {
|
|
292
|
+
if (type === "deviceorientation") compassHandler = handler;
|
|
293
|
+
},
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
const el = fixture<HTMLProviderOrientationElement>(
|
|
297
|
+
`<provider-orientation></provider-orientation>`,
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
const spy = vi.fn();
|
|
301
|
+
el.addEventListener("provider-orientation-success", spy);
|
|
302
|
+
|
|
303
|
+
compassHandler!({ alpha: 90, absolute: false });
|
|
304
|
+
|
|
305
|
+
expect(spy).not.toHaveBeenCalled();
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it("throttles updates based on compassThrottleMs", async () => {
|
|
309
|
+
let compassHandler: Function;
|
|
310
|
+
vi.spyOn(window, "addEventListener").mockImplementation(
|
|
311
|
+
(type: string, handler: any) => {
|
|
312
|
+
if (type === "deviceorientation") compassHandler = handler;
|
|
313
|
+
},
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
const el = fixture<HTMLProviderOrientationElement>(
|
|
317
|
+
`<provider-orientation compass-throttle-ms="200"></provider-orientation>`,
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
const spy = vi.fn();
|
|
321
|
+
el.addEventListener("provider-orientation-success", spy);
|
|
322
|
+
|
|
323
|
+
let now = 1000;
|
|
324
|
+
vi.spyOn(Date, "now").mockImplementation(() => now);
|
|
325
|
+
|
|
326
|
+
compassHandler!({ webkitCompassHeading: 90 });
|
|
327
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
328
|
+
|
|
329
|
+
now = 1100;
|
|
330
|
+
compassHandler!({ webkitCompassHeading: 180 });
|
|
331
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
332
|
+
|
|
333
|
+
now = 1200;
|
|
334
|
+
compassHandler!({ webkitCompassHeading: 270 });
|
|
335
|
+
expect(spy).toHaveBeenCalledTimes(2);
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
describe("state management", () => {
|
|
340
|
+
it("sets provision and isSuccess on compass success", async () => {
|
|
341
|
+
let compassHandler: Function;
|
|
342
|
+
vi.spyOn(window, "addEventListener").mockImplementation(
|
|
343
|
+
(type: string, handler: any) => {
|
|
344
|
+
if (type === "deviceorientation") compassHandler = handler;
|
|
345
|
+
},
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
const el = fixture<HTMLProviderOrientationElement>(
|
|
349
|
+
`<provider-orientation></provider-orientation>`,
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
await waitForEvent(el, "provider-orientation-success", () => {
|
|
353
|
+
compassHandler!({ webkitCompassHeading: 45 });
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
expect(el.isSuccess).toBe(true);
|
|
357
|
+
expect(el.isError).toBeFalsy();
|
|
358
|
+
expect(el.provision).toEqual({ bearing: 45, alpha: undefined });
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it("sets provision and isError on permission denial", async () => {
|
|
362
|
+
vi.stubGlobal("DeviceOrientationEvent", {
|
|
363
|
+
requestPermission: () => Promise.resolve("denied"),
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
const el = fixture<HTMLProviderOrientationElement>(
|
|
367
|
+
`<provider-orientation></provider-orientation>`,
|
|
368
|
+
);
|
|
369
|
+
|
|
370
|
+
await waitForEvent(el, "provider-orientation-error");
|
|
371
|
+
|
|
372
|
+
expect(el).dom.to.equalTag(
|
|
373
|
+
`<provider-orientation is-error></provider-orientation>`,
|
|
374
|
+
);
|
|
375
|
+
expect(el.provision).toBe("Device orientation permission denied");
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
describe("disconnect", () => {
|
|
380
|
+
it("removes deviceorientation listener on disconnect", async () => {
|
|
381
|
+
const removeSpy = vi.spyOn(window, "removeEventListener");
|
|
382
|
+
|
|
383
|
+
const el = fixture<HTMLProviderOrientationElement>(
|
|
384
|
+
`<provider-orientation></provider-orientation>`,
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
el.remove();
|
|
388
|
+
await wait(0);
|
|
389
|
+
|
|
390
|
+
expect(removeSpy).toHaveBeenCalledWith(
|
|
391
|
+
"deviceorientation",
|
|
392
|
+
expect.any(Function),
|
|
393
|
+
CAPTURE,
|
|
394
|
+
);
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
it("removes deviceorientationabsolute listener when available", async () => {
|
|
398
|
+
(window as any).ondeviceorientationabsolute = null;
|
|
399
|
+
const removeSpy = vi.spyOn(window, "removeEventListener");
|
|
400
|
+
|
|
401
|
+
const el = fixture<HTMLProviderOrientationElement>(
|
|
402
|
+
`<provider-orientation></provider-orientation>`,
|
|
403
|
+
);
|
|
404
|
+
|
|
405
|
+
el.remove();
|
|
406
|
+
await wait(0);
|
|
407
|
+
|
|
408
|
+
expect(removeSpy).toHaveBeenCalledWith(
|
|
409
|
+
"deviceorientationabsolute",
|
|
410
|
+
expect.any(Function),
|
|
411
|
+
CAPTURE,
|
|
412
|
+
);
|
|
413
|
+
|
|
414
|
+
delete (window as any).ondeviceorientationabsolute;
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
it("keeps its listener when moved synchronously within the document", async () => {
|
|
418
|
+
let compassHandler: Function;
|
|
419
|
+
const addSpy = vi
|
|
420
|
+
.spyOn(window, "addEventListener")
|
|
421
|
+
.mockImplementation((type: string, handler: any) => {
|
|
422
|
+
if (type === "deviceorientation") compassHandler = handler;
|
|
423
|
+
});
|
|
424
|
+
const removeSpy = vi.spyOn(window, "removeEventListener");
|
|
425
|
+
const countCalls = (spy: typeof addSpy) =>
|
|
426
|
+
spy.mock.calls.filter(([type]) => type === "deviceorientation").length;
|
|
427
|
+
|
|
428
|
+
const el = fixture<HTMLProviderOrientationElement>(
|
|
429
|
+
`<provider-orientation></provider-orientation>`,
|
|
430
|
+
);
|
|
431
|
+
const parent = el.parentElement!;
|
|
432
|
+
|
|
433
|
+
el.remove();
|
|
434
|
+
parent.append(el);
|
|
435
|
+
await wait(0);
|
|
436
|
+
|
|
437
|
+
// Neutron detaches and restores the tracked listener across the move,
|
|
438
|
+
// without a second request: adds and removes balance to one listener
|
|
439
|
+
expect(countCalls(addSpy) - countCalls(removeSpy)).toBe(1);
|
|
440
|
+
expect(el).dom.to.equalTag(
|
|
441
|
+
`<provider-orientation is-success></provider-orientation>`,
|
|
442
|
+
);
|
|
443
|
+
|
|
444
|
+
// still wired: a reading after the move still lands
|
|
445
|
+
await waitForEvent(el, "provider-orientation-success", () => {
|
|
446
|
+
compassHandler!({ webkitCompassHeading: 12 });
|
|
447
|
+
});
|
|
448
|
+
expect(el.provision).toEqual({ bearing: 12, alpha: undefined });
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
it("stops delivering readings after a real disconnect", async () => {
|
|
452
|
+
let compassHandler: Function;
|
|
453
|
+
vi.spyOn(window, "addEventListener").mockImplementation(
|
|
454
|
+
(type: string, handler: any) => {
|
|
455
|
+
if (type === "deviceorientation") compassHandler = handler;
|
|
456
|
+
},
|
|
457
|
+
);
|
|
458
|
+
const removeSpy = vi.spyOn(window, "removeEventListener");
|
|
459
|
+
|
|
460
|
+
const el = fixture<HTMLProviderOrientationElement>(
|
|
461
|
+
`<provider-orientation></provider-orientation>`,
|
|
462
|
+
);
|
|
463
|
+
el.remove();
|
|
464
|
+
await wait(0);
|
|
465
|
+
|
|
466
|
+
expect(removeSpy).toHaveBeenCalledWith(
|
|
467
|
+
"deviceorientation",
|
|
468
|
+
compassHandler!,
|
|
469
|
+
CAPTURE,
|
|
470
|
+
);
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
it("re-attaches the listener after a genuine disconnect and reconnect", async () => {
|
|
474
|
+
const addSpy = vi.spyOn(window, "addEventListener");
|
|
475
|
+
const removeSpy = vi.spyOn(window, "removeEventListener");
|
|
476
|
+
const countCalls = (spy: typeof addSpy) =>
|
|
477
|
+
spy.mock.calls.filter(([type]) => type === "deviceorientation").length;
|
|
478
|
+
|
|
479
|
+
const el = fixture<HTMLProviderOrientationElement>(
|
|
480
|
+
`<provider-orientation></provider-orientation>`,
|
|
481
|
+
);
|
|
482
|
+
const parent = el.parentElement!;
|
|
483
|
+
expect(countCalls(addSpy)).toBe(1);
|
|
484
|
+
|
|
485
|
+
el.remove();
|
|
486
|
+
await wait(0);
|
|
487
|
+
expect(countCalls(removeSpy)).toBe(1);
|
|
488
|
+
|
|
489
|
+
parent.append(el);
|
|
490
|
+
await wait(0);
|
|
491
|
+
// restored by Neutron, and the connect-time request deduplicates
|
|
492
|
+
expect(countCalls(addSpy) - countCalls(removeSpy)).toBe(1);
|
|
493
|
+
expect(el).dom.to.equalTag(
|
|
494
|
+
`<provider-orientation is-success></provider-orientation>`,
|
|
495
|
+
);
|
|
496
|
+
});
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
describe("disconnect while the permission prompt is open (iOS)", () => {
|
|
500
|
+
const stubPendingPermission = () => {
|
|
501
|
+
let resolvePermission!: (state: string) => void;
|
|
502
|
+
let rejectPermission!: (error: Error) => void;
|
|
503
|
+
vi.stubGlobal("DeviceOrientationEvent", {
|
|
504
|
+
requestPermission: () =>
|
|
505
|
+
new Promise<string>((resolve, reject) => {
|
|
506
|
+
resolvePermission = resolve;
|
|
507
|
+
rejectPermission = reject;
|
|
508
|
+
}),
|
|
509
|
+
});
|
|
510
|
+
return {
|
|
511
|
+
resolvePermission: (state: string) => resolvePermission(state),
|
|
512
|
+
rejectPermission: (error: Error) => rejectPermission(error),
|
|
513
|
+
};
|
|
514
|
+
};
|
|
515
|
+
|
|
516
|
+
it("does not attach a window listener when permission is granted after removal", async () => {
|
|
517
|
+
const { resolvePermission } = stubPendingPermission();
|
|
518
|
+
const addSpy = vi.spyOn(window, "addEventListener");
|
|
519
|
+
|
|
520
|
+
const el = fixture<HTMLProviderOrientationElement>(
|
|
521
|
+
`<provider-orientation></provider-orientation>`,
|
|
522
|
+
);
|
|
523
|
+
expect(el.isRequesting).toBe(true);
|
|
524
|
+
|
|
525
|
+
el.remove();
|
|
526
|
+
await wait(0);
|
|
527
|
+
resolvePermission("granted");
|
|
528
|
+
await wait(10);
|
|
529
|
+
|
|
530
|
+
expect(addSpy).not.toHaveBeenCalledWith(
|
|
531
|
+
"deviceorientation",
|
|
532
|
+
expect.any(Function),
|
|
533
|
+
expect.anything(),
|
|
534
|
+
);
|
|
535
|
+
expect(addSpy).not.toHaveBeenCalledWith(
|
|
536
|
+
"deviceorientationabsolute",
|
|
537
|
+
expect.any(Function),
|
|
538
|
+
expect.anything(),
|
|
539
|
+
);
|
|
540
|
+
expect(el.isSuccess).toBeFalsy();
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
it("leaves no window listener behind when the absolute event is available", async () => {
|
|
544
|
+
(window as any).ondeviceorientationabsolute = null;
|
|
545
|
+
const { resolvePermission } = stubPendingPermission();
|
|
546
|
+
const addSpy = vi.spyOn(window, "addEventListener");
|
|
547
|
+
const removeSpy = vi.spyOn(window, "removeEventListener");
|
|
548
|
+
|
|
549
|
+
const el = fixture<HTMLProviderOrientationElement>(
|
|
550
|
+
`<provider-orientation></provider-orientation>`,
|
|
551
|
+
);
|
|
552
|
+
el.remove();
|
|
553
|
+
await wait(0);
|
|
554
|
+
resolvePermission("granted");
|
|
555
|
+
await wait(10);
|
|
556
|
+
|
|
557
|
+
// never attached, so there is nothing a later removal could miss
|
|
558
|
+
expect(addSpy).not.toHaveBeenCalledWith(
|
|
559
|
+
"deviceorientationabsolute",
|
|
560
|
+
expect.any(Function),
|
|
561
|
+
expect.anything(),
|
|
562
|
+
);
|
|
563
|
+
expect(removeSpy).not.toHaveBeenCalledWith(
|
|
564
|
+
"deviceorientationabsolute",
|
|
565
|
+
expect.any(Function),
|
|
566
|
+
expect.anything(),
|
|
567
|
+
);
|
|
568
|
+
|
|
569
|
+
delete (window as any).ondeviceorientationabsolute;
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
it("does not emit an error on a removed element when permission is denied", async () => {
|
|
573
|
+
const { resolvePermission } = stubPendingPermission();
|
|
574
|
+
const el = fixture<HTMLProviderOrientationElement>(
|
|
575
|
+
`<provider-orientation></provider-orientation>`,
|
|
576
|
+
);
|
|
577
|
+
const spy = vi.fn();
|
|
578
|
+
el.addEventListener("provider-orientation-error", spy);
|
|
579
|
+
|
|
580
|
+
el.remove();
|
|
581
|
+
await wait(0);
|
|
582
|
+
resolvePermission("denied");
|
|
583
|
+
await wait(10);
|
|
584
|
+
|
|
585
|
+
expect(spy).not.toHaveBeenCalled();
|
|
586
|
+
expect(el.isError).toBeFalsy();
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
it("requests again when reconnected after the abandoned prompt", async () => {
|
|
590
|
+
const { resolvePermission } = stubPendingPermission();
|
|
591
|
+
const requestPermission = vi.spyOn(
|
|
592
|
+
DeviceOrientationEvent as any,
|
|
593
|
+
"requestPermission",
|
|
594
|
+
);
|
|
595
|
+
const addSpy = vi.spyOn(window, "addEventListener");
|
|
596
|
+
|
|
597
|
+
const el = fixture<HTMLProviderOrientationElement>(
|
|
598
|
+
`<provider-orientation></provider-orientation>`,
|
|
599
|
+
);
|
|
600
|
+
const parent = el.parentElement!;
|
|
601
|
+
expect(requestPermission).toHaveBeenCalledTimes(1);
|
|
602
|
+
|
|
603
|
+
el.remove();
|
|
604
|
+
await wait(0);
|
|
605
|
+
resolvePermission("granted");
|
|
606
|
+
await wait(10);
|
|
607
|
+
|
|
608
|
+
parent.append(el);
|
|
609
|
+
expect(requestPermission).toHaveBeenCalledTimes(2);
|
|
610
|
+
resolvePermission("granted");
|
|
611
|
+
await wait(10);
|
|
612
|
+
|
|
613
|
+
// only the second (connected) grant attaches, exactly one listener
|
|
614
|
+
expect(
|
|
615
|
+
addSpy.mock.calls.filter(([type]) => type === "deviceorientation"),
|
|
616
|
+
).toHaveLength(1);
|
|
617
|
+
expect(addSpy).toHaveBeenCalledWith(
|
|
618
|
+
"deviceorientation",
|
|
619
|
+
expect.any(Function),
|
|
620
|
+
CAPTURE,
|
|
621
|
+
);
|
|
622
|
+
});
|
|
623
|
+
});
|
|
624
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
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
|
+
expect(root.querySelector("provider-orientation")?.hasAttribute("is-paused")).toBe(
|
|
30
|
+
true,
|
|
31
|
+
);
|
|
32
|
+
const button = root.querySelector<HTMLButtonElement>("button[command]")!;
|
|
33
|
+
expect(button.getAttribute("command")).toBe("--request");
|
|
34
|
+
// happy-dom has no Command API: dispatch what the button would have
|
|
35
|
+
invokeCommand(root.querySelector("provider-orientation")!, "--request", button);
|
|
36
|
+
await flush();
|
|
37
|
+
expect(root.querySelector("output")?.textContent).toBeTruthy();
|
|
38
|
+
});
|
|
39
|
+
});
|