// Copyright 2009 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

goog.provide('goog.structs.StringSetTest');
goog.setTestOnly('goog.structs.StringSetTest');

goog.require('goog.array');
goog.require('goog.iter');
goog.require('goog.structs.StringSet');
goog.require('goog.testing.asserts');
goog.require('goog.testing.jsunit');

var TEST_VALUES = [
  '', ' ', '  ', 'true', 'null', 'undefined', '0', 'new', 'constructor',
  'prototype', '__proto__', 'set', 'hasOwnProperty', 'toString', 'valueOf'
];

var TEST_VALUES_WITH_DUPLICATES = [
  '',          '',
  ' ',         '  ',
  'true',      true,
  'null',      null,
  'undefined', undefined,
  '0',         0,
  'new',       'constructor',
  'prototype', '__proto__',
  'set',       'hasOwnProperty',
  'toString',  'valueOf'
];

function testConstructor() {
  var empty = new goog.structs.StringSet;
  assertSameElements('elements in empty set', [], empty.getValues());

  var s = new goog.structs.StringSet(TEST_VALUES_WITH_DUPLICATES);
  assertSameElements(
      'duplicates are filtered out by their string value', TEST_VALUES,
      s.getValues());
}

function testConstructorAssertsThatObjectPrototypeHasNoEnumerableKeys() {
  assertNotThrows(goog.structs.StringSet);
  Object.prototype.foo = 0;
  try {
    assertThrows(goog.structs.StringSet);
  } finally {
    delete Object.prototype.foo;
  }
  assertNotThrows(goog.structs.StringSet);
}

function testOverridingObjectPrototypeToStringIsSafe() {
  var originalToString = Object.prototype.toString;
  Object.prototype.toString = function() { return 'object'; };
  try {
    assertEquals(0, new goog.structs.StringSet().getCount());
    assertFalse(new goog.structs.StringSet().contains('toString'));
  } finally {
    Object.prototype.toString = originalToString;
  }
}

function testAdd() {
  var s = new goog.structs.StringSet;
  goog.array.forEach(TEST_VALUES_WITH_DUPLICATES, s.add, s);
  assertSameElements(TEST_VALUES, s.getValues());
}

function testAddArray() {
  var s = new goog.structs.StringSet;
  s.addArray(TEST_VALUES_WITH_DUPLICATES);
  assertSameElements('added elements from array', TEST_VALUES, s.getValues());
}

function testAddSet() {
  var s = new goog.structs.StringSet;
  s.addSet(new goog.structs.StringSet([1, 2]));
  assertSameElements('empty set + {1, 2}', ['1', '2'], s.getValues());
  s.addSet(new goog.structs.StringSet([2, 3]));
  assertSameElements('{1, 2} + {2, 3}', ['1', '2', '3'], s.getValues());
}

function testClear() {
  var s = new goog.structs.StringSet([1, 2]);
  s.clear();
  assertSameElements('cleared set', [], s.getValues());
}

function testClone() {
  var s = new goog.structs.StringSet([1, 2]);
  var c = s.clone();
  assertSameElements('elements in clone', ['1', '2'], c.getValues());
  s.add(3);
  assertSameElements(
      'changing the original set does not affect the clone', ['1', '2'],
      c.getValues());
}

function testContains() {
  var e = new goog.structs.StringSet;
  goog.array.forEach(TEST_VALUES, function(element) {
    assertFalse('empty set does not contain ' + element, e.contains(element));
  });

  var s = new goog.structs.StringSet(TEST_VALUES);
  goog.array.forEach(TEST_VALUES_WITH_DUPLICATES, function(element) {
    assertTrue('s contains ' + element, s.contains(element));
  });
  assertFalse('s does not contain 42', s.contains(42));
}

function testContainsArray() {
  var s = new goog.structs.StringSet(TEST_VALUES);
  assertTrue('set contains empty array', s.containsArray([]));
  assertTrue(
      'set contains all elements of itself with some duplication',
      s.containsArray(TEST_VALUES_WITH_DUPLICATES));
  assertFalse('set does not contain 42', s.containsArray([42]));
}

function testEquals() {
  var s = new goog.structs.StringSet([1, 2]);
  assertTrue('set equals to itself', s.equals(s));
  assertTrue('set equals to its clone', s.equals(s.clone()));
  assertFalse(
      'set does not equal to its subset',
      s.equals(new goog.structs.StringSet([1])));
  assertFalse(
      'set does not equal to its superset',
      s.equals(new goog.structs.StringSet([1, 2, 3])));
}

function testForEach() {
  var s = new goog.structs.StringSet(TEST_VALUES);
  var values = [];
  var context = {};
  s.forEach(function(value, key, stringSet) {
    assertEquals('context of forEach callback', context, this);
    values.push(value);
    assertUndefined('key argument of forEach callback', key);
    assertEquals('set argument of forEach callback', s, stringSet);
  }, context);
  assertSameElements('values passed to forEach callback', TEST_VALUES, values);
}

