Source: Tree/AndNode.js

'use strict';

var Node = require('./Node.js');
var ExpectedCallNode = require('./ExpectedCallNode.js');

/**
 * Represent a result of 'and'ing two {@link Expectation}s.
 * Execution order of the {@link ExpectedCall}s in a {@link AndNode} is unrestricted.
 *
 * IE: AND(1, 2, 3) can execute in any of the following orders:
 * <ul>
 *  <li>1, 2, 3</li>
 *  <li>1, 3, 2</li>
 *  <li>2, 1, 3</li>
 *  <li>2, 3, 1</li>
 *  <li>3, 1, 2</li>
 *  <li>3, 2, 1</li>
 * </ul>
 * @memberof Tree
 */
class AndNode extends Node {
  /**
   * Creates a new {@link Tree.AndNode}
   * @param {ExpectedCall} expectedCall Initial expected call for this node.
   */
  constructor(expectedCall) {
    super('AND');
    /**
     * {@link ExpectedCall}s for this node.
     * @name Tree.AndNode#expectedCalls
     * @type ExpectedCall[]
     */
    this.expectedCalls = [expectedCall];
  }

  /**
   * Gets the human readable name for this node.
   * @returns {string} Human readable name of this node.
   */
  get name() {
    let calls = [];

    for(let expectedCall of this.expectedCalls) {
      calls.push(expectedCall.name);
    }

    return this._name + ' {{ ' + calls.join(', ') + ' }}';
  }

  /**
   * Merges this node and another {@link Tree.AndNode} or {@link Tree.ExpectedCallNode}
   * @param {Tree.AndNode|Tree.ExpectedCallNode} node Node to merge with this node.
   */
  merge(node) {
    let andNode;
    if(node instanceof ExpectedCallNode) {
      andNode = new AndNode(node.expectedCall);
    }
    else if(node instanceof AndNode) {
      andNode = node;
    }
    else {
      throw new Error('Unexpected type for node, expected AndNode or ExpectedCallNode');
    }

    for(let expectedCall of andNode.expectedCalls) {
      this.expectedCalls.push(expectedCall);
    }
  }

  /**
   * Determines the the {@link Mock} and args match any {@link ExpectedCall}s in this node.
   * @param {Mock} mock Mock that was called.
   * @param {object[]} args Arguments for the call.
   * @return {ExpectedCall|undefined} The matching {@link ExpectedCall} if found; otherwise undefined.
   */
  match(mock, args) {
    for(let expectedCall of this.expectedCalls) {
      if(expectedCall.completed) {
        continue;
      }

      if(expectedCall.matches(mock, args)) {
        return expectedCall;
      }
    }

    return undefined;
  }

  /**
   * Determines the the {@link Mock} partially matches any {@link ExpectedCall}s in this node.
   * @param {Mock} mock Mock that was called.
   * @return {boolean} True if the mock partially matches; otherwise false.
   */
  partialMatch(mock) {
    for(let expectedCall of this.expectedCalls) {
      if(expectedCall.completed) {
        continue;
      }

      if(expectedCall.matchesFunction(mock)) {
        return true;
      }
    }

    return false;
  }

  /**
   * Checks to see if only optional {@link ExpectedCall}s remain in this node.
   * @return {boolean} True if all incomplete calls are optional; otherwise false.
   */
  onlyOptionalRemain() {
    for(let expectedCall of this.expectedCalls) {
      if(expectedCall.completed) {
        continue;
      }

      if(expectedCall.required) {
        return false;
      }
    }

    return true;
  }
}

module.exports = AndNode;