• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Protocol Buffers - Google's data interchange format
2// Copyright 2008 Google Inc.  All rights reserved.
3// https://developers.google.com/protocol-buffers/
4//
5// Redistribution and use in source and binary forms, with or without
6// modification, are permitted provided that the following conditions are
7// met:
8//
9//     * Redistributions of source code must retain the above copyright
10// notice, this list of conditions and the following disclaimer.
11//     * Redistributions in binary form must reproduce the above
12// copyright notice, this list of conditions and the following disclaimer
13// in the documentation and/or other materials provided with the
14// distribution.
15//     * Neither the name of Google Inc. nor the names of its
16// contributors may be used to endorse or promote products derived from
17// this software without specific prior written permission.
18//
19// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31goog.require('goog.testing.asserts');
32goog.require('goog.userAgent');
33
34// CommonJS-LoadFromFile: testbinary_pb proto.jspb.test
35goog.require('proto.jspb.test.MapValueEnum');
36goog.require('proto.jspb.test.MapValueMessage');
37goog.require('proto.jspb.test.TestMapFields');
38goog.require('proto.jspb.test.TestMapFieldsOptionalKeys');
39goog.require('proto.jspb.test.TestMapFieldsOptionalValues');
40goog.require('proto.jspb.test.MapEntryOptionalKeysStringKey');
41goog.require('proto.jspb.test.MapEntryOptionalKeysInt32Key');
42goog.require('proto.jspb.test.MapEntryOptionalKeysInt64Key');
43goog.require('proto.jspb.test.MapEntryOptionalKeysBoolKey');
44goog.require('proto.jspb.test.MapEntryOptionalValuesStringValue');
45goog.require('proto.jspb.test.MapEntryOptionalValuesInt32Value');
46goog.require('proto.jspb.test.MapEntryOptionalValuesInt64Value');
47goog.require('proto.jspb.test.MapEntryOptionalValuesBoolValue');
48goog.require('proto.jspb.test.MapEntryOptionalValuesDoubleValue');
49goog.require('proto.jspb.test.MapEntryOptionalValuesEnumValue');
50goog.require('proto.jspb.test.MapEntryOptionalValuesMessageValue');
51
52// CommonJS-LoadFromFile: test_pb proto.jspb.test
53goog.require('proto.jspb.test.MapValueMessageNoBinary');
54goog.require('proto.jspb.test.TestMapFieldsNoBinary');
55
56goog.requireType('jspb.Map');
57
58/**
59 * Helper: check that the given map has exactly this set of (sorted) entries.
60 * @param {!jspb.Map} map
61 * @param {!Array<!Array<?>>} entries
62 */
63function checkMapEquals(map, entries) {
64  var arr = map.toArray();
65  assertEquals(arr.length, entries.length);
66  for (var i = 0; i < arr.length; i++) {
67    if (Array.isArray(arr[i])) {
68      assertTrue(Array.isArray(entries[i]));
69      assertArrayEquals(arr[i], entries[i]);
70    } else {
71      assertElementsEquals(arr[i], entries[i]);
72    }
73  }
74}
75
76/**
77 * Converts an ES6 iterator to an array.
78 * @template T
79 * @param {!Iterator<T>} iter an iterator
80 * @return {!Array<T>}
81 */
82function toArray(iter) {
83  var arr = [];
84  while (true) {
85    var val = iter.next();
86    if (val.done) {
87      break;
88    }
89    arr.push(val.value);
90  }
91  return arr;
92}
93
94
95/**
96 * Helper: generate test methods for this TestMapFields class.
97 * @param {?} msgInfo
98 * @param {?} submessageCtor
99 * @param {string} suffix
100 */
101function makeTests(msgInfo, submessageCtor, suffix) {
102  /**
103   * Helper: fill all maps on a TestMapFields.
104   * @param {?} msg
105   */
106  var fillMapFields = function(msg) {
107    msg.getMapStringStringMap().set('asdf', 'jkl;').set('key 2', 'hello world');
108    msg.getMapStringInt32Map().set('a', 1).set('b', -2);
109    msg.getMapStringInt64Map().set('c', 0x100000000).set('d', 0x200000000);
110    msg.getMapStringBoolMap().set('e', true).set('f', false);
111    msg.getMapStringDoubleMap().set('g', 3.14159).set('h', 2.71828);
112    msg.getMapStringEnumMap()
113        .set('i', proto.jspb.test.MapValueEnum.MAP_VALUE_BAR)
114        .set('j', proto.jspb.test.MapValueEnum.MAP_VALUE_BAZ);
115    msg.getMapStringMsgMap()
116        .set('k', new submessageCtor())
117        .set('l', new submessageCtor());
118    msg.getMapStringMsgMap().get('k').setFoo(42);
119    msg.getMapStringMsgMap().get('l').setFoo(84);
120    msg.getMapInt32StringMap().set(-1, 'a').set(42, 'b');
121    msg.getMapInt64StringMap()
122        .set(0x123456789abc, 'c')
123        .set(0xcba987654321, 'd');
124    msg.getMapBoolStringMap().set(false, 'e').set(true, 'f');
125  };
126
127  /**
128   * Helper: check all maps on a TestMapFields.
129   * @param {?} msg
130   */
131  var checkMapFields = function(msg) {
132    checkMapEquals(
133        msg.getMapStringStringMap(),
134        [['asdf', 'jkl;'], ['key 2', 'hello world']]);
135    checkMapEquals(msg.getMapStringInt32Map(), [['a', 1], ['b', -2]]);
136    checkMapEquals(
137        msg.getMapStringInt64Map(), [['c', 0x100000000], ['d', 0x200000000]]);
138    checkMapEquals(msg.getMapStringBoolMap(), [['e', true], ['f', false]]);
139    checkMapEquals(
140        msg.getMapStringDoubleMap(), [['g', 3.14159], ['h', 2.71828]]);
141    checkMapEquals(msg.getMapStringEnumMap(), [
142      ['i', proto.jspb.test.MapValueEnum.MAP_VALUE_BAR],
143      ['j', proto.jspb.test.MapValueEnum.MAP_VALUE_BAZ]
144    ]);
145    checkMapEquals(msg.getMapInt32StringMap(), [[-1, 'a'], [42, 'b']]);
146    checkMapEquals(
147        msg.getMapInt64StringMap(),
148        [[0x123456789abc, 'c'], [0xcba987654321, 'd']]);
149    checkMapEquals(msg.getMapBoolStringMap(), [[false, 'e'], [true, 'f']]);
150
151    assertEquals(msg.getMapStringMsgMap().getLength(), 2);
152    assertEquals(msg.getMapStringMsgMap().get('k').getFoo(), 42);
153    assertEquals(msg.getMapStringMsgMap().get('l').getFoo(), 84);
154
155    var entries = toArray(msg.getMapStringMsgMap().entries());
156    assertEquals(entries.length, 2);
157    entries.forEach(function(entry) {
158      var key = entry[0];
159      var val = entry[1];
160      assert(val === msg.getMapStringMsgMap().get(key));
161    });
162
163    msg.getMapStringMsgMap().forEach(function(val, key) {
164      assert(val === msg.getMapStringMsgMap().get(key));
165    });
166  };
167
168  it('testMapStringStringField' + suffix, function() {
169    var msg = new msgInfo.constructor();
170    assertEquals(msg.getMapStringStringMap().getLength(), 0);
171    assertEquals(msg.getMapStringInt32Map().getLength(), 0);
172    assertEquals(msg.getMapStringInt64Map().getLength(), 0);
173    assertEquals(msg.getMapStringBoolMap().getLength(), 0);
174    assertEquals(msg.getMapStringDoubleMap().getLength(), 0);
175    assertEquals(msg.getMapStringEnumMap().getLength(), 0);
176    assertEquals(msg.getMapStringMsgMap().getLength(), 0);
177
178    // Re-create to clear out any internally-cached wrappers, etc.
179    msg = new msgInfo.constructor();
180    var m = msg.getMapStringStringMap();
181    assertEquals(m.has('asdf'), false);
182    assertEquals(m.get('asdf'), undefined);
183    m.set('asdf', 'hello world');
184    assertEquals(m.has('asdf'), true);
185    assertEquals(m.get('asdf'), 'hello world');
186    m.set('jkl;', 'key 2');
187    assertEquals(m.has('jkl;'), true);
188    assertEquals(m.get('jkl;'), 'key 2');
189    assertEquals(m.getLength(), 2);
190    var it = m.entries();
191    assertElementsEquals(it.next().value, ['asdf', 'hello world']);
192    assertElementsEquals(it.next().value, ['jkl;', 'key 2']);
193    assertEquals(it.next().done, true);
194    checkMapEquals(m, [['asdf', 'hello world'], ['jkl;', 'key 2']]);
195    m.del('jkl;');
196    assertEquals(m.has('jkl;'), false);
197    assertEquals(m.get('jkl;'), undefined);
198    assertEquals(m.getLength(), 1);
199    it = m.keys();
200    assertEquals(it.next().value, 'asdf');
201    assertEquals(it.next().done, true);
202    it = m.values();
203    assertEquals(it.next().value, 'hello world');
204    assertEquals(it.next().done, true);
205
206    var count = 0;
207    m.forEach(function(value, key, map) {
208      assertEquals(map, m);
209      assertEquals(key, 'asdf');
210      assertEquals(value, 'hello world');
211      count++;
212    });
213    assertEquals(count, 1);
214
215    m.clear();
216    assertEquals(m.getLength(), 0);
217  });
218
219
220  /**
221   * Tests operations on maps with all key and value types.
222   */
223  it('testAllMapTypes' + suffix, function() {
224    var msg = new msgInfo.constructor();
225    fillMapFields(msg);
226    checkMapFields(msg);
227  });
228
229
230  if (msgInfo.deserializeBinary) {
231    /**
232     * Tests serialization and deserialization in binary format.
233     */
234    it('testBinaryFormat' + suffix, function() {
235      if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(10)) {
236        // IE8/9 currently doesn't support binary format because they lack
237        // TypedArray.
238        return;
239      }
240
241      // Check that the format is correct.
242      var msg = new msgInfo.constructor();
243      msg.getMapStringStringMap().set('A', 'a');
244      var serialized = msg.serializeBinary();
245      var expectedSerialized = [
246        0x0a, 0x6,  // field 1 (map_string_string), delimited, length 6
247        0x0a, 0x1,  // field 1 in submessage (key), delimited, length 1
248        0x41,       // ASCII 'A'
249        0x12, 0x1,  // field 2 in submessage (value), delimited, length 1
250        0x61        // ASCII 'a'
251      ];
252      assertEquals(serialized.length, expectedSerialized.length);
253      for (var i = 0; i < serialized.length; i++) {
254        assertEquals(serialized[i], expectedSerialized[i]);
255      }
256
257      // Check that all map fields successfully round-trip.
258      msg = new msgInfo.constructor();
259      fillMapFields(msg);
260      serialized = msg.serializeBinary();
261      var decoded = msgInfo.deserializeBinary(serialized);
262      checkMapFields(decoded);
263    });
264
265    /**
266     * Tests deserialization of undefined map keys go to default values in
267     * binary format.
268     */
269    it('testMapDeserializationForUndefinedKeys', function() {
270      var testMessageOptionalKeys =
271          new proto.jspb.test.TestMapFieldsOptionalKeys();
272      var mapEntryStringKey =
273          new proto.jspb.test.MapEntryOptionalKeysStringKey();
274      mapEntryStringKey.setValue('a');
275      testMessageOptionalKeys.setMapStringString(mapEntryStringKey);
276      var mapEntryInt32Key = new proto.jspb.test.MapEntryOptionalKeysInt32Key();
277      mapEntryInt32Key.setValue('b');
278      testMessageOptionalKeys.setMapInt32String(mapEntryInt32Key);
279      var mapEntryInt64Key = new proto.jspb.test.MapEntryOptionalKeysInt64Key();
280      mapEntryInt64Key.setValue('c');
281      testMessageOptionalKeys.setMapInt64String(mapEntryInt64Key);
282      var mapEntryBoolKey = new proto.jspb.test.MapEntryOptionalKeysBoolKey();
283      mapEntryBoolKey.setValue('d');
284      testMessageOptionalKeys.setMapBoolString(mapEntryBoolKey);
285      var deserializedMessage =
286          msgInfo.deserializeBinary(testMessageOptionalKeys.serializeBinary());
287      checkMapEquals(deserializedMessage.getMapStringStringMap(), [['', 'a']]);
288      checkMapEquals(deserializedMessage.getMapInt32StringMap(), [[0, 'b']]);
289      checkMapEquals(deserializedMessage.getMapInt64StringMap(), [[0, 'c']]);
290      checkMapEquals(deserializedMessage.getMapBoolStringMap(), [[false, 'd']]);
291    });
292
293    /**
294     * Tests deserialization of undefined map values go to default values in
295     * binary format.
296     */
297    it('testMapDeserializationForUndefinedValues', function() {
298      var testMessageOptionalValues =
299          new proto.jspb.test.TestMapFieldsOptionalValues();
300      var mapEntryStringValue =
301          new proto.jspb.test.MapEntryOptionalValuesStringValue();
302      mapEntryStringValue.setKey('a');
303      testMessageOptionalValues.setMapStringString(mapEntryStringValue);
304      var mapEntryInt32Value =
305          new proto.jspb.test.MapEntryOptionalValuesInt32Value();
306      mapEntryInt32Value.setKey('b');
307      testMessageOptionalValues.setMapStringInt32(mapEntryInt32Value);
308      var mapEntryInt64Value =
309          new proto.jspb.test.MapEntryOptionalValuesInt64Value();
310      mapEntryInt64Value.setKey('c');
311      testMessageOptionalValues.setMapStringInt64(mapEntryInt64Value);
312      var mapEntryBoolValue =
313          new proto.jspb.test.MapEntryOptionalValuesBoolValue();
314      mapEntryBoolValue.setKey('d');
315      testMessageOptionalValues.setMapStringBool(mapEntryBoolValue);
316      var mapEntryDoubleValue =
317          new proto.jspb.test.MapEntryOptionalValuesDoubleValue();
318      mapEntryDoubleValue.setKey('e');
319      testMessageOptionalValues.setMapStringDouble(mapEntryDoubleValue);
320      var mapEntryEnumValue =
321          new proto.jspb.test.MapEntryOptionalValuesEnumValue();
322      mapEntryEnumValue.setKey('f');
323      testMessageOptionalValues.setMapStringEnum(mapEntryEnumValue);
324      var mapEntryMessageValue =
325          new proto.jspb.test.MapEntryOptionalValuesMessageValue();
326      mapEntryMessageValue.setKey('g');
327      testMessageOptionalValues.setMapStringMsg(mapEntryMessageValue);
328      var deserializedMessage = msgInfo.deserializeBinary(
329          testMessageOptionalValues.serializeBinary());
330      checkMapEquals(deserializedMessage.getMapStringStringMap(), [['a', '']]);
331      checkMapEquals(deserializedMessage.getMapStringInt32Map(), [['b', 0]]);
332      checkMapEquals(deserializedMessage.getMapStringInt64Map(), [['c', 0]]);
333      checkMapEquals(deserializedMessage.getMapStringBoolMap(), [['d', false]]);
334      checkMapEquals(deserializedMessage.getMapStringDoubleMap(), [['e', 0.0]]);
335      checkMapEquals(deserializedMessage.getMapStringEnumMap(), [['f', 0]]);
336      checkMapEquals(deserializedMessage.getMapStringMsgMap(), [['g', []]]);
337    });
338  }
339
340
341  /**
342   * Exercises the lazy map<->underlying array sync.
343   */
344  it('testLazyMapSync' + suffix, function() {
345    // Start with a JSPB array containing a few map entries.
346    var entries = [['a', 'entry 1'], ['c', 'entry 2'], ['b', 'entry 3']];
347    var msg = new msgInfo.constructor([entries]);
348    assertEquals(entries.length, 3);
349    assertEquals(entries[0][0], 'a');
350    assertEquals(entries[1][0], 'c');
351    assertEquals(entries[2][0], 'b');
352    msg.getMapStringStringMap().del('a');
353    assertEquals(entries.length, 3);  // not yet sync'd
354    msg.toArray();                    // force a sync
355    assertEquals(entries.length, 2);
356    assertEquals(entries[0][0], 'b');  // now in sorted order
357    assertEquals(entries[1][0], 'c');
358
359    var a = msg.toArray();
360    assertEquals(a[0], entries);  // retains original reference
361  });
362
363  /**
364   * Returns IteratorIterables for entries(), keys() and values().
365   */
366  it('testIteratorIterables' + suffix, function() {
367    var msg = new msgInfo.constructor();
368    var m = msg.getMapStringStringMap();
369    m.set('key1', 'value1');
370    m.set('key2', 'value2');
371    var entryIterator = m.entries();
372    assertElementsEquals(entryIterator.next().value, ['key1', 'value1']);
373    assertElementsEquals(entryIterator.next().value, ['key2', 'value2']);
374    assertEquals(entryIterator.next().done, true);
375
376    try {
377      var entryIterable = m.entries()[Symbol.iterator]();
378      assertElementsEquals(entryIterable.next().value, ['key1', 'value1']);
379      assertElementsEquals(entryIterable.next().value, ['key2', 'value2']);
380      assertEquals(entryIterable.next().done, true);
381    } catch (err) {
382      // jspb.Map.ArrayIteratorIterable_.prototype[Symbol.iterator] may be
383      // undefined in some environment.
384      if (err.name != 'TypeError' && err.name != 'ReferenceError') {
385        throw err;
386      }
387    }
388
389    var keyIterator = m.keys();
390    assertEquals(keyIterator.next().value, 'key1');
391    assertEquals(keyIterator.next().value, 'key2');
392    assertEquals(keyIterator.next().done, true);
393
394    try {
395      var keyIterable = m.keys()[Symbol.iterator]();
396      assertEquals(keyIterable.next().value, 'key1');
397      assertEquals(keyIterable.next().value, 'key2');
398      assertEquals(keyIterable.next().done, true);
399    } catch (err) {
400      // jspb.Map.ArrayIteratorIterable_.prototype[Symbol.iterator] may be
401      // undefined in some environment.
402      if (err.name != 'TypeError' && err.name != 'ReferenceError') {
403        throw err;
404      }
405    }
406    var valueIterator = m.values();
407    assertEquals(valueIterator.next().value, 'value1');
408    assertEquals(valueIterator.next().value, 'value2');
409    assertEquals(valueIterator.next().done, true);
410
411    try {
412      var valueIterable = m.values()[Symbol.iterator]();
413      assertEquals(valueIterable.next().value, 'value1');
414      assertEquals(valueIterable.next().value, 'value2');
415      assertEquals(valueIterable.next().done, true);
416    } catch (err) {
417      // jspb.Map.ArrayIteratorIterable_.prototype[Symbol.iterator] may be
418      // undefined in some environment.
419      if (err.name != 'TypeError' && err.name != 'ReferenceError') {
420        throw err;
421      }
422    }
423  });
424}
425
426describe('mapsTest', function() {
427  makeTests(
428      {
429        constructor: proto.jspb.test.TestMapFields,
430        deserializeBinary: proto.jspb.test.TestMapFields.deserializeBinary
431      },
432      proto.jspb.test.MapValueMessage, '_Binary');
433  makeTests(
434      {
435        constructor: proto.jspb.test.TestMapFieldsNoBinary,
436        deserializeBinary: null
437      },
438      proto.jspb.test.MapValueMessageNoBinary, '_NoBinary');
439});
440