/**
 * @license
 * Copyright The Closure Library Authors.
 * SPDX-License-Identifier: Apache-2.0
 */

goog.module('goog.dom.TagIteratorTest');
goog.setTestOnly();

const TagIterator = goog.require('goog.dom.TagIterator');
const TagName = goog.require('goog.dom.TagName');
const TagWalkType = goog.require('goog.dom.TagWalkType');
const dom = goog.require('goog.dom');
const iter = goog.require('goog.iter');
const testSuite = goog.require('goog.testing.testSuite');
const testingDom = goog.require('goog.testing.dom');

let it;
let pos;

function assertStartTag(type) {
  assertEquals(
      `Position ${pos} should be start tag`, TagWalkType.START_TAG, it.tagType);
  assertTrue('isStartTag should return true', it.isStartTag());
  assertFalse('isEndTag should return false', it.isEndTag());
  assertFalse('isNonElement should return false', it.isNonElement());
  assertEquals(
      `Position ${pos} should be ${type}`, String(type), it.node.tagName);
}

function assertEndTag(type) {
  assertEquals(
      `Position ${pos} should be end tag`, TagWalkType.END_TAG, it.tagType);
  assertFalse('isStartTag should return false', it.isStartTag());
  assertTrue('isEndTag should return true', it.isEndTag());
  assertFalse('isNonElement should return false', it.isNonElement());
  assertEquals(
      `Position ${pos} should be ${type}`, String(type), it.node.tagName);
}

function assertTextNode(value) {
  assertEquals(
      `Position ${pos} should be text node`, TagWalkType.OTHER, it.tagType);
  assertFalse('isStartTag should return false', it.isStartTag());
  assertFalse('isEndTag should return false', it.isEndTag());
  assertTrue('isNonElement should return true', it.isNonElement());
  assertEquals(
      `Position ${pos} should be "${value}"`, value, it.node.nodeValue);
}

