// nsDoTestsForAutoCompleteWithComposition tests autocomplete with composition.
// Users must include SimpleTest.js and EventUtils.js.

function waitForCondition(condition, nextTest) {
  var tries = 0;
  var interval = setInterval(function() {
    if (condition() || tries >= 30) {
      moveOn();
    }
    tries++;
  }, 100);
  var moveOn = function() { clearInterval(interval); nextTest(); };
}

function nsDoTestsForAutoCompleteWithComposition(aDescription,
                                                 aWindow,
                                                 aTarget,
                                                 aAutoCompleteController,
                                                 aIsFunc,
                                                 aGetTargetValueFunc,
                                                 aOnFinishFunc) {
  this._description = aDescription;
  this._window = aWindow;
  this._target = aTarget;
  this._controller = aAutoCompleteController;

  this._is = aIsFunc;
  this._getTargetValue = aGetTargetValueFunc;
  this._onFinish = aOnFinishFunc;

  this._target.focus();

  this._DefaultCompleteDefaultIndex =
    this._controller.input.completeDefaultIndex;

  this._doTests();
}

nsDoTestsForAutoCompleteWithComposition.prototype = {
  _window: null,
  _target: null,
  _controller: null,
  _DefaultCompleteDefaultIndex: false,
  _description: "",

  _is: null,
  _getTargetValue() { return "not initialized"; },
  _onFinish: null,

  _doTests() {
    if (++this._testingIndex == this._tests.length) {
      this._controller.input.completeDefaultIndex =
        this._DefaultCompleteDefaultIndex;
      this._onFinish();
      return;
    }

    var test = this._tests[this._testingIndex];
    if (this._controller.input.completeDefaultIndex != test.completeDefaultIndex) {
      this._controller.input.completeDefaultIndex = test.completeDefaultIndex;
    }
    test.execute(this._window);

    waitForCondition(() => {
      return this._controller.searchStatus >=
             Components.interfaces.nsIAutoCompleteController.STATUS_COMPLETE_NO_MATCH;
    },
    this._checkResult.bind(this));
  },

  _checkResult() {
    var test = this._tests[this._testingIndex];
    this._is(this._getTargetValue(), test.value,
             this._description + ", " + test.description + ": value");
    this._is(this._controller.searchString, test.searchString,
             this._description + ", " + test.description + ": searchString");
    this._is(this._controller.input.popupOpen, test.popup,
             this._description + ", " + test.description + ": popupOpen");
    this._doTests();
  },

  _testingIndex: -1,
  _tests: [
    // Simple composition when popup hasn't been shown.
    // The autocomplete popup should not be shown during composition, but
    // after compositionend, the popup should be shown.
    { description: "compositionstart shouldn't open the popup",
      completeDefaultIndex: false,
      execute(aWindow) {
        synthesizeCompositionChange(
          { "composition":
            { "string": "M",
              "clauses":
              [
                { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
              ]
            },
            "caret": { "start": 1, "length": 0 },
            "key": { key: "M", code: "KeyM", keyCode: KeyboardEvent.DOM_VK_M,
                     shiftKey: true },
          }, aWindow);
      }, popup: false, value: "M", searchString: ""
    },
    { description: "modifying composition string shouldn't open the popup",
      completeDefaultIndex: false,
      execute(aWindow) {
        synthesizeCompositionChange(
          { "composition":
            { "string": "Mo",
              "clauses":
              [
                { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
              ]
            },
            "caret": { "start": 2, "length": 0 },
            "key": { key: "o", code: "KeyO", keyCode: KeyboardEvent.DOM_VK_O },
          }, aWindow);
      }, popup: false, value: "Mo", searchString: ""
    },
    { description: "compositionend should open the popup",
      completeDefaultIndex: false,
      execute(aWindow) {
        synthesizeComposition({ type: "compositioncommitasis",
          key: { key: "KEY_Enter", code: "Enter" } }, aWindow);
      }, popup: true, value: "Mo", searchString: "Mo"
    },
    // If composition starts when popup is shown, the compositionstart event
    // should cause closing the popup.
    { description: "compositionstart should close the popup",
      completeDefaultIndex: false,
      execute(aWindow) {
        synthesizeCompositionChange(
          { "composition":
            { "string": "z",
              "clauses":
              [
                { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
              ]
            },
            "caret": { "start": 1, "length": 0 },
            "key": { key: "z", code: "KeyZ", keyCode: KeyboardEvent.DOM_VK_Z },
          }, aWindow);
      }, popup: false, value: "Moz", searchString: "Mo"
    },
    { description: "modifying composition string shouldn't reopen the popup",
      completeDefaultIndex: false,
      execute(aWindow) {
        synthesizeCompositionChange(
          { "composition":
            { "string": "zi",
              "clauses":
              [
                { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
              ]
            },
            "caret": { "start": 2, "length": 0 },
            "key": { key: "i", code: "KeyI", keyCode: KeyboardEvent.DOM_VK_I },
          }, aWindow);
      }, popup: false, value: "Mozi", searchString: "Mo"
    },
    { description: "compositionend should research the result and open the popup",
      completeDefaultIndex: false,
      execute(aWindow) {
        synthesizeComposition({ type: "compositioncommitasis",
          key: { key: "KEY_Enter", code: "Enter" } }, aWindow);
      }, popup: true, value: "Mozi", searchString: "Mozi"
    },
    // If composition is cancelled, the value shouldn't be changed.
    { description: "compositionstart should reclose the popup",
      completeDefaultIndex: false,
      execute(aWindow) {
        synthesizeCompositionChange(
          { "composition":
            { "string": "l",
              "clauses":
              [
                { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
              ]
            },
            "caret": { "start": 1, "length": 0 },
            "key": { key: "l", code: "KeyL", keyCode: KeyboardEvent.DOM_VK_L },
          }, aWindow);
      }, popup: false, value: "Mozil", searchString: "Mozi"
    },
    { description: "modifying composition string shouldn't reopen the popup",
      completeDefaultIndex: false,
      execute(aWindow) {
        synthesizeCompositionChange(
          { "composition":
            { "string": "ll",
              "clauses":
              [
                { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
              ]
            },
            "caret": { "start": 2, "length": 0 },
            "key": { key: "l", code: "KeyL", keyCode: KeyboardEvent.DOM_VK_L },
          }, aWindow);
      }, popup: false, value: "Mozill", searchString: "Mozi"
    },
    { description: "modifying composition string to empty string shouldn't reopen the popup",
      completeDefaultIndex: false,
      execute(aWindow) {
        synthesizeCompositionChange(
          { "composition":
            { "string": "",
              "clauses":
              [
                { "length": 0, "attr": 0 }
              ]
            },
            "caret": { "start": 0, "length": 0 }
          }, aWindow);
      }, popup: false, value: "Mozi", searchString: "Mozi"
    },
    { description: "cancled compositionend should reopen the popup",
      completeDefaultIndex: false,
      execute(aWindow) {
        synthesizeComposition({ type: "compositioncommit", data: "",
          key: { key: "KEY_Escape", code: "Escape" } }, aWindow);
      }, popup: true, value: "Mozi", searchString: "Mozi"
    },
    // But if composition replaces some characters and canceled, the search
    // string should be the latest value.
    { description: "compositionstart with selected string should close the popup",
      completeDefaultIndex: false,
      execute(aWindow) {
        synthesizeKey("VK_LEFT", { shiftKey: true }, aWindow);
        synthesizeKey("VK_LEFT", { shiftKey: true }, aWindow);
        synthesizeCompositionChange(
          { "composition":
            { "string": "z",
              "clauses":
              [
                { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
              ]
            },
            "caret": { "start": 1, "length": 0 },
            "key": { key: "z", code: "KeyZ", keyCode: KeyboardEvent.DOM_VK_Z },
          }, aWindow);
      }, popup: false, value: "Moz", searchString: "Mozi"
    },
    { description: "modifying composition string shouldn't reopen the popup",
      completeDefaultIndex: false,
      execute(aWindow) {
        synthesizeCompositionChange(
          { "composition":
            { "string": "zi",
              "clauses":
              [
                { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
              ]
            },
            "caret": { "start": 2, "length": 0 },
            "key": { key: "i", code: "KeyI", keyCode: KeyboardEvent.DOM_VK_I },
          }, aWindow);
      }, popup: false, value: "Mozi", searchString: "Mozi"
    },
    { description: "modifying composition string to empty string shouldn't reopen the popup",
      completeDefaultIndex: false,
      execute(aWindow) {
        synthesizeCompositionChange(
          { "composition":
            { "string": "",
              "clauses":
              [
                { "length": 0, "attr": 0 }
              ]
            },
            "caret": { "start": 0, "length": 0 }
          }, aWindow);
      }, popup: false, value: "Mo", searchString: "Mozi"
    },
    { description: "canceled compositionend should search the result with the latest value",
      completeDefaultIndex: false,
      execute(aWindow) {
        synthesizeComposition({ type: "compositioncommitasis",
          key: { key: "KEY_Escape", code: "Escape" } }, aWindow);
      }, popup: true, value: "Mo", searchString: "Mo"
    },
    // If all characters are removed, the popup should be closed.
    { description: "the value becomes empty by backspace, the popup should be closed",
      completeDefaultIndex: false,
      execute(aWindow) {
        synthesizeKey("VK_BACK_SPACE", {}, aWindow);
        synthesizeKey("VK_BACK_SPACE", {}, aWindow);
      }, popup: false, value: "", searchString: ""
    },
    // composition which is canceled shouldn't cause opening the popup.
    { description: "compositionstart shouldn't open the popup",
      completeDefaultIndex: false,
      execute(aWindow) {
        synthesizeCompositionChange(
          { "composition":
            { "string": "M",
              "clauses":
              [
                { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
              ]
            },
            "caret": { "start": 1, "length": 0 },
            "key": { key: "m", code: "KeyM", keyCode: KeyboardEvent.DOM_VK_M,
                     shiftKey: true },
          }, aWindow);
      }, popup: false, value: "M", searchString: ""
    },
    { description: "modifying composition string shouldn't open the popup",
      completeDefaultIndex: false,
      execute(aWindow) {
        synthesizeCompositionChange(
          { "composition":
            { "string": "Mo",
              "clauses":
              [
                { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
              ]
            },
            "caret": { "start": 2, "length": 0 },
            "key": { key: "o", code: "KeyO", keyCode: KeyboardEvent.DOM_VK_O },
          }, aWindow);
      }, popup: false, value: "Mo", searchString: ""
    },
    { description: "modifying composition string to empty string shouldn't open the popup",
      completeDefaultIndex: false,
      execute(aWindow) {
        synthesizeCompositionChange(
          { "composition":
            { "string": "",
              "clauses":
              [
                { "length": 0, "attr": 0 }
              ]
            },
            "caret": { "start": 0, "length": 0 }
          }, aWindow);
      }, popup: false, value: "", searchString: ""
    },
    { description: "canceled compositionend shouldn't open the popup if it was closed",
      completeDefaultIndex: false,
      execute(aWindow) {
        synthesizeComposition({ type: "compositioncommitasis",
          key: { key: "KEY_Escape", code: "Escape" } }, aWindow);
      }, popup: false, value: "", searchString: ""
    },
    // Down key should open the popup even if the editor is empty.
    { description: "DOWN key should open the popup even if the value is empty",
      completeDefaultIndex: false,
      execute(aWindow) {
        synthesizeKey("VK_DOWN", {}, aWindow);
      }, popup: true, value: "", searchString: ""
    },
    // If popup is open at starting composition, the popup should be reopened
    // after composition anyway.
    { description: "compositionstart shouldn't open the popup",
      completeDefaultIndex: false,
      execute(aWindow) {
        synthesizeCompositionChange(
          { "composition":
            { "string": "M",
              "clauses":
              [
                { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
              ]
            },
            "caret": { "start": 1, "length": 0 },
            "key": { key: "M", code: "KeyM", keyCode: KeyboardEvent.DOM_VK_M,
                     shiftKey: true },
          }, aWindow);
      }, popup: false, value: "M", searchString: ""
    },
    { description: "modifying composition string shouldn't open the popup",
      completeDefaultIndex: false,
      execute(aWindow) {
        synthesizeCompositionChange(
          { "composition":
            { "string": "Mo",
              "clauses":
              [
                { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
              ]
            },
            "caret": { "start": 2, "length": 0 },
            "key": { key: "o", code: "KeyO", keyCode: KeyboardEvent.DOM_VK_O },
          }, aWindow);
      }, popup: false, value: "Mo", searchString: ""
    },
    { description: "modifying composition string to empty string shouldn't open the popup",
      completeDefaultIndex: false,
      execute(aWindow) {
        synthesizeCompositionChange(
          { "composition":
            { "string": "",
              "clauses":
              [
                { "length": 0, "attr": 0 }
              ]
            },
            "caret": { "start": 0, "length": 0 }
          }, aWindow);
      }, popup: false, value: "", searchString: ""
    },
    { description: "canceled compositionend should open the popup if it was opened",
      completeDefaultIndex: false,
      execute(aWindow) {
        synthesizeComposition({ type: "compositioncommitasis",
          key: { key: "KEY_Escape", code: "Escape" } }, aWindow);
      }, popup: true, value: "", searchString: ""
    },
    // Type normally, and hit escape, the popup should be closed.
    { description: "ESCAPE should close the popup after typing something",
      completeDefaultIndex: false,
      execute(aWindow) {
        synthesizeKey("M", { shiftKey: true }, aWindow);
        synthesizeKey("o", { shiftKey: true }, aWindow);
        synthesizeKey("VK_ESCAPE", {}, aWindow);
      }, popup: false, value: "Mo", searchString: "Mo"
    },
    // Even if the popup is closed, composition which is canceled should open
    // the popup if the value isn't empty.
    // XXX This might not be good behavior, but anyway, this is minor issue...
    { description: "compositionstart shouldn't open the popup",
      completeDefaultIndex: false,
      execute(aWindow) {
        synthesizeCompositionChange(
          { "composition":
            { "string": "z",
              "clauses":
              [
                { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
              ]
            },
            "caret": { "start": 1, "length": 0 },
            "key": { key: "z", code: "KeyZ", keyCode: KeyboardEvent.DOM_VK_Z },
          }, aWindow);
      }, popup: false, value: "Moz", searchString: "Mo"
    },
    { description: "modifying composition string shouldn't open the popup",
      completeDefaultIndex: false,
      execute(aWindow) {
        synthesizeCompositionChange(
          { "composition":
            { "string": "zi",
              "clauses":
              [
                { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
              ]
            },
            "caret": { "start": 2, "length": 0 },
            "key": { key: "i", code: "KeyI", keyCode: KeyboardEvent.DOM_VK_I },
          }, aWindow);
      }, popup: false, value: "Mozi", searchString: "Mo"
    },
    { description: "modifying composition string to empty string shouldn't open the popup",
      completeDefaultIndex: false,
      execute(aWindow) {
        synthesizeCompositionChange(
          { "composition":
            { "string": "",
              "clauses":
              [
                { "length": 0, "attr": 0 }
              ]
            },
            "caret": { "start": 0, "length": 0 }
          }, aWindow);
      }, popup: false, value: "Mo", searchString: "Mo"
    },
    { description: "canceled compositionend shouldn't open the popup if the popup was closed",
      completeDefaultIndex: false,
      execute(aWindow) {
        synthesizeComposition({ type: "compositioncommitasis",
          key: { key: "KEY_Escape", code: "Escape" } }, aWindow);
      }, popup: true, value: "Mo", searchString: "Mo"
    },
    // House keeping...
    { description: "house keeping for next tests",
      completeDefaultIndex: false,
      execute(aWindow) {
        synthesizeKey("VK_BACK_SPACE", {}, aWindow);
        synthesizeKey("VK_BACK_SPACE", {}, aWindow);
      }, popup: false, value: "", searchString: ""
    },
    // Testing for nsIAutoCompleteInput.completeDefaultIndex being true.
    { description: "compositionstart shouldn't open the popup (completeDefaultIndex is true)",
      completeDefaultIndex: true,
      execute(aWindow) {
        synthesizeCompositionChange(
          { "composition":
            { "string": "M",
              "clauses":
              [
                { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
              ]
            },
            "caret": { "start": 1, "length": 0 },
            "key": { key: "M", code: "KeyM", keyCode: KeyboardEvent.DOM_VK_M,
                     shiftKey: true },
          }, aWindow);
      }, popup: false, value: "M", searchString: ""
    },
    { description: "modifying composition string shouldn't open the popup (completeDefaultIndex is true)",
      completeDefaultIndex: true,
      execute(aWindow) {
        synthesizeCompositionChange(
          { "composition":
            { "string": "Mo",
              "clauses":
              [
                { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
              ]
            },
            "caret": { "start": 2, "length": 0 },
            "key": { key: "o", code: "KeyO", keyCode: KeyboardEvent.DOM_VK_O },
          }, aWindow);
      }, popup: false, value: "Mo", searchString: ""
    },
    { description: "compositionend should open the popup (completeDefaultIndex is true)",
      completeDefaultIndex: true,
      execute(aWindow) {
        synthesizeComposition({ type: "compositioncommitasis",
          key: { key: "KEY_Enter", code: "Enter" } }, aWindow);
      }, popup: true, value: "Mozilla", searchString: "Mo"
    },
    // House keeping...
    { description: "house keeping for next tests",
      completeDefaultIndex: false,
      execute(aWindow) {
        synthesizeKey("VK_BACK_SPACE", {}, aWindow);
        synthesizeKey("VK_BACK_SPACE", {}, aWindow);
        synthesizeKey("VK_BACK_SPACE", {}, aWindow);
        synthesizeKey("VK_BACK_SPACE", {}, aWindow);
        synthesizeKey("VK_BACK_SPACE", {}, aWindow);
        synthesizeKey("VK_BACK_SPACE", {}, aWindow);
      }, popup: false, value: "", searchString: ""
    }
  ]
};