function testGetCount() {
  var empty = new goog.structs.StringSet;
  assertEquals('count(empty set)', 0, empty.getCount());

  var s = new goog.structs.StringSet(TEST_VALUES);
  assertEquals('count(non-empty set)', TEST_VALUES.length, s.getCount());
}

function testGetDifference() {
  var s1 = new goog.structs.StringSet([1, 2]);
  var s2 = new goog.structs.StringSet([2, 3]);
  assertSameElements(
      '{1, 2} - {2, 3}', ['1'], s1.getDifference(s2).getValues());
}

function testGetIntersection() {
  var s1 = new goog.structs.StringSet([1, 2]);
  var s2 = new goog.structs.StringSet([2, 3]);
  assertSameElements(
      '{1, 2} * {2, 3}', ['2'], s1.getIntersection(s2).getValues());
}

function testGetSymmetricDifference() {
  var s1 = new goog.structs.StringSet([1, 2]);
  var s2 = new goog.structs.StringSet([2, 3]);
  assertSameElements(
      '{1, 2} sym.diff. {2, 3}', ['1', '3'],
      s1.getSymmetricDifference(s2).getValues());
}

function testGetUnion() {
  var s1 = new goog.structs.StringSet([1, 2]);
  var s2 = new goog.structs.StringSet([2, 3]);
  assertSameElements(
      '{1, 2} + {2, 3}', ['1', '2', '3'], s1.getUnion(s2).getValues());
}

function testIsDisjoint() {
  var s = new goog.structs.StringSet;
  var s12 = new goog.structs.StringSet([1, 2]);
  var s23 = new goog.structs.StringSet([2, 3]);
  var s34 = new goog.structs.StringSet([3, 4]);

  assertTrue('{} and {1, 2} are disjoint', s.isDisjoint(s12));
  assertTrue('{1, 2} and {3, 4} are disjoint', s12.isDisjoint(s34));
  assertFalse('{1, 2} and {2, 3} are not disjoint', s12.isDisjoint(s23));
}

function testIsEmpty() {
  assertTrue('empty set', new goog.structs.StringSet().isEmpty());
  assertFalse('non-empty set', new goog.structs.StringSet(['']).isEmpty());
}

function testIsSubsetOf() {
  var s1 = new goog.structs.StringSet([1]);
  var s12 = new goog.structs.StringSet([1, 2]);
  var s123 = new goog.structs.StringSet([1, 2, 3]);
  var s23 = new goog.structs.StringSet([2, 3]);

  assertTrue('{1, 2} is subset of {1, 2}', s12.isSubsetOf(s12));
  assertTrue('{1, 2} is subset of {1, 2, 3}', s12.isSubsetOf(s123));
  assertFalse('{1, 2} is not subset of {1}', s12.isSubsetOf(s1));
  assertFalse('{1, 2} is not subset of {2, 3}', s12.isSubsetOf(s23));
}

function testIsSupersetOf() {
  var s1 = new goog.structs.StringSet([1]);
  var s12 = new goog.structs.StringSet([1, 2]);
  var s123 = new goog.structs.StringSet([1, 2, 3]);
  var s23 = new goog.structs.StringSet([2, 3]);

  assertTrue('{1, 2} is superset of {1}', s12.isSupersetOf(s1));
  assertTrue('{1, 2} is superset of {1, 2}', s12.isSupersetOf(s12));
  assertFalse('{1, 2} is not superset of {1, 2, 3}', s12.isSupersetOf(s123));
  assertFalse('{1, 2} is not superset of {2, 3}', s12.isSupersetOf(s23));
}

function testRemove() {
  var n = new goog.structs.StringSet([1, 2]);
  assertFalse('3 not removed from {1, 2}', n.remove(3));
  assertSameElements('set == {1, 2}', ['1', '2'], n.getValues());
  assertTrue('2 removed from {1, 2}', n.remove(2));
  assertSameElements('set == {1}', ['1'], n.getValues());
  assertTrue('"1" removed from {1}', n.remove('1'));
  assertSameElements('set == {}', [], n.getValues());

  var s = new goog.structs.StringSet(TEST_VALUES);
  goog.array.forEach(TEST_VALUES, s.remove, s);
  assertSameElements('all special values have been removed', [], s.getValues());
}

function testRemoveArray() {
  var s = new goog.structs.StringSet(TEST_VALUES);
  s.removeArray(TEST_VALUES.slice(0, TEST_VALUES.length - 2));
  assertSameElements(
      'removed elements from array', TEST_VALUES.slice(TEST_VALUES.length - 2),
      s.getValues());
}

function testRemoveSet() {
  var s1 = new goog.structs.StringSet([1, 2]);
  var s2 = new goog.structs.StringSet([2, 3]);
  s1.removeSet(s2);
  assertSameElements('{1, 2} - {2, 3}', ['1'], s1.getValues());
}

function testIterator() {
  var s = new goog.structs.StringSet(TEST_VALUES_WITH_DUPLICATES);
  var values = [];
  goog.iter.forEach(s, function(value) { values.push(value); });
  assertSameElements(
      '__iterator__ takes all elements once', TEST_VALUES, values);
}