testSuite({
  testBasicHTML() {
    it = new TagIterator(dom.getElement('test'));
    pos = 0;
    function doCheck() {
      pos++;
      switch (pos) {
        case 1:
          assertStartTag(TagName.DIV);
          break;
        case 2:
          assertStartTag(TagName.A);
          break;
        case 3:
          assertTextNode('T');
          break;
        case 4:
          assertStartTag(TagName.B);
          assertEquals('Depth at <B> should be 3', 3, it.depth);
          break;
        case 5:
          assertTextNode('e');
          break;
        case 6:
          assertEndTag(TagName.B);
          break;
        case 7:
          assertTextNode('xt');
          break;
        case 8:
          assertEndTag(TagName.A);
          break;
        case 9:
          assertStartTag(TagName.SPAN);
          break;
        case 10:
          assertEndTag(TagName.SPAN);
          break;
        case 11:
          assertStartTag(TagName.P);
          break;
        case 12:
          assertTextNode('Text');
          break;
        case 13:
          assertEndTag(TagName.P);
          break;
        case 14:
          assertEndTag(TagName.DIV);
          assertEquals('Depth at end should be 0', 0, it.depth);
          break;
        default:
          throw new Error('Unknown position.');
      }
    }
    iter.forEach(it, () => {
      doCheck();
    });

    // Reset, and do the same thing using ES6 Iteration
    it = new TagIterator(dom.getElement('test'));
    pos = 0;
    const iterable = /** @type {!Iterable<?>} */ ({
      [Symbol.iterator]: () => it,
    });
    for (const unused of iterable) {
      doCheck();
    }
  },

  testSkipTag() {
    it = new TagIterator(dom.getElement('test'));
    pos = 0;

    iter.forEach(it, () => {
      pos++;
      switch (pos) {
        case 1:
          assertStartTag(TagName.DIV);
          break;
        case 2:
          assertStartTag(TagName.A);
          it.skipTag();
          break;
        case 3:
          assertStartTag(TagName.SPAN);
          break;
        case 4:
          assertEndTag(TagName.SPAN);
          break;
        case 5:
          assertStartTag(TagName.P);
          break;
        case 6:
          assertTextNode('Text');
          break;
        case 7:
          assertEndTag(TagName.P);
          break;
        case 8:
          assertEndTag(TagName.DIV);
          assertEquals('Depth at end should be 0', 0, it.depth);
          break;
        default:
          throw new Error('Unknown position.');
      }
    });
  },

  testRestartTag() {
    it = new TagIterator(dom.getElement('test'));
    pos = 0;
    let done = false;

    iter.forEach(it, () => {
      pos++;
      switch (pos) {
        case 1:
          assertStartTag(TagName.DIV);
          break;
        case 2:
          assertStartTag(TagName.A);
          it.skipTag();
          break;
        case 3:
          assertStartTag(TagName.SPAN);
          break;
        case 4:
          assertEndTag(TagName.SPAN);
          break;
        case 5:
          assertStartTag(TagName.P);
          break;
        case 6:
          assertTextNode('Text');
          break;
        case 7:
          assertEndTag(TagName.P);
          break;
        case 8:
          assertEndTag(TagName.DIV);
          assertEquals('Depth at end should be 0', 0, it.depth);

          // Do them all again, starting after this element.
          if (!done) {
            pos = 1;
            it.restartTag();
            done = true;
          }
          break;
        default:
          throw new Error('Unknown position.');
      }
    });
  },

  testSkipTagReverse() {
    it = new TagIterator(dom.getElement('test'), true);
    pos = 9;

    iter.forEach(it, () => {
      pos--;
      switch (pos) {
        case 1:
          assertStartTag(TagName.DIV);
          assertEquals('Depth at end should be 0', 0, it.depth);
          break;
        case 2:
          assertEndTag(TagName.A);
          it.skipTag();
          break;
        case 3:
          assertStartTag(TagName.SPAN);
          break;
        case 4:
          assertEndTag(TagName.SPAN);
          break;
        case 5:
          assertStartTag(TagName.P);
          break;
        case 6:
          assertTextNode('Text');
          break;
        case 7:
          assertEndTag(TagName.P);
          break;
        case 8:
          assertEndTag(TagName.DIV);
          break;
        default:
          throw new Error('Unknown position.');
      }
    });
  },

  testUnclosedLI() {
    it = new TagIterator(dom.getElement('test2'));
    pos = 0;

    iter.forEach(it, () => {
      pos++;
      switch (pos) {
        case 1:
          assertStartTag(TagName.UL);
          break;
        case 2:
          assertStartTag(TagName.LI);
          assertEquals('Depth at <LI> should be 2', 2, it.depth);
          break;
        case 3:
          assertTextNode('Not');
          break;
        case 4:
          assertEndTag(TagName.LI);
          break;
        case 5:
          assertStartTag(TagName.LI);
          assertEquals('Depth at second <LI> should be 2', 2, it.depth);
          break;
        case 6:
          assertTextNode('Closed');
          break;
        case 7:
          assertEndTag(TagName.LI);
          break;
        case 8:
          assertEndTag(TagName.UL);
          assertEquals('Depth at end should be 0', 0, it.depth);
          break;
        default:
          throw new Error('Unknown position.');
      }
    });
  },

  testReversedUnclosedLI() {
    it = new TagIterator(dom.getElement('test2'), true);
    pos = 9;

    iter.forEach(it, () => {
      pos--;
      switch (pos) {
        case 1:
          assertStartTag(TagName.UL);
          assertEquals('Depth at start should be 0', 0, it.depth);
          break;
        case 2:
          assertStartTag(TagName.LI);
          break;
        case 3:
          assertTextNode('Not');
          break;
        case 4:
          assertEndTag(TagName.LI);
          assertEquals('Depth at <LI> should be 2', 2, it.depth);
          break;
        case 5:
          assertStartTag(TagName.LI);
          break;
        case 6:
          assertTextNode('Closed');
          break;
        case 7:
          assertEndTag(TagName.LI);
          assertEquals('Depth at second <LI> should be 2', 2, it.depth);
          break;
        case 8:
          assertEndTag(TagName.UL);
          break;
        default:
          throw new Error('Unknown position.');
      }
    });
  },

  testConstrained() {
    it = new TagIterator(dom.getElement('test3'), false, false);
    pos = 0;

    iter.forEach(it, () => {
      pos++;
      switch (pos) {
        case 1:
          assertStartTag(TagName.DIV);
          break;
        case 2:
          assertTextNode('text');
          break;
        case 3:
          assertEndTag(TagName.DIV);
          break;
      }
    });

    assertEquals('Constrained iterator should stop at position 3.', 3, pos);
  },

  testUnconstrained() {
    it = new TagIterator(dom.getElement('test3'), false, true);
    pos = 0;

    iter.forEach(it, () => {
      pos++;
      switch (pos) {
        case 1:
          assertStartTag(TagName.DIV);
          break;
        case 2:
          assertTextNode('text');
          break;
        case 3:
          assertEndTag(TagName.DIV);
          break;
      }
    });

    assertNotEquals(
        'Unonstrained iterator should not stop at position 3.', 3, pos);
  },

  testConstrainedText() {
    it = new TagIterator(dom.getElement('test3').firstChild, false, false);
    pos = 0;

    iter.forEach(it, () => {
      pos++;
      switch (pos) {
        case 1:
          assertTextNode('text');
          break;
      }
    });

    assertEquals(
        'Constrained text iterator should stop at position 1.', 1, pos);
  },

  testReverseConstrained() {
    it = new TagIterator(dom.getElement('test3'), true, false);
    pos = 4;

    iter.forEach(it, () => {
      pos--;
      switch (pos) {
        case 1:
          assertStartTag(TagName.DIV);
          break;
        case 2:
          assertTextNode('text');
          break;
        case 3:
          assertEndTag(TagName.DIV);
          break;
      }
    });

    assertEquals(
        'Constrained reversed iterator should stop at position 1.', 1, pos);
  },

  testSpliceRemoveSingleNode() {
    const testDiv = dom.getElement('testSplice');
    testDiv.innerHTML = '<br/>';
    it = new TagIterator(testDiv.firstChild);

    iter.forEach(
        it, /**
               @suppress {strictMissingProperties} suppression added to enable
               type checking
             */
        (node, dummy, i) => {
          i.splice();
        });

    assertEquals('Node not removed', 0, testDiv.childNodes.length);
  },

  testSpliceRemoveFirstTextNode() {
    const testDiv = dom.getElement('testSplice');
    testDiv.innerHTML = 'hello<b>world</b><em>goodbye</em>';
    it = new TagIterator(testDiv.firstChild, false, true);

    iter.forEach(
        it, /**
               @suppress {strictMissingProperties} suppression added to enable
               type checking
             */
        (node, dummy, i) => {
          if (node.nodeType == 3 && node.data == 'hello') {
            i.splice();
          }
          if (node.nodeName == TagName.EM) {
            i.splice(dom.createDom(TagName.I, null, node.childNodes));
          }
        });

    testingDom.assertHtmlMatches(
        '<b>world</b><i>goodbye</i>', testDiv.innerHTML);
  },

  testSpliceReplaceFirstTextNode() {
    const testDiv = dom.getElement('testSplice');
    testDiv.innerHTML = 'hello<b>world</b>';
    it = new TagIterator(testDiv.firstChild, false, true);

    iter.forEach(
        it, /**
               @suppress {strictMissingProperties} suppression added to enable
               type checking
             */
        (node, dummy, i) => {
          if (node.nodeType == 3 && node.data == 'hello') {
            i.splice(dom.createDom(TagName.EM, null, 'HELLO'));
          } else if (node.nodeName == TagName.EM) {
            i.splice(dom.createDom(TagName.I, null, node.childNodes));
          }
        });

    testingDom.assertHtmlMatches('<i>HELLO</i><b>world</b>', testDiv.innerHTML);
  },

  testSpliceReplaceSingleNode() {
    const testDiv = dom.getElement('testSplice');
    testDiv.innerHTML = '<br/>';
    it = new TagIterator(testDiv.firstChild);

    iter.forEach(
        it, /**
               @suppress {strictMissingProperties} suppression added to enable
               type checking
             */
        (node, dummy, i) => {
          i.splice(dom.createDom(TagName.LINK), dom.createDom(TagName.IMG));
        });

    testingDom.assertHtmlMatches('<link><img>', testDiv.innerHTML);
  },

  testSpliceFlattenSingleNode() {
    const testDiv = dom.getElement('testSplice');
    testDiv.innerHTML = '<div><b>one</b>two<i>three</i></div>';
    it = new TagIterator(testDiv.firstChild);

    iter.forEach(
        it, /**
               @suppress {strictMissingProperties} suppression added to enable
               type checking
             */
        (node, dummy, i) => {
          i.splice(node.childNodes);
        });

    testingDom.assertHtmlMatches(
        '<b>one</b>two<i>three</i>', testDiv.innerHTML);
  },

  testSpliceMiddleNode() {
    const testDiv = dom.getElement('testSplice');
    testDiv.innerHTML = 'a<b>hello<span>world</span></b>c';
    it = new TagIterator(testDiv);

    iter.forEach(
        it, /**
               @suppress {strictMissingProperties} suppression added to enable
               type checking
             */
        (node, dummy, i) => {
          if (node.nodeName == TagName.B) {
            i.splice(dom.createDom(TagName.IMG));
          }
        });

    testingDom.assertHtmlMatches('a<img>c', testDiv.innerHTML);
  },

  testSpliceMiddleNodeReversed() {
    const testDiv = dom.getElement('testSplice');
    testDiv.innerHTML = 'a<b>hello<span>world</span></b>c';
    it = new TagIterator(testDiv, true);

    iter.forEach(
        it, /**
               @suppress {strictMissingProperties} suppression added to enable
               type checking
             */
        (node, dummy, i) => {
          if (node.nodeName == TagName.B) {
            i.splice(dom.createDom(TagName.IMG));
          }
        });

    testingDom.assertHtmlMatches('a<img>c', testDiv.innerHTML);
  },

  testSpliceMiddleNodeAtEndTag() {
    const testDiv = dom.getElement('testSplice');
    testDiv.innerHTML = 'a<b>hello<span>world</span></b>c';
    it = new TagIterator(testDiv);

    iter.forEach(
        it, /**
               @suppress {strictMissingProperties} suppression added to enable
               type checking
             */
        (node, dummy, i) => {
          if (node.tagName == TagName.B && i.isEndTag()) {
            i.splice(dom.createDom(TagName.IMG));
          }
        });

    testingDom.assertHtmlMatches('a<img>c', testDiv.innerHTML);
  },

  testSpliceMultipleNodes() {
    const testDiv = dom.getElement('testSplice');
    testDiv.innerHTML = '<strong>this</strong> is <em>from IE</em>';
    it = new TagIterator(testDiv);

    iter.forEach(
        it, /**
               @suppress {strictMissingProperties} suppression added to enable
               type checking
             */
        (node, dummy, i) => {
          let replace = null;
          if (node.nodeName == TagName.STRONG) {
            replace = dom.createDom(TagName.B, null, node.childNodes);
          } else if (node.nodeName == TagName.EM) {
            replace = dom.createDom(TagName.I, null, node.childNodes);
          }
          if (replace) {
            i.splice(replace);
          }
        });

    testingDom.assertHtmlMatches(
        '<b>this</b> is <i>from IE</i>', testDiv.innerHTML);
  },

  testSpliceMultipleNodesAtEnd() {
    const testDiv = dom.getElement('testSplice');
    testDiv.innerHTML = '<strong>this</strong> is <em>from IE</em>';
    it = new TagIterator(testDiv);

    iter.forEach(
        it, /**
               @suppress {strictMissingProperties} suppression added to enable
               type checking
             */
        (node, dummy, i) => {
          let replace = null;
          if (node.nodeName == TagName.STRONG && i.isEndTag()) {
            replace = dom.createDom(TagName.B, null, node.childNodes);
          } else if (node.nodeName == TagName.EM && i.isEndTag()) {
            replace = dom.createDom(TagName.I, null, node.childNodes);
          }
          if (replace) {
            i.splice(replace);
          }
        });

    testingDom.assertHtmlMatches(
        '<b>this</b> is <i>from IE</i>', testDiv.innerHTML);
  },

  testSpliceMultipleNodesReversed() {
    const testDiv = dom.getElement('testSplice');
    testDiv.innerHTML = '<strong>this</strong> is <em>from IE</em>';
    it = new TagIterator(testDiv, true);

    iter.forEach(
        it, /**
               @suppress {strictMissingProperties} suppression added to enable
               type checking
             */
        (node, dummy, i) => {
          let replace = null;
          if (node.nodeName == TagName.STRONG) {
            replace = dom.createDom(TagName.B, null, node.childNodes);
          } else if (node.nodeName == TagName.EM) {
            replace = dom.createDom(TagName.I, null, node.childNodes);
          }
          if (replace) {
            i.splice(replace);
          }
        });

    testingDom.assertHtmlMatches(
        '<b>this</b> is <i>from IE</i>', testDiv.innerHTML);
  },
});
