blob: c52c668c3a8ab76851f323a8453d48948087ff68 [file] [log] [blame] [edit]
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://protobuf.dev/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
goog.require('goog.userAgent');
// CommonJS-LoadFromFile: protos/testbinary_pb proto.jspb.test
goog.require('proto.jspb.test.MapValueEnum');
goog.require('proto.jspb.test.MapValueMessage');
goog.require('proto.jspb.test.TestMapFields');
goog.require('proto.jspb.test.TestMapFieldsOptionalKeys');
goog.require('proto.jspb.test.TestMapFieldsOptionalValues');
goog.require('proto.jspb.test.MapEntryOptionalKeysStringKey');
goog.require('proto.jspb.test.MapEntryOptionalKeysInt32Key');
goog.require('proto.jspb.test.MapEntryOptionalKeysInt64Key');
goog.require('proto.jspb.test.MapEntryOptionalKeysBoolKey');
goog.require('proto.jspb.test.MapEntryOptionalValuesStringValue');
goog.require('proto.jspb.test.MapEntryOptionalValuesInt32Value');
goog.require('proto.jspb.test.MapEntryOptionalValuesInt64Value');
goog.require('proto.jspb.test.MapEntryOptionalValuesBoolValue');
goog.require('proto.jspb.test.MapEntryOptionalValuesDoubleValue');
goog.require('proto.jspb.test.MapEntryOptionalValuesEnumValue');
goog.require('proto.jspb.test.MapEntryOptionalValuesMessageValue');
// CommonJS-LoadFromFile: protos/test_pb proto.jspb.test
goog.require('proto.jspb.test.MapValueMessageNoBinary');
goog.require('proto.jspb.test.TestMapFieldsNoBinary');
goog.requireType('jspb.Map');
/**
* Helper: check that the given map has exactly this set of (sorted) entries.
* @param {!jspb.Map} map
* @param {!Array<!Array<?>>} entries
*/
function checkMapEquals(map, entries) {
const arr = map.toArray();
expect(entries.length).toEqual(arr.length);
for (let i = 0; i < arr.length; i++) {
if (Array.isArray(arr[i])) {
expect(Array.isArray(entries[i])).toBeTrue();;
expect(entries[i]).toEqual(arr[i]);
} else {
expect(entries[i]).toEqual(arr[i]);
}
}
}
/**
* Converts an ES6 iterator to an array.
* @template T
* @param {!Iterator<T>} iter an iterator
* @return {!Array<T>}
*/
function toArray(iter) {
const arr = [];
while (true) {
const val = iter.next();
if (val.done) {
break;
}
arr.push(val.value);
}
return arr;
}
/**
* Helper: generate test methods for this TestMapFields class.
* @param {?} msgInfo
* @param {?} submessageCtor
* @param {string} suffix
*/
function makeTests(msgInfo, submessageCtor, suffix) {
/**
* Helper: fill all maps on a TestMapFields.
* @param {?} msg
*/
const fillMapFields = function (msg) {
msg.getMapStringStringMap().set('asdf', 'jkl;').set('key 2', 'hello world');
msg.getMapStringInt32Map().set('a', 1).set('b', -2);
msg.getMapStringInt64Map().set('c', 0x100000000).set('d', 0x200000000);
msg.getMapStringBoolMap().set('e', true).set('f', false);
msg.getMapStringDoubleMap().set('g', 3.14159).set('h', 2.71828);
msg.getMapStringEnumMap()
.set('i', proto.jspb.test.MapValueEnum.MAP_VALUE_BAR)
.set('j', proto.jspb.test.MapValueEnum.MAP_VALUE_BAZ);
msg.getMapStringMsgMap()
.set('k', new submessageCtor())
.set('l', new submessageCtor());
msg.getMapStringMsgMap().get('k').setFoo(42);
msg.getMapStringMsgMap().get('l').setFoo(84);
msg.getMapInt32StringMap().set(-1, 'a').set(42, 'b');
msg.getMapInt64StringMap()
.set(0x123456789abc, 'c')
.set(0xcba987654321, 'd');
msg.getMapBoolStringMap().set(false, 'e').set(true, 'f');
};
/**
* Helper: check all maps on a TestMapFields.
* @param {?} msg
*/
const checkMapFields = function (msg) {
checkMapEquals(
msg.getMapStringStringMap(),
[['asdf', 'jkl;'], ['key 2', 'hello world']]);
checkMapEquals(msg.getMapStringInt32Map(), [['a', 1], ['b', -2]]);
checkMapEquals(
msg.getMapStringInt64Map(), [['c', 0x100000000], ['d', 0x200000000]]);
checkMapEquals(msg.getMapStringBoolMap(), [['e', true], ['f', false]]);
checkMapEquals(
msg.getMapStringDoubleMap(), [['g', 3.14159], ['h', 2.71828]]);
checkMapEquals(msg.getMapStringEnumMap(), [
['i', proto.jspb.test.MapValueEnum.MAP_VALUE_BAR],
['j', proto.jspb.test.MapValueEnum.MAP_VALUE_BAZ]
]);
checkMapEquals(msg.getMapInt32StringMap(), [[-1, 'a'], [42, 'b']]);
checkMapEquals(
msg.getMapInt64StringMap(),
[[0x123456789abc, 'c'], [0xcba987654321, 'd']]);
checkMapEquals(msg.getMapBoolStringMap(), [[false, 'e'], [true, 'f']]);
expect(msg.getMapStringMsgMap().getLength()).toEqual(2);
expect(msg.getMapStringMsgMap().get('k').getFoo()).toEqual(42);
expect(msg.getMapStringMsgMap().get('l').getFoo()).toEqual(84);
const entries = toArray(msg.getMapStringMsgMap().entries());
expect(entries.length).toEqual(2);
entries.forEach(function (entry) {
const key = entry[0];
const val = entry[1];
expect(msg.getMapStringMsgMap().get(key)).toEqual(val);
});
msg.getMapStringMsgMap().forEach(function (val, key) {
expect(msg.getMapStringMsgMap().get(key)).toEqual(val);
});
};
it('testMapStringStringField' + suffix, () => {
let msg = new msgInfo.constructor();
expect(msg.getMapStringStringMap().getLength()).toEqual(0);
expect(msg.getMapStringInt32Map().getLength()).toEqual(0);
expect(msg.getMapStringInt64Map().getLength()).toEqual(0);
expect(msg.getMapStringBoolMap().getLength()).toEqual(0);
expect(msg.getMapStringDoubleMap().getLength()).toEqual(0);
expect(msg.getMapStringEnumMap().getLength()).toEqual(0);
expect(msg.getMapStringMsgMap().getLength()).toEqual(0);
// Re-create to clear out any internally-cached wrappers, etc.
msg = new msgInfo.constructor();
const m = msg.getMapStringStringMap();
expect(m.has('asdf')).toBeFalse()
expect(m.get('asdf')).toBeUndefined()
m.set('asdf', 'hello world');
expect(m.has('asdf')).toBeTrue();
expect(m.get('asdf')).toEqual('hello world');
m.set('jkl;', 'key 2');
expect(m.has('jkl;')).toBeTrue();
expect(m.get('jkl;')).toEqual('key 2');
expect(m.getLength()).toEqual(2);
let it = m.entries();
expect(it.next().value).toEqual(['asdf', 'hello world']);
expect(it.next().value).toEqual(['jkl;', 'key 2']);
expect(it.next().done).toBeTrue();
checkMapEquals(m, [['asdf', 'hello world'], ['jkl;', 'key 2']]);
m.del('jkl;');
expect(m.has('jkl;')).toBeFalse()
expect(m.get('jkl;')).toBeUndefined()
expect(m.getLength()).toEqual(1);
it = m.keys();
expect(it.next().value).toEqual('asdf');
expect(it.next().done).toBeTrue();
it = m.values();
expect(it.next().value).toEqual('hello world');
expect(it.next().done).toBeTrue();
let count = 0;
m.forEach(function (value, key, map) {
expect(m).toEqual(map);
expect(key).toEqual('asdf');
expect(value).toEqual('hello world');
count++;
});
expect(count).toEqual(1);
m.clear();
expect(m.getLength()).toEqual(0);
});
/**
* Tests operations on maps with all key and value types.
*/
it('testAllMapTypes' + suffix, () => {
const msg = new msgInfo.constructor();
fillMapFields(msg);
checkMapFields(msg);
});
if (msgInfo.deserializeBinary) {
/**
* Tests serialization and deserialization in binary format.
*/
it('testBinaryFormat' + suffix, () => {
if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(10)) {
// IE8/9 currently doesn't support binary format because they lack
// TypedArray.
return;
}
// Check that the format is correct.
let msg = new msgInfo.constructor();
msg.getMapStringStringMap().set('A', 'a');
let serialized = msg.serializeBinary();
const expectedSerialized = [
0x0a, 0x6, // field 1 (map_string_string), delimited, length 6
0x0a, 0x1, // field 1 in submessage (key), delimited, length 1
0x41, // ASCII 'A'
0x12, 0x1, // field 2 in submessage (value), delimited, length 1
0x61 // ASCII 'a'
];
expect(expectedSerialized.length).toEqual(serialized.length);
for (let i = 0; i < serialized.length; i++) {
expect(expectedSerialized[i]).toEqual(serialized[i]);
}
// Check that all map fields successfully round-trip.
msg = new msgInfo.constructor();
fillMapFields(msg);
serialized = msg.serializeBinary();
const decoded = msgInfo.deserializeBinary(serialized);
checkMapFields(decoded);
});
/**
* Tests deserialization of undefined map keys go to default values in
* binary format.
*/
it('testMapDeserializationForUndefinedKeys', () => {
const testMessageOptionalKeys =
new proto.jspb.test.TestMapFieldsOptionalKeys();
const mapEntryStringKey =
new proto.jspb.test.MapEntryOptionalKeysStringKey();
mapEntryStringKey.setValue('a');
testMessageOptionalKeys.setMapStringString(mapEntryStringKey);
const mapEntryInt32Key = new proto.jspb.test.MapEntryOptionalKeysInt32Key();
mapEntryInt32Key.setValue('b');
testMessageOptionalKeys.setMapInt32String(mapEntryInt32Key);
const mapEntryInt64Key = new proto.jspb.test.MapEntryOptionalKeysInt64Key();
mapEntryInt64Key.setValue('c');
testMessageOptionalKeys.setMapInt64String(mapEntryInt64Key);
const mapEntryBoolKey = new proto.jspb.test.MapEntryOptionalKeysBoolKey();
mapEntryBoolKey.setValue('d');
testMessageOptionalKeys.setMapBoolString(mapEntryBoolKey);
const deserializedMessage =
msgInfo.deserializeBinary(testMessageOptionalKeys.serializeBinary());
checkMapEquals(deserializedMessage.getMapStringStringMap(), [['', 'a']]);
checkMapEquals(deserializedMessage.getMapInt32StringMap(), [[0, 'b']]);
checkMapEquals(deserializedMessage.getMapInt64StringMap(), [[0, 'c']]);
checkMapEquals(deserializedMessage.getMapBoolStringMap(), [[false, 'd']]);
});
/**
* Tests deserialization of undefined map values go to default values in
* binary format.
*/
it('testMapDeserializationForUndefinedValues', () => {
const testMessageOptionalValues =
new proto.jspb.test.TestMapFieldsOptionalValues();
const mapEntryStringValue =
new proto.jspb.test.MapEntryOptionalValuesStringValue();
mapEntryStringValue.setKey('a');
testMessageOptionalValues.setMapStringString(mapEntryStringValue);
const mapEntryInt32Value =
new proto.jspb.test.MapEntryOptionalValuesInt32Value();
mapEntryInt32Value.setKey('b');
testMessageOptionalValues.setMapStringInt32(mapEntryInt32Value);
const mapEntryInt64Value =
new proto.jspb.test.MapEntryOptionalValuesInt64Value();
mapEntryInt64Value.setKey('c');
testMessageOptionalValues.setMapStringInt64(mapEntryInt64Value);
const mapEntryBoolValue =
new proto.jspb.test.MapEntryOptionalValuesBoolValue();
mapEntryBoolValue.setKey('d');
testMessageOptionalValues.setMapStringBool(mapEntryBoolValue);
const mapEntryDoubleValue =
new proto.jspb.test.MapEntryOptionalValuesDoubleValue();
mapEntryDoubleValue.setKey('e');
testMessageOptionalValues.setMapStringDouble(mapEntryDoubleValue);
const mapEntryEnumValue =
new proto.jspb.test.MapEntryOptionalValuesEnumValue();
mapEntryEnumValue.setKey('f');
testMessageOptionalValues.setMapStringEnum(mapEntryEnumValue);
const mapEntryMessageValue =
new proto.jspb.test.MapEntryOptionalValuesMessageValue();
mapEntryMessageValue.setKey('g');
testMessageOptionalValues.setMapStringMsg(mapEntryMessageValue);
const deserializedMessage = msgInfo.deserializeBinary(
testMessageOptionalValues.serializeBinary());
checkMapEquals(deserializedMessage.getMapStringStringMap(), [['a', '']]);
checkMapEquals(deserializedMessage.getMapStringInt32Map(), [['b', 0]]);
checkMapEquals(deserializedMessage.getMapStringInt64Map(), [['c', 0]]);
checkMapEquals(deserializedMessage.getMapStringBoolMap(), [['d', false]]);
checkMapEquals(deserializedMessage.getMapStringDoubleMap(), [['e', 0.0]]);
checkMapEquals(deserializedMessage.getMapStringEnumMap(), [['f', 0]]);
checkMapEquals(deserializedMessage.getMapStringMsgMap(), [['g', []]]);
});
}
/**
* Exercises the lazy map<->underlying array sync.
*/
it('testLazyMapSync' + suffix, () => {
// Start with a JSPB array containing a few map entries.
const entries = [['a', 'entry 1'], ['c', 'entry 2'], ['b', 'entry 3']];
const msg = new msgInfo.constructor([entries]);
expect(entries.length).toEqual(3);
expect(entries[0][0]).toEqual('a');
expect(entries[1][0]).toEqual('c');
expect(entries[2][0]).toEqual('b');
msg.getMapStringStringMap().del('a');
expect(entries.length).toEqual(3); // not yet sync'd
msg.toArray(); // force a sync
expect(entries.length).toEqual(2);
expect(entries[0][0]).toEqual('b'); // now in sorted order
expect(entries[1][0]).toEqual('c');
const a = msg.toArray();
expect(entries).toEqual(a[0]); // retains original reference
});
/**
* Returns IteratorIterables for entries(), keys() and values().
*/
it('testIteratorIterables' + suffix, () => {
const msg = new msgInfo.constructor();
const m = msg.getMapStringStringMap();
m.set('key1', 'value1');
m.set('key2', 'value2');
const entryIterator = m.entries();
expect(entryIterator.next().value).toEqual(['key1', 'value1']);
expect(entryIterator.next().value).toEqual(['key2', 'value2']);
expect(entryIterator.next().done).toBeTrue();
try {
const entryIterable = m.entries()[Symbol.iterator]();
expect(entryIterable.next().value).toEqual(['key1', 'value1']);
expect(entryIterable.next().value).toEqual(['key2', 'value2']);
expect(entryIterable.next().done).toBeTrue();
} catch (err) {
// jspb.Map.ArrayIteratorIterable_.prototype[Symbol.iterator] may be
// undefined in some environment.
if (err.name != 'TypeError' && err.name != 'ReferenceError') {
throw err;
}
}
const keyIterator = m.keys();
expect(keyIterator.next().value).toEqual('key1');
expect(keyIterator.next().value).toEqual('key2');
expect(keyIterator.next().done).toBeTrue();
try {
const keyIterable = m.keys()[Symbol.iterator]();
expect(keyIterable.next().value).toEqual('key1');
expect(keyIterable.next().value).toEqual('key2');
expect(keyIterable.next().done).toBeTrue();
} catch (err) {
// jspb.Map.ArrayIteratorIterable_.prototype[Symbol.iterator] may be
// undefined in some environment.
if (err.name != 'TypeError' && err.name != 'ReferenceError') {
throw err;
}
}
const valueIterator = m.values();
expect(valueIterator.next().value).toEqual('value1');
expect(valueIterator.next().value).toEqual('value2');
expect(valueIterator.next().done).toBeTrue();
try {
const valueIterable = m.values()[Symbol.iterator]();
expect(valueIterable.next().value).toEqual('value1');
expect(valueIterable.next().value).toEqual('value2');
expect(valueIterable.next().done).toBeTrue();
} catch (err) {
// jspb.Map.ArrayIteratorIterable_.prototype[Symbol.iterator] may be
// undefined in some environment.
if (err.name != 'TypeError' && err.name != 'ReferenceError') {
throw err;
}
}
});
}
describe('mapsTest', () => {
makeTests(
{
constructor: proto.jspb.test.TestMapFields,
deserializeBinary: proto.jspb.test.TestMapFields.deserializeBinary
},
proto.jspb.test.MapValueMessage, '_Binary');
makeTests(
{
constructor: proto.jspb.test.TestMapFieldsNoBinary,
deserializeBinary: null
},
proto.jspb.test.MapValueMessageNoBinary, '_NoBinary');
});