<?xml version="1.0"?> <?xml-stylesheet href="chrome://global/skin" type="text/css"?> <?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> <window title="Autocomplete Widget Test" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="runTest();"> <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> <script type="application/javascript" src="chrome://global/content/globalOverlay.js"/> <textbox id="autocomplete" type="autocomplete" completedefaultindex="true" timeout="0" autocompletesearch="simple"/> <script class="testbody" type="application/javascript"> <![CDATA[ Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); function autoCompleteSimpleResult(aString, searchId) { this.searchString = aString; this.searchResult = Components.interfaces.nsIAutoCompleteResult.RESULT_SUCCESS; this.matchCount = 1; if (aString.startsWith('ret')) { this._param = autoCompleteSimpleResult.retireCompletion; } else { this._param = "Result"; } this._searchId = searchId; } autoCompleteSimpleResult.retireCompletion = "Retire"; autoCompleteSimpleResult.prototype = { _param: "", searchString: null, searchResult: Components.interfaces.nsIAutoCompleteResult.RESULT_FAILURE, defaultIndex: 0, errorDescription: null, matchCount: 0, getValueAt: function() { return this._param; }, getCommentAt: function() { return null; }, getStyleAt: function() { return null; }, getImageAt: function() { return null; }, getLabelAt: function() { return null; }, removeValueAt: function() {} }; var searchCounter = 0; // A basic autocomplete implementation that returns one result. let autoCompleteSimple = { classID: Components.ID("0a2afbdb-f30e-47d1-9cb1-0cd160240aca"), contractID: "@mozilla.org/autocomplete/search;1?name=simple", searchAsync: false, pendingSearch: null, QueryInterface: XPCOMUtils.generateQI([ Components.interfaces.nsIFactory, Components.interfaces.nsIAutoCompleteSearch ]), createInstance: function (outer, iid) { return this.QueryInterface(iid); }, registerFactory: function () { let registrar = Components.manager.QueryInterface(Components.interfaces.nsIComponentRegistrar); registrar.registerFactory(this.classID, "Test Simple Autocomplete", this.contractID, this); }, unregisterFactory: function () { let registrar = Components.manager.QueryInterface(Components.interfaces.nsIComponentRegistrar); registrar.unregisterFactory(this.classID, this); }, startSearch: function (aString, aParam, aResult, aListener) { let result = new autoCompleteSimpleResult(aString); if (this.searchAsync) { // Simulate an async search by using a timeout before invoking the // |onSearchResult| callback. // Store the searchTimeout such that it can be canceled if stopSearch is called. this.pendingSearch = setTimeout(() => { this.pendingSearch = null; aListener.onSearchResult(this, result); // Move to the next step in the async test. asyncTest.next(); }, 0); } else { aListener.onSearchResult(this, result); } }, stopSearch: function () { clearTimeout(this.pendingSearch); } }; SimpleTest.waitForExplicitFinish(); // XPFE AutoComplete needs to register early. autoCompleteSimple.registerFactory(); let gACTimer; let gAutoComplete; let asyncTest; let searchCompleteTimeoutId = null; function finishTest() { // Unregister the factory so that we don't get in the way of other tests autoCompleteSimple.unregisterFactory(); SimpleTest.finish(); } function runTest() { gAutoComplete = $("autocomplete"); gAutoComplete.focus(); // Return the search results synchronous, which also makes the completion // happen synchronous. autoCompleteSimple.searchAsync = false; synthesizeKey("r", {}); is(gAutoComplete.value, "result", "Value should be autocompleted immediately"); synthesizeKey("e", {}); is(gAutoComplete.value, "result", "Value should be autocompleted immediately"); synthesizeKey("VK_DELETE", {}); is(gAutoComplete.value, "re", "Deletion should not complete value"); synthesizeKey("VK_BACK_SPACE", {}); is(gAutoComplete.value, "r", "Backspace should not complete value"); synthesizeKey("VK_LEFT", {}); is(gAutoComplete.value, "r", "Value should stay same when navigating with cursor"); runAsyncTest(); } function* asyncTestGenerator() { synthesizeKey("r", {}); synthesizeKey("e", {}); is(gAutoComplete.value, "re", "Value should not be autocompleted immediately"); // Calling |yield undefined| makes this generator function wait until // |asyncTest.next();| is called. This happens from within the // |autoCompleteSimple.startSearch()| function once the simulated async // search has finished. // Therefore, the effect of the |yield undefined;| here (and the ones) below // is to wait until the async search result comes back. yield undefined; is(gAutoComplete.value, "result", "Value should be autocompleted"); // Test if typing the `s` character completes directly based on the last // completion synthesizeKey("s", {}); is(gAutoComplete.value, "result", "Value should be completed immediately"); yield undefined; is(gAutoComplete.value, "result", "Value should be autocompleted to same value"); synthesizeKey("VK_DELETE", {}); is(gAutoComplete.value, "res", "Deletion should not complete value"); // No |yield undefined| needed here as no completion is triggered by the deletion. is(gAutoComplete.value, "res", "Still no complete value after deletion"); synthesizeKey("VK_BACK_SPACE", {}); is(gAutoComplete.value, "re", "Backspace should not complete value"); yield undefined; is(gAutoComplete.value, "re", "Value after search due to backspace should stay the same"); (3) // Typing a character that is not like the previous match. In this case, the // completion cannot happen directly and therefore the value will be completed // only after the search has finished. synthesizeKey("t", {}); is(gAutoComplete.value, "ret", "Value should not be autocompleted immediately"); yield undefined; is(gAutoComplete.value, "retire", "Value should be autocompleted"); synthesizeKey("i", {}); is(gAutoComplete.value, "retire", "Value should be autocompleted immediately"); yield undefined; is(gAutoComplete.value, "retire", "Value should be autocompleted to the same value"); // Setup the scene to test how the completion behaves once the placeholder // completion and the result from the search do not agree with each other. gAutoComplete.value = 'r'; // Need to type two characters as the input was reset and the autocomplete // controller things, ther user hit the backspace button, in which case // no completion is performed. But as a completion is desired, another // character `t` is typed afterwards. synthesizeKey("e", {}); yield undefined; synthesizeKey("t", {}); is(gAutoComplete.value, "ret", "Value should not be autocompleted"); yield undefined; is(gAutoComplete.value, "retire", "Value should be autocompleted"); // The placeholder string is now set to "retire". Changing the completion // string to "retirement" and see what the completion will turn out like. autoCompleteSimpleResult.retireCompletion = "Retirement"; synthesizeKey("i", {}); is(gAutoComplete.value, "retire", "Value should be autocompleted based on placeholder"); yield undefined; is(gAutoComplete.value, "retirement", "Value should be autocompleted based on search result"); // Change the search result to `Retire` again and see if the new result is // complited. autoCompleteSimpleResult.retireCompletion = "Retire"; synthesizeKey("r", {}); is(gAutoComplete.value, "retirement", "Value should be autocompleted based on placeholder"); yield undefined; is(gAutoComplete.value, "retire", "Value should be autocompleted based on search result"); // Complete the value gAutoComplete.value = 're'; synthesizeKey("t", {}); yield undefined; synthesizeKey("i", {}); is(gAutoComplete.value, "reti", "Value should not be autocompleted"); yield undefined; is(gAutoComplete.value, "retire", "Value should be autocompleted"); // Remove the selected text "re" (1) and the "et" (2). Afterwards, add it again (3). // This should not cause the completion to kick in. synthesizeKey("VK_DELETE", {}); // (1) is(gAutoComplete.value, "reti", "Value should not complete after deletion"); gAutoComplete.selectionStart = 1; gAutoComplete.selectionEnd = 3; synthesizeKey("VK_DELETE", {}); // (2) is(gAutoComplete.value, "ri", "Value should stay unchanged after removing character in the middle"); yield undefined; synthesizeKey("e", {}); // (3.1) is(gAutoComplete.value, "rei", "Inserting a character in the middle should not complete the value"); yield undefined; synthesizeKey("t", {}); // (3.2) is(gAutoComplete.value, "reti", "Inserting a character in the middle should not complete the value"); yield undefined; // Adding a new character at the end should not cause the completion to happen again // as the completion failed before. gAutoComplete.selectionStart = 4; gAutoComplete.selectionEnd = 4; synthesizeKey("r", {}); is(gAutoComplete.value, "retir", "Value should not be autocompleted immediately"); yield undefined; is(gAutoComplete.value, "retire", "Value should be autocompleted"); finishTest(); yield undefined; } function runAsyncTest() { gAutoComplete.value = ''; autoCompleteSimple.searchAsync = true; asyncTest = asyncTestGenerator(); asyncTest.next(); } ]]> </script> <body xmlns="http://www.w3.org/1999/xhtml"> <p id="display"> </p> <div id="content" style="display: none"> </div> <pre id="test"> </pre> </body> </window>