import QUnit from "qunit-tests";
import { transport_globals } from 'cockpit/_internal/transport';
import { Channel, ChannelControlMessage, ChannelOptions, ChannelPayload } from 'cockpit/channel';
function use(..._args: unknown[]) {
}
function is_str(_x: string): void {
}
function is_bytes(_x: Uint8Array): void {
}
function verify_type_assertions() {
const bytes = new Uint8Array();
const str = "";
is_str(str);
is_bytes(bytes);
is_str(bytes);
is_bytes(str);
}
function is_str_channel(x: Channel<string>): void {
x.on('data', msg => is_str(msg));
x.on('data', msg => is_bytes(msg));
}
function is_bytes_channel(x: Channel<Uint8Array>): void {
x.on('data', msg => is_bytes(msg));
x.on('data', msg => is_str(msg));
}
type AlwaysVoid<P> = P extends string ? void : void;
function simple_void(_x: void): void { }
function weird_void<P>(_x: AlwaysVoid<P>): void { }
class VoidCtor<P> { constructor(x: AlwaysVoid<P>) { use(x) } }
class VoidCtorSubclass extends VoidCtor<unknown> { }
function test_void_weirdness() {
const v = (() => {})();
simple_void();
weird_void();
weird_void(v);
use(new VoidCtor<unknown>());
use(new VoidCtor<unknown>(v));
use(new VoidCtorSubclass());
}
function test_channel_api_types() {
const no_args_channel = new Channel();
use(no_args_channel);
const no_payload_channel = new Channel({});
use(no_payload_channel);
const text_no_payload_channel = new Channel({ binary: false });
use(text_no_payload_channel);
const bytes_no_payload_channel = new Channel({ binary: true });
use(bytes_no_payload_channel);
const text_channel = new Channel({ payload: 'echo' });
is_str_channel(text_channel);
const explicit_type_text_channel = new Channel<string>({ payload: 'echo' });
is_str_channel(explicit_type_text_channel);
const explicit_opt_text_channel = new Channel({ payload: 'echo', binary: false });
is_str_channel(explicit_opt_text_channel);
const very_explicit_text_channel = new Channel<string>({ payload: 'echo', binary: false });
is_str_channel(very_explicit_text_channel);
const binary_channel = new Channel<Uint8Array>({ payload: 'echo', binary: true });
is_bytes_channel(binary_channel);
const autodetect_binary_channel = new Channel({ payload: 'echo', binary: true });
use(autodetect_binary_channel);
is_str_channel(new Channel({ payload: 'echo' }));
is_str_channel(new Channel({ payload: 'echo', binary: false }));
is_bytes_channel(new Channel({ payload: 'echo', binary: true }));
is_bytes_channel(new Channel({ payload: 'echo' }));
is_bytes_channel(new Channel({ payload: 'echo', binary: false }));
is_str_channel(new Channel({ payload: 'echo', binary: true }));
const not_text_channel = new Channel<Uint8Array>({ payload: 'echo' });
use(not_text_channel);
const not_explicit_text_channel = new Channel<Uint8Array>({ payload: 'echo', binary: false });
use(not_explicit_text_channel);
const not_binary_channel = new Channel<string>({ payload: 'echo', binary: true });
use(not_binary_channel);
}
function open_echo<P extends ChannelPayload = string>(options: ChannelOptions<P>): Channel<P> {
return new Channel({ ...options, payload: 'echo' });
}
function test_open_echo_types() {
const string_echo = open_echo({});
is_str_channel(string_echo);
const string_echo_void = open_echo();
is_str_channel(string_echo_void);
const binary_echo = open_echo<Uint8Array>({ binary: true });
is_bytes_channel(binary_echo);
}
class EchoChannel<P extends ChannelPayload = string> extends Channel<P> {
constructor(options: ChannelOptions<P>) {
super({ ...options, payload: 'echo' });
}
}
function test_echo_channel_types() {
const text_echo_channel = new EchoChannel({});
is_str_channel(text_echo_channel);
const void_text_echo_channel = new EchoChannel();
is_str_channel(void_text_echo_channel);
const binary_echo_channel = new EchoChannel<Uint8Array>({ binary: true });
is_bytes_channel(binary_echo_channel);
}
export class TextEchoChannel extends EchoChannel<string> {
constructor() {
super({ binary: false });
}
}
export class TextEchoChannelDefaultArg extends EchoChannel<string> {
constructor() {
super({ });
}
}
export class TextEchoChannelTrivial extends EchoChannel<string> {
constructor() {
super();
}
}
export class TextEchoChannelNoConstructor extends EchoChannel<string> {
}
export class BinaryEchoChannel extends EchoChannel<Uint8Array> {
constructor() {
super({ binary: true });
}
}
export class BinaryEchoChannelNoConstructor extends EchoChannel<Uint8Array> {
}
function test_fixed_type_echo_channels_types() {
const text_echo_channel = new TextEchoChannel();
is_str_channel(text_echo_channel);
const text_echo_channel_default_arg = new TextEchoChannelDefaultArg();
is_str_channel(text_echo_channel_default_arg);
const text_echo_channel_trivial = new TextEchoChannelTrivial();
is_str_channel(text_echo_channel_trivial);
const text_echo_channel_no_constructor = new TextEchoChannelNoConstructor();
is_str_channel(text_echo_channel_no_constructor);
const text_binary_channel = new TextEchoChannelNoConstructor({ binary: true });
is_str_channel(text_binary_channel);
const binary_echo_channel = new BinaryEchoChannel();
is_bytes_channel(binary_echo_channel);
const binary_echo_no_ctor = new BinaryEchoChannelNoConstructor({ binary: true });
is_bytes_channel(binary_echo_no_ctor);
const binary_echo_no_arg = new BinaryEchoChannelNoConstructor();
is_bytes_channel(binary_echo_no_arg);
const binary_echo_wrong_arg = new BinaryEchoChannelNoConstructor({ binary: false });
is_bytes_channel(binary_echo_wrong_arg);
}
export class BrokenTextEchoChannelWrongConstructor extends EchoChannel<string> {
constructor() {
super({ binary: true });
}
}
export class BrokenBinaryEchoChannelWrongConstructor extends EchoChannel<Uint8Array> {
constructor() {
super({ binary: false });
}
}
export class BrokenBinaryEchoChannelTrivialConstructor extends EchoChannel<Uint8Array> {
constructor() {
super();
}
}
function open_echo_subclass<P extends ChannelPayload = string>(options: ChannelOptions<P>): EchoChannel<P> {
return new EchoChannel(options);
}
function is_echo_channel<P extends ChannelPayload>(_channel: EchoChannel<P>) {
}
function test_open_echo_subclass_type() {
const no_arg = open_echo_subclass();
is_echo_channel<string>(no_arg);
const empty_arg = open_echo_subclass({});
is_echo_channel<string>(empty_arg);
const false_arg = open_echo_subclass({ binary: false });
is_echo_channel<string>(false_arg);
const true_arg = open_echo_subclass<Uint8Array>({ binary: true });
is_echo_channel<Uint8Array>(true_arg);
is_echo_channel<string>(open_echo_subclass({}));
is_echo_channel<string>(open_echo_subclass({ binary: false }));
is_echo_channel<Uint8Array>(open_echo_subclass({ binary: true }));
is_echo_channel<Uint8Array>(open_echo_subclass({}));
is_echo_channel<Uint8Array>(open_echo_subclass({ binary: false }));
is_echo_channel<string>(open_echo_subclass({ binary: true }));
}
function test_payload_types() {
const text_channel = new Channel({ payload: 'echo' });
text_channel.on('data', msg => {
is_str(msg);
is_bytes(msg);
});
text_channel.send_data('');
text_channel.send_data(new Uint8Array());
const binary_channel = new Channel<Uint8Array>({ payload: 'echo', binary: true });
binary_channel.on('data', msg => {
is_bytes(msg);
is_str(msg);
});
binary_channel.send_data(new Uint8Array());
binary_channel.send_data('');
}
export function test_unknown_payload(binary: boolean) {
const unknown0 = new Channel<ChannelPayload>({ payload: 'echo', binary });
const unknown1: Channel<ChannelPayload> =
new Channel<string>({ payload: 'echo' });
use(unknown1);
const unknown2: Channel<ChannelPayload> =
new Channel<Uint8Array>({ payload: 'echo', binary: true });
use(unknown2);
const known1: Channel<string> = unknown0;
use(known1);
const known2: Channel<string> = unknown0;
use(known2);
const unknown3 = new Channel<ChannelPayload>({ payload: 'echo' });
use(unknown3);
const unknown4 = new Channel<ChannelPayload>({ payload: 'echo', binary: true });
use(unknown4);
const unknown5 = new Channel<ChannelPayload>({ payload: 'echo', binary: false });
use(unknown5);
unknown0.on('data', msg => {
is_str(msg);
is_bytes(msg);
});
unknown0.send_data(new Uint8Array());
unknown0.send_data('');
}
export function test_typing() {
verify_type_assertions();
test_void_weirdness();
test_channel_api_types();
test_open_echo_types();
test_echo_channel_types();
test_fixed_type_echo_channels_types();
test_open_echo_subclass_type();
test_payload_types();
}
QUnit.test("test startup queue", async assert => {
assert.equal(transport_globals.default_transport, null, 'no transport yet');
const echo = new Channel({ payload: 'echo' });
echo.send_data('a');
echo.send_control({ command: 'x' });
echo.send_data('b');
echo.send_control({ command: 'y' });
echo.send_data('c');
echo.send_control({ command: 'z' });
echo.done();
const incoming_messages: (string | ChannelControlMessage)[] = [];
echo.on('data', msg => incoming_messages.push(msg));
echo.on('control', msg => incoming_messages.push(msg));
echo.on('done', msg => incoming_messages.push(msg));
echo.on('close', msg => incoming_messages.push(msg));
assert.false(transport_globals.default_transport?.ready, 'transport not ready');
assert.true(echo.toString().includes('waiting for transport'), 'waiting for transport');
assert.equal(echo.id, null, 'null ID');
await echo.wait();
assert.true(transport_globals.default_transport?.ready, 'transport now ready');
assert.false(echo.toString().includes('waiting'), 'no longer waiting for anything');
assert.notEqual(echo.id, null, 'non-null ID');
const expected_incoming_messages = [
'a',
'b',
'c',
{ command: 'done', channel: echo.id },
{ command: 'close', channel: echo.id },
];
for (let i = 0; i < 100; i++) {
if (incoming_messages.length === expected_incoming_messages.length)
break;
await new Promise(resolve => window.setTimeout(resolve, 10));
}
assert.deepEqual(incoming_messages, expected_incoming_messages, 'received echoed messages');
assert.true(echo.toString().includes('closed'), 'channel closed');
});
QUnit.test("simple channel api", async assert => {
const binary_foo = new TextEncoder().encode('foo');
const echo = new Channel({ payload: 'echo' });
assert.true(echo.toString().includes('waiting for open'), 'waiting for open');
await echo.wait();
assert.true(echo.toString().includes('opened'), 'opened');
echo.send_data('foo');
const unknown_echo: Channel<ChannelPayload> = echo;
unknown_echo.send_data(binary_foo);
assert.equal(await new Promise(resolve => echo.on('data', resolve)), 'foo');
echo.done();
await new Promise(resolve => echo.on('done', resolve));
await new Promise(resolve => echo.on('close', resolve));
assert.true(echo.toString().includes('closed'), 'opened');
const binary_echo = new Channel<Uint8Array>({ payload: 'echo', binary: true });
await echo.wait();
const unknown_binary_echo: Channel<ChannelPayload> = binary_echo;
unknown_binary_echo.send_data('foo');
assert.deepEqual(await new Promise(resolve => binary_echo.on('data', resolve)), binary_foo);
binary_echo.done();
await new Promise(resolve => binary_echo.on('done', resolve));
await new Promise(resolve => binary_echo.on('close', resolve));
});
QUnit.test("unsupported channel", async assert => {
const not_supported = new Channel({ payload: 'nonesuch' });
let err = null;
try {
await not_supported.wait();
} catch (exc) {
err = exc as ChannelControlMessage;
}
assert.equal(err?.problem, 'not-supported');
assert.true(not_supported.toString().includes('error not-supported'), 'got error');
});
QUnit.test("close with error", async assert => {
const channel = new Channel({ payload: 'nonesuch' });
let closed = null;
channel.on('close', msg => { closed = msg });
channel.close('xyz', { extra: 55 });
assert.deepEqual(closed, { command: 'close', problem: 'xyz', extra: 55 });
let err = null;
try {
await channel.wait();
} catch (exc) {
err = exc as ChannelControlMessage;
}
assert.equal(err?.problem, 'xyz');
});
QUnit.test("no signals after manual close", async assert => {
let saw_data = false;
const channel = new Channel({ payload: 'echo' });
channel.on('data', () => { saw_data = true });
channel.send_data('a');
channel.send_data('b');
channel.send_data('c');
channel.close();
let err = null;
try {
await channel.wait();
} catch (exc) {
err = exc;
}
assert.deepEqual(err, { command: 'close' });
await new Promise(resolve => window.setTimeout(resolve, 100));
assert.false(saw_data, 'no data callbacks after close');
});
QUnit.start();