/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

Cu.importGlobalProperties(["NodeFilter"]);

function run_test()
{
  /*
   * NOTE: [i] is not allowed in this test, since it's done via classinfo and
   * we don't have that in xpcshell; the workaround is item(i).  Suck.
   */
  init();

  test_isEqualNode_setAttribute();
  test_isEqualNode_clones();
  test_isEqualNode_variety();
  test_isEqualNode_normalization();
  test_isEqualNode_whitespace();
  test_isEqualNode_namespaces();
  test_isEqualNode_wholeDoc();

  // XXX should Node.isEqualNode(null) throw or return false?
  //test_isEqualNode_null();

}

// TEST CODE

var doc, root; // cache for use in all tests

function init()
{
  doc = ParseFile("isequalnode_data.xml");
  root = doc.documentElement;
}

function test_isEqualNode_setAttribute()
{
  // NOTE: 0, 2 are whitespace
  var test1 = doc.getElementById("test_setAttribute");
  var node1 = test1.childNodes.item(1);
  var node2 = test1.childNodes.item(3);

  check_eq_nodes(node1, node2);


  el(node1).setAttribute("bar", "baz");
  check_neq_nodes(node1, node2);

  el(node2).setAttribute("bar", "baz");
  check_eq_nodes(node1, node2);


  // the null namespace is equivalent to no namespace -- section 1.3.3
  // (XML Namespaces) of DOM 3 Core
  node1.setAttributeNS(null, "quux", "17");
  check_neq_nodes(node1, node2);

  node2.setAttribute("quux", "17");
  check_eq_nodes(node1, node2);


  node2.setAttributeNS("http://mozilla.org/", "seamonkey", "rheet");
  check_neq_nodes(node1, node2);

  node1.setAttribute("seamonkey", "rheet");
  check_neq_nodes(node1, node2);

  node1.setAttributeNS("http://mozilla.org/", "seamonkey", "rheet");
  check_neq_nodes(node1, node2);

  // this overwrites the namespaced "seamonkey" attribute added to node2
  // earlier, because this simply sets whatever attribute has the fully
  // qualified name "seamonkey" (the setAttributeNS attribute string wasn't
  // prefixed) -- consequently, node1 and node2 are still unequal
  node2.setAttribute("seamonkey", "rheet");
  check_neq_nodes(node1, node2);
}

function test_isEqualNode_clones()
{
  // tests all elements and attributes in the document
  var all_elts = doc.getElementsByTagName("*");
  for (var i = 0; i < all_elts.length; i++)
  {
    var elt = el(all_elts.item(i));
    check_eq_nodes(elt, elt.cloneNode(true));

    var attrs = elt.attributes;
    for (var j = 0; j < attrs.length; j++)
    {
      var attr = attrs.item(j);
      check_eq_nodes(attr, attr.cloneNode(true));
    }
  }

  var elm = doc.createElement("foo");
  check_eq_nodes(elm, elm.cloneNode(true));
  check_eq_nodes(elm, elm.cloneNode(false));

  elm.setAttribute("fiz", "eit");
  check_eq_nodes(elm, elm.cloneNode(true));
  check_eq_nodes(elm, elm.cloneNode(false));

  elm.setAttributeNS("http://example.com/", "trendoid", "arthroscope");
  check_eq_nodes(elm, elm.cloneNode(true));
  check_eq_nodes(elm, elm.cloneNode(false));

  var elm2 = elm.cloneNode(true);
  check_eq_nodes(elm, elm2);

  const TEXT = "fetishist";

  elm.textContent = TEXT;
  check_neq_nodes(elm, elm2);

  check_neq_nodes(elm, elm.cloneNode(false));
  check_eq_nodes(elm, elm.cloneNode(true));

  elm2.appendChild(doc.createTextNode(TEXT));
  check_eq_nodes(elm, elm2);

  var att = doc.createAttribute("bar");
  check_eq_nodes(att, att.cloneNode(true));
  check_eq_nodes(att, att.cloneNode(false));
}

function test_isEqualNode_variety()
{
  const nodes =
    [
      doc.createElement("foo"),
      doc.createElementNS("http://example.com/", "foo"),
      doc.createElementNS("http://example.org/", "foo"),
      doc.createElementNS("http://example.com/", "FOO"),
      doc.createAttribute("foo", "href='biz'"),
      doc.createAttributeNS("http://example.com/", "foo", "href='biz'"),
      doc.createTextNode("foo"),
      doc.createTextNode("   "),
      doc.createTextNode("    "),
      doc.createComment("foo"),
      doc.createProcessingInstruction("foo", "href='biz'"),
      doc.implementation.createDocumentType("foo", "href='biz'", ""),
      doc.implementation.createDocument("http://example.com/", "foo", null),
      doc.createDocumentFragment()
    ];

  for (var i = 0; i < nodes.length; i++)
  {
    for (var j = i; j < nodes.length; j++)
    {
      if (i == j)
        check_eq_nodes(nodes[i], nodes[j]);
      else
        check_neq_nodes(nodes[i], nodes[j]);
    }
  }
}

