UNPKG

14.8 kBJavaScriptView Raw
1/**
2 * Copyright 2017 Google Inc. All Rights Reserved.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 * http://www.apache.org/licenses/LICENSE-2.0
7 * Unless required by applicable law or agreed to in writing, software
8 * distributed under the License is distributed on an "AS IS" BASIS,
9 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 * See the License for the specific language governing permissions and
11 * limitations under the License.
12 */
13
14import * as Comlink from "/base/dist/esm/comlink.mjs";
15
16class SampleClass {
17 constructor(counterInit = 1) {
18 this._counter = counterInit;
19 this._promise = Promise.resolve(4);
20 }
21
22 static get SOME_NUMBER() {
23 return 4;
24 }
25
26 static ADD(a, b) {
27 return a + b;
28 }
29
30 get counter() {
31 return this._counter;
32 }
33
34 set counter(value) {
35 this._counter = value;
36 }
37
38 get promise() {
39 return this._promise;
40 }
41
42 method() {
43 return 4;
44 }
45
46 increaseCounter(delta = 1) {
47 this._counter += delta;
48 }
49
50 promiseFunc() {
51 return new Promise(resolve => setTimeout(_ => resolve(4), 100));
52 }
53
54 proxyFunc() {
55 return Comlink.proxy({
56 counter: 0,
57 inc() {
58 this.counter++;
59 }
60 });
61 }
62
63 throwsAnError() {
64 throw Error("OMG");
65 }
66}
67
68describe("Comlink in the same realm", function() {
69 beforeEach(function() {
70 const { port1, port2 } = new MessageChannel();
71 port1.start();
72 port2.start();
73 this.port1 = port1;
74 this.port2 = port2;
75 });
76
77 it("can work with objects", async function() {
78 const thing = Comlink.wrap(this.port1);
79 Comlink.expose({ value: 4 }, this.port2);
80 expect(await thing.value).to.equal(4);
81 });
82
83 it("can work functions on an object", async function() {
84 const thing = Comlink.wrap(this.port1);
85 Comlink.expose({ f: _ => 4 }, this.port2);
86 expect(await thing.f()).to.equal(4);
87 });
88
89 it("can work with functions", async function() {
90 const thing = Comlink.wrap(this.port1);
91 Comlink.expose(_ => 4, this.port2);
92 expect(await thing()).to.equal(4);
93 });
94
95 it("can work with objects that have undefined properties", async function() {
96 const thing = Comlink.wrap(this.port1);
97 Comlink.expose({ x: undefined }, this.port2);
98 expect(await thing.x).to.be.undefined;
99 });
100
101 it("can keep the stack and message of thrown errors", async function() {
102 let stack;
103 const thing = Comlink.wrap(this.port1);
104 Comlink.expose(_ => {
105 const error = Error("OMG");
106 stack = error.stack;
107 throw error;
108 }, this.port2);
109 try {
110 await thing();
111 throw "Should have thrown";;
112 } catch (err) {
113 expect(err).to.not.eq("Should have thrown");
114 expect(err.message).to.equal("OMG");
115 expect(err.stack).to.equal(stack);
116 }
117 });
118
119 it("can rethrow non-error objects", async function() {
120 const thing = Comlink.wrap(this.port1);
121 Comlink.expose(_ => {
122 throw { test: true };
123 }, this.port2);
124 try {
125 await thing();
126 throw "Should have thrown";
127 } catch (err) {
128 expect(err).to.not.eq("Should have thrown");
129 expect(err.test).to.equal(true);
130 }
131 });
132
133 it("can work with parameterized functions", async function() {
134 const thing = Comlink.wrap(this.port1);
135 Comlink.expose((a, b) => a + b, this.port2);
136 expect(await thing(1, 3)).to.equal(4);
137 });
138
139 it("can work with functions that return promises", async function() {
140 const thing = Comlink.wrap(this.port1);
141 Comlink.expose(
142 _ => new Promise(resolve => setTimeout(_ => resolve(4), 100)),
143 this.port2
144 );
145 expect(await thing()).to.equal(4);
146 });
147
148 it("can work with classes", async function() {
149 const thing = Comlink.wrap(this.port1);
150 Comlink.expose(SampleClass, this.port2);
151 const instance = await new thing();
152 expect(await instance.method()).to.equal(4);
153 });
154
155 it("can pass parameters to class constructor", async function() {
156 const thing = Comlink.wrap(this.port1);
157 Comlink.expose(SampleClass, this.port2);
158 const instance = await new thing(23);
159 expect(await instance.counter).to.equal(23);
160 });
161
162 it("can access a class in an object", async function() {
163 const thing = Comlink.wrap(this.port1);
164 Comlink.expose({ SampleClass }, this.port2);
165 const instance = await new thing.SampleClass();
166 expect(await instance.method()).to.equal(4);
167 });
168
169 it("can work with class instance properties", async function() {
170 const thing = Comlink.wrap(this.port1);
171 Comlink.expose(SampleClass, this.port2);
172 const instance = await new thing();
173 expect(await instance._counter).to.equal(1);
174 });
175
176 it("can set class instance properties", async function() {
177 const thing = Comlink.wrap(this.port1);
178 Comlink.expose(SampleClass, this.port2);
179 const instance = await new thing();
180 expect(await instance._counter).to.equal(1);
181 await (instance._counter = 4);
182 expect(await instance._counter).to.equal(4);
183 });
184
185 it("can work with class instance methods", async function() {
186 const thing = Comlink.wrap(this.port1);
187 Comlink.expose(SampleClass, this.port2);
188 const instance = await new thing();
189 expect(await instance.counter).to.equal(1);
190 await instance.increaseCounter();
191 expect(await instance.counter).to.equal(2);
192 });
193
194 it("can handle throwing class instance methods", async function() {
195 const thing = Comlink.wrap(this.port1);
196 Comlink.expose(SampleClass, this.port2);
197 const instance = await new thing();
198 return instance
199 .throwsAnError()
200 .then(_ => Promise.reject())
201 .catch(err => {});
202 });
203
204 it("can work with class instance methods multiple times", async function() {
205 const thing = Comlink.wrap(this.port1);
206 Comlink.expose(SampleClass, this.port2);
207 const instance = await new thing();
208 expect(await instance.counter).to.equal(1);
209 await instance.increaseCounter();
210 await instance.increaseCounter(5);
211 expect(await instance.counter).to.equal(7);
212 });
213
214 it("can work with class instance methods that return promises", async function() {
215 const thing = Comlink.wrap(this.port1);
216 Comlink.expose(SampleClass, this.port2);
217 const instance = await new thing();
218 expect(await instance.promiseFunc()).to.equal(4);
219 });
220
221 it("can work with class instance properties that are promises", async function() {
222 const thing = Comlink.wrap(this.port1);
223 Comlink.expose(SampleClass, this.port2);
224 const instance = await new thing();
225 expect(await instance._promise).to.equal(4);
226 });
227
228 it("can work with class instance getters that are promises", async function() {
229 const thing = Comlink.wrap(this.port1);
230 Comlink.expose(SampleClass, this.port2);
231 const instance = await new thing();
232 expect(await instance.promise).to.equal(4);
233 });
234
235 it("can work with static class properties", async function() {
236 const thing = Comlink.wrap(this.port1);
237 Comlink.expose(SampleClass, this.port2);
238 expect(await thing.SOME_NUMBER).to.equal(4);
239 });
240
241 it("can work with static class methods", async function() {
242 const thing = Comlink.wrap(this.port1);
243 Comlink.expose(SampleClass, this.port2);
244 expect(await thing.ADD(1, 3)).to.equal(4);
245 });
246
247 it("can work with bound class instance methods", async function() {
248 const thing = Comlink.wrap(this.port1);
249 Comlink.expose(SampleClass, this.port2);
250 const instance = await new thing();
251 expect(await instance.counter).to.equal(1);
252 const method = instance.increaseCounter.bind(instance);
253 await method();
254 expect(await instance.counter).to.equal(2);
255 });
256
257 it("can work with class instance getters", async function() {
258 const thing = Comlink.wrap(this.port1);
259 Comlink.expose(SampleClass, this.port2);
260 const instance = await new thing();
261 expect(await instance.counter).to.equal(1);
262 await instance.increaseCounter();
263 expect(await instance.counter).to.equal(2);
264 });
265
266 it("can work with class instance setters", async function() {
267 const thing = Comlink.wrap(this.port1);
268 Comlink.expose(SampleClass, this.port2);
269 const instance = await new thing();
270 expect(await instance._counter).to.equal(1);
271 await (instance.counter = 4);
272 expect(await instance._counter).to.equal(4);
273 });
274
275 const hasBroadcastChannel = _ => "BroadcastChannel" in self;
276 guardedIt(hasBroadcastChannel)(
277 "will work with BroadcastChannel",
278 async function() {
279 const b1 = new BroadcastChannel("comlink_bc_test");
280 const b2 = new BroadcastChannel("comlink_bc_test");
281 const thing = Comlink.wrap(b1);
282 Comlink.expose(b => 40 + b, b2);
283 expect(await thing(2)).to.equal(42);
284 }
285 );
286
287 // Buffer transfers seem to have regressed in Safari 11.1, it’s fixed in 11.2.
288 const isNotSafari11_1 = _ =>
289 !/11\.1(\.[0-9]+)? Safari/.test(navigator.userAgent);
290 guardedIt(isNotSafari11_1)("will transfer buffers", async function() {
291 const thing = Comlink.wrap(this.port1);
292 Comlink.expose(b => b.byteLength, this.port2);
293 const buffer = new Uint8Array([1, 2, 3]).buffer;
294 expect(await thing(Comlink.transfer(buffer, [buffer]))).to.equal(3);
295 expect(buffer.byteLength).to.equal(0);
296 });
297
298 guardedIt(isNotSafari11_1)(
299 "will transfer deeply nested buffers",
300 async function() {
301 const thing = Comlink.wrap(this.port1);
302 Comlink.expose(a => a.b.c.d.byteLength, this.port2);
303 const buffer = new Uint8Array([1, 2, 3]).buffer;
304 expect(
305 await thing(Comlink.transfer({ b: { c: { d: buffer } } }, [buffer]))
306 ).to.equal(3);
307 expect(buffer.byteLength).to.equal(0);
308 }
309 );
310
311 it("will transfer a message port", async function() {
312 const thing = Comlink.wrap(this.port1);
313 Comlink.expose(a => a.postMessage("ohai"), this.port2);
314 const { port1, port2 } = new MessageChannel();
315 await thing(Comlink.transfer(port2, [port2]));
316 return new Promise(resolve => {
317 port1.onmessage = event => {
318 expect(event.data).to.equal("ohai");
319 resolve();
320 };
321 });
322 });
323
324 it("will wrap marked return values", async function() {
325 const thing = Comlink.wrap(this.port1);
326 Comlink.expose(
327 _ =>
328 Comlink.proxy({
329 counter: 0,
330 inc() {
331 this.counter += 1;
332 }
333 }),
334 this.port2
335 );
336 const obj = await thing();
337 expect(await obj.counter).to.equal(0);
338 await obj.inc();
339 expect(await obj.counter).to.equal(1);
340 });
341
342 it("will wrap marked return values from class instance methods", async function() {
343 const thing = Comlink.wrap(this.port1);
344 Comlink.expose(SampleClass, this.port2);
345 const instance = await new thing();
346 const obj = await instance.proxyFunc();
347 expect(await obj.counter).to.equal(0);
348 await obj.inc();
349 expect(await obj.counter).to.equal(1);
350 });
351
352 it("will wrap marked parameter values", async function() {
353 const thing = Comlink.wrap(this.port1);
354 const local = {
355 counter: 0,
356 inc() {
357 this.counter++;
358 }
359 };
360 Comlink.expose(async function(f) {
361 await f.inc();
362 }, this.port2);
363 expect(local.counter).to.equal(0);
364 await thing(Comlink.proxy(local));
365 expect(await local.counter).to.equal(1);
366 });
367
368 it("will wrap marked assignments", function(done) {
369 const thing = Comlink.wrap(this.port1);
370 const obj = {
371 onready: null,
372 call() {
373 this.onready();
374 }
375 };
376 Comlink.expose(obj, this.port2);
377
378 thing.onready = Comlink.proxy(() => done());
379 thing.call();
380 });
381
382 it("will wrap marked parameter values, simple function", async function() {
383 const thing = Comlink.wrap(this.port1);
384 Comlink.expose(async function(f) {
385 await f();
386 }, this.port2);
387 // Weird code because Mocha
388 await new Promise(async resolve => {
389 thing(Comlink.proxy(_ => resolve()));
390 });
391 });
392
393 it("will wrap multiple marked parameter values, simple function", async function() {
394 const thing = Comlink.wrap(this.port1);
395 Comlink.expose(async function(f1, f2, f3) {
396 return await f1() + await f2() + await f3();
397 }, this.port2);
398 // Weird code because Mocha
399 expect(
400 await thing(
401 Comlink.proxy(_ => 1),
402 Comlink.proxy(_ => 2),
403 Comlink.proxy(_ => 3)
404 )
405 ).to.equal(6);
406 });
407
408 it("will proxy deeply nested values", async function() {
409 const thing = Comlink.wrap(this.port1);
410 const obj = {
411 a: {
412 v: 4
413 },
414 b: Comlink.proxy({
415 v: 5
416 })
417 };
418 Comlink.expose(obj, this.port2);
419
420 const a = await thing.a;
421 const b = await thing.b;
422 expect(await a.v).to.equal(4);
423 expect(await b.v).to.equal(5);
424 await (a.v = 8);
425 await (b.v = 9);
426 expect(await thing.a.v).to.equal(4);
427 expect(await thing.b.v).to.equal(9);
428 });
429
430 it("will handle undefined parameters", async function() {
431 const thing = Comlink.wrap(this.port1);
432 Comlink.expose({ f: _ => 4 }, this.port2);
433 expect(await thing.f(undefined)).to.equal(4);
434 });
435
436 it("can handle destructuring", async function() {
437 Comlink.expose(
438 {
439 a: 4,
440 get b() {
441 return 5;
442 },
443 c() {
444 return 6;
445 }
446 },
447 this.port2
448 );
449 const { a, b, c } = Comlink.wrap(this.port1);
450 expect(await a).to.equal(4);
451 expect(await b).to.equal(5);
452 expect(await c()).to.equal(6);
453 });
454
455 it("lets users define transfer handlers", function(done) {
456 Comlink.transferHandlers.set("event", {
457 canHandle(obj) {
458 return obj instanceof Event
459 },
460 serialize(obj) {
461 return [obj.data, []];
462 },
463 deserialize(data) {
464 return new MessageEvent("message", {data});
465 }
466 });
467
468 Comlink.expose((ev) => {
469 expect(ev).to.be.an.instanceOf(Event);
470 expect(ev.data).to.deep.equal({a: 1});
471 done();
472 }, this.port1);
473 const thing = Comlink.wrap(this.port2);
474
475 const {port1, port2} = new MessageChannel();
476 port1.addEventListener("message", thing.bind(this));
477 port1.start();
478 port2.postMessage({a: 1});
479 });
480
481 it("can tunnels a new endpoint with createEndpoint", async function() {
482 Comlink.expose({
483 a: 4,
484 c() {
485 return 5;
486 }
487 },
488 this.port2
489 );
490 const proxy = Comlink.wrap(this.port1);
491 const otherEp = await proxy[Comlink.createEndpoint]();
492 const otherProxy = Comlink.wrap(otherEp);
493 expect(await otherProxy.a).to.equal(4);
494 expect(await proxy.a).to.equal(4);
495 expect(await otherProxy.c()).to.equal(5);
496 expect(await proxy.c()).to.equal(5);
497 });
498});
499
500function guardedIt(f) {
501 return f() ? it : xit;
502}