data-transport
Version:
A simple and responsive transport
390 lines (340 loc) • 9.56 kB
text/typescript
import { Transport, listen, createTransport, mockPorts } from '../src';
test('base', async () => {
type Internal = {
hello(options: { num: number }, word: string): Promise<{ text: string }>;
};
const ports = mockPorts();
class InternalTransport
extends Transport<{ emit: Internal }>
implements Internal
{
constructor() {
super(ports.main);
}
async hello(options: { num: number }, word: string) {
const response = await this.emit('hello', options, word);
return response;
}
}
class ExternalTransport extends Transport implements Internal {
constructor() {
super(ports.create());
}
async hello(options: { num: number }, word: string) {
return {
text: `hello ${options.num} ${word}`,
};
}
}
const internal = new InternalTransport();
const external = new ExternalTransport();
expect(await internal.hello({ num: 42 }, 'Universe')).toEqual({
text: 'hello 42 Universe',
});
});
test('base with `{ hasRespond: false }`', async () => {
type Internal = {
hello(options: { num: number }): Promise<void>;
};
let mockExternalSend: ((...args: any) => void) | undefined;
let mockInternalSend: (...args: any) => void;
class InternalTransport
extends Transport<{ emit: Internal }>
implements Internal
{
constructor() {
super({
listener: (callback) => {
mockExternalSend = callback;
return () => {
mockExternalSend = undefined;
};
},
sender: (message) => {
mockInternalSend(JSON.parse(JSON.stringify(message)));
},
});
}
async hello(options: { num: number }) {
const response = await this.emit(
{ name: 'hello', respond: false, timeout: 42 },
options
);
return response;
}
}
class ExternalTransport extends Transport implements Internal {
constructor() {
super({
listener: (callback) => {
mockInternalSend = callback;
},
sender: (message) => {
mockExternalSend?.(JSON.parse(JSON.stringify(message)));
},
});
}
async hello(options: { num: number }) {
expect(options.num).toBe(42);
}
}
const internal = new InternalTransport();
const external = new ExternalTransport();
expect(await internal.hello({ num: 42 })).toBeUndefined();
});
test('base with two-way', async () => {
type Internal = {
hello(options: { num: number }): Promise<{ text: string }>;
};
type External = {
help(options: { key: number }): Promise<{ text: string }>;
};
let mockExternalSend: (...args: any) => void;
let mockInternalSend: (...args: any) => void;
const externalTransportListener = jest.fn();
const internalTransportListener = jest.fn();
const serializer = {
parse: JSON.parse,
stringify: JSON.stringify,
};
class InternalTransport
extends Transport<{ emit: Internal }>
implements External, Internal
{
constructor() {
super({
listener: (callback) => {
mockExternalSend = (data: any) => {
callback(data);
internalTransportListener(data);
};
},
sender: (message) => {
mockInternalSend(JSON.parse(JSON.stringify(message)));
},
serializer,
});
}
async help(options: { key: number }) {
return {
text: String.fromCharCode(options.key),
};
}
async hello(options: { num: number }) {
const response = await this.emit('hello', options);
return response;
}
}
class ExternalTransport
extends Transport<{ emit: External }>
implements External, Internal
{
constructor() {
super({
listener: (callback) => {
mockInternalSend = (data: any) => {
callback(data);
externalTransportListener(data);
};
},
sender: (message) => {
mockExternalSend(JSON.parse(JSON.stringify(message)));
},
serializer,
});
}
async help(options: { key: number }) {
return await this.emit('help', options);
}
async hello(options: { num: number }) {
return {
text: `hello ${options.num}`,
};
}
}
const internal = new InternalTransport();
const external = new ExternalTransport();
expect(await internal.hello({ num: 42 })).toEqual({ text: 'hello 42' });
expect(externalTransportListener).toBeCalledTimes(1);
expect(
Object.prototype.hasOwnProperty.call(
externalTransportListener.mock.calls[0][0],
'response'
)
).toBe(false);
expect(
Object.prototype.hasOwnProperty.call(
externalTransportListener.mock.calls[0][0],
'request'
)
).toBe(true);
expect(typeof externalTransportListener.mock.calls[0][0].request).toBe(
'string'
);
expect(internalTransportListener).toBeCalledTimes(1);
expect(
Object.prototype.hasOwnProperty.call(
internalTransportListener.mock.calls[0][0],
'request'
)
).toBe(false);
expect(
Object.prototype.hasOwnProperty.call(
internalTransportListener.mock.calls[0][0],
'response'
)
).toBe(true);
expect(typeof internalTransportListener.mock.calls[0][0].response).toBe(
'string'
);
// clean up serializer
// @ts-ignore
delete serializer.parse;
// @ts-ignore
delete serializer.stringify;
expect(await internal.hello({ num: 42 })).toEqual({ text: 'hello 42' });
expect(typeof externalTransportListener.mock.calls[1][0].request).toBe(
'object'
);
expect(typeof internalTransportListener.mock.calls[1][0].response).toBe(
'object'
);
expect(await external.help({ key: 65 })).toEqual({ text: 'A' });
expect(() => {
external.hello({ num: 42 });
}).toThrowError(
"The method 'hello' is a listen function that can NOT be actively called."
);
expect(() => {
internal.help({ key: 1 });
}).toThrowError(
"The method 'help' is a listen function that can NOT be actively called."
);
});
test('base with non-decorator', async () => {
type Internal = {
hello(options: { num: number }): Promise<{ text: string }>;
};
let mockExternalSend: (...args: any) => void;
let mockInternalSend: (...args: any) => void;
class InternalTransport
extends Transport<{ emit: Internal }>
implements Internal
{
constructor() {
super({
listener: (callback) => {
mockExternalSend = callback;
},
sender: (message) => {
mockInternalSend(JSON.parse(JSON.stringify(message)));
},
});
}
async hello(options: { num: number }) {
const response = await this.emit('hello', options);
return response;
}
}
class ExternalTransport extends Transport implements Internal {
constructor() {
super({
listener: (callback) => {
mockInternalSend = callback;
},
sender: (message) => {
mockExternalSend(JSON.parse(JSON.stringify(message)));
},
listenKeys: ['hello'],
});
}
async hello(options: { num: number }) {
return {
text: `hello ${options.num}`,
};
}
}
const internal = new InternalTransport();
const external = new ExternalTransport();
expect(await internal.hello({ num: 42 })).toEqual({ text: 'hello 42' });
});
test('base with `undefined`', async () => {
type Internal = {
hello(): Promise<void>;
};
let mockExternalSend: (...args: any) => void;
let mockInternalSend: (...args: any) => void;
class InternalTransport
extends Transport<{ emit: Internal }>
implements Internal
{
constructor() {
super({
listener: (callback) => {
mockExternalSend = callback;
},
sender: (message) => {
mockInternalSend(JSON.parse(JSON.stringify(message)));
},
});
}
async hello() {
const response = await this.emit('hello');
return response;
}
}
class ExternalTransport extends Transport implements Internal {
constructor() {
super({
listener: (callback) => {
mockInternalSend = callback;
},
sender: (message) => {
mockExternalSend(JSON.parse(JSON.stringify(message)));
},
});
}
async hello() {
//
}
}
const internal = new InternalTransport();
const external = new ExternalTransport();
expect(await internal.hello()).toBeUndefined();
});
test('base with createTransport', async () => {
type Internal = {
hello(options: { num: number }, word: string): Promise<{ text: string }>;
};
const ports = mockPorts();
const internal: Transport<{ emit: Internal }> = createTransport(
'Base',
ports.main
);
const external: Transport<{ listen: Internal }> = createTransport(
'Base',
ports.create()
);
const dispose = external.listen('hello', async (options, word) => ({
text: `hello ${options.num} ${word}`,
}));
expect(await internal.emit('hello', { num: 42 }, 'Universe')).toEqual({
text: 'hello 42 Universe',
});
const warn = jest.spyOn(console, 'warn').mockImplementation(() => {});
dispose?.();
const result = await internal.emit(
{ name: 'hello', timeout: 1000 },
{ num: 42 },
'Universe'
);
expect(result).toBeUndefined();
expect(warn.mock.calls[0][0]).toBe(
"The event 'DataTransport-hello' timed out for 1000 seconds..."
);
});