// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
'use strict';
// This file tests that JS can correctly decode optional numerics response
// params (by binding to an interface implemented in C++) and that JS can
// correctly encode optional numerics response params (by binding to a interface
// implemented in JS).
//
// These tests can be imported as a module or added as a script to tests lite
// bindings.
class ResponseParamsJsImpl {
constructor() {
const nullMethods = [
'getNullBool',
'getNullUint8',
'getNullInt8',
'getNullUint16',
'getNullInt16',
'getNullUint32',
'getNullInt32',
'getNullUint64',
'getNullInt64',
'getNullFloat',
'getNullDouble',
'getNullEnum',
'getNullBools',
'getNullInt16s',
'getNullUint32s',
'getNullDoubles',
'getNullEnums',
'getNullBoolMap',
'getNullInt32Map',
'getNullEnumMap',
];
for (const method of nullMethods) {
this[method] = this.getNullOptional;
}
const methods = [
'getOptionalBool',
'getOptionalUint8',
'getOptionalInt8',
'getOptionalUint16',
'getOptionalInt16',
'getOptionalUint32',
'getOptionalInt32',
'getOptionalUint64',
'getOptionalInt64',
'getOptionalFloat',
'getOptionalDouble',
'getOptionalEnum',
'getOptionalBools',
'getOptionalInt16s',
'getOptionalUint32s',
'getOptionalDoubles',
'getOptionalEnums',
'getOptionalBoolMap',
'getOptionalFloatMap',
'getOptionalEnumMap',
];
for (const method of methods) {
this[method] = this.getOptional;
}
this.receiver = new OptionalNumericsResponseParamsReceiver(this);
}
async getOptional(value) {
return {optionalValue: value};
}
async getNullOptional() {
if (typeof this.nullType_ === 'undefined') {
throw new Error('setNullForNextResponse should be called first.');
}
const response = {};
switch (this.nullType_) {
case 'undefined':
response.optionalValue = undefined;
break;
case 'null':
response.optionalValue = null;
break;
case 'empty':
break;
}
delete this.nullType_;
return response;
}
// nullType can be `undefined`, `null`, or `empty` and they correspond to
// what we use as a response for null values.
setNullForNextResponse(nullType) {
assert_true(nullType === 'undefined' ||
nullType === 'null' ||
nullType === 'empty');
this.nullType_ = nullType;
}
async getNullStructWithOptionalNumerics() {
let response = {};
switch (this.nullType_) {
case 'undefined':
response.s = undefined;
break;
case 'null':
response.s = null;
break;
case 'empty':
break;
}
delete this.nullType_;
return response;
}
async getStructWithNullOptionalNumerics() {
if (typeof this.nullType_ === 'undefined') {
throw new Error('setNullForNextResponse should be called first.');
}
const structFields = [
'optionalBool',
'optionalUint8',
'optionalInt8',
'optionalUint16',
'optionalInt16',
'optionalUint32',
'optionalInt32',
'optionalUint64',
'optionalInt64',
'optionalFloat',
'optionalDouble',
'optionalEnum',
];
let s = {};
switch (this.nullType_) {
case 'undefined':
for (const field of structFields) {
s[field] = undefined;
}
break;
case 'null':
for (const field of structFields) {
s[field] = null;
}
break;
case 'empty':
break;
}
delete this.nullType_;
return {s};
}
async getStructWithOptionalNumerics(
boolValue,
uint8Value,
int8Value,
uint16Value,
int16Value,
uint32Value,
int32Value,
uint64Value,
int64Value,
floatValue,
doubleValue,
enumValue) {
const struct = {
optionalBool: boolValue,
optionalUint8: uint8Value,
optionalInt8: int8Value,
optionalUint16: uint16Value,
optionalInt16: int16Value,
optionalUint32: uint32Value,
optionalInt32: int32Value,
optionalUint64: uint64Value,
optionalInt64: int64Value,
optionalFloat: floatValue,
optionalDouble: doubleValue,
optionalEnum: enumValue,
};
return {
s: struct
};
}
};
const cpp = new OptionalNumericsResponseParamsRemote();
cpp.$.bindNewPipeAndPassReceiver().bindInBrowser('process');
const jsImpl = new ResponseParamsJsImpl();
const js = jsImpl.receiver.$.bindNewPipeAndPassRemote();
const testGetNullMethods = [{
method: 'getNullBool',
numericType: 'bool',
}, {
method: 'getNullUint8',
numericType: 'uint8',
}, {
method: 'getNullInt8',
numericType: 'int8',
}, {
method: 'getNullUint16',
numericType: 'uint16',
}, {
method: 'getNullInt16',
numericType: 'int16',
}, {
method: 'getNullUint32',
numericType: 'uint32',
}, {
method: 'getNullInt32',
numericType: 'int32',
}, {
method: 'getNullUint64',
numericType: 'uint64',
}, {
method: 'getNullInt64',
numericType: 'int64',
}, {
method: 'getNullFloat',
numericType: 'float',
}, {
method: 'getNullDouble',
numericType: 'double',
}, {
method: 'getNullEnum',
numericType: 'enum',
}];
for (const {method, responseArgName, numericType} of testGetNullMethods) {
promise_test(async () => {
const response = await cpp[method]();
assert_true('optionalValue' in response);
assert_equals(response.optionalValue, null);
}, `C++ encoding and JS decoding of null ${numericType} response param.`);
promise_test(async () => {
{
jsImpl.setNullForNextResponse('null');
const response = await js[method]();
assert_true('optionalValue' in response);
assert_equals(response.optionalValue, null);
}
{
jsImpl.setNullForNextResponse('undefined');
const response = await js[method]();
assert_true('optionalValue' in response);
assert_equals(response.optionalValue, null);
}
{
jsImpl.setNullForNextResponse('empty');
const response = await js[method]();
assert_true('optionalValue' in response);
assert_equals(response.optionalValue, null);
}
}, `JS decoding of null ${numericType} response param.`);
}
promise_test(async () => {
const response = await cpp.getNullStructWithOptionalNumerics();
assert_true('s' in response);
assert_equals(response.s, null);
}, 'C++ encoding and JS decoding of null struct with optional numerics ' +
'in response param.');
promise_test(async () => {
{
jsImpl.setNullForNextResponse('null');
const response = await js.getNullStructWithOptionalNumerics();
assert_true('s' in response);
assert_equals(response.s, null);
}
{
jsImpl.setNullForNextResponse('undefined');
const response = await js.getNullStructWithOptionalNumerics();
assert_true('s' in response);
assert_equals(response.s, null);
}
{
jsImpl.setNullForNextResponse('empty');
const response = await js.getNullStructWithOptionalNumerics();
assert_true('s' in response);
assert_equals(response.s, null);
}
}, 'JS decoding of null struct with optional numerics.');
const testGetArraysOfNullsMethods = [
'getNullBools',
'getNullInt16s',
'getNullUint32s',
'getNullDoubles',
'getNullEnums',
];
for (const method of testGetArraysOfNullsMethods) {
promise_test(async() => {
const response = await cpp[method]();
assert_array_equals(response.optionalValues, [null]);
});
}
const testGetMapOfNullsMethods = [
'getNullBoolMap',
'getNullInt32Map',
'getNullEnumMap',
];
for (const method of testGetMapOfNullsMethods) {
promise_test(async() => {
const response = await cpp[method]();
assert_object_equals(response.optionalValues, {0: null});
});
}
const testMethods = [{
method: 'getOptionalBool',
valueToUse: true,
numericType: 'bool',
}, {
method: 'getOptionalUint8',
valueToUse: 8,
numericType: 'uint8',
}, {
method: 'getOptionalInt8',
valueToUse: -8,
numericType: 'int8',
}, {
method: 'getOptionalUint16',
valueToUse: 16,
numericType: 'uint16',
}, {
method: 'getOptionalInt16',
valueToUse: -16,
numericType: 'int16',
}, {
method: 'getOptionalUint32',
valueToUse: 32,
numericType: 'uint32',
}, {
method: 'getOptionalInt32',
valueToUse: -32,
numericType: 'int32',
}, {
method: 'getOptionalUint64',
valueToUse: BigInt('64'),
numericType: 'uint64',
}, {
method: 'getOptionalInt64',
valueToUse: BigInt('-64'),
numericType: 'int64',
}, {
method: 'getOptionalFloat',
valueToUse: -0.5,
numericType: 'float',
}, {
method: 'getOptionalDouble',
valueToUse: 0.25,
numericType: 'double',
}, {
method: 'getOptionalEnum',
valueToUse: OptionalNumericsRegularEnum.kBar,
numericType: 'enum',
}];
for (const {method, valueToUse, numericType} of testMethods) {
promise_test(async () => {
const {optionalValue} = await cpp[method](valueToUse);
assert_equals(optionalValue, valueToUse);
}, `C++ encoding and JS decoding of optional ${numericType} response param.`);
promise_test(async () => {
const {optionalValue} = await js[method](valueToUse);
assert_equals(optionalValue, valueToUse);
}, `JS encoding and decoding of ${numericType} response param.`);
}
const structFields = [
'optionalBool',
'optionalUint8',
'optionalInt8',
'optionalUint16',
'optionalInt16',
'optionalUint32',
'optionalInt32',
'optionalUint64',
'optionalInt64',
'optionalFloat',
'optionalDouble',
'optionalEnum',
];
promise_test(async () => {
const {s} = await cpp.getStructWithNullOptionalNumerics();
for (const field of structFields) {
assert_true(field in s);
assert_equals(s[field], null);
}
}, 'C++ encoding and JS decoding of struct with null optional numerics ' +
'in response params.');
promise_test(async () => {
{
jsImpl.setNullForNextResponse('empty');
const {s} = await js.getStructWithNullOptionalNumerics();
for (const field of structFields) {
assert_true(field in s);
assert_equals(s[field], null);
}
}
{
jsImpl.setNullForNextResponse('undefined');
const {s} = await js.getStructWithNullOptionalNumerics();
for (const field of structFields) {
assert_true(field in s);
assert_equals(s[field], null);
}
}
{
jsImpl.setNullForNextResponse('null');
const {s} = await js.getStructWithNullOptionalNumerics();
for (const field of structFields) {
assert_true(field in s);
assert_equals(s[field], null);
}
}
}, 'JS encoding and decoding of struct with null optional numerics in ' +
'response params.');
const testStructFields = [{
name: 'optionalBool',
value: true,
}, {
name: 'optionalUint8',
value: 8,
}, {
name: 'optionalInt8',
value: -8,
}, {
name: 'optionalUint16',
value: 16,
}, {
name: 'optionalInt16',
value: -16,
}, {
name: 'optionalUint32',
value: 32,
}, {
name: 'optionalInt32',
value: -32,
}, {
name: 'optionalUint64',
value: BigInt("64"),
}, {
name: 'optionalInt64',
value: BigInt("-64"),
}, {
name: 'optionalFloat',
value: -0.5,
}, {
name: 'optionalDouble',
value: 0.25,
}, {
name: 'optionalEnum',
value: OptionalNumericsRegularEnum.kFoo,
}];
promise_test(async () => {
const args = testStructFields.map((field) => {
return field.value;
});
const {s} = await cpp.getStructWithOptionalNumerics(...args);
assert_equals(Object.keys(s).length, testStructFields.length);
for (const field of testStructFields) {
assert_true(field.name in s, field.name);
assert_equals(s[field.name], field.value, field.name);
}
}, 'C++ encoding and JS decoding of struct with optional numerics in ' +
'response params.');
promise_test(async () => {
const args = testStructFields.map((field) => {
return field.value;
});
const {s} = await js.getStructWithOptionalNumerics(...args);
assert_equals(Object.keys(s).length, testStructFields.length);
for (const field of testStructFields) {
assert_true(field.name in s, field.name);
assert_equals(s[field.name], field.value, field.name);
}
}, 'JS encoding of struct with optional numerics in response params.');
const testNullWrapping = [{
name: 'getOptionalBools',
value: true,
}, {
name: 'getOptionalInt16s',
value: 16,
}, {
name: 'getOptionalUint32s',
value: 32,
}, {
name: 'getOptionalDoubles',
value: 22.2,
}, {
name: 'getOptionalEnums',
value: OptionalNumericsRegularEnum.kFoo,
}];
for (const {name, value} of testNullWrapping) {
promise_test(async() => {
const response = await cpp[name](value);
assert_array_equals(response.optionalValues, [null, value, null]);
});
}
const testNullWrappingForMap = [{
name: 'getOptionalBoolMap',
key: 6,
value: false,
}, {
name: 'getOptionalFloatMap',
key: 7,
value: 1.25,
}, {
name: 'getOptionalEnumMap',
key: 8,
value: OptionalNumericsRegularEnum.kFoo,
}];
for (const {name, key, value} of testNullWrappingForMap) {
promise_test(async() => {
const response = await cpp[name](key, value);
assert_object_equals(response.optionalValues, {
[key - 1]: null,
[key]: value,
[key + 1]: null,
});
});
}