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

// Sanity test for dominator trees, their focused nodes, and keyboard navigating
// through nodes across incrementally fetching subtrees.

"use strict";

const {
  dominatorTreeState,
  snapshotState,
  viewState,
} = require("devtools/client/memory/constants");
const {
  expandDominatorTreeNode,
  takeSnapshotAndCensus,
} = require("devtools/client/memory/actions/snapshot");
const { toggleInverted } = require("devtools/client/memory/actions/inverted");
const { changeView } = require("devtools/client/memory/actions/view");

const TEST_URL = "http://example.com/browser/devtools/client/memory/test/browser/doc_big_tree.html";

this.test = makeMemoryTest(TEST_URL, function* ({ tab, panel }) {
  // Taking snapshots and computing dominator trees is slow :-/
  requestLongerTimeout(4);

  const heapWorker = panel.panelWin.gHeapAnalysesClient;
  const front = panel.panelWin.gFront;
  const store = panel.panelWin.gStore;
  const { getState, dispatch } = store;
  const doc = panel.panelWin.document;

  dispatch(changeView(viewState.DOMINATOR_TREE));

  // Take a snapshot.

  const takeSnapshotButton = doc.getElementById("take-snapshot");
  EventUtils.synthesizeMouseAtCenter(takeSnapshotButton, {}, panel.panelWin);

  // Wait for the dominator tree to be computed and fetched.

  yield waitUntilState(store, state =>
    state.snapshots[0] &&
    state.snapshots[0].dominatorTree &&
    state.snapshots[0].dominatorTree.state === dominatorTreeState.LOADED);
  ok(true, "Computed and fetched the dominator tree.");

  // Expand all the dominator tree nodes that are eagerly fetched, except for
  // the leaves which will trigger fetching their lazily loaded subtrees.

  const id = getState().snapshots[0].id;
  const root = getState().snapshots[0].dominatorTree.root;
  (function expandAllEagerlyFetched(node = root) {
    if (!node.moreChildrenAvailable || node.children) {
      dispatch(expandDominatorTreeNode(id, node));
    }

    if (node.children) {
      for (let child of node.children) {
        expandAllEagerlyFetched(child);
      }
    }
  }());

  // Find the deepest eagerly loaded node: one which has more children but none
  // of them are loaded.

  const deepest = (function findDeepest(node = root) {
    if (node.moreChildrenAvailable && !node.children) {
      return node;
    }

    if (node.children) {
      for (let child of node.children) {
        const found = findDeepest(child);
        if (found) {
          return found;
        }
      }
    }

    return null;
  }());

  ok(deepest, "Found the deepest node");
  ok(!getState().snapshots[0].dominatorTree.expanded.has(deepest.nodeId),
     "The deepest node should not be expanded");

  // Select the deepest node.

  EventUtils.synthesizeMouseAtCenter(doc.querySelector(`.node-${deepest.nodeId}`),
                                     {},
                                     panel.panelWin);
  yield waitUntilState(store, state =>
    state.snapshots[0].dominatorTree.focused.nodeId === deepest.nodeId);
  ok(doc.querySelector(`.node-${deepest.nodeId}`).classList.contains("focused"),
     "The deepest node should be focused now");

  // Expand the deepest node, which triggers an incremental fetch of its lazily
  // loaded subtree.

  EventUtils.synthesizeKey("VK_RIGHT", {}, panel.panelWin);
  yield waitUntilState(store, state =>
    state.snapshots[0].dominatorTree.expanded.has(deepest.nodeId));
  is(getState().snapshots[0].dominatorTree.state,
     dominatorTreeState.INCREMENTAL_FETCHING,
     "Expanding the deepest node should start an incremental fetch of its subtree");
  ok(doc.querySelector(`.node-${deepest.nodeId}`).classList.contains("focused"),
     "The deepest node should still be focused after expansion");

  // Wait for the incremental fetch to complete.

  yield waitUntilState(store, state =>
    state.snapshots[0].dominatorTree.state === dominatorTreeState.LOADED);
  ok(true, "And the incremental fetch completes.");
  ok(doc.querySelector(`.node-${deepest.nodeId}`).classList.contains("focused"),
     "The deepest node should still be focused after we have loaded its children");

  // Find the most up-to-date version of the node whose children we just
  // incrementally fetched.

  const newDeepest = (function findNewDeepest(node = getState().snapshots[0].dominatorTree.root) {
    if (node.nodeId === deepest.nodeId) {
      return node;
    }

    if (node.children) {
      for (let child of node.children) {
        const found = findNewDeepest(child);
        if (found) {
          return found;
        }
      }
    }

    return null;
  }());

  ok(newDeepest, "We found the up-to-date version of deepest");
  ok(newDeepest.children, "And its children are loaded");
  ok(newDeepest.children.length, "And there are more than 0 children");

  const firstChild = newDeepest.children[0];
  ok(firstChild, "deepest should have a first child");
  ok(doc.querySelector(`.node-${firstChild.nodeId}`),
     "and the first child should exist in the dom");

  // Select the newly loaded first child by pressing the right arrow once more.

  EventUtils.synthesizeKey("VK_RIGHT", {}, panel.panelWin);
  yield waitUntilState(store, state =>
    state.snapshots[0].dominatorTree.focused === firstChild);
  ok(doc.querySelector(`.node-${firstChild.nodeId}`).classList.contains("focused"),
     "The first child should now be focused");
});
