// 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'); });