<!DOCTYPE html>
<meta charset=utf-8>
<title>ParentNode.replaceChildren</title>
<link rel=help href="https://dom.spec.whatwg.org/#dom-parentnode-replacechildren">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="pre-insertion-validation-hierarchy.js"></script>
<script>
  preInsertionValidateHierarchy("replaceChildren");

  function test_replacechildren(node, nodeName) {
    test(() => {
      const parent = node.cloneNode();
      parent.replaceChildren();
      assert_array_equals(parent.childNodes, []);
    }, `${nodeName}.replaceChildren() without any argument, on a parent having no child.`);

    test(() => {
      const parent = node.cloneNode();
      parent.replaceChildren(null);
      assert_equals(parent.childNodes[0].textContent, 'null');
    }, `${nodeName}.replaceChildren() with null as an argument, on a parent having no child.`);

    test(() => {
      const parent = node.cloneNode();
      parent.replaceChildren(undefined);
      assert_equals(parent.childNodes[0].textContent, 'undefined');
    }, `${nodeName}.replaceChildren() with undefined as an argument, on a parent having no child.`);

    test(() => {
      const parent = node.cloneNode();
      parent.replaceChildren('text');
      assert_equals(parent.childNodes[0].textContent, 'text');
    }, `${nodeName}.replaceChildren() with only text as an argument, on a parent having no child.`);

    test(() => {
      const parent = node.cloneNode();
      const x = document.createElement('x');
      parent.replaceChildren(x);
      assert_array_equals(parent.childNodes, [x]);
    }, `${nodeName}.replaceChildren() with only one element as an argument, on a parent having no child.`);

    test(() => {
      const parent = node.cloneNode();
      const child = document.createElement('test');
      parent.appendChild(child);
      parent.replaceChildren();
      assert_array_equals(parent.childNodes, []);
    }, `${nodeName}.replaceChildren() without any argument, on a parent having a child.`);

    test(() => {
      const parent = node.cloneNode();
      const child = document.createElement('test');
      parent.appendChild(child);
      parent.replaceChildren(null);
      assert_equals(parent.childNodes.length, 1);
      assert_equals(parent.childNodes[0].textContent, 'null');
    }, `${nodeName}.replaceChildren() with null as an argument, on a parent having a child.`);

    test(() => {
      const parent = node.cloneNode();
      const x = document.createElement('x');
      const child = document.createElement('test');
      parent.appendChild(child);
      parent.replaceChildren(x, 'text');
      assert_equals(parent.childNodes.length, 2);
      assert_equals(parent.childNodes[0], x);
      assert_equals(parent.childNodes[1].textContent, 'text');
    }, `${nodeName}.replaceChildren() with one element and text as argument, on a parent having a child.`);

    async_test(t => {
      let phase = 0;

      const previousParent = node.cloneNode();
      const insertions = [
        document.createElement("test1"),
        document.createElement("test2")
      ];
      previousParent.append(...insertions);

      const parent = node.cloneNode();
      const children = [
        document.createElement("test3"),
        document.createElement("test4")
      ];
      parent.append(...children);

      const previousObserver = new MutationObserver(mutations => {
        t.step(() => {
          assert_equals(phase, 0);
          assert_equals(mutations.length, 2);
          for (const [i, mutation] of Object.entries(mutations)) {
            assert_equals(mutation.type, "childList");
            assert_equals(mutation.addedNodes.length, 0);
            assert_equals(mutation.removedNodes.length, 1);
            assert_equals(mutation.removedNodes[0], insertions[i]);
          }
          phase = 1;
        });
      });
      previousObserver.observe(previousParent, { childList: true });

      const observer = new MutationObserver(mutations => {
        t.step(() => {
          assert_equals(phase, 1, "phase");
          assert_equals(mutations.length, 1, "mutations.length");
          const mutation = mutations[0];
          assert_equals(mutation.type, "childList", "mutation.type");
          assert_equals(mutation.addedNodes.length, 2, "added nodes length");
          assert_array_equals([...mutation.addedNodes], insertions, "added nodes");
          assert_equals(mutation.removedNodes.length, 2, "removed nodes length");
          assert_array_equals([...mutation.removedNodes], children, "removed nodes");
        });
        t.done();
      });
      observer.observe(parent, { childList: true });

      parent.replaceChildren(...previousParent.children);
    }, `${nodeName}.replaceChildren() should move nodes in the right order`);
  }

  test_replacechildren(document.createElement('div'), 'Element');
  test_replacechildren(document.createDocumentFragment(), 'DocumentFragment');

  async_test(t => {
    let root = document.createElement("div");
    root.innerHTML = "<div id='a'>text<div id='b'>text2</div></div>";
    const a = root.firstChild;
    const b = a.lastChild;
    const txt = b.previousSibling;
    const txt2 = b.firstChild;

    const observer = new MutationObserver((mutations) => {

      assert_equals(mutations.length, 2, "mutations.length");

      assert_equals(mutations[0].target.id, "a", "Target of the removal");
      assert_equals(mutations[0].addedNodes.length, 0, "Should not have added nodes");
      assert_equals(mutations[0].removedNodes.length, 1, "Should have 1 removed node");
      assert_equals(mutations[0].removedNodes[0], txt, "Should have removed txt node");

      assert_equals(mutations[1].target.id, "b", "Target of the replaceChildren");
      assert_equals(mutations[1].removedNodes.length, 1, "Should have removed 1 node");
      assert_equals(mutations[1].removedNodes[0], txt2, "Should have removed txt2 node");
      assert_equals(mutations[1].addedNodes.length, 1, "Should have added a node");
      assert_equals(mutations[1].addedNodes[0], txt, "Should have added txt node");

      observer.disconnect();
      t.done();
    });

    observer.observe(a,  {
      subtree: true,
      childList: true
    });

    b.replaceChildren(txt);
  }, "There should be a MutationRecord for the node removed from another parent node.");

  async_test(t => {
    // This is almost the same test as above, but passes two nodes to replaceChildren.

    let root = document.createElement("div");
    root.innerHTML = "<div id='a'><div id='c'></div>text<div id='b'>text2</div></div>";
    const a = root.firstChild;
    const b = a.lastChild;
    const c = a.firstChild;
    const txt = b.previousSibling;
    const txt2 = b.firstChild;

    const observer = new MutationObserver((mutations) => {

      assert_equals(mutations.length, 3, "mutations.length");

      assert_equals(mutations[0].target.id, "a", "Target of the removal");
      assert_equals(mutations[0].addedNodes.length, 0, "Should not have added nodes");
      assert_equals(mutations[0].removedNodes.length, 1, "Should have 1 removed node");
      assert_equals(mutations[0].removedNodes[0], c, "Should have removed c node");

      assert_equals(mutations[1].target.id, "a", "Target of the removal");
      assert_equals(mutations[1].addedNodes.length, 0, "Should not have added nodes");
      assert_equals(mutations[1].removedNodes.length, 1, "Should have 1 removed node");
      assert_equals(mutations[1].removedNodes[0], txt, "Should have removed txt node");

      assert_equals(mutations[2].target.id, "b", "Target of the replaceChildren");
      assert_equals(mutations[2].removedNodes.length, 1, "Should have removed 1 node");
      assert_equals(mutations[2].removedNodes[0], txt2, "Should have removed txt2 node");
      assert_equals(mutations[2].addedNodes.length, 2, "Should have added a node");
      assert_equals(mutations[2].addedNodes[0], c, "Should have added c node");
      assert_equals(mutations[2].addedNodes[1], txt, "Should have added txt node");

      observer.disconnect();
      t.done();
    });

    observer.observe(a,  {
      subtree: true,
      childList: true
    });

    b.replaceChildren(c, txt);
  }, "There should be MutationRecords for the nodes removed from another parent node.");
</script>

</html>
