@excom/service-worker 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/service-worker.apply-exports.chunks.jsonl +1 -0
- package/.rush/temp/chunked-rush-logs/service-worker.build_docs.chunks.jsonl +1 -0
- package/.rush/temp/chunked-rush-logs/service-worker.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 +17 -0
- package/package.json +43 -0
- package/rush-logs/service-worker.apply-exports.cache.log +1 -0
- package/rush-logs/service-worker.apply-exports.log +1 -0
- package/rush-logs/service-worker.build_docs.cache.log +1 -0
- package/rush-logs/service-worker.build_docs.log +1 -0
- package/rush-logs/service-worker.build_package-metas.cache.log +1 -0
- package/rush-logs/service-worker.build_package-metas.log +1 -0
- package/service-worker.ts +217 -0
- package/support/custom-elements.json +162 -0
- package/support/demos/simple.html +3 -0
- package/support/dist-docs/service-worker.md +111 -0
- package/support/docs/INTERNAL.md +13 -0
- package/support/docs/README.md +55 -0
- package/support/package-meta.json +104 -0
- package/support/tests/service-worker.test.ts +525 -0
- package/support/tests/simple.view.test.ts +19 -0
- package/tsconfig.json +5 -0
|
@@ -0,0 +1,525 @@
|
|
|
1
|
+
import "../../index";
|
|
2
|
+
import {
|
|
3
|
+
afterEach,
|
|
4
|
+
beforeEach,
|
|
5
|
+
describe,
|
|
6
|
+
expect,
|
|
7
|
+
fixture,
|
|
8
|
+
it,
|
|
9
|
+
vi,
|
|
10
|
+
wait,
|
|
11
|
+
} from "@excom/heft-rig/profiles/default/config/test-utils";
|
|
12
|
+
|
|
13
|
+
/*
|
|
14
|
+
* Like a real `EventTarget`: several listeners per type, de-duplicated
|
|
15
|
+
* and removed by identity. The element registers its own
|
|
16
|
+
* `controllerchange` next to the relayed one.
|
|
17
|
+
*/
|
|
18
|
+
function createSwMock() {
|
|
19
|
+
const listeners: Record<string, Function[]> = {};
|
|
20
|
+
return {
|
|
21
|
+
addEventListener: vi.fn((type: string, cb: Function) => {
|
|
22
|
+
const list = (listeners[type] ??= []);
|
|
23
|
+
if (!list.includes(cb)) list.push(cb);
|
|
24
|
+
}),
|
|
25
|
+
removeEventListener: vi.fn((type: string, cb: Function) => {
|
|
26
|
+
const list = (listeners[type] ?? []).filter((l) => l !== cb);
|
|
27
|
+
if (list.length) listeners[type] = list;
|
|
28
|
+
else delete listeners[type];
|
|
29
|
+
}),
|
|
30
|
+
dispatchEvent: (event: Event) => {
|
|
31
|
+
[...(listeners[event.type] ?? [])].forEach((cb) => cb(event));
|
|
32
|
+
},
|
|
33
|
+
ready: Promise.resolve() as Promise<unknown>,
|
|
34
|
+
controller: null as unknown,
|
|
35
|
+
_listeners: listeners,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const relayCalls = (spy: ReturnType<typeof vi.fn>, type: string) =>
|
|
40
|
+
spy.mock.calls.filter(([t]) => t === type);
|
|
41
|
+
|
|
42
|
+
describe("service-worker", () => {
|
|
43
|
+
let swMock: ReturnType<typeof createSwMock>;
|
|
44
|
+
const actualSW = navigator.serviceWorker;
|
|
45
|
+
|
|
46
|
+
beforeEach(() => {
|
|
47
|
+
swMock = createSwMock();
|
|
48
|
+
Object.defineProperty(navigator, "serviceWorker", {
|
|
49
|
+
value: swMock,
|
|
50
|
+
configurable: true,
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
afterEach(() => {
|
|
55
|
+
document.body.innerHTML = "";
|
|
56
|
+
vi.restoreAllMocks();
|
|
57
|
+
Object.defineProperty(navigator, "serviceWorker", {
|
|
58
|
+
value: actualSW,
|
|
59
|
+
configurable: true,
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("defines the service-worker custom element", () => {
|
|
64
|
+
expect(customElements.get("service-worker")).toBeTruthy();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("sets isSupported and isReady on connect", async () => {
|
|
68
|
+
const el = fixture<HTMLServiceWorkerElement>(
|
|
69
|
+
`<service-worker></service-worker>`,
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
expect(el.isSupported).toBe(true);
|
|
73
|
+
|
|
74
|
+
await wait(0);
|
|
75
|
+
|
|
76
|
+
expect(el).dom.to.equalTag(
|
|
77
|
+
`<service-worker is-mounted is-ready is-supported></service-worker>`,
|
|
78
|
+
);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("reflects isMounted by default", () => {
|
|
82
|
+
const el = fixture<HTMLServiceWorkerElement>(
|
|
83
|
+
`<service-worker></service-worker>`,
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
expect(el).dom.to.equalTag(
|
|
87
|
+
`<service-worker is-mounted is-supported></service-worker>`,
|
|
88
|
+
);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("relays all default SW events when relay-events is bare", () => {
|
|
92
|
+
const el = fixture<HTMLServiceWorkerElement>(
|
|
93
|
+
`<service-worker relay-events></service-worker>`,
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
let received = "";
|
|
97
|
+
el.addEventListener("message", (e) => {
|
|
98
|
+
received = (e as CustomEvent).detail;
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
swMock.dispatchEvent(new MessageEvent("message", { data: "hello" }));
|
|
102
|
+
|
|
103
|
+
expect(received).toBe("hello");
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("only relays specified events when relay-events has a value", () => {
|
|
107
|
+
const el = fixture<HTMLServiceWorkerElement>(
|
|
108
|
+
`<service-worker relay-events="controllerchange"></service-worker>`,
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
const messageSpy = vi.fn();
|
|
112
|
+
const controllerSpy = vi.fn();
|
|
113
|
+
el.addEventListener("message", messageSpy);
|
|
114
|
+
el.addEventListener("controllerchange", controllerSpy);
|
|
115
|
+
|
|
116
|
+
swMock.dispatchEvent(new MessageEvent("message", { data: "hello" }));
|
|
117
|
+
expect(messageSpy).not.toHaveBeenCalled();
|
|
118
|
+
|
|
119
|
+
swMock.dispatchEvent(new Event("controllerchange"));
|
|
120
|
+
expect(controllerSpy).toHaveBeenCalledTimes(1);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("removes all SW event listeners when relay-events is removed", () => {
|
|
124
|
+
const el = fixture<HTMLServiceWorkerElement>(
|
|
125
|
+
`<service-worker relay-events></service-worker>`,
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
const messageSpy = vi.fn();
|
|
129
|
+
el.addEventListener("message", messageSpy);
|
|
130
|
+
|
|
131
|
+
el.removeAttribute("relay-events");
|
|
132
|
+
|
|
133
|
+
swMock.dispatchEvent(new MessageEvent("message", { data: "hello" }));
|
|
134
|
+
|
|
135
|
+
expect(messageSpy).not.toHaveBeenCalled();
|
|
136
|
+
expect(swMock.removeEventListener).toHaveBeenCalledWith(
|
|
137
|
+
"message",
|
|
138
|
+
expect.any(Function),
|
|
139
|
+
);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("removes previous relay listeners when relay-events value changes", () => {
|
|
143
|
+
const el = fixture<HTMLServiceWorkerElement>(
|
|
144
|
+
`<service-worker relay-events="message"></service-worker>`,
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
expect(swMock._listeners["message"]).toBeDefined();
|
|
148
|
+
|
|
149
|
+
el.setAttribute("relay-events", "controllerchange");
|
|
150
|
+
|
|
151
|
+
expect(swMock.removeEventListener).toHaveBeenCalledWith(
|
|
152
|
+
"message",
|
|
153
|
+
expect.any(Function),
|
|
154
|
+
);
|
|
155
|
+
expect(swMock._listeners["controllerchange"]).toBeDefined();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("cleans up event listeners on disconnect", async () => {
|
|
159
|
+
const el = fixture<HTMLServiceWorkerElement>(
|
|
160
|
+
`<service-worker relay-events></service-worker>`,
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
el.remove();
|
|
164
|
+
await wait(0);
|
|
165
|
+
|
|
166
|
+
expect(swMock.removeEventListener).toHaveBeenCalledWith(
|
|
167
|
+
"message",
|
|
168
|
+
expect.any(Function),
|
|
169
|
+
);
|
|
170
|
+
expect(swMock.removeEventListener).toHaveBeenCalledWith(
|
|
171
|
+
"messageerror",
|
|
172
|
+
expect.any(Function),
|
|
173
|
+
);
|
|
174
|
+
expect(swMock.removeEventListener).toHaveBeenCalledWith(
|
|
175
|
+
"controllerchange",
|
|
176
|
+
expect.any(Function),
|
|
177
|
+
);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("relays messageerror events", () => {
|
|
181
|
+
const el = fixture<HTMLServiceWorkerElement>(
|
|
182
|
+
`<service-worker relay-events></service-worker>`,
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
const spy = vi.fn();
|
|
186
|
+
el.addEventListener("messageerror", spy);
|
|
187
|
+
|
|
188
|
+
swMock.dispatchEvent(
|
|
189
|
+
new MessageEvent("messageerror", { data: "error data" }),
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
expect(spy).toHaveBeenCalled();
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it("emits event with correct detail from SW message data", () => {
|
|
196
|
+
const el = fixture<HTMLServiceWorkerElement>(
|
|
197
|
+
`<service-worker relay-events></service-worker>`,
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
let detail: any;
|
|
201
|
+
el.addEventListener("message", (e) => {
|
|
202
|
+
detail = (e as CustomEvent).detail;
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
swMock.dispatchEvent(
|
|
206
|
+
new MessageEvent("message", { data: { type: "CACHE_UPDATED", url: "/api" } }),
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
expect(detail).toEqual({ type: "CACHE_UPDATED", url: "/api" });
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it("relays controllerchange", () => {
|
|
213
|
+
const el = fixture<HTMLServiceWorkerElement>(
|
|
214
|
+
`<service-worker relay-events="controllerchange"></service-worker>`,
|
|
215
|
+
);
|
|
216
|
+
const spy = vi.fn();
|
|
217
|
+
el.addEventListener("controllerchange", spy);
|
|
218
|
+
|
|
219
|
+
swMock.dispatchEvent(new Event("controllerchange"));
|
|
220
|
+
|
|
221
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
222
|
+
// a plain Event has no `data`, so the relayed detail is empty
|
|
223
|
+
expect(spy.mock.calls[0][0].detail).toBeNull();
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it("relays several named events at once", () => {
|
|
227
|
+
const el = fixture<HTMLServiceWorkerElement>(
|
|
228
|
+
`<service-worker relay-events="message controllerchange"></service-worker>`,
|
|
229
|
+
);
|
|
230
|
+
const message = vi.fn();
|
|
231
|
+
const controllerchange = vi.fn();
|
|
232
|
+
const messageerror = vi.fn();
|
|
233
|
+
el.addEventListener("message", message);
|
|
234
|
+
el.addEventListener("controllerchange", controllerchange);
|
|
235
|
+
el.addEventListener("messageerror", messageerror);
|
|
236
|
+
|
|
237
|
+
swMock.dispatchEvent(new MessageEvent("message", { data: 1 }));
|
|
238
|
+
swMock.dispatchEvent(new Event("controllerchange"));
|
|
239
|
+
swMock.dispatchEvent(new MessageEvent("messageerror", { data: 2 }));
|
|
240
|
+
|
|
241
|
+
expect(message).toHaveBeenCalledTimes(1);
|
|
242
|
+
expect(controllerchange).toHaveBeenCalledTimes(1);
|
|
243
|
+
expect(messageerror).not.toHaveBeenCalled();
|
|
244
|
+
expect(swMock._listeners["messageerror"]).toBeUndefined();
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it("starts relaying when relay-events is added after connect", () => {
|
|
248
|
+
const el = fixture<HTMLServiceWorkerElement>(
|
|
249
|
+
`<service-worker></service-worker>`,
|
|
250
|
+
);
|
|
251
|
+
// only the element's own `controllerchange` listener so far
|
|
252
|
+
expect(relayCalls(swMock.addEventListener, "message")).toHaveLength(0);
|
|
253
|
+
expect(relayCalls(swMock.addEventListener, "messageerror")).toHaveLength(0);
|
|
254
|
+
|
|
255
|
+
el.setAttribute("relay-events", "message");
|
|
256
|
+
|
|
257
|
+
expect(swMock.addEventListener).toHaveBeenCalledWith(
|
|
258
|
+
"message",
|
|
259
|
+
expect.any(Function),
|
|
260
|
+
);
|
|
261
|
+
const spy = vi.fn();
|
|
262
|
+
el.addEventListener("message", spy);
|
|
263
|
+
swMock.dispatchEvent(new MessageEvent("message", { data: "late" }));
|
|
264
|
+
expect(spy.mock.calls[0][0].detail).toBe("late");
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it("stores the container in swContainer", () => {
|
|
268
|
+
const el = fixture<HTMLServiceWorkerElement>(
|
|
269
|
+
`<service-worker></service-worker>`,
|
|
270
|
+
);
|
|
271
|
+
expect(el.swContainer).toBe(swMock);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it("does not set is-ready while navigator.serviceWorker.ready is pending", async () => {
|
|
275
|
+
let resolveReady!: () => void;
|
|
276
|
+
swMock.ready = new Promise<void>((resolve) => {
|
|
277
|
+
resolveReady = resolve;
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
const el = fixture<HTMLServiceWorkerElement>(
|
|
281
|
+
`<service-worker></service-worker>`,
|
|
282
|
+
);
|
|
283
|
+
await wait(0);
|
|
284
|
+
expect(el).dom.to.equalTag(
|
|
285
|
+
`<service-worker is-mounted is-supported></service-worker>`,
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
resolveReady();
|
|
289
|
+
await wait(0);
|
|
290
|
+
expect(el).dom.to.equalTag(
|
|
291
|
+
`<service-worker is-mounted is-ready is-supported></service-worker>`,
|
|
292
|
+
);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it("keeps its relay listeners when moved synchronously within the document", async () => {
|
|
296
|
+
const el = fixture<HTMLServiceWorkerElement>(
|
|
297
|
+
`<service-worker relay-events></service-worker>`,
|
|
298
|
+
);
|
|
299
|
+
const parent = el.parentElement!;
|
|
300
|
+
const spy = vi.fn();
|
|
301
|
+
el.addEventListener("message", spy);
|
|
302
|
+
|
|
303
|
+
el.remove();
|
|
304
|
+
parent.append(el);
|
|
305
|
+
await wait(0);
|
|
306
|
+
|
|
307
|
+
// the relay listeners are untouched by a move (Neutron re-registers
|
|
308
|
+
// only its own tracked `controllerchange` listener)
|
|
309
|
+
expect(relayCalls(swMock.removeEventListener, "message")).toHaveLength(0);
|
|
310
|
+
expect(
|
|
311
|
+
relayCalls(swMock.removeEventListener, "messageerror"),
|
|
312
|
+
).toHaveLength(0);
|
|
313
|
+
swMock.dispatchEvent(new MessageEvent("message", { data: "moved" }));
|
|
314
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
315
|
+
expect(el.swContainer).toBe(swMock);
|
|
316
|
+
|
|
317
|
+
// the internal listener survives the move too
|
|
318
|
+
swMock.controller = {};
|
|
319
|
+
swMock.dispatchEvent(new Event("controllerchange"));
|
|
320
|
+
expect(el.provision?.hasController).toBe(true);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it("stops relaying after a real disconnect", async () => {
|
|
324
|
+
const el = fixture<HTMLServiceWorkerElement>(
|
|
325
|
+
`<service-worker relay-events></service-worker>`,
|
|
326
|
+
);
|
|
327
|
+
const spy = vi.fn();
|
|
328
|
+
el.addEventListener("message", spy);
|
|
329
|
+
|
|
330
|
+
el.remove();
|
|
331
|
+
await wait(0);
|
|
332
|
+
|
|
333
|
+
swMock.dispatchEvent(new MessageEvent("message", { data: "gone" }));
|
|
334
|
+
expect(spy).not.toHaveBeenCalled();
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
it("re-attaches the relay after a real disconnect and reconnect", async () => {
|
|
338
|
+
const el = fixture<HTMLServiceWorkerElement>(
|
|
339
|
+
`<service-worker relay-events="message controllerchange"></service-worker>`,
|
|
340
|
+
);
|
|
341
|
+
const message = vi.fn();
|
|
342
|
+
const controllerchange = vi.fn();
|
|
343
|
+
const messageerror = vi.fn();
|
|
344
|
+
el.addEventListener("message", message);
|
|
345
|
+
el.addEventListener("controllerchange", controllerchange);
|
|
346
|
+
el.addEventListener("messageerror", messageerror);
|
|
347
|
+
|
|
348
|
+
el.remove();
|
|
349
|
+
await wait(0);
|
|
350
|
+
expect(swMock._listeners["message"]).toBeUndefined();
|
|
351
|
+
|
|
352
|
+
document.body.append(el);
|
|
353
|
+
await wait(0);
|
|
354
|
+
|
|
355
|
+
swMock.dispatchEvent(new MessageEvent("message", { data: "back" }));
|
|
356
|
+
swMock.dispatchEvent(new Event("controllerchange"));
|
|
357
|
+
swMock.dispatchEvent(new MessageEvent("messageerror", { data: "x" }));
|
|
358
|
+
expect(message).toHaveBeenCalledTimes(1);
|
|
359
|
+
expect(message.mock.calls[0][0].detail).toBe("back");
|
|
360
|
+
expect(controllerchange).toHaveBeenCalledTimes(1);
|
|
361
|
+
// still only the configured names
|
|
362
|
+
expect(messageerror).not.toHaveBeenCalled();
|
|
363
|
+
// and not registered twice
|
|
364
|
+
expect(swMock._listeners["message"]).toHaveLength(1);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
describe("provision", () => {
|
|
368
|
+
it("is set on connect and again when ready resolves", async () => {
|
|
369
|
+
swMock.ready = Promise.resolve({ scope: "/app/" });
|
|
370
|
+
const el = fixture<HTMLServiceWorkerElement>(
|
|
371
|
+
`<service-worker></service-worker>`,
|
|
372
|
+
);
|
|
373
|
+
expect(el.provision).toEqual({
|
|
374
|
+
isSupported: true,
|
|
375
|
+
isReady: false,
|
|
376
|
+
hasController: false,
|
|
377
|
+
scope: null,
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
await wait(0);
|
|
381
|
+
expect(el.provision).toEqual({
|
|
382
|
+
isSupported: true,
|
|
383
|
+
isReady: true,
|
|
384
|
+
hasController: false,
|
|
385
|
+
scope: "/app/",
|
|
386
|
+
});
|
|
387
|
+
expect(el).dom.to.equalTag(
|
|
388
|
+
`<service-worker is-mounted is-ready is-supported></service-worker>`,
|
|
389
|
+
);
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
it("reads an existing controller on connect and leaves scope null without a registration", async () => {
|
|
393
|
+
swMock.controller = {};
|
|
394
|
+
const el = fixture<HTMLServiceWorkerElement>(
|
|
395
|
+
`<service-worker></service-worker>`,
|
|
396
|
+
);
|
|
397
|
+
expect(el.provision?.hasController).toBe(true);
|
|
398
|
+
await wait(0);
|
|
399
|
+
expect(el.provision).toEqual({
|
|
400
|
+
isSupported: true,
|
|
401
|
+
isReady: true,
|
|
402
|
+
hasController: true,
|
|
403
|
+
scope: null,
|
|
404
|
+
});
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
it("tracks controllerchange without relay-events and emits neutron-provision", async () => {
|
|
408
|
+
const el = fixture<HTMLServiceWorkerElement>(
|
|
409
|
+
`<service-worker></service-worker>`,
|
|
410
|
+
);
|
|
411
|
+
await wait(0);
|
|
412
|
+
const relayed = vi.fn();
|
|
413
|
+
const provisionSpy = vi.fn();
|
|
414
|
+
el.addEventListener("controllerchange", relayed);
|
|
415
|
+
el.addEventListener("neutron-provision", provisionSpy);
|
|
416
|
+
const before = el.provision;
|
|
417
|
+
|
|
418
|
+
swMock.controller = {};
|
|
419
|
+
swMock.dispatchEvent(new Event("controllerchange"));
|
|
420
|
+
|
|
421
|
+
expect(el.provision).toEqual({
|
|
422
|
+
isSupported: true,
|
|
423
|
+
isReady: true,
|
|
424
|
+
hasController: true,
|
|
425
|
+
scope: null,
|
|
426
|
+
});
|
|
427
|
+
expect(el.provision).not.toBe(before);
|
|
428
|
+
expect(provisionSpy).toHaveBeenCalledTimes(1);
|
|
429
|
+
// the internal listener never relays
|
|
430
|
+
expect(relayed).not.toHaveBeenCalled();
|
|
431
|
+
|
|
432
|
+
swMock.controller = null;
|
|
433
|
+
swMock.dispatchEvent(new Event("controllerchange"));
|
|
434
|
+
expect(el.provision?.hasController).toBe(false);
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
it("keeps tracking controllerchange after a real disconnect and reconnect", async () => {
|
|
438
|
+
const el = fixture<HTMLServiceWorkerElement>(
|
|
439
|
+
`<service-worker></service-worker>`,
|
|
440
|
+
);
|
|
441
|
+
el.remove();
|
|
442
|
+
await wait(0);
|
|
443
|
+
expect(swMock._listeners["controllerchange"]).toBeUndefined();
|
|
444
|
+
|
|
445
|
+
document.body.append(el);
|
|
446
|
+
await wait(0);
|
|
447
|
+
expect(swMock._listeners["controllerchange"]).toHaveLength(1);
|
|
448
|
+
|
|
449
|
+
swMock.controller = {};
|
|
450
|
+
swMock.dispatchEvent(new Event("controllerchange"));
|
|
451
|
+
expect(el.provision?.hasController).toBe(true);
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
it("ignores a ready that settles after the element left the document", async () => {
|
|
455
|
+
let resolveReady!: (value: unknown) => void;
|
|
456
|
+
swMock.ready = new Promise((resolve) => {
|
|
457
|
+
resolveReady = resolve;
|
|
458
|
+
});
|
|
459
|
+
const el = fixture<HTMLServiceWorkerElement>(
|
|
460
|
+
`<service-worker></service-worker>`,
|
|
461
|
+
);
|
|
462
|
+
el.remove();
|
|
463
|
+
await wait(0);
|
|
464
|
+
|
|
465
|
+
resolveReady({ scope: "/late/" });
|
|
466
|
+
await wait(0);
|
|
467
|
+
|
|
468
|
+
expect(el.isReady).toBe(false);
|
|
469
|
+
expect(el.provision?.isReady).toBe(false);
|
|
470
|
+
expect(el.provision?.scope).toBe(null);
|
|
471
|
+
});
|
|
472
|
+
});
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
describe("service-worker (unsupported)", () => {
|
|
476
|
+
let desc: PropertyDescriptor | undefined;
|
|
477
|
+
|
|
478
|
+
beforeEach(() => {
|
|
479
|
+
desc = Object.getOwnPropertyDescriptor(navigator, "serviceWorker");
|
|
480
|
+
// @ts-expect-error
|
|
481
|
+
delete navigator.serviceWorker;
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
afterEach(() => {
|
|
485
|
+
document.body.innerHTML = "";
|
|
486
|
+
vi.restoreAllMocks();
|
|
487
|
+
if (desc) {
|
|
488
|
+
Object.defineProperty(navigator, "serviceWorker", desc);
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
it("does not set isSupported when serviceWorker is unavailable", () => {
|
|
493
|
+
const el = fixture<HTMLServiceWorkerElement>(
|
|
494
|
+
`<service-worker></service-worker>`,
|
|
495
|
+
);
|
|
496
|
+
|
|
497
|
+
expect(el).dom.to.equalTag(`<service-worker is-mounted></service-worker>`);
|
|
498
|
+
expect(el.swContainer).toBe(null);
|
|
499
|
+
expect(el.isReady).toBeFalsy();
|
|
500
|
+
expect(el.provision).toEqual({
|
|
501
|
+
isSupported: false,
|
|
502
|
+
isReady: false,
|
|
503
|
+
hasController: false,
|
|
504
|
+
scope: null,
|
|
505
|
+
});
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
it("tolerates relay-events being set, changed, removed and disconnected", async () => {
|
|
509
|
+
const el = fixture<HTMLServiceWorkerElement>(
|
|
510
|
+
`<service-worker relay-events></service-worker>`,
|
|
511
|
+
);
|
|
512
|
+
expect(el).dom.to.equalTag(
|
|
513
|
+
`<service-worker is-mounted relay-events></service-worker>`,
|
|
514
|
+
);
|
|
515
|
+
|
|
516
|
+
expect(() => {
|
|
517
|
+
el.setAttribute("relay-events", "message");
|
|
518
|
+
el.removeAttribute("relay-events");
|
|
519
|
+
}).not.toThrow();
|
|
520
|
+
|
|
521
|
+
el.remove();
|
|
522
|
+
await wait(0);
|
|
523
|
+
expect(el.isMounted).toBe(false);
|
|
524
|
+
});
|
|
525
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import "../../index";
|
|
2
|
+
import {
|
|
3
|
+
afterEach,
|
|
4
|
+
describe,
|
|
5
|
+
expect,
|
|
6
|
+
it,
|
|
7
|
+
} from "@excom/heft-rig/profiles/default/config/test-utils";
|
|
8
|
+
import { mountView, readDemo } from "@excom/quark/support/tests/view-helpers";
|
|
9
|
+
|
|
10
|
+
describe("simple view", () => {
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
document.body.innerHTML = "";
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("defines the service-worker element", async () => {
|
|
16
|
+
const { root } = await mountView(readDemo(import.meta.url, "simple"));
|
|
17
|
+
expect(root.querySelector("service-worker")).toBeTruthy();
|
|
18
|
+
});
|
|
19
|
+
});
|