lib/subject/setSubject.js

/** @module certainty */
/* global Set */
var Subject = require('./subject');
var EachSubject = require('./eachSubject');

/** Subclass of Subject which provides assertions methods for Set types.
    @param {FailureStrategy} failureStrategy The failure strategy to use when an assertion fails.
    @param {object} value The value being checked.
    @constructor
    @extends Subject
*/
function SetSubject(failureStrategy, value) {
  Subject.call(this, failureStrategy, value);
}
SetSubject.prototype = Object.create(Subject.prototype);
SetSubject.prototype.constructor = SetSubject;

/** Ensure that the set is empty.
    @return {SetSubject} `this` for chaining.
*/
SetSubject.prototype.isEmpty = function () {
  if (this.value.size !== 0) {
    this.fail('Expected ' + this.describe() + ' to be an empty set.');
  }
  return this;
};

/** Ensure that the set is non-empty.
    @return {SetSubject} `this` for chaining.
*/
SetSubject.prototype.isNotEmpty = function () {
  if (this.value.size === 0) {
    this.fail('Expected ' + this.describe() + ' to be a non-empty set.');
  }
  return this;
};

/** Ensure that the set is the expected size.
    @param {number} size The expected size of the set.
    @return {SetSubject} `this` for chaining.
*/
SetSubject.prototype.hasSize = function (size) {
  if (this.value.size !== size) {
    this.fail('Expected ' + this.describe() + ' to be of size ' + size +
      ', actual size was ' + this.value.size + '.');
  }
  return this;
};

/** Ensure that the set contains a given item
    @param {*} value The value expected to be in the set.
    @return {SetSubject} `this` for chaining.
*/
SetSubject.prototype.contains = function (value) {
  if (!this.value.has(value)) {
    this.fail('Expected ' + this.describe() + ' to contain ' + this.format(value) + '.');
  }
  return this;
};

/** Ensure that the set does not contain a given item
    @param {*} value The value not expected to be in the set.
    @return {SetSubject} `this` for chaining.
*/
SetSubject.prototype.doesNotContain = function (value) {
  if (this.value.has(value)) {
    this.fail('Expected ' + this.describe() + ' to not contain ' +
      this.format(value) + '.');
  }
  return this;
};

/** Ensure that the set contains all of the elements specified.
    @param {...*} elements The elements that the set must contain.
    @return {SetSubject} `this` for chaining.
*/
SetSubject.prototype.containsAllOf = function () {
  return this.containsAllIn(arguments);
};

/** Ensure that the set contains exactly the elements specified.
    @param {...*} elements The elements that the set must contain.
    @return {SetSubject} `this` for chaining.
*/
SetSubject.prototype.containsExactly = function () {
  return this.containsExactlyIn(arguments);
};

/** Ensure that the set contains any of the elements specified.
    @param {...*} elements The elements that the set must contain.
    @return {SetSubject} `this` for chaining.
*/
SetSubject.prototype.containsAnyOf = function () {
  return this.containsAnyIn(arguments);
};

/** Ensure that the set contains none of the elements specified.
    @param {...*} elements The elements that the set must not contain.
    @return {SetSubject} `this` for chaining.
*/
SetSubject.prototype.containsNoneOf = function () {
  return this.containsNoneIn(arguments);
};

/** Ensure that the array contains all of the elements specified.
    @param {Iterable} elements Iterable which produces the elements that the array must contain.
    @return {InOrder} An InOrder object which allows the 'inOrder' assertion for the
      given elements.
*/
SetSubject.prototype.containsAllIn = function (elements) {
  var missing = [];
  for (var key in elements) {
    var el = elements[key];
    if (!this.value.has(el)) {
      missing.push(el);
    }
  }

  if (missing.length > 0) {
    this.fail('Expected ' + this.describe() + ' to contain all of ' +
      this.format(Array.prototype.slice.call(elements)) + ', is missing ' +
      this.format(missing) + '.');
  }

  return this;
};

