Skip to content

Commit 0082ce7

Browse files
committed
Add failing type tests
1 parent cda7b24 commit 0082ce7

File tree

4 files changed

+275
-5
lines changed

4 files changed

+275
-5
lines changed

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
"scripts": {
99
"build": "rollup -c",
1010
"test:unit": "karma start",
11-
"test:types": "tsc -t esnext -m esnext --lib esnext,dom --moduleResolution node --noEmit tests/type-checks.ts",
11+
"test:types": "tsc -p ./tests/tsconfig.json",
12+
"test:types:watch": "npm run test:types -- --watch",
1213
"test": "npm run fmt_test && npm run build && npm run test:types && npm run test:unit",
1314
"fmt": "prettier --write ./*.{mjs,js,ts,md,json,html} ./{src,docs,tests}/{,**/}*.{mjs,js,ts,md,json,html}",
1415
"fmt_test": "test $(prettier -l ./*.{mjs,js,ts,md,json,html} ./{src,docs,tests}/{**/,}*.{mjs,js,ts,md,json,html} | wc -l) -eq 0",
@@ -46,7 +47,7 @@
4647
"rollup": "1.27.8",
4748
"rollup-plugin-terser": "5.1.2",
4849
"rollup-plugin-typescript2": "0.25.3",
49-
"typescript": "3.7.3"
50+
"typescript": "3.9.0-beta"
5051
},
5152
"dependencies": {}
5253
}

tests/tsconfig.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"extends": "../tsconfig.json",
3+
"compilerOptions": {
4+
"noEmit": true
5+
},
6+
"include": ["./**/*.ts"]
7+
}

tests/type-checks.ts

Lines changed: 263 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
import { assert, Has, NotHas, IsAny } from "conditional-type-checks";
1+
import {
2+
assert,
3+
Has,
4+
NotHas,
5+
IsAny,
6+
IsExact,
7+
IsNever
8+
} from "conditional-type-checks";
29

310
import * as Comlink from "../src/comlink.js";
411

