From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 21:33:14 +0200 Subject: Adding upstream version 115.7.0esr. Signed-off-by: Daniel Baumann --- .../autocomplete/tests/unit/head_autocomplete.js | 203 ++++++++++++++++ .../autocomplete/tests/unit/test_330578.js | 41 ++++ .../autocomplete/tests/unit/test_378079.js | 255 ++++++++++++++++++++ .../autocomplete/tests/unit/test_393191.js | 240 +++++++++++++++++++ .../autocomplete/tests/unit/test_440866.js | 256 ++++++++++++++++++++ .../autocomplete/tests/unit/test_463023.js | 13 ++ .../autocomplete/tests/unit/test_660156.js | 113 +++++++++ .../tests/unit/test_autocomplete_multiple.js | 250 ++++++++++++++++++++ .../tests/unit/test_autocomplete_userContextId.js | 44 ++++ .../tests/unit/test_autofillSelectedPopupIndex.js | 109 +++++++++ .../tests/unit/test_badDefaultIndex.js | 100 ++++++++ .../tests/unit/test_completeDefaultIndex_casing.js | 64 +++++ .../tests/unit/test_finalCompleteValue.js | 62 +++++ .../unit/test_finalCompleteValueSelectedIndex.js | 139 +++++++++++ .../unit/test_finalCompleteValue_defaultIndex.js | 112 +++++++++ .../unit/test_finalCompleteValue_forceComplete.js | 138 +++++++++++ .../tests/unit/test_finalDefaultCompleteValue.js | 80 +++++++ .../tests/unit/test_immediate_search.js | 174 ++++++++++++++ .../autocomplete/tests/unit/test_insertMatchAt.js | 15 ++ .../autocomplete/tests/unit/test_previousResult.js | 257 +++++++++++++++++++++ .../autocomplete/tests/unit/test_removeMatchAt.js | 16 ++ .../autocomplete/tests/unit/test_stopSearch.js | 171 ++++++++++++++ .../autocomplete/tests/unit/xpcshell.ini | 24 ++ 23 files changed, 2876 insertions(+) create mode 100644 toolkit/components/autocomplete/tests/unit/head_autocomplete.js create mode 100644 toolkit/components/autocomplete/tests/unit/test_330578.js create mode 100644 toolkit/components/autocomplete/tests/unit/test_378079.js create mode 100644 toolkit/components/autocomplete/tests/unit/test_393191.js create mode 100644 toolkit/components/autocomplete/tests/unit/test_440866.js create mode 100644 toolkit/components/autocomplete/tests/unit/test_463023.js create mode 100644 toolkit/components/autocomplete/tests/unit/test_660156.js create mode 100644 toolkit/components/autocomplete/tests/unit/test_autocomplete_multiple.js create mode 100644 toolkit/components/autocomplete/tests/unit/test_autocomplete_userContextId.js create mode 100644 toolkit/components/autocomplete/tests/unit/test_autofillSelectedPopupIndex.js create mode 100644 toolkit/components/autocomplete/tests/unit/test_badDefaultIndex.js create mode 100644 toolkit/components/autocomplete/tests/unit/test_completeDefaultIndex_casing.js create mode 100644 toolkit/components/autocomplete/tests/unit/test_finalCompleteValue.js create mode 100644 toolkit/components/autocomplete/tests/unit/test_finalCompleteValueSelectedIndex.js create mode 100644 toolkit/components/autocomplete/tests/unit/test_finalCompleteValue_defaultIndex.js create mode 100644 toolkit/components/autocomplete/tests/unit/test_finalCompleteValue_forceComplete.js create mode 100644 toolkit/components/autocomplete/tests/unit/test_finalDefaultCompleteValue.js create mode 100644 toolkit/components/autocomplete/tests/unit/test_immediate_search.js create mode 100644 toolkit/components/autocomplete/tests/unit/test_insertMatchAt.js create mode 100644 toolkit/components/autocomplete/tests/unit/test_previousResult.js create mode 100644 toolkit/components/autocomplete/tests/unit/test_removeMatchAt.js create mode 100644 toolkit/components/autocomplete/tests/unit/test_stopSearch.js create mode 100644 toolkit/components/autocomplete/tests/unit/xpcshell.ini (limited to 'toolkit/components/autocomplete/tests/unit') diff --git a/toolkit/components/autocomplete/tests/unit/head_autocomplete.js b/toolkit/components/autocomplete/tests/unit/head_autocomplete.js new file mode 100644 index 0000000000..6aa0382d82 --- /dev/null +++ b/toolkit/components/autocomplete/tests/unit/head_autocomplete.js @@ -0,0 +1,203 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); + +/** + * Dummy nsIAutoCompleteInput source that returns + * the given list of AutoCompleteSearch names. + * + * Implements only the methods needed for this test. + */ +function AutoCompleteInputBase(aSearches) { + this.searches = aSearches; +} +AutoCompleteInputBase.prototype = { + // Array of AutoCompleteSearch names + searches: null, + + minResultsForPopup: 0, + timeout: 10, + searchParam: "", + textValue: "", + disableAutoComplete: false, + completeDefaultIndex: false, + + // Text selection range + _selStart: 0, + _selEnd: 0, + get selectionStart() { + return this._selStart; + }, + get selectionEnd() { + return this._selEnd; + }, + selectTextRange(aStart, aEnd) { + this._selStart = aStart; + this._selEnd = aEnd; + }, + + get searchCount() { + return this.searches.length; + }, + + getSearchAt(aIndex) { + return this.searches[aIndex]; + }, + + onSearchBegin() {}, + onSearchComplete() {}, + + popupOpen: false, + + get popup() { + if (!this._popup) { + this._popup = new AutocompletePopupBase(this); + } + return this._popup; + }, + + // nsISupports implementation + QueryInterface: ChromeUtils.generateQI(["nsIAutoCompleteInput"]), +}; + +/** + * nsIAutoCompleteResult implementation + */ +function AutoCompleteResultBase(aValues) { + this._values = aValues; +} +AutoCompleteResultBase.prototype = { + // Arrays + _values: null, + _comments: [], + _styles: [], + _finalCompleteValues: [], + + searchString: "", + searchResult: null, + + defaultIndex: -1, + + get matchCount() { + return this._values.length; + }, + + getValueAt(aIndex) { + return this._values[aIndex]; + }, + + getLabelAt(aIndex) { + return this.getValueAt(aIndex); + }, + + getCommentAt(aIndex) { + return this._comments[aIndex]; + }, + + getStyleAt(aIndex) { + return this._styles[aIndex]; + }, + + getImageAt(aIndex) { + return ""; + }, + + getFinalCompleteValueAt(aIndex) { + return this._finalCompleteValues[aIndex] || this._values[aIndex]; + }, + + isRemovableAt(aRowIndex) { + return true; + }, + + removeValueAt(aRowIndex) {}, + + // nsISupports implementation + QueryInterface: ChromeUtils.generateQI(["nsIAutoCompleteResult"]), +}; + +/** + * nsIAutoCompleteSearch implementation that always returns + * the same result set. + */ +function AutoCompleteSearchBase(aName, aResult) { + this.name = aName; + this._result = aResult; +} +AutoCompleteSearchBase.prototype = { + // Search name. Used by AutoCompleteController + name: null, + + // AutoCompleteResult + _result: null, + + startSearch(aSearchString, aSearchParam, aPreviousResult, aListener) { + var result = this._result; + + result.searchResult = Ci.nsIAutoCompleteResult.RESULT_SUCCESS; + aListener.onSearchResult(this, result); + }, + + stopSearch() {}, + + // nsISupports implementation + QueryInterface: ChromeUtils.generateQI([ + "nsIFactory", + "nsIAutoCompleteSearch", + ]), + + // nsIFactory implementation + createInstance(iid) { + return this.QueryInterface(iid); + }, +}; + +function AutocompletePopupBase(input) { + this.input = input; +} +AutocompletePopupBase.prototype = { + selectedIndex: 0, + invalidate() {}, + selectBy(reverse, page) { + let numRows = this.input.controller.matchCount; + if (numRows > 0) { + let delta = reverse ? -1 : 1; + this.selectedIndex = (this.selectedIndex + delta) % numRows; + if (this.selectedIndex < 0) { + this.selectedIndex = numRows - 1; + } + } + }, + QueryInterface: ChromeUtils.generateQI(["nsIAutoCompletePopup"]), +}; + +/** + * Helper to register an AutoCompleteSearch with the given name. + * Allows the AutoCompleteController to find the search. + */ +function registerAutoCompleteSearch(aSearch) { + var name = "@mozilla.org/autocomplete/search;1?name=" + aSearch.name; + var cid = Services.uuid.generateUUID(); + + var desc = "Test AutoCompleteSearch"; + var componentManager = Components.manager.QueryInterface( + Ci.nsIComponentRegistrar + ); + componentManager.registerFactory(cid, desc, name, aSearch); + + // Keep the id on the object so we can unregister later + aSearch.cid = cid; +} + +/** + * Helper to unregister an AutoCompleteSearch. + */ +function unregisterAutoCompleteSearch(aSearch) { + var componentManager = Components.manager.QueryInterface( + Ci.nsIComponentRegistrar + ); + componentManager.unregisterFactory(aSearch.cid, aSearch); +} diff --git a/toolkit/components/autocomplete/tests/unit/test_330578.js b/toolkit/components/autocomplete/tests/unit/test_330578.js new file mode 100644 index 0000000000..5f549cb4c7 --- /dev/null +++ b/toolkit/components/autocomplete/tests/unit/test_330578.js @@ -0,0 +1,41 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var gResultListener = { + _lastResult: null, + _lastValue: "", + + onValueRemoved(aResult, aValue) { + this._lastResult = aResult; + this._lastValue = aValue; + }, +}; + +// main +function run_test() { + var result = Cc["@mozilla.org/autocomplete/simple-result;1"].createInstance( + Ci.nsIAutoCompleteSimpleResult + ); + result.appendMatch("a", ""); + result.appendMatch("b", ""); + result.appendMatch("c", ""); + result.setListener(gResultListener); + Assert.equal(result.matchCount, 3); + result.removeValueAt(0); + Assert.equal(result.matchCount, 2); + Assert.equal(gResultListener._lastResult, result); + Assert.equal(gResultListener._lastValue, "a"); + + result.removeValueAt(0); + Assert.equal(result.matchCount, 1); + Assert.equal(gResultListener._lastValue, "b"); + + // check that we don't get notified if the listener is unset + result.setListener(null); + result.removeValueAt(0); // "c" + Assert.equal(result.matchCount, 0); + Assert.equal(gResultListener._lastValue, "b"); +} diff --git a/toolkit/components/autocomplete/tests/unit/test_378079.js b/toolkit/components/autocomplete/tests/unit/test_378079.js new file mode 100644 index 0000000000..007d36122f --- /dev/null +++ b/toolkit/components/autocomplete/tests/unit/test_378079.js @@ -0,0 +1,255 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Unit test for Bug 378079 - AutoComplete returns invalid rows when + * more than one AutoCompleteSearch is used. + */ + +/** + * Dummy nsIAutoCompleteInput source that returns + * the given list of AutoCompleteSearch names. + * + * Implements only the methods needed for this test. + */ +function AutoCompleteInput(aSearches) { + this.searches = aSearches; +} +AutoCompleteInput.prototype = { + constructor: AutoCompleteInput, + + // Array of AutoCompleteSearch names + searches: null, + + minResultsForPopup: 0, + timeout: 10, + searchParam: "", + textValue: "", + disableAutoComplete: false, + completeDefaultIndex: false, + + get searchCount() { + return this.searches.length; + }, + + getSearchAt(aIndex) { + return this.searches[aIndex]; + }, + + onSearchBegin() {}, + onSearchComplete() {}, + + popupOpen: false, + + popup: { + setSelectedIndex(aIndex) {}, + invalidate() {}, + + // nsISupports implementation + QueryInterface: ChromeUtils.generateQI(["nsIAutoCompletePopup"]), + }, + + // nsISupports implementation + QueryInterface: ChromeUtils.generateQI(["nsIAutoCompleteInput"]), +}; + +/** + * nsIAutoCompleteResult implementation + */ +function AutoCompleteResult(aValues, aComments, aStyles) { + this._values = aValues; + this._comments = aComments; + this._styles = aStyles; + + if (this._values.length) { + this.searchResult = Ci.nsIAutoCompleteResult.RESULT_SUCCESS; + } else { + this.searchResult = Ci.nsIAutoCompleteResult.RESULT_NOMATCH; + } +} +AutoCompleteResult.prototype = { + constructor: AutoCompleteResult, + + // Arrays + _values: null, + _comments: null, + _styles: null, + + searchString: "", + searchResult: null, + + defaultIndex: 0, + + get matchCount() { + return this._values.length; + }, + + getValueAt(aIndex) { + return this._values[aIndex]; + }, + + getLabelAt(aIndex) { + return this.getValueAt(aIndex); + }, + + getCommentAt(aIndex) { + return this._comments[aIndex]; + }, + + getStyleAt(aIndex) { + return this._styles[aIndex]; + }, + + getImageAt(aIndex) { + return ""; + }, + + getFinalCompleteValueAt(aIndex) { + return this.getValueAt(aIndex); + }, + + isRemovableAt(aRowIndex) { + return true; + }, + + removeValueAt(aRowIndex) {}, + + // nsISupports implementation + QueryInterface: ChromeUtils.generateQI(["nsIAutoCompleteResult"]), +}; + +/** + * nsIAutoCompleteSearch implementation that always returns + * the same result set. + */ +function AutoCompleteSearch(aName, aResult) { + this.name = aName; + this._result = aResult; +} +AutoCompleteSearch.prototype = { + constructor: AutoCompleteSearch, + + // Search name. Used by AutoCompleteController + name: null, + + // AutoCompleteResult + _result: null, + + /** + * Return the same result set for every search + */ + startSearch(aSearchString, aSearchParam, aPreviousResult, aListener) { + aListener.onSearchResult(this, this._result); + }, + + stopSearch() {}, + + // nsISupports implementation + QueryInterface: ChromeUtils.generateQI([ + "nsIFactory", + "nsIAutoCompleteSearch", + ]), + + // nsIFactory implementation + createInstance(iid) { + return this.QueryInterface(iid); + }, +}; + +/** + * Helper to register an AutoCompleteSearch with the given name. + * Allows the AutoCompleteController to find the search. + */ +function registerAutoCompleteSearch(aSearch) { + var name = "@mozilla.org/autocomplete/search;1?name=" + aSearch.name; + + var uuidGenerator = Services.uuid; + var cid = uuidGenerator.generateUUID(); + + var desc = "Test AutoCompleteSearch"; + + var componentManager = Components.manager.QueryInterface( + Ci.nsIComponentRegistrar + ); + componentManager.registerFactory(cid, desc, name, aSearch); + + // Keep the id on the object so we can unregister later + aSearch.cid = cid; +} + +/** + * Helper to unregister an AutoCompleteSearch. + */ +function unregisterAutoCompleteSearch(aSearch) { + var componentManager = Components.manager.QueryInterface( + Ci.nsIComponentRegistrar + ); + componentManager.unregisterFactory(aSearch.cid, aSearch); +} + +/** + * Test AutoComplete with multiple AutoCompleteSearch sources. + */ +function run_test() { + // Make an AutoCompleteSearch that always returns nothing + var emptySearch = new AutoCompleteSearch( + "test-empty-search", + new AutoCompleteResult([], [], []) + ); + + // Make an AutoCompleteSearch that returns two values + var expectedValues = ["test1", "test2"]; + var regularSearch = new AutoCompleteSearch( + "test-regular-search", + new AutoCompleteResult(expectedValues, [], []) + ); + + // Register searches so AutoCompleteController can find them + registerAutoCompleteSearch(emptySearch); + registerAutoCompleteSearch(regularSearch); + + var controller = Cc["@mozilla.org/autocomplete/controller;1"].getService( + Ci.nsIAutoCompleteController + ); + + // Make an AutoCompleteInput that uses our searches + // and confirms results on search complete + var input = new AutoCompleteInput([emptySearch.name, regularSearch.name]); + var numSearchesStarted = 0; + + input.onSearchBegin = function () { + numSearchesStarted++; + Assert.equal(numSearchesStarted, 1); + }; + + input.onSearchComplete = function () { + Assert.equal(numSearchesStarted, 1); + + Assert.equal( + controller.searchStatus, + Ci.nsIAutoCompleteController.STATUS_COMPLETE_MATCH + ); + Assert.equal(controller.matchCount, 2); + + // Confirm expected result values + for (var i = 0; i < expectedValues.length; i++) { + Assert.equal(expectedValues[i], controller.getValueAt(i)); + } + + // Unregister searches + unregisterAutoCompleteSearch(emptySearch); + unregisterAutoCompleteSearch(regularSearch); + + do_test_finished(); + }; + + controller.input = input; + + // Search is asynchronous, so don't let the test finish immediately + do_test_pending(); + + controller.startSearch("test"); +} diff --git a/toolkit/components/autocomplete/tests/unit/test_393191.js b/toolkit/components/autocomplete/tests/unit/test_393191.js new file mode 100644 index 0000000000..439a140126 --- /dev/null +++ b/toolkit/components/autocomplete/tests/unit/test_393191.js @@ -0,0 +1,240 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Unit test for Bug 393191 - AutoComplete crashes if result is null + */ + +/** + * Dummy nsIAutoCompleteInput source that returns + * the given list of AutoCompleteSearch names. + * + * Implements only the methods needed for this test. + */ +function AutoCompleteInput(aSearches) { + this.searches = aSearches; +} +AutoCompleteInput.prototype = { + constructor: AutoCompleteInput, + + // Array of AutoCompleteSearch names + searches: null, + + minResultsForPopup: 0, + timeout: 10, + searchParam: "", + textValue: "", + disableAutoComplete: false, + completeDefaultIndex: false, + + get searchCount() { + return this.searches.length; + }, + + getSearchAt(aIndex) { + return this.searches[aIndex]; + }, + + onSearchBegin() {}, + onSearchComplete() {}, + + popupOpen: false, + + popup: { + setSelectedIndex(aIndex) {}, + invalidate() {}, + + // nsISupports implementation + QueryInterface: ChromeUtils.generateQI(["nsIAutoCompletePopup"]), + }, + + // nsISupports implementation + QueryInterface: ChromeUtils.generateQI(["nsIAutoCompleteInput"]), +}; + +/** + * nsIAutoCompleteResult implementation + */ +function AutoCompleteResult(aValues, aComments, aStyles) { + this._values = aValues; + this._comments = aComments; + this._styles = aStyles; + + if (this._values.length) { + this.searchResult = Ci.nsIAutoCompleteResult.RESULT_SUCCESS; + } else { + this.searchResult = Ci.nsIAutoCompleteResult.RESULT_NOMATCH; + } +} +AutoCompleteResult.prototype = { + constructor: AutoCompleteResult, + + // Arrays + _values: null, + _comments: null, + _styles: null, + + searchString: "", + searchResult: null, + + defaultIndex: 0, + + get matchCount() { + return this._values.length; + }, + + getValueAt(aIndex) { + return this._values[aIndex]; + }, + + getLabelAt(aIndex) { + return this.getValueAt(aIndex); + }, + + getCommentAt(aIndex) { + return this._comments[aIndex]; + }, + + getStyleAt(aIndex) { + return this._styles[aIndex]; + }, + + getImageAt(aIndex) { + return ""; + }, + + getFinalCompleteValueAt(aIndex) { + return this.getValueAt(aIndex); + }, + + isRemovableAt(aRowIndex) { + return true; + }, + + removeValueAt(aRowIndex) {}, + + // nsISupports implementation + QueryInterface: ChromeUtils.generateQI(["nsIAutoCompleteResult"]), +}; + +/** + * nsIAutoCompleteSearch implementation that always returns + * the same result set. + */ +function AutoCompleteSearch(aName, aResult) { + this.name = aName; + this._result = aResult; +} +AutoCompleteSearch.prototype = { + constructor: AutoCompleteSearch, + + // Search name. Used by AutoCompleteController + name: null, + + // AutoCompleteResult + _result: null, + + /** + * Return the same result set for every search + */ + startSearch(aSearchString, aSearchParam, aPreviousResult, aListener) { + aListener.onSearchResult(this, this._result); + }, + + stopSearch() {}, + + // nsISupports implementation + QueryInterface: ChromeUtils.generateQI([ + "nsIFactory", + "nsIAutoCompleteSearch", + ]), + + // nsIFactory implementation + createInstance(iid) { + return this.QueryInterface(iid); + }, +}; + +/** + * Helper to register an AutoCompleteSearch with the given name. + * Allows the AutoCompleteController to find the search. + */ +function registerAutoCompleteSearch(aSearch) { + var name = "@mozilla.org/autocomplete/search;1?name=" + aSearch.name; + + var uuidGenerator = Services.uuid; + var cid = uuidGenerator.generateUUID(); + + var desc = "Test AutoCompleteSearch"; + + var componentManager = Components.manager.QueryInterface( + Ci.nsIComponentRegistrar + ); + componentManager.registerFactory(cid, desc, name, aSearch); + + // Keep the id on the object so we can unregister later + aSearch.cid = cid; +} + +/** + * Helper to unregister an AutoCompleteSearch. + */ +function unregisterAutoCompleteSearch(aSearch) { + var componentManager = Components.manager.QueryInterface( + Ci.nsIComponentRegistrar + ); + componentManager.unregisterFactory(aSearch.cid, aSearch); +} + +/** + * Test AutoComplete with a search that returns a null result + */ +function run_test() { + // Make an AutoCompleteSearch that always returns nothing + var emptySearch = new AutoCompleteSearch( + "test-empty-search", + new AutoCompleteResult([], [], []) + ); + + // Register search so AutoCompleteController can find them + registerAutoCompleteSearch(emptySearch); + + var controller = Cc["@mozilla.org/autocomplete/controller;1"].getService( + Ci.nsIAutoCompleteController + ); + + // Make an AutoCompleteInput that uses our search + // and confirms results on search complete + var input = new AutoCompleteInput([emptySearch.name]); + var numSearchesStarted = 0; + + input.onSearchBegin = function () { + numSearchesStarted++; + Assert.equal(numSearchesStarted, 1); + }; + + input.onSearchComplete = function () { + Assert.equal(numSearchesStarted, 1); + + Assert.equal( + controller.searchStatus, + Ci.nsIAutoCompleteController.STATUS_COMPLETE_NO_MATCH + ); + Assert.equal(controller.matchCount, 0); + + // Unregister searches + unregisterAutoCompleteSearch(emptySearch); + + do_test_finished(); + }; + + controller.input = input; + + // Search is asynchronous, so don't let the test finish immediately + do_test_pending(); + + controller.startSearch("test"); +} diff --git a/toolkit/components/autocomplete/tests/unit/test_440866.js b/toolkit/components/autocomplete/tests/unit/test_440866.js new file mode 100644 index 0000000000..16059924bc --- /dev/null +++ b/toolkit/components/autocomplete/tests/unit/test_440866.js @@ -0,0 +1,256 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Unit test for Bug 440866 - First AutoCompleteSearch that returns + * RESULT_NOMATCH cancels all other searches when popup is open + */ + +/** + * Dummy nsIAutoCompleteInput source that returns + * the given list of AutoCompleteSearch names. + * + * Implements only the methods needed for this test. + */ +function AutoCompleteInput(aSearches) { + this.searches = aSearches; +} +AutoCompleteInput.prototype = { + constructor: AutoCompleteInput, + + // Array of AutoCompleteSearch names + searches: null, + + minResultsForPopup: 0, + timeout: 10, + searchParam: "", + textValue: "", + disableAutoComplete: false, + completeDefaultIndex: false, + + get searchCount() { + return this.searches.length; + }, + + getSearchAt(aIndex) { + return this.searches[aIndex]; + }, + + onSearchBegin() {}, + onSearchComplete() {}, + + popupOpen: false, + + popup: { + setSelectedIndex(aIndex) {}, + invalidate() {}, + + // nsISupports implementation + QueryInterface: ChromeUtils.generateQI(["nsIAutoCompletePopup"]), + }, + + // nsISupports implementation + QueryInterface: ChromeUtils.generateQI(["nsIAutoCompleteInput"]), +}; + +/** + * nsIAutoCompleteResult implementation + */ +function AutoCompleteResult(aValues, aComments, aStyles) { + this._values = aValues; + this._comments = aComments; + this._styles = aStyles; + + if (this._values.length) { + this.searchResult = Ci.nsIAutoCompleteResult.RESULT_SUCCESS; + } else { + this.searchResult = Ci.nsIAutoCompleteResult.RESULT_NOMATCH; + } +} +AutoCompleteResult.prototype = { + constructor: AutoCompleteResult, + + // Arrays + _values: null, + _comments: null, + _styles: null, + + searchString: "", + searchResult: null, + + defaultIndex: 0, + + get matchCount() { + return this._values.length; + }, + + getValueAt(aIndex) { + return this._values[aIndex]; + }, + + getLabelAt(aIndex) { + return this.getValueAt(aIndex); + }, + + getCommentAt(aIndex) { + return this._comments[aIndex]; + }, + + getStyleAt(aIndex) { + return this._styles[aIndex]; + }, + + getImageAt(aIndex) { + return ""; + }, + + getFinalCompleteValueAt(aIndex) { + return this.getValueAt(aIndex); + }, + + isRemovableAt(aRowIndex) { + return true; + }, + + removeValueAt(aRowIndex) {}, + + // nsISupports implementation + QueryInterface: ChromeUtils.generateQI(["nsIAutoCompleteResult"]), +}; + +/** + * nsIAutoCompleteSearch implementation that always returns + * the same result set. + */ +function AutoCompleteSearch(aName, aResult) { + this.name = aName; + this._result = aResult; +} +AutoCompleteSearch.prototype = { + constructor: AutoCompleteSearch, + + // Search name. Used by AutoCompleteController + name: null, + + // AutoCompleteResult + _result: null, + + /** + * Return the same result set for every search + */ + startSearch(aSearchString, aSearchParam, aPreviousResult, aListener) { + aListener.onSearchResult(this, this._result); + }, + + stopSearch() {}, + + // nsISupports implementation + QueryInterface: ChromeUtils.generateQI([ + "nsIFactory", + "nsIAutoCompleteSearch", + ]), + + // nsIFactory implementation + createInstance(iid) { + return this.QueryInterface(iid); + }, +}; + +/** + * Helper to register an AutoCompleteSearch with the given name. + * Allows the AutoCompleteController to find the search. + */ +function registerAutoCompleteSearch(aSearch) { + var name = "@mozilla.org/autocomplete/search;1?name=" + aSearch.name; + + var uuidGenerator = Services.uuid; + var cid = uuidGenerator.generateUUID(); + + var desc = "Test AutoCompleteSearch"; + + var componentManager = Components.manager.QueryInterface( + Ci.nsIComponentRegistrar + ); + componentManager.registerFactory(cid, desc, name, aSearch); + + // Keep the id on the object so we can unregister later + aSearch.cid = cid; +} + +/** + * Helper to unregister an AutoCompleteSearch. + */ +function unregisterAutoCompleteSearch(aSearch) { + var componentManager = Components.manager.QueryInterface( + Ci.nsIComponentRegistrar + ); + componentManager.unregisterFactory(aSearch.cid, aSearch); +} + +/** + * Test AutoComplete with multiple AutoCompleteSearch sources. + */ +function run_test() { + // Make an AutoCompleteSearch that always returns nothing + var emptySearch = new AutoCompleteSearch( + "test-empty-search", + new AutoCompleteResult([], [], []) + ); + + // Make an AutoCompleteSearch that returns two values + var expectedValues = ["test1", "test2"]; + var regularSearch = new AutoCompleteSearch( + "test-regular-search", + new AutoCompleteResult(expectedValues, [], []) + ); + + // Register searches so AutoCompleteController can find them + registerAutoCompleteSearch(emptySearch); + registerAutoCompleteSearch(regularSearch); + + var controller = Cc["@mozilla.org/autocomplete/controller;1"].getService( + Ci.nsIAutoCompleteController + ); + + // Make an AutoCompleteInput that uses our searches + // and confirms results on search complete + var input = new AutoCompleteInput([emptySearch.name, regularSearch.name]); + var numSearchesStarted = 0; + + input.onSearchBegin = function () { + numSearchesStarted++; + Assert.equal(numSearchesStarted, 1); + Assert.equal(input.searchCount, 2); + }; + + input.onSearchComplete = function () { + Assert.equal(numSearchesStarted, 1); + + Assert.equal( + controller.searchStatus, + Ci.nsIAutoCompleteController.STATUS_COMPLETE_MATCH + ); + Assert.equal(controller.matchCount, 2); + + // Confirm expected result values + for (var i = 0; i < expectedValues.length; i++) { + Assert.equal(expectedValues[i], controller.getValueAt(i)); + } + + Assert.ok(input.popupOpen); + + // Unregister searches + unregisterAutoCompleteSearch(emptySearch); + unregisterAutoCompleteSearch(regularSearch); + + do_test_finished(); + }; + + controller.input = input; + + // Search is asynchronous, so don't let the test finish immediately + do_test_pending(); + + controller.startSearch("test"); +} diff --git a/toolkit/components/autocomplete/tests/unit/test_463023.js b/toolkit/components/autocomplete/tests/unit/test_463023.js new file mode 100644 index 0000000000..48d2284c1b --- /dev/null +++ b/toolkit/components/autocomplete/tests/unit/test_463023.js @@ -0,0 +1,13 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// main +function run_test() { + var result = Cc["@mozilla.org/autocomplete/controller;1"].createInstance( + Ci.nsIAutoCompleteController + ); + Assert.equal(result.searchStatus, Ci.nsIAutoCompleteController.STATUS_NONE); +} diff --git a/toolkit/components/autocomplete/tests/unit/test_660156.js b/toolkit/components/autocomplete/tests/unit/test_660156.js new file mode 100644 index 0000000000..a5f167e15b --- /dev/null +++ b/toolkit/components/autocomplete/tests/unit/test_660156.js @@ -0,0 +1,113 @@ +/** + * Search object that returns results at different times. + * First, the search that returns results asynchronously. + */ +function AutoCompleteAsyncSearch(aName, aResult) { + this.name = aName; + this._result = aResult; +} +AutoCompleteAsyncSearch.prototype = Object.create( + AutoCompleteSearchBase.prototype +); +AutoCompleteAsyncSearch.prototype.startSearch = function ( + aSearchString, + aSearchParam, + aPreviousResult, + aListener +) { + this._result.searchResult = Ci.nsIAutoCompleteResult.RESULT_NOMATCH_ONGOING; + aListener.onSearchResult(this, this._result); + + do_timeout(500, () => { + this._returnResults(aListener); + }); +}; + +AutoCompleteAsyncSearch.prototype._returnResults = function (aListener) { + var result = this._result; + + result.searchResult = Ci.nsIAutoCompleteResult.RESULT_SUCCESS; + aListener.onSearchResult(this, result); +}; + +/** + * The synchronous version + */ +function AutoCompleteSyncSearch(aName, aResult) { + this.name = aName; + this._result = aResult; +} +AutoCompleteSyncSearch.prototype = Object.create( + AutoCompleteAsyncSearch.prototype +); +AutoCompleteSyncSearch.prototype.startSearch = function ( + aSearchString, + aSearchParam, + aPreviousResult, + aListener +) { + this._returnResults(aListener); +}; + +/** + * Results object + */ +function AutoCompleteResult(aValues, aDefaultIndex) { + this._values = aValues; + this.defaultIndex = aDefaultIndex; +} +AutoCompleteResult.prototype = Object.create(AutoCompleteResultBase.prototype); + +/** + * Test AutoComplete with multiple AutoCompleteSearch sources, with one of them + * (index != 0) returning before the rest. + */ +function run_test() { + do_test_pending(); + + var results = ["mozillaTest"]; + var inputStr = "moz"; + + // Async search + var asyncSearch = new AutoCompleteAsyncSearch( + "Async", + new AutoCompleteResult(results, -1) + ); + // Sync search + var syncSearch = new AutoCompleteSyncSearch( + "Sync", + new AutoCompleteResult(results, 0) + ); + + // Register searches so AutoCompleteController can find them + registerAutoCompleteSearch(asyncSearch); + registerAutoCompleteSearch(syncSearch); + + var controller = Cc["@mozilla.org/autocomplete/controller;1"].getService( + Ci.nsIAutoCompleteController + ); + + // Make an AutoCompleteInput that uses our searches + // and confirms results on search complete. + // Async search MUST be FIRST to trigger the bug this tests. + var input = new AutoCompleteInputBase([asyncSearch.name, syncSearch.name]); + input.completeDefaultIndex = true; + input.textValue = inputStr; + + // Caret must be at the end. Autofill doesn't happen unless you're typing + // characters at the end. + var strLen = inputStr.length; + input.selectTextRange(strLen, strLen); + + controller.input = input; + controller.startSearch(inputStr); + + input.onSearchComplete = function () { + Assert.equal(input.textValue, results[0]); + + // Unregister searches + unregisterAutoCompleteSearch(asyncSearch); + unregisterAutoCompleteSearch(syncSearch); + do_test_finished(); + }; +} diff --git a/toolkit/components/autocomplete/tests/unit/test_autocomplete_multiple.js b/toolkit/components/autocomplete/tests/unit/test_autocomplete_multiple.js new file mode 100644 index 0000000000..99cc103871 --- /dev/null +++ b/toolkit/components/autocomplete/tests/unit/test_autocomplete_multiple.js @@ -0,0 +1,250 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Dummy nsIAutoCompleteInput source that returns + * the given list of AutoCompleteSearch names. + * + * Implements only the methods needed for this test. + */ +function AutoCompleteInput(aSearches) { + this.searches = aSearches; +} +AutoCompleteInput.prototype = { + constructor: AutoCompleteInput, + + // Array of AutoCompleteSearch names + searches: null, + + minResultsForPopup: 0, + timeout: 10, + searchParam: "", + textValue: "", + disableAutoComplete: false, + completeDefaultIndex: false, + + get searchCount() { + return this.searches.length; + }, + + getSearchAt(aIndex) { + return this.searches[aIndex]; + }, + + onSearchBegin() {}, + onSearchComplete() {}, + + popupOpen: false, + + popup: { + setSelectedIndex(aIndex) {}, + invalidate() {}, + + // nsISupports implementation + QueryInterface: ChromeUtils.generateQI(["nsIAutoCompletePopup"]), + }, + + // nsISupports implementation + QueryInterface: ChromeUtils.generateQI(["nsIAutoCompleteInput"]), +}; + +/** + * nsIAutoCompleteResult implementation + */ +function AutoCompleteResult(aValues, aComments, aStyles) { + this._values = aValues; + this._comments = aComments; + this._styles = aStyles; +} +AutoCompleteResult.prototype = { + constructor: AutoCompleteResult, + + // Arrays + _values: null, + _comments: null, + _styles: null, + + searchString: "", + searchResult: null, + + defaultIndex: 0, + + get matchCount() { + return this._values.length; + }, + + getValueAt(aIndex) { + return this._values[aIndex]; + }, + + getLabelAt(aIndex) { + return this.getValueAt(aIndex); + }, + + getCommentAt(aIndex) { + return this._comments[aIndex]; + }, + + getStyleAt(aIndex) { + return this._styles[aIndex]; + }, + + getImageAt(aIndex) { + return ""; + }, + + getFinalCompleteValueAt(aIndex) { + return this.getValueAt(aIndex); + }, + + isRemovableAt(aRowIndex) { + return true; + }, + + removeValueAt(aRowIndex) {}, + + // nsISupports implementation + QueryInterface: ChromeUtils.generateQI(["nsIAutoCompleteResult"]), +}; + +/** + * nsIAutoCompleteSearch implementation that always returns + * the same result set. + */ +function AutoCompleteSearch(aName, aResult) { + this.name = aName; + this._result = aResult; +} +AutoCompleteSearch.prototype = { + constructor: AutoCompleteSearch, + + // Search name. Used by AutoCompleteController + name: null, + + // AutoCompleteResult + _result: null, + + /** + * Return the same result set for every search + */ + startSearch(aSearchString, aSearchParam, aPreviousResult, aListener) { + var result = this._result; + if (result._values.length) { + result.searchResult = Ci.nsIAutoCompleteResult.RESULT_SUCCESS_ONGOING; + } else { + result.searchResult = Ci.nsIAutoCompleteResult.RESULT_NOMATCH_ONGOING; + } + aListener.onSearchResult(this, result); + + if (result._values.length) { + result.searchResult = Ci.nsIAutoCompleteResult.RESULT_SUCCESS; + } else { + result.searchResult = Ci.nsIAutoCompleteResult.RESULT_NOMATCH; + } + aListener.onSearchResult(this, result); + }, + + stopSearch() {}, + + // nsISupports implementation + QueryInterface: ChromeUtils.generateQI([ + "nsIFactory", + "nsIAutoCompleteSearch", + ]), + + // nsIFactory implementation + createInstance(iid) { + return this.QueryInterface(iid); + }, +}; + +/** + * Helper to register an AutoCompleteSearch with the given name. + * Allows the AutoCompleteController to find the search. + */ +function registerAutoCompleteSearch(aSearch) { + var name = "@mozilla.org/autocomplete/search;1?name=" + aSearch.name; + + var uuidGenerator = Services.uuid; + var cid = uuidGenerator.generateUUID(); + + var desc = "Test AutoCompleteSearch"; + + var componentManager = Components.manager.QueryInterface( + Ci.nsIComponentRegistrar + ); + componentManager.registerFactory(cid, desc, name, aSearch); + + // Keep the id on the object so we can unregister later + aSearch.cid = cid; +} + +/** + * Helper to unregister an AutoCompleteSearch. + */ +function unregisterAutoCompleteSearch(aSearch) { + var componentManager = Components.manager.QueryInterface( + Ci.nsIComponentRegistrar + ); + componentManager.unregisterFactory(aSearch.cid, aSearch); +} + +/** + * Test AutoComplete with multiple AutoCompleteSearch sources. + */ +function run_test() { + var expected1 = ["1", "2", "3"]; + var expected2 = ["a", "b", "c"]; + var search1 = new AutoCompleteSearch( + "search1", + new AutoCompleteResult(expected1, [], []) + ); + var search2 = new AutoCompleteSearch( + "search2", + new AutoCompleteResult(expected2, [], []) + ); + + // Register searches so AutoCompleteController can find them + registerAutoCompleteSearch(search1); + registerAutoCompleteSearch(search2); + + var controller = Cc["@mozilla.org/autocomplete/controller;1"].getService( + Ci.nsIAutoCompleteController + ); + + // Make an AutoCompleteInput that uses our searches + // and confirms results on search complete + var input = new AutoCompleteInput([search1.name, search2.name]); + var numSearchesStarted = 0; + + input.onSearchBegin = function () { + numSearchesStarted++; + Assert.equal(numSearchesStarted, 1); + }; + + input.onSearchComplete = function () { + Assert.equal(numSearchesStarted, 1); + + Assert.equal( + controller.searchStatus, + Ci.nsIAutoCompleteController.STATUS_COMPLETE_MATCH + ); + Assert.equal(controller.matchCount, expected1.length + expected2.length); + + // Unregister searches + unregisterAutoCompleteSearch(search1); + unregisterAutoCompleteSearch(search2); + + do_test_finished(); + }; + + controller.input = input; + + // Search is asynchronous, so don't let the test finish immediately + do_test_pending(); + + controller.startSearch("test"); +} diff --git a/toolkit/components/autocomplete/tests/unit/test_autocomplete_userContextId.js b/toolkit/components/autocomplete/tests/unit/test_autocomplete_userContextId.js new file mode 100644 index 0000000000..4fbec408dc --- /dev/null +++ b/toolkit/components/autocomplete/tests/unit/test_autocomplete_userContextId.js @@ -0,0 +1,44 @@ +"use strict"; + +function AutoCompleteInput(aSearches, aUserContextId) { + this.searches = aSearches; + this.userContextId = aUserContextId; + this.popup.selectedIndex = -1; +} +AutoCompleteInput.prototype = Object.create(AutoCompleteInputBase.prototype); + +function AutoCompleteSearch(aName) { + this.name = aName; +} +AutoCompleteSearch.prototype = Object.create(AutoCompleteSearchBase.prototype); + +add_task(async function test_userContextId() { + let searchParam = await doSearch("test", 1); + Assert.equal(searchParam, " user-context-id:1"); +}); + +function doSearch(aString, aUserContextId) { + return new Promise(resolve => { + let search = new AutoCompleteSearch("test"); + + search.startSearch = function ( + aSearchString, + aSearchParam, + aPreviousResult, + aListener + ) { + unregisterAutoCompleteSearch(search); + resolve(aSearchParam); + }; + + registerAutoCompleteSearch(search); + + let controller = Cc["@mozilla.org/autocomplete/controller;1"].getService( + Ci.nsIAutoCompleteController + ); + + let input = new AutoCompleteInput([search.name], aUserContextId); + controller.input = input; + controller.startSearch(aString); + }); +} diff --git a/toolkit/components/autocomplete/tests/unit/test_autofillSelectedPopupIndex.js b/toolkit/components/autocomplete/tests/unit/test_autofillSelectedPopupIndex.js new file mode 100644 index 0000000000..9b38b95c9c --- /dev/null +++ b/toolkit/components/autocomplete/tests/unit/test_autofillSelectedPopupIndex.js @@ -0,0 +1,109 @@ +"use strict"; + +add_task(async function sameCaseAsMatch() { + await runTest("moz"); +}); + +add_task(async function differentCaseFromMatch() { + await runTest("MOZ"); +}); + +async function runTest(searchStr) { + let matches = ["mozilla.org", "example.com"]; + let result = new AutoCompleteResultBase(matches); + result.defaultIndex = 0; + + let search = new AutoCompleteSearchBase("search", result); + registerAutoCompleteSearch(search); + + let input = new AutoCompleteInputBase([search.name]); + input.completeSelectedIndex = true; + input.completeDefaultIndex = true; + + // Start off with the search string in the input. The selection must be + // collapsed and the caret must be at the end to trigger autofill below. + input.textValue = searchStr; + input.selectTextRange(searchStr.length, searchStr.length); + Assert.equal( + input.selectionStart, + searchStr.length, + "Selection should start at the end of the input" + ); + Assert.equal( + input.selectionEnd, + searchStr.length, + "Selection should end at the end of the input" + ); + + let controller = Cc["@mozilla.org/autocomplete/controller;1"].createInstance( + Ci.nsIAutoCompleteController + ); + controller.input = input; + input.controller = controller; + + // Start a search. + await new Promise(resolve => { + controller.startSearch(searchStr); + input.onSearchComplete = () => { + // The first match should have autofilled, but the case of the search + // string should be preserved. + let expectedValue = searchStr + matches[0].substr(searchStr.length); + Assert.equal(input.textValue, expectedValue, "Should have autofilled"); + Assert.equal( + input.selectionStart, + searchStr.length, + "Selection should start after search string" + ); + Assert.equal( + input.selectionEnd, + expectedValue.length, + "Selection should end at the end of the input" + ); + resolve(); + }; + }); + + // Key down to select the second match in the popup. + // Hardcode KeyboardEvent.DOM_VK_DOWN, because we can't easily + // include KeyboardEvent here. + controller.handleKeyNavigation(0x28 /* KeyboardEvent.DOM_VK_DOWN */); + let expectedValue = matches[1]; + Assert.equal( + input.textValue, + expectedValue, + "Should have filled second match" + ); + Assert.equal( + input.selectionStart, + expectedValue.length, + "Selection should start at the end of the input" + ); + Assert.equal( + input.selectionEnd, + expectedValue.length, + "Selection should end at the end of the input" + ); + + // Key up to select the first match again. The input should be restored + // exactly as it was when the first match was autofilled above: the search + // string's case should be preserved, and the selection should be preserved. + // Hardcode KeyboardEvent.DOM_VK_UP, because we can't easily + // include KeyboardEvent here. + controller.handleKeyNavigation(0x26 /* KeyboardEvent.DOM_VK_UP */); + expectedValue = searchStr + matches[0].substr(searchStr.length); + Assert.equal( + input.textValue, + expectedValue, + "Should have filled first match again" + ); + Assert.equal( + input.selectionStart, + searchStr.length, + "Selection should start after search string again" + ); + Assert.equal( + input.selectionEnd, + expectedValue.length, + "Selection should end at the end of the input again" + ); +} diff --git a/toolkit/components/autocomplete/tests/unit/test_badDefaultIndex.js b/toolkit/components/autocomplete/tests/unit/test_badDefaultIndex.js new file mode 100644 index 0000000000..52da4f4b4a --- /dev/null +++ b/toolkit/components/autocomplete/tests/unit/test_badDefaultIndex.js @@ -0,0 +1,100 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * A results that wants to defaultComplete to 0, but it has no matches, + * though it notifies SUCCESS to the controller. + */ +function AutoCompleteNoMatchResult() { + this.defaultIndex = 0; +} +AutoCompleteNoMatchResult.prototype = Object.create( + AutoCompleteResultBase.prototype +); + +/** + * A results that wants to defaultComplete to an index greater than the number + * of matches. + */ +function AutoCompleteBadIndexResult(aValues, aDefaultIndex) { + Assert.ok(aValues.length <= aDefaultIndex); + this._values = aValues; + this.defaultIndex = aDefaultIndex; +} +AutoCompleteBadIndexResult.prototype = Object.create( + AutoCompleteResultBase.prototype +); + +add_test(function autocomplete_noMatch_success() { + const INPUT_STR = "moz"; + + let searchNoMatch = new AutoCompleteSearchBase( + "searchNoMatch", + new AutoCompleteNoMatchResult() + ); + registerAutoCompleteSearch(searchNoMatch); + + // Make an AutoCompleteInput that uses our search and confirms results. + let input = new AutoCompleteInputBase([searchNoMatch.name]); + input.completeDefaultIndex = true; + input.textValue = INPUT_STR; + + // Caret must be at the end for autoFill to happen. + let strLen = INPUT_STR.length; + input.selectTextRange(strLen, strLen); + Assert.equal(input.selectionStart, strLen); + Assert.equal(input.selectionEnd, strLen); + + let controller = Cc["@mozilla.org/autocomplete/controller;1"].getService( + Ci.nsIAutoCompleteController + ); + controller.input = input; + controller.startSearch(INPUT_STR); + + input.onSearchComplete = function () { + // Should not try to autoFill to an empty value. + Assert.equal(input.textValue, "moz"); + + // Clean up. + unregisterAutoCompleteSearch(searchNoMatch); + run_next_test(); + }; +}); + +add_test(function autocomplete_defaultIndex_exceeds_matchCount() { + const INPUT_STR = "moz"; + + // Result returning matches, but a bad defaultIndex. + let searchBadIndex = new AutoCompleteSearchBase( + "searchBadIndex", + new AutoCompleteBadIndexResult(["mozillaTest"], 1) + ); + registerAutoCompleteSearch(searchBadIndex); + + // Make an AutoCompleteInput that uses our search and confirms results. + let input = new AutoCompleteInputBase([searchBadIndex.name]); + input.completeDefaultIndex = true; + input.textValue = INPUT_STR; + + // Caret must be at the end for autoFill to happen. + let strLen = INPUT_STR.length; + input.selectTextRange(strLen, strLen); + Assert.equal(input.selectionStart, strLen); + Assert.equal(input.selectionEnd, strLen); + + let controller = Cc["@mozilla.org/autocomplete/controller;1"].getService( + Ci.nsIAutoCompleteController + ); + controller.input = input; + controller.startSearch(INPUT_STR); + + input.onSearchComplete = function () { + // Should not try to autoFill to an empty value. + Assert.equal(input.textValue, "moz"); + + // Clean up. + unregisterAutoCompleteSearch(searchBadIndex); + run_next_test(); + }; +}); diff --git a/toolkit/components/autocomplete/tests/unit/test_completeDefaultIndex_casing.js b/toolkit/components/autocomplete/tests/unit/test_completeDefaultIndex_casing.js new file mode 100644 index 0000000000..2d1dfdf34d --- /dev/null +++ b/toolkit/components/autocomplete/tests/unit/test_completeDefaultIndex_casing.js @@ -0,0 +1,64 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function AutoCompleteResult(aValues) { + this._values = aValues; + this.defaultIndex = 0; +} +AutoCompleteResult.prototype = Object.create(AutoCompleteResultBase.prototype); + +function AutoCompleteInput(aSearches) { + this.searches = aSearches; + this.popup.selectedIndex = -1; + this.completeDefaultIndex = true; +} +AutoCompleteInput.prototype = Object.create(AutoCompleteInputBase.prototype); + +add_test(function test_keyNavigation() { + doSearch("MOZ", "mozilla", function (aController) { + Assert.equal(aController.input.textValue, "MOZilla"); + // Hardcode KeyboardEvent.DOM_VK_RIGHT, because we can't easily + // include KeyboardEvent here. + aController.handleKeyNavigation(0x27 /* KeyboardEvent.DOM_VK_RIGHT */); + Assert.equal(aController.input.textValue, "mozilla"); + }); +}); + +add_test(function test_handleEnter() { + doSearch("MOZ", "mozilla", function (aController) { + Assert.equal(aController.input.textValue, "MOZilla"); + aController.handleEnter(false); + Assert.equal(aController.input.textValue, "mozilla"); + }); +}); + +function doSearch(aSearchString, aResultValue, aOnCompleteCallback) { + let search = new AutoCompleteSearchBase( + "search", + new AutoCompleteResult(["mozilla", "toolkit"], 0) + ); + registerAutoCompleteSearch(search); + + let controller = Cc["@mozilla.org/autocomplete/controller;1"].getService( + Ci.nsIAutoCompleteController + ); + + // Make an AutoCompleteInput that uses our searches and confirms results. + let input = new AutoCompleteInput([search.name]); + input.textValue = aSearchString; + + // Caret must be at the end for autofill to happen. + let strLen = aSearchString.length; + input.selectTextRange(strLen, strLen); + controller.input = input; + controller.startSearch(aSearchString); + + input.onSearchComplete = function onSearchComplete() { + aOnCompleteCallback(controller); + + // Clean up. + unregisterAutoCompleteSearch(search); + run_next_test(); + }; +} diff --git a/toolkit/components/autocomplete/tests/unit/test_finalCompleteValue.js b/toolkit/components/autocomplete/tests/unit/test_finalCompleteValue.js new file mode 100644 index 0000000000..9b777d08a5 --- /dev/null +++ b/toolkit/components/autocomplete/tests/unit/test_finalCompleteValue.js @@ -0,0 +1,62 @@ +function AutoCompleteResult(aValues, aFinalCompleteValues) { + this._values = aValues; + this._finalCompleteValues = aFinalCompleteValues; +} +AutoCompleteResult.prototype = Object.create(AutoCompleteResultBase.prototype); + +function AutoCompleteInput(aSearches) { + this.searches = aSearches; + this.popup.selectedIndex = 0; +} +AutoCompleteInput.prototype = Object.create(AutoCompleteInputBase.prototype); + +add_test(function test_handleEnter_mouse() { + doSearch( + "moz", + "mozilla.com", + "http://www.mozilla.com", + function (aController) { + Assert.equal(aController.input.textValue, "moz"); + Assert.equal( + aController.getFinalCompleteValueAt(0), + "http://www.mozilla.com" + ); + // Keyboard interaction is tested by test_finalCompleteValueSelectedIndex.js + // so here just test popup selection. + aController.handleEnter(true); + Assert.equal(aController.input.textValue, "http://www.mozilla.com"); + } + ); +}); + +function doSearch( + aSearchString, + aResultValue, + aFinalCompleteValue, + aOnCompleteCallback +) { + let search = new AutoCompleteSearchBase( + "search", + new AutoCompleteResult([aResultValue], [aFinalCompleteValue]) + ); + registerAutoCompleteSearch(search); + + let controller = Cc["@mozilla.org/autocomplete/controller;1"].getService( + Ci.nsIAutoCompleteController + ); + + // Make an AutoCompleteInput that uses our searches and confirms results. + let input = new AutoCompleteInput([search.name]); + input.textValue = aSearchString; + + controller.input = input; + controller.startSearch(aSearchString); + + input.onSearchComplete = function onSearchComplete() { + aOnCompleteCallback(controller); + + // Clean up. + unregisterAutoCompleteSearch(search); + run_next_test(); + }; +} diff --git a/toolkit/components/autocomplete/tests/unit/test_finalCompleteValueSelectedIndex.js b/toolkit/components/autocomplete/tests/unit/test_finalCompleteValueSelectedIndex.js new file mode 100644 index 0000000000..ec9491d781 --- /dev/null +++ b/toolkit/components/autocomplete/tests/unit/test_finalCompleteValueSelectedIndex.js @@ -0,0 +1,139 @@ +function AutoCompleteResult(aResultValues) { + this._values = aResultValues.map(x => x[0]); + this._finalCompleteValues = aResultValues.map(x => x[1]); +} +AutoCompleteResult.prototype = Object.create(AutoCompleteResultBase.prototype); + +var selectByWasCalled = false; +function AutoCompleteInput(aSearches) { + this.searches = aSearches; + this.popup.selectedIndex = 0; + this.popup.selectBy = function (reverse, page) { + Assert.equal(selectByWasCalled, false); + selectByWasCalled = true; + Assert.equal(reverse, false); + Assert.equal(page, false); + this.selectedIndex += (reverse ? -1 : 1) * (page ? 100 : 1); + }; + this.completeSelectedIndex = true; +} +AutoCompleteInput.prototype = Object.create(AutoCompleteInputBase.prototype); + +add_test(function test_handleEnter_key() { + let results = [ + ["mozilla.com", "http://www.mozilla.com"], + ["mozilla.org", "http://www.mozilla.org"], + ]; + // First check the case where we do select a value with the keyboard: + doSearch("moz", results, function (aController) { + Assert.equal(aController.input.textValue, "moz"); + Assert.equal( + aController.getFinalCompleteValueAt(0), + "http://www.mozilla.com" + ); + Assert.equal( + aController.getFinalCompleteValueAt(1), + "http://www.mozilla.org" + ); + + Assert.equal(aController.input.popup.selectedIndex, 0); + // Hardcode KeyboardEvent.DOM_VK_DOWN, because we can't easily + // include KeyboardEvent here. + aController.handleKeyNavigation(0x28 /* KeyboardEvent.DOM_VK_DOWN */); + Assert.equal(aController.input.popup.selectedIndex, 1); + // Simulate mouse interaction changing selectedIndex + // ie NOT keyboard interaction: + aController.input.popup.selectedIndex = 0; + + aController.handleEnter(false); + // Verify that the keyboard-selected thing got inserted, + // and not the mouse selection: + Assert.equal(aController.input.textValue, "http://www.mozilla.org"); + }); +}); + +add_test(function test_handleEnter_mouse() { + let results = [ + ["mozilla.com", "http://www.mozilla.com"], + ["mozilla.org", "http://www.mozilla.org"], + ]; + // Then the case where we do not: + doSearch("moz", results, function (aController) { + Assert.equal(aController.input.textValue, "moz"); + Assert.equal( + aController.getFinalCompleteValueAt(0), + "http://www.mozilla.com" + ); + Assert.equal( + aController.getFinalCompleteValueAt(1), + "http://www.mozilla.org" + ); + + Assert.equal(aController.input.popup.selectedIndex, 0); + aController.input.popupOpen = true; + // Simulate mouse interaction changing selectedIndex + // ie NOT keyboard interaction: + aController.input.popup.selectedIndex = 1; + Assert.equal(selectByWasCalled, false); + Assert.equal(aController.input.popup.selectedIndex, 1); + + aController.handleEnter(false); + // Verify that the input stayed the same, because no selection was made + // with the keyboard: + Assert.equal(aController.input.textValue, "moz"); + }); +}); + +add_test(function test_handleEnter_preselected() { + let results = [ + ["mozilla.com", "http://www.mozilla.com"], + ["mozilla.org", "http://www.mozilla.org"], + ]; + // Then test a preselection. + doSearch("moz", results, function (aController) { + Assert.equal(aController.input.textValue, "moz"); + Assert.equal( + aController.getFinalCompleteValueAt(0), + "http://www.mozilla.com" + ); + Assert.equal( + aController.getFinalCompleteValueAt(1), + "http://www.mozilla.org" + ); + + aController.setInitiallySelectedIndex(0); + + aController.handleEnter(false); + // Verify that the input stayed the same, because no selection was made + // with the keyboard: + Assert.equal(aController.input.textValue, "http://www.mozilla.com"); + }); +}); + +function doSearch(aSearchString, aResults, aOnCompleteCallback) { + selectByWasCalled = false; + let search = new AutoCompleteSearchBase( + "search", + new AutoCompleteResult(aResults) + ); + registerAutoCompleteSearch(search); + + let controller = Cc["@mozilla.org/autocomplete/controller;1"].getService( + Ci.nsIAutoCompleteController + ); + + // Make an AutoCompleteInput that uses our searches and confirms results. + let input = new AutoCompleteInput([search.name]); + input.textValue = aSearchString; + + controller.input = input; + controller.startSearch(aSearchString); + + input.onSearchComplete = function onSearchComplete() { + aOnCompleteCallback(controller); + + // Clean up. + unregisterAutoCompleteSearch(search); + run_next_test(); + }; +} diff --git a/toolkit/components/autocomplete/tests/unit/test_finalCompleteValue_defaultIndex.js b/toolkit/components/autocomplete/tests/unit/test_finalCompleteValue_defaultIndex.js new file mode 100644 index 0000000000..7b8a51f03a --- /dev/null +++ b/toolkit/components/autocomplete/tests/unit/test_finalCompleteValue_defaultIndex.js @@ -0,0 +1,112 @@ +function AutoCompleteResult(aResultValues) { + this.defaultIndex = 0; + this._values = aResultValues.map(x => x[0]); + this._finalCompleteValues = aResultValues.map(x => x[1]); +} +AutoCompleteResult.prototype = Object.create(AutoCompleteResultBase.prototype); + +function AutoCompleteInput(aSearches) { + this.searches = aSearches; + this.popup.selectedIndex = 0; + this.completeSelectedIndex = true; + this.completeDefaultIndex = true; +} +AutoCompleteInput.prototype = Object.create(AutoCompleteInputBase.prototype); + +add_test(function test_handleEnter() { + let results = [ + ["mozilla.com", "https://www.mozilla.com"], + ["gomozilla.org", "http://www.gomozilla.org"], + ]; + doSearch("moz", results, { selectedIndex: 0 }, controller => { + let input = controller.input; + Assert.equal(input.textValue, "mozilla.com"); + Assert.equal(controller.getFinalCompleteValueAt(0), results[0][1]); + Assert.equal(controller.getFinalCompleteValueAt(1), results[1][1]); + Assert.equal(input.popup.selectedIndex, 0); + + controller.handleEnter(false); + // Verify that the keyboard-selected thing got inserted, + // and not the mouse selection: + Assert.equal(controller.input.textValue, "https://www.mozilla.com"); + }); +}); + +add_test(function test_handleEnter_otherSelected() { + // The popup selection may not coincide with what is filled into the input + // field, for example if the user changed it with the mouse and then pressed + // Enter. In such a case we should still use the inputField value and not the + // popup selected value. + let results = [ + ["mozilla.com", "https://www.mozilla.com"], + ["gomozilla.org", "http://www.gomozilla.org"], + ]; + doSearch("moz", results, { selectedIndex: 1 }, controller => { + let input = controller.input; + Assert.equal(input.textValue, "mozilla.com"); + Assert.equal(controller.getFinalCompleteValueAt(0), results[0][1]); + Assert.equal(controller.getFinalCompleteValueAt(1), results[1][1]); + Assert.equal(input.popup.selectedIndex, 1); + + controller.handleEnter(false); + // Verify that the keyboard-selected thing got inserted, + // and not the mouse selection: + Assert.equal(controller.input.textValue, "https://www.mozilla.com"); + }); +}); + +add_test(function test_handleEnter_otherSelected_nocompleteselectedindex() { + let results = [ + ["mozilla.com", "https://www.mozilla.com"], + ["gomozilla.org", "http://www.gomozilla.org"], + ]; + doSearch( + "moz", + results, + { selectedIndex: 1, completeSelectedIndex: false }, + controller => { + let input = controller.input; + Assert.equal(input.textValue, "mozilla.com"); + Assert.equal(controller.getFinalCompleteValueAt(0), results[0][1]); + Assert.equal(controller.getFinalCompleteValueAt(1), results[1][1]); + Assert.equal(input.popup.selectedIndex, 1); + + controller.handleEnter(false); + // Verify that the keyboard-selected result is inserted, not the + // defaultComplete. + Assert.equal(controller.input.textValue, "http://www.gomozilla.org"); + } + ); +}); + +function doSearch(aSearchString, aResults, aOptions, aOnCompleteCallback) { + let search = new AutoCompleteSearchBase( + "search", + new AutoCompleteResult(aResults) + ); + registerAutoCompleteSearch(search); + + let input = new AutoCompleteInput([search.name]); + input.textValue = aSearchString; + if ("selectedIndex" in aOptions) { + input.popup.selectedIndex = aOptions.selectedIndex; + } + if ("completeSelectedIndex" in aOptions) { + input.completeSelectedIndex = aOptions.completeSelectedIndex; + } + // Needed for defaultIndex completion. + input.selectTextRange(aSearchString.length, aSearchString.length); + + let controller = Cc["@mozilla.org/autocomplete/controller;1"].getService( + Ci.nsIAutoCompleteController + ); + controller.input = input; + controller.startSearch(aSearchString); + + input.onSearchComplete = function onSearchComplete() { + aOnCompleteCallback(controller); + + unregisterAutoCompleteSearch(search); + run_next_test(); + }; +} diff --git a/toolkit/components/autocomplete/tests/unit/test_finalCompleteValue_forceComplete.js b/toolkit/components/autocomplete/tests/unit/test_finalCompleteValue_forceComplete.js new file mode 100644 index 0000000000..5456a8d2ab --- /dev/null +++ b/toolkit/components/autocomplete/tests/unit/test_finalCompleteValue_forceComplete.js @@ -0,0 +1,138 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function AutoCompleteResult(aValues, aFinalCompleteValues) { + this._values = aValues; + this._finalCompleteValues = aFinalCompleteValues; + this.defaultIndex = 0; +} +AutoCompleteResult.prototype = Object.create(AutoCompleteResultBase.prototype); + +function AutoCompleteInput(aSearches) { + this.searches = aSearches; + this.popup.selectedIndex = -1; +} +AutoCompleteInput.prototype = Object.create(AutoCompleteInputBase.prototype); + +add_test(function test_handleEnterWithDirectMatchCompleteSelectedIndex() { + doSearch( + "moz", + "mozilla.com", + "http://www.mozilla.com", + { forceComplete: true, completeSelectedIndex: true }, + function (aController) { + Assert.equal(aController.input.textValue, "moz"); + Assert.equal( + aController.getFinalCompleteValueAt(0), + "http://www.mozilla.com" + ); + aController.handleEnter(false); + // After enter the final complete value should be shown in the input. + Assert.equal(aController.input.textValue, "http://www.mozilla.com"); + } + ); +}); + +add_test(function test_handleEnterWithDirectMatch() { + doSearch( + "mozilla", + "mozilla.com", + "http://www.mozilla.com", + { forceComplete: true, completeDefaultIndex: true }, + function (aController) { + // Should autocomplete the search string to a suggestion. + Assert.equal(aController.input.textValue, "mozilla.com"); + Assert.equal( + aController.getFinalCompleteValueAt(0), + "http://www.mozilla.com" + ); + aController.handleEnter(false); + // After enter the final complete value should be shown in the input. + Assert.equal(aController.input.textValue, "http://www.mozilla.com"); + } + ); +}); + +add_test(function test_handleEnterWithNoMatch() { + doSearch( + "mozilla", + "mozilla.com", + "http://www.mozilla.com", + { forceComplete: true, completeDefaultIndex: true }, + function (aController) { + // Should autocomplete the search string to a suggestion. + Assert.equal(aController.input.textValue, "mozilla.com"); + Assert.equal( + aController.getFinalCompleteValueAt(0), + "http://www.mozilla.com" + ); + // Now input something that does not match... + aController.input.textValue = "mozillax"; + // ... and confirm. We don't want one of the values from the previous + // results to be taken, since what's now in the input field doesn't match. + aController.handleEnter(false); + Assert.equal(aController.input.textValue, "mozillax"); + } + ); +}); + +add_test(function test_handleEnterWithIndirectMatch() { + doSearch( + "com", + "mozilla.com", + "http://www.mozilla.com", + { forceComplete: true, completeDefaultIndex: true }, + function (aController) { + // Should autocomplete the search string to a suggestion. + Assert.equal(aController.input.textValue, "com >> mozilla.com"); + Assert.equal( + aController.getFinalCompleteValueAt(0), + "http://www.mozilla.com" + ); + aController.handleEnter(false); + // After enter the final complete value from the suggestion should be shown + // in the input. + Assert.equal(aController.input.textValue, "http://www.mozilla.com"); + } + ); +}); + +function doSearch( + aSearchString, + aResultValue, + aFinalCompleteValue, + aInputProps, + aOnCompleteCallback +) { + let search = new AutoCompleteSearchBase( + "search", + new AutoCompleteResult([aResultValue], [aFinalCompleteValue]) + ); + registerAutoCompleteSearch(search); + + let controller = Cc["@mozilla.org/autocomplete/controller;1"].getService( + Ci.nsIAutoCompleteController + ); + + // Make an AutoCompleteInput that uses our searches and confirms results. + let input = new AutoCompleteInput([search.name]); + for (var p in aInputProps) { + input[p] = aInputProps[p]; + } + input.textValue = aSearchString; + // Place the cursor at the end of the input so that completion to + // default index will kick in. + input.selectTextRange(aSearchString.length, aSearchString.length); + + controller.input = input; + controller.startSearch(aSearchString); + + input.onSearchComplete = function onSearchComplete() { + aOnCompleteCallback(controller); + + // Clean up. + unregisterAutoCompleteSearch(search); + run_next_test(); + }; +} diff --git a/toolkit/components/autocomplete/tests/unit/test_finalDefaultCompleteValue.js b/toolkit/components/autocomplete/tests/unit/test_finalDefaultCompleteValue.js new file mode 100644 index 0000000000..95cff2d4a8 --- /dev/null +++ b/toolkit/components/autocomplete/tests/unit/test_finalDefaultCompleteValue.js @@ -0,0 +1,80 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function AutoCompleteResult(aValues, aFinalCompleteValues) { + this._values = aValues; + this._finalCompleteValues = aFinalCompleteValues; + this.defaultIndex = 0; +} +AutoCompleteResult.prototype = Object.create(AutoCompleteResultBase.prototype); + +function AutoCompleteInput(aSearches) { + this.searches = aSearches; + this.popup.selectedIndex = -1; + this.completeDefaultIndex = true; +} +AutoCompleteInput.prototype = Object.create(AutoCompleteInputBase.prototype); + +add_test(function test_keyNavigation() { + doSearch( + "moz", + "mozilla.com", + "http://www.mozilla.com", + function (aController) { + Assert.equal(aController.input.textValue, "mozilla.com"); + // Hardcode KeyboardEvent.DOM_VK_RIGHT, because we can't easily + // include KeyboardEvent here. + aController.handleKeyNavigation(0x27 /* KeyboardEvent.DOM_VK_RIGHT */); + Assert.equal(aController.input.textValue, "mozilla.com"); + } + ); +}); + +add_test(function test_handleEnter() { + doSearch( + "moz", + "mozilla.com", + "http://www.mozilla.com", + function (aController) { + Assert.equal(aController.input.textValue, "mozilla.com"); + aController.handleEnter(false); + Assert.equal(aController.input.textValue, "http://www.mozilla.com"); + } + ); +}); + +function doSearch( + aSearchString, + aResultValue, + aFinalCompleteValue, + aOnCompleteCallback +) { + let search = new AutoCompleteSearchBase( + "search", + new AutoCompleteResult([aResultValue], [aFinalCompleteValue]) + ); + registerAutoCompleteSearch(search); + + let controller = Cc["@mozilla.org/autocomplete/controller;1"].getService( + Ci.nsIAutoCompleteController + ); + + // Make an AutoCompleteInput that uses our searches and confirms results. + let input = new AutoCompleteInput([search.name]); + input.textValue = aSearchString; + + // Caret must be at the end for autofill to happen. + let strLen = aSearchString.length; + input.selectTextRange(strLen, strLen); + controller.input = input; + controller.startSearch(aSearchString); + + input.onSearchComplete = function onSearchComplete() { + aOnCompleteCallback(controller); + + // Clean up. + unregisterAutoCompleteSearch(search); + run_next_test(); + }; +} diff --git a/toolkit/components/autocomplete/tests/unit/test_immediate_search.js b/toolkit/components/autocomplete/tests/unit/test_immediate_search.js new file mode 100644 index 0000000000..a4524ca5c9 --- /dev/null +++ b/toolkit/components/autocomplete/tests/unit/test_immediate_search.js @@ -0,0 +1,174 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function AutoCompleteImmediateSearch(aName, aResult) { + this.name = aName; + this._result = aResult; +} +AutoCompleteImmediateSearch.prototype = Object.create( + AutoCompleteSearchBase.prototype +); +AutoCompleteImmediateSearch.prototype.searchType = + Ci.nsIAutoCompleteSearchDescriptor.SEARCH_TYPE_IMMEDIATE; +AutoCompleteImmediateSearch.prototype.QueryInterface = ChromeUtils.generateQI([ + "nsIFactory", + "nsIAutoCompleteSearch", + "nsIAutoCompleteSearchDescriptor", +]); + +function AutoCompleteDelayedSearch(aName, aResult) { + this.name = aName; + this._result = aResult; +} +AutoCompleteDelayedSearch.prototype = Object.create( + AutoCompleteSearchBase.prototype +); + +function AutoCompleteResult(aValues, aDefaultIndex) { + this._values = aValues; + this.defaultIndex = aDefaultIndex; +} +AutoCompleteResult.prototype = Object.create(AutoCompleteResultBase.prototype); + +/** + * An immediate search should be executed synchronously. + */ +add_test(function test_immediate_search() { + let inputStr = "moz"; + + let immediateSearch = new AutoCompleteImmediateSearch( + "immediate", + new AutoCompleteResult(["moz-immediate"], 0) + ); + registerAutoCompleteSearch(immediateSearch); + let delayedSearch = new AutoCompleteDelayedSearch( + "delayed", + new AutoCompleteResult(["moz-delayed"], 0) + ); + registerAutoCompleteSearch(delayedSearch); + + let controller = Cc["@mozilla.org/autocomplete/controller;1"].getService( + Ci.nsIAutoCompleteController + ); + + let input = new AutoCompleteInputBase([ + delayedSearch.name, + immediateSearch.name, + ]); + input.completeDefaultIndex = true; + input.textValue = inputStr; + + // Caret must be at the end. Autofill doesn't happen unless you're typing + // characters at the end. + let strLen = inputStr.length; + input.selectTextRange(strLen, strLen); + + controller.input = input; + controller.startSearch(inputStr); + + // Immediately check the result, the immediate search should have finished. + Assert.equal(input.textValue, "moz-immediate"); + + // Wait for both queries to finish. + input.onSearchComplete = function () { + // Sanity check. + Assert.equal(input.textValue, "moz-immediate"); + + unregisterAutoCompleteSearch(immediateSearch); + unregisterAutoCompleteSearch(delayedSearch); + run_next_test(); + }; +}); + +/** + * An immediate search should be executed before any delayed search. + */ +add_test(function test_immediate_search_notimeout() { + let inputStr = "moz"; + + let immediateSearch = new AutoCompleteImmediateSearch( + "immediate", + new AutoCompleteResult(["moz-immediate"], 0) + ); + registerAutoCompleteSearch(immediateSearch); + + let delayedSearch = new AutoCompleteDelayedSearch( + "delayed", + new AutoCompleteResult(["moz-delayed"], 0) + ); + registerAutoCompleteSearch(delayedSearch); + + let controller = Cc["@mozilla.org/autocomplete/controller;1"].getService( + Ci.nsIAutoCompleteController + ); + + let input = new AutoCompleteInputBase([ + delayedSearch.name, + immediateSearch.name, + ]); + input.completeDefaultIndex = true; + input.textValue = inputStr; + input.timeout = 0; + + // Caret must be at the end. Autofill doesn't happen unless you're typing + // characters at the end. + let strLen = inputStr.length; + input.selectTextRange(strLen, strLen); + + controller.input = input; + let complete = false; + input.onSearchComplete = function () { + complete = true; + }; + controller.startSearch(inputStr); + Assert.ok(complete); + + // Immediately check the result, the immediate search should have finished. + Assert.equal(input.textValue, "moz-immediate"); + + unregisterAutoCompleteSearch(immediateSearch); + unregisterAutoCompleteSearch(delayedSearch); + run_next_test(); +}); + +/** + * A delayed search should be executed synchronously with a zero timeout. + */ +add_test(function test_delayed_search_notimeout() { + let inputStr = "moz"; + + let delayedSearch = new AutoCompleteDelayedSearch( + "delayed", + new AutoCompleteResult(["moz-delayed"], 0) + ); + registerAutoCompleteSearch(delayedSearch); + + let controller = Cc["@mozilla.org/autocomplete/controller;1"].getService( + Ci.nsIAutoCompleteController + ); + + let input = new AutoCompleteInputBase([delayedSearch.name]); + input.completeDefaultIndex = true; + input.textValue = inputStr; + input.timeout = 0; + + // Caret must be at the end. Autofill doesn't happen unless you're typing + // characters at the end. + let strLen = inputStr.length; + input.selectTextRange(strLen, strLen); + + controller.input = input; + let complete = false; + input.onSearchComplete = function () { + complete = true; + }; + controller.startSearch(inputStr); + Assert.ok(complete); + + // Immediately check the result, the delayed search should have finished. + Assert.equal(input.textValue, "moz-delayed"); + + unregisterAutoCompleteSearch(delayedSearch); + run_next_test(); +}); diff --git a/toolkit/components/autocomplete/tests/unit/test_insertMatchAt.js b/toolkit/components/autocomplete/tests/unit/test_insertMatchAt.js new file mode 100644 index 0000000000..cef3fb9683 --- /dev/null +++ b/toolkit/components/autocomplete/tests/unit/test_insertMatchAt.js @@ -0,0 +1,15 @@ +function run_test() { + let result = Cc["@mozilla.org/autocomplete/simple-result;1"].createInstance( + Ci.nsIAutoCompleteSimpleResult + ); + result.appendMatch("a", ""); + result.appendMatch("c", ""); + result.insertMatchAt(1, "b", ""); + result.insertMatchAt(3, "d", ""); + + Assert.equal(result.matchCount, 4); + Assert.equal(result.getValueAt(0), "a"); + Assert.equal(result.getValueAt(1), "b"); + Assert.equal(result.getValueAt(2), "c"); + Assert.equal(result.getValueAt(3), "d"); +} diff --git a/toolkit/components/autocomplete/tests/unit/test_previousResult.js b/toolkit/components/autocomplete/tests/unit/test_previousResult.js new file mode 100644 index 0000000000..77659c8268 --- /dev/null +++ b/toolkit/components/autocomplete/tests/unit/test_previousResult.js @@ -0,0 +1,257 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Unit test for Bug 438861 - Previous search results not returned to multiple + * searches. + */ + +/** + * Dummy nsIAutoCompleteInput source that returns + * the given list of AutoCompleteSearch names. + * + * Implements only the methods needed for this test. + */ +function AutoCompleteInput(aSearches) { + this.searches = aSearches; +} +AutoCompleteInput.prototype = { + constructor: AutoCompleteInput, + + // Array of AutoCompleteSearch names + searches: null, + + minResultsForPopup: 0, + timeout: 10, + searchParam: "", + textValue: "", + disableAutoComplete: false, + completeDefaultIndex: false, + + get searchCount() { + return this.searches.length; + }, + + getSearchAt(aIndex) { + return this.searches[aIndex]; + }, + + onSearchBegin() {}, + onSearchComplete() {}, + + popupOpen: false, + + popup: { + setSelectedIndex(aIndex) {}, + invalidate() {}, + + // nsISupports implementation + QueryInterface: ChromeUtils.generateQI(["nsIAutoCompletePopup"]), + }, + + // nsISupports implementation + QueryInterface: ChromeUtils.generateQI(["nsIAutoCompleteInput"]), +}; + +/** + * nsIAutoCompleteResult implementation + */ +function AutoCompleteResult(aValues, aComments, aStyles) { + this._values = aValues; + this._comments = aComments; + this._styles = aStyles; + if (this._values.length) { + this.searchResult = Ci.nsIAutoCompleteResult.RESULT_SUCCESS; + } else { + this.searchResult = Ci.nsIAutoCompleteResult.RESULT_NOMATCH; + } +} +AutoCompleteResult.prototype = { + constructor: AutoCompleteResult, + + // Arrays + _values: null, + _comments: null, + _styles: null, + + searchString: "", + searchResult: null, + + defaultIndex: 0, + + get matchCount() { + return this._values.length; + }, + + getValueAt(aIndex) { + return this._values[aIndex]; + }, + + getLabelAt(aIndex) { + return this.getValueAt(aIndex); + }, + + getCommentAt(aIndex) { + return this._comments[aIndex]; + }, + + getStyleAt(aIndex) { + return this._styles[aIndex]; + }, + + getImageAt(aIndex) { + return ""; + }, + + getFinalCompleteValueAt(aIndex) { + return this.getValueAt(aIndex); + }, + + isRemovableAt(aRowIndex) { + return true; + }, + + removeValueAt(aRowIndex) {}, + + // nsISupports implementation + QueryInterface: ChromeUtils.generateQI(["nsIAutoCompleteResult"]), +}; + +/** + * nsIAutoCompleteSearch implementation that always returns + * the same result set. + */ +function AutoCompleteSearch(aName, aResult) { + this.name = aName; + this._result = aResult; +} +AutoCompleteSearch.prototype = { + constructor: AutoCompleteSearch, + + // Search name. Used by AutoCompleteController + name: null, + + // AutoCompleteResult + _result: null, + + _previousResult: null, + + /** + * Return the same result set for every search + */ + startSearch(aSearchString, aSearchParam, aPreviousResult, aListener) { + this._previousResult = aPreviousResult; + aListener.onSearchResult(this, this._result); + }, + + stopSearch() {}, + + // nsISupports implementation + QueryInterface: ChromeUtils.generateQI([ + "nsIFactory", + "nsIAutoCompleteSearch", + ]), + + // nsIFactory implementation + createInstance(iid) { + return this.QueryInterface(iid); + }, +}; + +/** + * Helper to register an AutoCompleteSearch with the given name. + * Allows the AutoCompleteController to find the search. + */ +function registerAutoCompleteSearch(aSearch) { + var name = "@mozilla.org/autocomplete/search;1?name=" + aSearch.name; + + var uuidGenerator = Services.uuid; + var cid = uuidGenerator.generateUUID(); + + var desc = "Test AutoCompleteSearch"; + + var componentManager = Components.manager.QueryInterface( + Ci.nsIComponentRegistrar + ); + componentManager.registerFactory(cid, desc, name, aSearch); + + // Keep the id on the object so we can unregister later + aSearch.cid = cid; +} + +/** + * Helper to unregister an AutoCompleteSearch. + */ +function unregisterAutoCompleteSearch(aSearch) { + var componentManager = Components.manager.QueryInterface( + Ci.nsIComponentRegistrar + ); + componentManager.unregisterFactory(aSearch.cid, aSearch); +} + +/** + */ +function run_test() { + // Make an AutoCompleteSearch that always returns nothing + var search1 = new AutoCompleteSearch( + "test-previous-result1", + new AutoCompleteResult(["hello1"], [""], [""]) + ); + + var search2 = new AutoCompleteSearch( + "test-previous-result2", + new AutoCompleteResult(["hello2"], [""], [""]) + ); + + // Register search so AutoCompleteController can find them + registerAutoCompleteSearch(search1); + registerAutoCompleteSearch(search2); + + var controller = Cc["@mozilla.org/autocomplete/controller;1"].getService( + Ci.nsIAutoCompleteController + ); + + // Make an AutoCompleteInput that uses our search + // and confirms results on search complete + var input = new AutoCompleteInput([search1.name, search2.name]); + var numSearchesStarted = 0; + + input.onSearchBegin = function () { + numSearchesStarted++; + }; + + input.onSearchComplete = function () { + Assert.equal( + controller.searchStatus, + Ci.nsIAutoCompleteController.STATUS_COMPLETE_MATCH + ); + Assert.equal(controller.matchCount, 2); + + if (numSearchesStarted == 1) { + Assert.equal(search1._previousResult, null); + Assert.equal(search2._previousResult, null); + + // Now start it again + controller.startSearch("test"); + return; + } + Assert.notEqual(search1._previousResult, null); + Assert.notEqual(search2._previousResult, null); + + // Unregister searches + unregisterAutoCompleteSearch(search1); + unregisterAutoCompleteSearch(search2); + + do_test_finished(); + }; + + controller.input = input; + + // Search is asynchronous, so don't let the test finish immediately + do_test_pending(); + + controller.startSearch("test"); +} diff --git a/toolkit/components/autocomplete/tests/unit/test_removeMatchAt.js b/toolkit/components/autocomplete/tests/unit/test_removeMatchAt.js new file mode 100644 index 0000000000..c8abf0b832 --- /dev/null +++ b/toolkit/components/autocomplete/tests/unit/test_removeMatchAt.js @@ -0,0 +1,16 @@ +function run_test() { + let result = Cc["@mozilla.org/autocomplete/simple-result;1"].createInstance( + Ci.nsIAutoCompleteSimpleResult + ); + result.appendMatch("a", ""); + result.appendMatch("b", ""); + result.removeMatchAt(0); + Assert.equal(result.matchCount, 1); + Assert.equal(result.getValueAt(0), "b"); + result.appendMatch("c", ""); + result.removeMatchAt(1); + Assert.equal(result.matchCount, 1); + Assert.equal(result.getValueAt(0), "b"); + result.removeMatchAt(0); + Assert.equal(result.matchCount, 0); +} diff --git a/toolkit/components/autocomplete/tests/unit/test_stopSearch.js b/toolkit/components/autocomplete/tests/unit/test_stopSearch.js new file mode 100644 index 0000000000..aecf572df0 --- /dev/null +++ b/toolkit/components/autocomplete/tests/unit/test_stopSearch.js @@ -0,0 +1,171 @@ +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/** + * Purpose of the test is to check that a stopSearch call comes always before a + * startSearch call. + */ + +/** + * Dummy nsIAutoCompleteInput source that returns + * the given list of AutoCompleteSearch names. + * + * Implements only the methods needed for this test. + */ +function AutoCompleteInput(aSearches) { + this.searches = aSearches; +} +AutoCompleteInput.prototype = { + constructor: AutoCompleteInput, + minResultsForPopup: 0, + timeout: 10, + searchParam: "", + textValue: "hello", + disableAutoComplete: false, + completeDefaultIndex: false, + set popupOpen(val) {}, // ignore + get popupOpen() { + return false; + }, + get searchCount() { + return this.searches.length; + }, + getSearchAt(aIndex) { + return this.searches[aIndex]; + }, + onSearchBegin() {}, + onSearchComplete() {}, + onTextReverted() {}, + onTextEntered() {}, + popup: { + selectBy() {}, + invalidate() {}, + set selectedIndex(val) {}, // ignore + get selectedIndex() { + return -1; + }, + QueryInterface: ChromeUtils.generateQI(["nsIAutoCompletePopup"]), + }, + QueryInterface: ChromeUtils.generateQI(["nsIAutoCompleteInput"]), +}; + +/** + * nsIAutoCompleteSearch implementation. + */ +function AutoCompleteSearch(aName) { + this.name = aName; +} +AutoCompleteSearch.prototype = { + constructor: AutoCompleteSearch, + stopSearchInvoked: true, + startSearch(aSearchString, aSearchParam, aPreviousResult, aListener) { + info("Check stop search has been called"); + Assert.ok(this.stopSearchInvoked); + this.stopSearchInvoked = false; + }, + stopSearch() { + this.stopSearchInvoked = true; + }, + QueryInterface: ChromeUtils.generateQI([ + "nsIFactory", + "nsIAutoCompleteSearch", + ]), + createInstance(iid) { + return this.QueryInterface(iid); + }, +}; + +/** + * Helper to register an AutoCompleteSearch with the given name. + * Allows the AutoCompleteController to find the search. + */ +function registerAutoCompleteSearch(aSearch) { + let name = "@mozilla.org/autocomplete/search;1?name=" + aSearch.name; + let uuidGenerator = Services.uuid; + let cid = uuidGenerator.generateUUID(); + let desc = "Test AutoCompleteSearch"; + let componentManager = Components.manager.QueryInterface( + Ci.nsIComponentRegistrar + ); + componentManager.registerFactory(cid, desc, name, aSearch); + // Keep the id on the object so we can unregister later + aSearch.cid = cid; +} + +/** + * Helper to unregister an AutoCompleteSearch. + */ +function unregisterAutoCompleteSearch(aSearch) { + let componentManager = Components.manager.QueryInterface( + Ci.nsIComponentRegistrar + ); + componentManager.unregisterFactory(aSearch.cid, aSearch); +} + +var gTests = [ + function (controller) { + info("handleText"); + controller.input.textValue = "hel"; + controller.handleText(); + }, + function (controller) { + info("handleStartComposition"); + controller.handleStartComposition(); + info("handleEndComposition"); + controller.handleEndComposition(); + // an input event always follows compositionend event. + controller.handleText(); + }, + function (controller) { + info("handleEscape"); + controller.handleEscape(); + }, + function (controller) { + info("handleEnter"); + controller.handleEnter(false); + }, + function (controller) { + info("handleTab"); + controller.handleTab(); + }, + + function (controller) { + info("handleKeyNavigation"); + // Hardcode KeyboardEvent.DOM_VK_RIGHT, because we can't easily + // include KeyboardEvent here. + controller.handleKeyNavigation(0x26 /* KeyboardEvent.DOM_VK_UP */); + }, +]; + +add_task(async function () { + // Make an AutoCompleteSearch that always returns nothing + let search = new AutoCompleteSearch("test"); + registerAutoCompleteSearch(search); + + let controller = Cc["@mozilla.org/autocomplete/controller;1"].getService( + Ci.nsIAutoCompleteController + ); + + // Make an AutoCompleteInput that uses our search. + let input = new AutoCompleteInput([search.name]); + controller.input = input; + + for (let testFn of gTests) { + input.onSearchBegin = function () { + executeSoon(() => testFn(controller)); + }; + let promise = new Promise(resolve => { + input.onSearchComplete = function () { + resolve(); + }; + }); + controller.startSearch("hello"); + await promise; + } + + unregisterAutoCompleteSearch(search); + controller.stopSearch(); + controller.input = null; +}); diff --git a/toolkit/components/autocomplete/tests/unit/xpcshell.ini b/toolkit/components/autocomplete/tests/unit/xpcshell.ini new file mode 100644 index 0000000000..32cdefe08e --- /dev/null +++ b/toolkit/components/autocomplete/tests/unit/xpcshell.ini @@ -0,0 +1,24 @@ +[DEFAULT] +head = head_autocomplete.js + +[test_330578.js] +[test_378079.js] +[test_393191.js] +[test_440866.js] +[test_463023.js] +[test_660156.js] +[test_autocomplete_multiple.js] +[test_autocomplete_userContextId.js] +[test_autofillSelectedPopupIndex.js] +[test_badDefaultIndex.js] +[test_completeDefaultIndex_casing.js] +[test_finalCompleteValue.js] +[test_finalCompleteValue_defaultIndex.js] +[test_finalCompleteValue_forceComplete.js] +[test_finalCompleteValueSelectedIndex.js] +[test_finalDefaultCompleteValue.js] +[test_immediate_search.js] +[test_insertMatchAt.js] +[test_previousResult.js] +[test_removeMatchAt.js] +[test_stopSearch.js] -- cgit v1.2.3