<!doctype html>
<title>NodeIterator tests</title>
<link rel="author" title="Aryeh Gregor" href=ayg@aryeh.name>
<meta name=timeout content=long>
<div id=log></div>
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
<script src=../common.js></script>
<script>
"use strict";

function check_iter(iter, root, whatToShowValue) {
    whatToShowValue = whatToShowValue === undefined ? 0xFFFFFFFF : whatToShowValue;

    assert_equals(iter.toString(), '[object NodeIterator]', 'toString');
    assert_equals(iter.root, root, 'root');
    assert_equals(iter.whatToShow, whatToShowValue, 'whatToShow');
    assert_equals(iter.filter, null, 'filter');
    assert_equals(iter.referenceNode, root, 'referenceNode');
    assert_equals(iter.pointerBeforeReferenceNode, true, 'pointerBeforeReferenceNode');
    assert_readonly(iter, 'root');
    assert_readonly(iter, 'whatToShow');
    assert_readonly(iter, 'filter');
    assert_readonly(iter, 'referenceNode');
    assert_readonly(iter, 'pointerBeforeReferenceNode');
}

test(function() {
  var iter = document.createNodeIterator(document);
  iter.detach();
  iter.detach();
}, "detach() should be a no-op");

test(function() {
  var iter = document.createNodeIterator(document);
  check_iter(iter, document);
}, "createNodeIterator() parameter defaults");

test(function() {
  var iter = document.createNodeIterator(document, null, null);
  check_iter(iter, document, 0);
}, "createNodeIterator() with null as arguments");

test(function() {
  var iter = document.createNodeIterator(document, undefined, undefined);
  check_iter(iter, document);
}, "createNodeIterator() with undefined as arguments");

test(function() {
  var err = {name: "failed"};
  var iter = document.createNodeIterator(document, NodeFilter.SHOW_ALL,
                                         function() { throw err; });
  assert_throws_exactly(err, function() { iter.nextNode() });
}, "Propagate exception from filter function");

test(function() {
  var depth = 0;
  var iter = document.createNodeIterator(document, NodeFilter.SHOW_ALL,
    function() {
      if (iter.referenceNode != document && depth == 0) {
        depth++;
        iter.nextNode();
      }
      return NodeFilter.FILTER_ACCEPT;
    });
  iter.nextNode();
  iter.nextNode();
  assert_throws_dom("InvalidStateError", function() { iter.nextNode() });
  depth--;
  assert_throws_dom("InvalidStateError", function() { iter.previousNode() });
}, "Recursive filters need to throw");

function testIterator(root, whatToShow, filter) {
  var iter = document.createNodeIterator(root, whatToShow, filter);

  assert_equals(iter.root, root, ".root");
  assert_equals(iter.referenceNode, root, "Initial .referenceNode");
  assert_equals(iter.pointerBeforeReferenceNode, true,
                ".pointerBeforeReferenceNode");
  assert_equals(iter.whatToShow, whatToShow, ".whatToShow");
  assert_equals(iter.filter, filter, ".filter");

  var expectedReferenceNode = root;
  var expectedBeforeNode = true;
  // "Let node be the value of the referenceNode attribute."
  var node = root;
  // "Let before node be the value of the pointerBeforeReferenceNode
  // attribute."
  var beforeNode = true;
  var i = 1;
  // Each loop iteration runs nextNode() once.
  while (node) {
    do {
      if (!beforeNode) {
        // "If before node is false, let node be the first node following node
        // in the iterator collection. If there is no such node return null."
        node = nextNode(node);
        if (!isInclusiveDescendant(node, root)) {
          node = null;
          break;
        }
      } else {
        // "If before node is true, set it to false."
        beforeNode = false;
      }
      // "Filter node and let result be the return value.
      //
      // "If result is FILTER_ACCEPT, go to the next step in the overall set of
      // steps.
      //
      // "Otherwise, run these substeps again."
      if (!((1 << (node.nodeType - 1)) & whatToShow)
          || (filter && filter(node) != NodeFilter.FILTER_ACCEPT)) {
        continue;
      }

      // "Set the referenceNode attribute to node, set the
      // pointerBeforeReferenceNode attribute to before node, and return node."
      expectedReferenceNode = node;
      expectedBeforeNode = beforeNode;

      break;
    } while (true);

    assert_equals(iter.nextNode(), node, ".nextNode() " + i + " time(s)");
    assert_equals(iter.referenceNode, expectedReferenceNode,
                  ".referenceNode after nextNode() " + i + " time(s)");
    assert_equals(iter.pointerBeforeReferenceNode, expectedBeforeNode,
             ".pointerBeforeReferenceNode after nextNode() " + i + " time(s)");

    i++;
  }

  // Same but for previousNode() (mostly copy-pasted, oh well)
  var iter = document.createNodeIterator(root, whatToShow, filter);

  var expectedReferenceNode = root;
  var expectedBeforeNode = true;
  // "Let node be the value of the referenceNode attribute."
  var node = root;
  // "Let before node be the value of the pointerBeforeReferenceNode
  // attribute."
  var beforeNode = true;
  var i = 1;
  // Each loop iteration runs previousNode() once.
  while (node) {
    do {
      if (beforeNode) {
        // "If before node is true, let node be the first node preceding node
        // in the iterator collection. If there is no such node return null."
        node = previousNode(node);
        if (!isInclusiveDescendant(node, root)) {
          node = null;
          break;
        }
      } else {
        // "If before node is false, set it to true."
        beforeNode = true;
      }
      // "Filter node and let result be the return value.
      //
      // "If result is FILTER_ACCEPT, go to the next step in the overall set of
      // steps.
      //
      // "Otherwise, run these substeps again."
      if (!((1 << (node.nodeType - 1)) & whatToShow)
          || (filter && filter(node) != NodeFilter.FILTER_ACCEPT)) {
        continue;
      }

      // "Set the referenceNode attribute to node, set the
      // pointerBeforeReferenceNode attribute to before node, and return node."
      expectedReferenceNode = node;
      expectedBeforeNode = beforeNode;

      break;
    } while (true);

    assert_equals(iter.previousNode(), node, ".previousNode() " + i + " time(s)");
    assert_equals(iter.referenceNode, expectedReferenceNode,
                  ".referenceNode after previousNode() " + i + " time(s)");
    assert_equals(iter.pointerBeforeReferenceNode, expectedBeforeNode,
         ".pointerBeforeReferenceNode after previousNode() " + i + " time(s)");

    i++;
  }
}

var whatToShows = [
  "0",
  "0xFFFFFFFF",
  "NodeFilter.SHOW_ELEMENT",
  "NodeFilter.SHOW_ATTRIBUTE",
  "NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_DOCUMENT",
];

var callbacks = [
  "null",
  "(function(node) { return true })",
  "(function(node) { return false })",
  "(function(node) { return node.nodeName[0] == '#' })",
];

for (var i = 0; i < testNodes.length; i++) {
  for (var j = 0; j < whatToShows.length; j++) {
    for (var k = 0; k < callbacks.length; k++) {
      test(() => {
        testIterator(eval(testNodes[i]), eval(whatToShows[j]), eval(callbacks[k]));
      }, "document.createNodeIterator(" + testNodes[i] + ", " + whatToShows[j] + ", " + callbacks[k] + ")");
    }
  }
}

testDiv.style.display = "none";
</script>