@@ -86,9 +93,263 @@ async function closureSoICanUseAwait() {
8693
assert<Has<typeof b, () => Promise<number>>>(true);
8794
assert<IsAny<typeof b>>(false);
8895
const subproxy = proxy.c;
89-
assert<Has<typeof subproxy, { d: Promise<number> }>>(true);
96+
assert<Has<typeof subproxy, Promise<{ d: number }>>>(true);
9097
assert<IsAny<typeof subproxy>>(false);
9198
const copy = await proxy.c;
9299
assert<Has<typeof copy, { d: number }>>(true);
93100
}
101+
102+
{
103+
Comlink.wrap(new MessageChannel().port1);
104+
Comlink.expose({}, new MessageChannel().port2);
105+
const connection = new RTCPeerConnection();
106+
const channel = connection.createDataChannel("comlink");
107+
108+
interface Baz {
109+
baz: number;
110+
method(): number;
111+
}
112+
113+
class Foo {
114+
constructor(cParam: string) {
115+
const self = this;
116+
assert<IsExact<typeof self.proxyProp, Bar & Comlink.ProxyMarked>>(true);
117+
}
118+
prop1: string = "abc";
119+
proxyProp = Comlink.proxy(new Bar());
120+
methodWithTupleParams(...args: [string] | [number, string]): number {
121+
return 123;
122+
}
123+
methodWithProxiedReturnValue(): Baz & Comlink.ProxyMarked {
124+
return Comlink.proxy({ baz: 123, method: () => 123 });
125+
}
126+
methodWithProxyParameter(param: Baz & Comlink.ProxyMarked): void {}
127+
}
128+
129+
class Bar {
130+
prop2: string | number = "abc";
131+
method(param: string): number {
132+
return 123;
133+
}
134+
methodWithProxiedReturnValue(): Baz & Comlink.ProxyMarked {
135+
return Comlink.proxy({ baz: 123, method: () => 123 });
136+
}
137+
}
138+
const proxy = Comlink.wrap<Foo>(Comlink.windowEndpoint(self));
139+
assert<IsExact<typeof proxy, Comlink.Remote<Foo>>>(true);
140+
141+
proxy[Comlink.releaseProxy]();
142+
const endp = proxy[Comlink.createEndpoint]();
143+
assert<IsExact<typeof endp, Promise<MessagePort>>>(true);
144+
145+
assert<IsAny<typeof proxy.prop1>>(false);
146+
assert<Has<typeof proxy.prop1, Promise<string>>>(true);
147+
148+
const r1 = proxy.methodWithTupleParams(123, "abc");
149+
assert<IsExact<typeof r1, Promise<number>>>(true);
150+
151+
const r2 = proxy.methodWithTupleParams("abc");
152+
assert<IsExact<typeof r2, Promise<number>>>(true);
153+
154+
assert<
155+
IsExact<typeof proxy.proxyProp, Comlink.Remote<Bar & Comlink.ProxyMarked>>
156+
>(true);
157+
158+
assert<IsAny<typeof proxy.proxyProp.prop2>>(false);
159+
assert<Has<typeof proxy.proxyProp.prop2, Promise<string>>>(true);
160+
assert<Has<typeof proxy.proxyProp.prop2, Promise<number>>>(true);
161+
162+
const r3 = proxy.proxyProp.method("param");
163+
assert<IsAny<typeof r3>>(false);
164+
assert<Has<typeof r3, Promise<number>>>(true);
165+
166+
// @ts-expect-error
167+
proxy.proxyProp.method(123);
168+
169+
// @ts-expect-error
170+
proxy.proxyProp.method();
171+
172+
const r4 = proxy.methodWithProxiedReturnValue();
173+
assert<IsAny<typeof r4>>(false);
174+
assert<
175+
IsExact<typeof r4, Promise<Comlink.Remote<Baz & Comlink.ProxyMarked>>>
176+
>(true);
177+
178+
const r5 = proxy.proxyProp.methodWithProxiedReturnValue();
179+
assert<
180+
IsExact<typeof r5, Promise<Comlink.Remote<Baz & Comlink.ProxyMarked>>>
181+
>(true);
182+
183+
const r6 = (await proxy.methodWithProxiedReturnValue()).baz;
184+
assert<IsAny<typeof r6>>(false);
185+
assert<Has<typeof r6, Promise<number>>>(true);
186+
187+
const r7 = (await proxy.methodWithProxiedReturnValue()).method();
188+
assert<IsAny<typeof r7>>(false);
189+
assert<Has<typeof r7, Promise<number>>>(true);
190+
191+
const ProxiedFooClass = Comlink.wrap<typeof Foo>(
192+
Comlink.windowEndpoint(self)
193+
);
194+
const inst1 = await new ProxiedFooClass("test");
195+
assert<IsExact<typeof inst1, Comlink.RemoteObject<Foo>>>(true);
196+
197+
// @ts-expect-error
198+
await new ProxiedFooClass(123);
199+
200+
// @ts-expect-error
201+
await new ProxiedFooClass();
202+
203+
//
204+
// Tests for advanced proxy use cases
205+
//
206+
207+
// Type round trips
208+
assert<
209+
IsExact<
210+
Comlink.Local<Comlink.Remote<Comlink.ProxyMarked>>,
211+
Comlink.ProxyMarked
212+
>
213+
>(true);
214+
assert<
215+
IsExact<
216+
Comlink.Local<Comlink.Remote<(a: number) => string>>,
217+
(a: number) => string | Promise<string>
218+
>
219+
>(true);
220+
221+
interface Subscriber<T> {
222+
closed?: boolean;
223+
next?: (value: T) => void;
224+
}
225+
interface Unsubscribable {
226+
unsubscribe(): void;
227+
}
228+
/** A Subscribable that can get proxied by Comlink */
229+
interface ProxyableSubscribable<T> extends Comlink.ProxyMarked {
230+
subscribe(
231+
subscriber: Comlink.Remote<Subscriber<T> & Comlink.ProxyMarked>
232+
): Unsubscribable & Comlink.ProxyMarked;
233+
}
234+
235+
/** Simple parameter object that gets cloned (not proxied) */
236+
interface Params {
237+
textDocument: string;
238+
}
239+
240+
class Registry {
241+
async registerProvider(
242+
provider: Comlink.Remote<
243+
((params: Params) => ProxyableSubscribable<string>) &
244+
Comlink.ProxyMarked
245+
>
246+
) {
247+
const resultPromise = provider({ textDocument: "foo" });
248+
assert<
249+
IsExact<
250+
typeof resultPromise,
251+
Promise<Comlink.Remote<ProxyableSubscribable<string>>>
252+
>
253+
>(true);
254+
const result = await resultPromise;
255+
256+
const subscriptionPromise = result.subscribe({
257+
[Comlink.proxyMarker]: true,
258+
next: value => {
259+
assert<IsExact<typeof value, string>>(true);
260+
}
261+
});
262+
assert<
263+
IsExact<
264+
typeof subscriptionPromise,
265+
Promise<Comlink.Remote<Unsubscribable & Comlink.ProxyMarked>>
266+
>
267+
>(true);
268+
const subscriber = Comlink.proxy({
269+
next: (value: string) => console.log(value)
270+
});
271+
result.subscribe(subscriber);
272+
273+
const r1 = (await subscriptionPromise).unsubscribe();
274+
assert<IsExact<typeof r1, Promise<void>>>(true);
275+
}
276+
}
277+
const proxy2 = Comlink.wrap<Registry>(Comlink.windowEndpoint(self));
278+
279+
proxy2.registerProvider(
280+
// Synchronous callback
281+
Comlink.proxy(({ textDocument }: Params) => {
282+
const subscribable = Comlink.proxy({
283+
subscribe(
284+
subscriber: Comlink.Remote<Subscriber<string> & Comlink.ProxyMarked>
285+
): Unsubscribable & Comlink.ProxyMarked {
286+
// Important to test here is that union types (such as Function | undefined) distribute properly
287+
// when wrapped in Promises/proxied
288+
289+
assert<IsAny<typeof subscriber.closed>>(false);
290+
assert<
291+
IsExact<
292+
typeof subscriber.closed,
293+
Promise<true> | Promise<false> | Promise<undefined> | undefined
294+
>
295+
>(true);
296+
297+
assert<IsAny<typeof subscriber.next>>(false);
298+
assert<
299+
IsExact<
300+
typeof subscriber.next,
301+
| Comlink.Remote<(value: string) => void>
302+
| Promise<undefined>
303+
| undefined
304+
>
305+
>(true);
306+
307+
// @ts-expect-error
308+
subscriber.next();
309+
310+
if (subscriber.next) {
311+
// Only checking for presence is not enough, since it could be a Promise
312+
313+
// @ts-expect-error
314+
subscriber.next();
315+
}
316+
317+
if (typeof subscriber.next === "function") {
318+
subscriber.next("abc");
319+
}
320+
321+
return Comlink.proxy({ unsubscribe() {} });
322+
}
323+
});
324+
assert<Has<typeof subscribable, Comlink.ProxyMarked>>(true);
325+
return subscribable;
326+
})
327+
);
328+
proxy2.registerProvider(
329+
// Async callback
330+
Comlink.proxy(async ({ textDocument }: Params) => {
331+
return Comlink.proxy({
332+
subscribe(
333+
subscriber: Comlink.Remote<Subscriber<string> & Comlink.ProxyMarked>
334+
): Unsubscribable & Comlink.ProxyMarked {
335+
assert<IsAny<typeof subscriber.next>>(false);
336+
assert<
337+
IsExact<
338+
typeof subscriber.next,
339+
| Comlink.Remote<(value: string) => void>
340+
| Promise<undefined>
341+
| undefined
342+
>
343+
>(true);
344+
345+
// Only checking for presence is not enough, since it could be a Promise
346+
if (typeof subscriber.next === "function") {
347+
subscriber.next("abc");
348+
}
349+
return Comlink.proxy({ unsubscribe() {} });
350+
}
351+
});
352+
})
353+
);
354+
}
94355
}

tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,5 +53,6 @@
5353
/* Experimental Options */
5454
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
5555
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
56-
}
56+
},
57+
"include": ["src/**/*.ts"]
5758
}

0 commit comments

Comments
 (0)