function test_isEqualNode_normalization()
{
  var norm = doc.getElementById("test_normalization");
  var node1 = norm.childNodes.item(1);
  var node2 = norm.childNodes.item(3);

  check_eq_nodes(node1, node2);

  node1.appendChild(doc.createTextNode(""));
  check_neq_nodes(node1, node2);

  node1.normalize();
  check_eq_nodes(node1, node2);

  node2.appendChild(doc.createTextNode("fun"));
  node2.appendChild(doc.createTextNode("ctor"));
  node1.appendChild(doc.createTextNode("functor"));
  check_neq_nodes(node1, node2);

  node1.normalize();
  check_neq_nodes(node1, node2);

  node2.normalize();
  check_eq_nodes(node1, node2);

  // reset
  while (node1.hasChildNodes())
    node1.removeChild(node1.childNodes.item(0));
  while (node2.hasChildNodes())
    node2.removeChild(node2.childNodes.item(0));

  // attribute normalization testing

  var at1 = doc.createAttribute("foo");
  var at2 = doc.createAttribute("foo");
  check_eq_nodes(at1, at2);

  // Attr.appendChild isn't implemented yet (bug 56758), so don't run this yet
  if (false)
  {
    at1.appendChild(doc.createTextNode("rasp"));
    at2.appendChild(doc.createTextNode("rasp"));
    check_eq_nodes(at1, at2);

    at1.appendChild(doc.createTextNode(""));
    check_neq_nodes(at1, at2);

    at1.normalize();
    check_eq_nodes(at1, at2);

    at1.appendChild(doc.createTextNode("berry"));
    check_neq_nodes(at1, at2);

    at2.appendChild(doc.createTextNode("ber"));
    check_neq_nodes(at1, at2);

    at2.appendChild(doc.createTextNode("ry"));
    check_neq_nodes(at1, at2);

    at1.normalize();
    check_neq_nodes(at1, at2);

    at2.normalize();
    check_eq_nodes(at1, at2);
  }

  node1.setAttributeNode(at1);
  check_neq_nodes(node1, node2);

  node2.setAttributeNode(at2);
  check_eq_nodes(node1, node2);

  var n1text1 = doc.createTextNode("ratfink");
  var n1elt = doc.createElement("fruitcake");
  var n1text2 = doc.createTextNode("hydrospanner");

  node1.appendChild(n1text1);
  node1.appendChild(n1elt);
  node1.appendChild(n1text2);

  check_neq_nodes(node1, node2);

  var n2text1a = doc.createTextNode("rat");
  var n2text1b = doc.createTextNode("fink");
  var n2elt = doc.createElement("fruitcake");
  var n2text2 = doc.createTextNode("hydrospanner");

  node2.appendChild(n2text1b);
  node2.appendChild(n2elt);
  node2.appendChild(n2text2);
  check_neq_nodes(node1, node2);

  node2.insertBefore(n2text1a, n2text1b);
  check_neq_nodes(node1, node2);

  var tmp_node1 = node1.cloneNode(true);
  tmp_node1.normalize();
  var tmp_node2 = node2.cloneNode(true);
  tmp_node2.normalize();
  check_eq_nodes(tmp_node1, tmp_node2);

  n2elt.appendChild(doc.createTextNode(""));
  check_neq_nodes(node1, node2);

  tmp_node1 = node1.cloneNode(true);
  tmp_node1.normalize();
  tmp_node2 = node2.cloneNode(true);
  tmp_node2.normalize();
  check_eq_nodes(tmp_node1, tmp_node2);

  var typeText1 = doc.createTextNode("type");
  n2elt.appendChild(typeText1);
  tmp_node1 = node1.cloneNode(true);
  tmp_node1.normalize();
  tmp_node2 = node2.cloneNode(true);
  tmp_node2.normalize();
  check_neq_nodes(tmp_node1, tmp_node2);

  n1elt.appendChild(doc.createTextNode("typedef"));
  tmp_node1 = node1.cloneNode(true);
  tmp_node1.normalize();
  tmp_node2 = node2.cloneNode(true);
  tmp_node2.normalize();
  check_neq_nodes(tmp_node1, tmp_node2);
  check_neq_nodes(n1elt, n2elt);

  var typeText2 = doc.createTextNode("def");
  n2elt.appendChild(typeText2);
  tmp_node1 = node1.cloneNode(true);
  tmp_node1.normalize();
  tmp_node2 = node2.cloneNode(true);
  tmp_node2.normalize();
  check_eq_nodes(tmp_node1, tmp_node2);
  check_neq_nodes(node1, node2);

  n2elt.insertBefore(doc.createTextNode(""), typeText2);
  check_neq_nodes(node1, node2);

  n2elt.insertBefore(doc.createTextNode(""), typeText2);
  check_neq_nodes(node1, node2);

  n2elt.insertBefore(doc.createTextNode(""), typeText1);
  check_neq_nodes(node1, node2);

  node1.normalize();
  node2.normalize();
  check_eq_nodes(node1, node2);
}

