/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/ */

// Test that makes sure web console autocomplete happens in the user-selected
// stackframe from the js debugger.

"use strict";

const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" +
                 "test/test-autocomplete-in-stackframe.html";

// Force the old debugger UI since it's directly used (see Bug 1301705)
Services.prefs.setBoolPref("devtools.debugger.new-debugger-frontend", false);
registerCleanupFunction(function* () {
  Services.prefs.clearUserPref("devtools.debugger.new-debugger-frontend");
});

var gStackframes;
registerCleanupFunction(function () {
  gStackframes = null;
});

requestLongerTimeout(2);
add_task(function* () {
  yield loadTab(TEST_URI);
  let hud = yield openConsole();
  yield testCompletion(hud);
});

function* testCompletion(hud) {
  let jsterm = hud.jsterm;
  let input = jsterm.inputNode;
  let popup = jsterm.autocompletePopup;

  // Test that document.title gives string methods. Native getters must execute.
  input.value = "document.title.";
  input.setSelectionRange(input.value.length, input.value.length);
  yield new Promise(resolve => {
    jsterm.complete(jsterm.COMPLETE_HINT_ONLY, resolve);
  });

  let newItems = popup.getItems();
  ok(newItems.length > 0, "'document.title.' gave a list of suggestions");
  ok(newItems.some(function (item) {
    return item.label == "substr";
  }), "autocomplete results do contain substr");
  ok(newItems.some(function (item) {
    return item.label == "toLowerCase";
  }), "autocomplete results do contain toLowerCase");
  ok(newItems.some(function (item) {
    return item.label == "strike";
  }), "autocomplete results do contain strike");

  // Test if 'f' gives 'foo1' but not 'foo2' or 'foo3'
  input.value = "f";
  input.setSelectionRange(1, 1);
  yield new Promise(resolve => {
    jsterm.complete(jsterm.COMPLETE_HINT_ONLY, resolve);
  });

  newItems = popup.getItems();
  ok(newItems.length > 0, "'f' gave a list of suggestions");
  ok(!newItems.every(function (item) {
    return item.label != "foo1";
  }), "autocomplete results do contain foo1");
  ok(!newItems.every(function (item) {
    return item.label != "foo1Obj";
  }), "autocomplete results do contain foo1Obj");
  ok(newItems.every(function (item) {
    return item.label != "foo2";
  }), "autocomplete results do not contain foo2");
  ok(newItems.every(function (item) {
    return item.label != "foo2Obj";
  }), "autocomplete results do not contain foo2Obj");
  ok(newItems.every(function (item) {
    return item.label != "foo3";
  }), "autocomplete results do not contain foo3");
  ok(newItems.every(function (item) {
    return item.label != "foo3Obj";
  }), "autocomplete results do not contain foo3Obj");

  // Test if 'foo1Obj.' gives 'prop1' and 'prop2'
  input.value = "foo1Obj.";
  input.setSelectionRange(8, 8);
  yield new Promise(resolve => {
    jsterm.complete(jsterm.COMPLETE_HINT_ONLY, resolve);
  });

  newItems = popup.getItems();
  ok(!newItems.every(function (item) {
    return item.label != "prop1";
  }), "autocomplete results do contain prop1");
  ok(!newItems.every(function (item) {
    return item.label != "prop2";
  }), "autocomplete results do contain prop2");

  // Test if 'foo1Obj.prop2.' gives 'prop21'
  input.value = "foo1Obj.prop2.";
  input.setSelectionRange(14, 14);
  yield new Promise(resolve => {
    jsterm.complete(jsterm.COMPLETE_HINT_ONLY, resolve);
  });

  newItems = popup.getItems();
  ok(!newItems.every(function (item) {
    return item.label != "prop21";
  }), "autocomplete results do contain prop21");

  info("Opening Debugger");
  let dbg = yield openDebugger();

  info("Waiting for pause");
  yield pauseDebugger(dbg);

  info("Opening Console again");
  yield openConsole();

  // From this point on the
  // Test if 'f' gives 'foo3' and 'foo1' but not 'foo2'
  input.value = "f";
  input.setSelectionRange(1, 1);
  yield new Promise(resolve => {
    jsterm.complete(jsterm.COMPLETE_HINT_ONLY, resolve);
  });

  newItems = popup.getItems();
  ok(newItems.length > 0, "'f' gave a list of suggestions");
  ok(!newItems.every(function (item) {
    return item.label != "foo3";
  }), "autocomplete results do contain foo3");
  ok(!newItems.every(function (item) {
    return item.label != "foo3Obj";
  }), "autocomplete results do contain foo3Obj");
  ok(!newItems.every(function (item) {
    return item.label != "foo1";
  }), "autocomplete results do contain foo1");
  ok(!newItems.every(function (item) {
    return item.label != "foo1Obj";
  }), "autocomplete results do contain foo1Obj");
  ok(newItems.every(function (item) {
    return item.label != "foo2";
  }), "autocomplete results do not contain foo2");
  ok(newItems.every(function (item) {
    return item.label != "foo2Obj";
  }), "autocomplete results do not contain foo2Obj");

  yield openDebugger();

  gStackframes.selectFrame(1);

  info("openConsole");
  yield openConsole();

  // Test if 'f' gives 'foo2' and 'foo1' but not 'foo3'
  input.value = "f";
  input.setSelectionRange(1, 1);
  yield new Promise(resolve => {
    jsterm.complete(jsterm.COMPLETE_HINT_ONLY, resolve);
  });

  newItems = popup.getItems();
  ok(newItems.length > 0, "'f' gave a list of suggestions");
  ok(!newItems.every(function (item) {
    return item.label != "foo2";
  }), "autocomplete results do contain foo2");
  ok(!newItems.every(function (item) {
    return item.label != "foo2Obj";
  }), "autocomplete results do contain foo2Obj");
  ok(!newItems.every(function (item) {
    return item.label != "foo1";
  }), "autocomplete results do contain foo1");
  ok(!newItems.every(function (item) {
    return item.label != "foo1Obj";
  }), "autocomplete results do contain foo1Obj");
  ok(newItems.every(function (item) {
    return item.label != "foo3";
  }), "autocomplete results do not contain foo3");
  ok(newItems.every(function (item) {
    return item.label != "foo3Obj";
  }), "autocomplete results do not contain foo3Obj");

  // Test if 'foo2Obj.' gives 'prop1'
  input.value = "foo2Obj.";
  input.setSelectionRange(8, 8);
  yield new Promise(resolve => {
    jsterm.complete(jsterm.COMPLETE_HINT_ONLY, resolve);
  });

  newItems = popup.getItems();
  ok(!newItems.every(function (item) {
    return item.label != "prop1";
  }), "autocomplete results do contain prop1");

  // Test if 'foo2Obj.prop1.' gives 'prop11'
  input.value = "foo2Obj.prop1.";
  input.setSelectionRange(14, 14);
  yield new Promise(resolve => {
    jsterm.complete(jsterm.COMPLETE_HINT_ONLY, resolve);
  });

  newItems = popup.getItems();
  ok(!newItems.every(function (item) {
    return item.label != "prop11";
  }), "autocomplete results do contain prop11");

  // Test if 'foo2Obj.prop1.prop11.' gives suggestions for a string
  // i.e. 'length'
  input.value = "foo2Obj.prop1.prop11.";
  input.setSelectionRange(21, 21);
  yield new Promise(resolve => {
    jsterm.complete(jsterm.COMPLETE_HINT_ONLY, resolve);
  });

  newItems = popup.getItems();
  ok(!newItems.every(function (item) {
    return item.label != "length";
  }), "autocomplete results do contain length");

  // Test if 'foo1Obj[0].' throws no errors.
  input.value = "foo2Obj[0].";
  input.setSelectionRange(11, 11);
  yield new Promise(resolve => {
    jsterm.complete(jsterm.COMPLETE_HINT_ONLY, resolve);
  });

  newItems = popup.getItems();
  is(newItems.length, 0, "no items for foo2Obj[0]");
}

function pauseDebugger(aResult) {
  let debuggerWin = aResult.panelWin;
  let debuggerController = debuggerWin.DebuggerController;
  let thread = debuggerController.activeThread;
  gStackframes = debuggerController.StackFrames;
  return new Promise(resolve => {
    thread.addOneTimeListener("framesadded", resolve);

    info("firstCall()");
    ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
      content.wrappedJSObject.firstCall();
    });
  });
}