/** Ensure that the array contains exactly the elements specified.
    @param {Iterable} elements Iterable which produces the elements that the array must contain.
    @return {InOrder} An InOrder object which allows the 'inOrder' assertion for the
      given elements
*/
SetSubject.prototype.containsExactlyIn = function (elements) {
  var extra = new Set(this.value);
  var missing = [];
  for (var key in elements) {
    var el = elements[key];
    if (this.value.has(el)) {
      extra.delete(el);
    } else {
      missing.push(el);
    }
  }

  var msg;
  if (missing.length || extra.size) {
    msg = 'Expected ' + this.describe() + ' to contain exactly elements ' +
      this.format(Array.prototype.slice.call(elements));
    if (missing.length) {
      msg += ', is missing ' + this.format(missing);
    }
    if (extra.size) {
      msg += ', extra elements ' + this.format(Array.from(extra));
    }
    msg += '.';
    this.fail(msg);
  }
  return this;
};

/** Ensure that the array contains any of the elements specified.
    @param {Iterable} elements Iterable which produces the elements that the array must contain.
    @return {SetSubject} `this` for chaining.
*/
SetSubject.prototype.containsAnyIn = function (elements) {
  for (var key in elements) {
    var el = elements[key];
    if (this.value.has(el)) {
      return this;
    }
  }
  this.fail('Expected ' + this.describe() + ' to contain any of ' +
    this.format(Array.prototype.slice.call(elements)) + '.');
  return this;
};

/** Ensure that the array contains none of the elements specified.
    @param {Iterable} elements Iterable which produces the elements that the array must not contain.
    @return {SetSubject} `this` for chaining.
*/
SetSubject.prototype.containsNoneIn = function (elements) {
  for (var key in elements) {
    var el = elements[key];
    if (this.value.has(el)) {
      this.fail('Expected ' + this.describe() + ' to contain none of ' +
        this.format(Array.prototype.slice.call(elements)) + ', however it contains ' +
        this.format(el) + '.');
      return this;
    }
  }
  return this;
};

/** Ensure that at least one element of the array satisfies a test function.
    @param {string} verb A description of the test, such as 'be even' or 'be prime'.
    @param {Function} testFn The test that elements must satisfy.
    @return {SetSubject} `this` for chaining.
*/
SetSubject.prototype.containsAny = function (verb, testFn) {
  var iter = this.value.values();
  for (var i = iter.next(); !i.done; i = iter.next()) {
    if (testFn(i.value)) {
      return this;
    }
  }
  this.fail('Expected any element of ' + this.describe() + ' to ' + verb + '.');
  return this;
};

/** Ensure that at all the elements of the array satisfy a test function.
    @param {string} verb A description of the test, such as 'be even' or 'be prime'.
    @param {Function} testFn The test that elements must satisfy.
    @return {SetSubject} `this` for chaining.
*/
SetSubject.prototype.containsAll = function (verb, testFn) {
  var iter = this.value.values();
  for (var i = iter.next(); !i.done; i = iter.next()) {
    if (!testFn(i.value)) {
      this.fail('Expected all elements of ' + this.describe() + ' to ' +
        verb + '.');
      return this;
    }
  }
  return this;
};

/** Ensure that at none the elements of the array satisfy a test function.
    @param {string} verb A description of the test, such as 'be even' or 'be prime'.
    @param {Function} testFn The test that elements must satisfy.
    @return {SetSubject} `this` for chaining.
*/
SetSubject.prototype.containsNone = function (verb, testFn) {
  var iter = this.value.values();
  for (var i = iter.next(); !i.done; i = iter.next()) {
    if (testFn(i.value)) {
      this.fail('Expected no elements of ' + this.describe() + ' to ' + verb + '.');
      return this;
    }
  }
  return this;
};

/** Causes any subsequent assertion methods to apply to each member of the set individually.
    @return {EachSubject} Fluent context that applies assertions to set members.
*/
SetSubject.prototype.eachMember = function () {
  return new EachSubject(this, Array.from(this.value), 'member', true);
};

module.exports = SetSubject;