function test_isEqualNode_whitespace()
{
  equality_check_kids("test_pi1", true);
  equality_check_kids("test_pi2", true);
  equality_check_kids("test_pi3", false);
  equality_check_kids("test_pi4", true);
  equality_check_kids("test_pi5", true);

  equality_check_kids("test_elt1", false);
  equality_check_kids("test_elt2", false);
  equality_check_kids("test_elt3", true);
  equality_check_kids("test_elt4", false);
  equality_check_kids("test_elt5", false);

  equality_check_kids("test_comment1", true);
  equality_check_kids("test_comment2", false);
  equality_check_kids("test_comment3", false);
  equality_check_kids("test_comment4", true);

  equality_check_kids("test_text1", true);
  equality_check_kids("test_text2", false);
  equality_check_kids("test_text3", false);

  equality_check_kids("test_cdata1", false);
  equality_check_kids("test_cdata2", true);
  equality_check_kids("test_cdata3", false);
  equality_check_kids("test_cdata4", false);
  equality_check_kids("test_cdata5", false);
}

function test_isEqualNode_namespaces()
{
  equality_check_kids("test_ns1", false);
  equality_check_kids("test_ns2", false);

  // XXX want more tests here!
}

function test_isEqualNode_null()
{
  check_neq_nodes(doc, null);

  var elts = doc.getElementsByTagName("*");
  for (var i = 0; i < elts.length; i++)
  {
    var elt = elts.item(i);
    check_neq_nodes(elt, null);

    var attrs = elt.attributes;
    for (var j = 0; j < attrs.length; j++)
    {
      var att = attrs.item(j);
      check_neq_nodes(att, null);

      for (var k = 0; k < att.childNodes.length; k++)
      {
        check_neq_nodes(att.childNodes.item(k), null);
      }
    }
  }
}

function test_isEqualNode_wholeDoc()
{
  doc = ParseFile("isequalnode_data.xml");
  var doc2 = ParseFile("isequalnode_data.xml");
  var tw1 =
    doc.createTreeWalker(doc, NodeFilter.SHOW_ALL,
                         null);
  var tw2 =
    doc2.createTreeWalker(doc2, NodeFilter.SHOW_ALL,
                          null);
  do {
    check_eq_nodes(tw1.currentNode, tw2.currentNode);
    tw1.nextNode();
  } while(tw2.nextNode());
}

// UTILITY FUNCTIONS

function n(node)  { return node ? node.QueryInterface(nsIDOMNode) : null; }
function el(node) { return node ? node.QueryInterface(nsIDOMElement) : null; }


// TESTING FUNCTIONS

/**
 * Compares the first and third (zero-indexed) child nodes of the element
 * (typically to allow whitespace) referenced by parentId for isEqualNode
 * equality or inequality based on the value of areEqual.
 *
 * Note that this means that the contents of the element referenced by parentId
 * are whitespace-sensitive, and a stray space introduced during an edit to the
 * file could result in a correct but unexpected (in)equality failure.
 */
function equality_check_kids(parentId, areEqual)
{
  var parent = doc.getElementById(parentId);
  var kid1 = parent.childNodes.item(1);
  var kid2 = parent.childNodes.item(3);

  if (areEqual)
    check_eq_nodes(kid1, kid2);
  else
    check_neq_nodes(kid1, kid2);
}

function check_eq_nodes(n1, n2)
{
  if (n1 && !n1.isEqualNode(n2))
    do_throw(n1 + " should be equal to " + n2);
  if (n2 && !n2.isEqualNode(n1))
    do_throw(n2 + " should be equal to " + n1);
  if (!n1 && !n2)
    do_throw("nodes both null!");
}

function check_neq_nodes(n1, n2)
{
  if (n1 && n1.isEqualNode(n2))
    do_throw(n1 + " should not be equal to " + n2);
  if (n2 && n2.isEqualNode(n1))
    do_throw(n2 + " should not be equal to " + n1);
  if (!n1 && !n2)
    do_throw("n1 and n2 both null!");
}
