/* Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";

// Test that the breadcrumbs widget content is correct.

const TEST_URI = URL_ROOT + "doc_inspector_breadcrumbs.html";
const NODES = [
  {
    selector: "#i1111",
    ids: "i1 i11 i111 i1111",
    nodeName: "div",
    title: "div#i1111",
  },
  { selector: "#i22", ids: "i2 i22", nodeName: "div", title: "div#i22" },
  {
    selector: "#i2111",
    ids: "i2 i21 i211 i2111",
    nodeName: "div",
    title: "div#i2111",
  },
  {
    selector: "#i21",
    ids: "i2 i21 i211 i2111",
    nodeName: "div",
    title: "div#i21",
  },
  {
    selector: "#i22211",
    ids: "i2 i22 i222 i2221 i22211",
    nodeName: "div",
    title: "div#i22211",
  },
  {
    selector: "#i22",
    ids: "i2 i22 i222 i2221 i22211",
    nodeName: "div",
    title: "div#i22",
  },
  { selector: "#i3", ids: "i3", nodeName: "article", title: "article#i3" },
  {
    selector: "clipPath",
    ids: "vector clip",
    nodeName: "clipPath",
    title: "clipPath#clip",
  },
];

add_task(async function () {
  const { inspector } = await openInspectorForURL(TEST_URI);
  const breadcrumbs = inspector.panelDoc.getElementById(
    "inspector-breadcrumbs"
  );
  const container = breadcrumbs.querySelector(".html-arrowscrollbox-inner");

  for (const node of NODES) {
    info("Testing node " + node.selector);

    info("Selecting node and waiting for breadcrumbs to update");
    const breadcrumbsUpdated = inspector.once("breadcrumbs-updated");
    await selectNode(node.selector, inspector);
    await breadcrumbsUpdated;

    info("Performing checks for node " + node.selector);
    const buttonsLabelIds = node.ids.split(" ");

    // html > body > …
    is(
      container.childNodes.length,
      buttonsLabelIds.length + 2,
      "Node " + node.selector + ": Items count"
    );

    for (let i = 2; i < container.childNodes.length; i++) {
      const expectedId = "#" + buttonsLabelIds[i - 2];
      const button = container.childNodes[i];
      const labelId = button.querySelector(".breadcrumbs-widget-item-id");
      is(
        labelId.textContent,
        expectedId,
        "Node " + node.selector + ": button " + i + " matches"
      );
    }

    const pressedButton = container.querySelector(
      `button[aria-pressed="true"]`
    );
    const labelId = pressedButton.querySelector(".breadcrumbs-widget-item-id");
    const id = inspector.selection.nodeFront.id;
    is(
      labelId.textContent,
      "#" + id,
      "Node " + node.selector + ": selection matches"
    );

    const labelTag = pressedButton.querySelector(
      ".breadcrumbs-widget-item-tag"
    );
    is(
      labelTag.textContent,
      node.nodeName,
      "Node " + node.selector + " has the expected tag name"
    );

    is(
      pressedButton.getAttribute("title"),
      node.title,
      "Node " + node.selector + " has the expected tooltip"
    );
  }

  await testPseudoElements(inspector, container);
  await testComments(inspector, container);
});

async function testPseudoElements(inspector, container) {
  info("Checking for pseudo elements");

  const checkBreadcrumbContent = async (nodeFront, expected, desc) => {
    const onBreadcrumbsUpdated = inspector.once("breadcrumbs-updated");
    await selectNode(nodeFront, inspector);
    await onBreadcrumbsUpdated;
    Assert.deepEqual(
      [...container.childNodes].map(el => el.textContent),
      expected,
      desc
    );
  };

  const pseudoParent = await getNodeFront("#pseudo-container", inspector);
  const children = await inspector.walker.children(pseudoParent);
  is(children.nodes.length, 2, "Pseudo children returned from walker");

  const beforeElement = children.nodes[0];
  await checkBreadcrumbContent(
    beforeElement,
    ["html", "body", "div#pseudo-container", "::before"],
    "::before shows up in breadcrumb"
  );

  const afterElement = children.nodes[1];
  await checkBreadcrumbContent(
    afterElement,
    ["html", "body", "div#pseudo-container", "::after"],
    "::after shows up in breadcrumb"
  );

  info("Check rules on ::view-transition");
  const htmlNodeFront = await getNodeFront("html", inspector);
  const onMarkupMutation = inspector.once("markupmutation");
  await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async () => {
    const document = content.document;
    content.testTransition = document.startViewTransition();
    await content.testTransition;
  });
  await onMarkupMutation;

  const htmlChildren = await inspector.markup.walker.children(htmlNodeFront);
  const viewTransitionNodeFront = htmlChildren.nodes[2];

  is(
    viewTransitionNodeFront.getAttribute("type"),
    ":view-transition",
    "Got expected ::view-transition node front"
  );

  await checkBreadcrumbContent(
    viewTransitionNodeFront,
    ["html", "::view-transition"],
    "::view-transition shows up in breadcrumb"
  );

  const viewTransitionChildren = await inspector.markup.walker.children(
    viewTransitionNodeFront
  );
  const viewTransitionGroupNodeFront = viewTransitionChildren.nodes[0];
  is(
    viewTransitionGroupNodeFront.getAttribute("type"),
    ":view-transition-group",
    "Got expected ::view-transition-group node front"
  );

  await checkBreadcrumbContent(
    viewTransitionGroupNodeFront,
    ["html", "::view-transition", "::view-transition-group(root)"],
    "::view-transition-group(root) shows up in breadcrumb"
  );

  const viewTransitionGroupChildren = await inspector.markup.walker.children(
    viewTransitionGroupNodeFront
  );
  const viewTransitionImagePairNodeFront = viewTransitionGroupChildren.nodes[0];
  is(
    viewTransitionImagePairNodeFront.getAttribute("type"),
    ":view-transition-image-pair",
    "Got expected ::view-transition-image-pair node front"
  );

  await checkBreadcrumbContent(
    viewTransitionImagePairNodeFront,
    [
      "html",
      "::view-transition",
      "::view-transition-group(root)",
      "::view-transition-image-pair(root)",
    ],
    "::view-transition-image-pair(root) shows up in breadcrumb"
  );

  const viewTransitionImagePairChildren =
    await inspector.markup.walker.children(viewTransitionImagePairNodeFront);
  const [viewTransitionOldNodeFront, viewTransitionNewNodeFront] =
    viewTransitionImagePairChildren.nodes;
  is(
    viewTransitionOldNodeFront.getAttribute("type"),
    ":view-transition-old",
    "Got expected ::view-transition-old node front"
  );
  is(
    viewTransitionNewNodeFront.getAttribute("type"),
    ":view-transition-new",
    "Got expected ::view-transition-new node front"
  );

  await checkBreadcrumbContent(
    viewTransitionOldNodeFront,
    [
      "html",
      "::view-transition",
      "::view-transition-group(root)",
      "::view-transition-image-pair(root)",
      "::view-transition-old(root)",
    ],
    "::view-transition-old(root) shows up in breadcrumb"
  );

  await checkBreadcrumbContent(
    viewTransitionNewNodeFront,
    [
      "html",
      "::view-transition",
      "::view-transition-group(root)",
      "::view-transition-image-pair(root)",
      "::view-transition-new(root)",
    ],
    "::view-transition-new(root) shows up in breadcrumb"
  );

  // Cancel transition
  await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async () => {
    content.testTransition.skipTransition();
    delete content.testTransition;
  });
}

async function testComments(inspector, container) {
  info("Checking for comment elements");

  const breadcrumbs = inspector.breadcrumbs;
  const pressedButtonIndex = 2;
  const button = container.childNodes[pressedButtonIndex];

  let onBreadcrumbsUpdated = inspector.once("breadcrumbs-updated");
  button.click();
  await onBreadcrumbsUpdated;

  is(breadcrumbs.currentIndex, pressedButtonIndex, "New button is selected");
  ok(
    breadcrumbs.outer.hasAttribute("aria-activedescendant"),
    "Active descendant must be set"
  );

  const comment = [...inspector.markup._containers].find(
    ([node]) => node.nodeType === Node.COMMENT_NODE
  )[0];

  let onInspectorUpdated = inspector.once("inspector-updated");
  inspector.selection.setNodeFront(comment);
  await onInspectorUpdated;

  is(
    breadcrumbs.currentIndex,
    -1,
    "When comment is selected no breadcrumb should be pressed"
  );
  ok(
    !breadcrumbs.outer.hasAttribute("aria-activedescendant"),
    "Active descendant must not be set"
  );

  onInspectorUpdated = inspector.once("inspector-updated");
  onBreadcrumbsUpdated = inspector.once("breadcrumbs-updated");
  button.click();
  await Promise.all([onInspectorUpdated, onBreadcrumbsUpdated]);

  is(
    breadcrumbs.currentIndex,
    pressedButtonIndex,
    "Same button is selected again"
  );
  ok(
    breadcrumbs.outer.hasAttribute("aria-activedescendant"),
    "Active descendant must be set again"
  );
}
