gecko-dev/browser/base/content/test/general/browser_searchSuggestionUI.js

307 lines
9.3 KiB
JavaScript

/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
const TEST_PAGE_BASENAME = "searchSuggestionUI.html";
const TEST_CONTENT_SCRIPT_BASENAME = "searchSuggestionUI.js";
const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
const TEST_MSG = "SearchSuggestionUIControllerTest";
add_task(function* emptyInput() {
yield setUp();
let state = yield msg("key", { key: "x", waitForSuggestions: true });
checkState(state, "x", ["xfoo", "xbar"], -1);
state = yield msg("key", "VK_BACK_SPACE");
checkState(state, "", [], -1);
yield msg("reset");
});
add_task(function* blur() {
yield setUp();
let state = yield msg("key", { key: "x", waitForSuggestions: true });
checkState(state, "x", ["xfoo", "xbar"], -1);
state = yield msg("blur");
checkState(state, "x", [], -1);
yield msg("reset");
});
add_task(function* arrowKeys() {
yield setUp();
let state = yield msg("key", { key: "x", waitForSuggestions: true });
checkState(state, "x", ["xfoo", "xbar"], -1);
// Cycle down the suggestions starting from no selection.
state = yield msg("key", "VK_DOWN");
checkState(state, "xfoo", ["xfoo", "xbar"], 0);
state = yield msg("key", "VK_DOWN");
checkState(state, "xbar", ["xfoo", "xbar"], 1);
state = yield msg("key", "VK_DOWN");
checkState(state, "x", ["xfoo", "xbar"], -1);
// Cycle up starting from no selection.
state = yield msg("key", "VK_UP");
checkState(state, "xbar", ["xfoo", "xbar"], 1);
state = yield msg("key", "VK_UP");
checkState(state, "xfoo", ["xfoo", "xbar"], 0);
state = yield msg("key", "VK_UP");
checkState(state, "x", ["xfoo", "xbar"], -1);
yield msg("reset");
});
// The right arrow and return key function the same.
function rightArrowOrReturn(keyName) {
return function* rightArrowOrReturnTest() {
yield setUp();
let state = yield msg("key", { key: "x", waitForSuggestions: true });
checkState(state, "x", ["xfoo", "xbar"], -1);
state = yield msg("key", "VK_DOWN");
checkState(state, "xfoo", ["xfoo", "xbar"], 0);
// This should make the xfoo suggestion sticky. To make sure it sticks,
// trigger suggestions again and cycle through them by pressing Down until
// nothing is selected again.
state = yield msg("key", keyName);
checkState(state, "xfoo", [], -1);
state = yield msg("key", { key: "VK_DOWN", waitForSuggestions: true });
checkState(state, "xfoo", ["xfoofoo", "xfoobar"], -1);
state = yield msg("key", "VK_DOWN");
checkState(state, "xfoofoo", ["xfoofoo", "xfoobar"], 0);
state = yield msg("key", "VK_DOWN");
checkState(state, "xfoobar", ["xfoofoo", "xfoobar"], 1);
state = yield msg("key", "VK_DOWN");
checkState(state, "xfoo", ["xfoofoo", "xfoobar"], -1);
yield msg("reset");
};
}
add_task(rightArrowOrReturn("VK_RIGHT"));
add_task(rightArrowOrReturn("VK_RETURN"));
add_task(function* mouse() {
yield setUp();
let state = yield msg("key", { key: "x", waitForSuggestions: true });
checkState(state, "x", ["xfoo", "xbar"], -1);
// Mouse over the first suggestion.
state = yield msg("mousemove", 0);
checkState(state, "x", ["xfoo", "xbar"], 0);
// Mouse over the second suggestion.
state = yield msg("mousemove", 1);
checkState(state, "x", ["xfoo", "xbar"], 1);
// Click the second suggestion. This should make it sticky. To make sure it
// sticks, trigger suggestions again and cycle through them by pressing Down
// until nothing is selected again.
state = yield msg("mousedown", 1);
checkState(state, "xbar", [], -1);
state = yield msg("key", { key: "VK_DOWN", waitForSuggestions: true });
checkState(state, "xbar", ["xbarfoo", "xbarbar"], -1);
state = yield msg("key", "VK_DOWN");
checkState(state, "xbarfoo", ["xbarfoo", "xbarbar"], 0);
state = yield msg("key", "VK_DOWN");
checkState(state, "xbarbar", ["xbarfoo", "xbarbar"], 1);
state = yield msg("key", "VK_DOWN");
checkState(state, "xbar", ["xbarfoo", "xbarbar"], -1);
yield msg("reset");
});
add_task(function* formHistory() {
yield setUp();
// Type an X and add it to form history.
let state = yield msg("key", { key: "x", waitForSuggestions: true });
checkState(state, "x", ["xfoo", "xbar"], -1);
yield msg("addInputValueToFormHistory");
// Wait for Satchel to say it's been added to form history.
let deferred = Promise.defer();
Services.obs.addObserver(function onAdd(subj, topic, data) {
if (data == "formhistory-add") {
Services.obs.removeObserver(onAdd, "satchel-storage-changed");
executeSoon(() => deferred.resolve());
}
}, "satchel-storage-changed", false);
yield deferred.promise;
// Reset the input.
state = yield msg("reset");
checkState(state, "", [], -1);
// Type an X again. The form history entry should appear.
state = yield msg("key", { key: "x", waitForSuggestions: true });
checkState(state, "x", [{ str: "x", type: "formHistory" }, "xfoo", "xbar"],
-1);
// Select the form history entry and delete it.
state = yield msg("key", "VK_DOWN");
checkState(state, "x", [{ str: "x", type: "formHistory" }, "xfoo", "xbar"],
0);
state = yield msg("key", "VK_DELETE");
checkState(state, "x", ["xfoo", "xbar"], -1);
// Wait for Satchel.
deferred = Promise.defer();
Services.obs.addObserver(function onRemove(subj, topic, data) {
if (data == "formhistory-remove") {
Services.obs.removeObserver(onRemove, "satchel-storage-changed");
executeSoon(() => deferred.resolve());
}
}, "satchel-storage-changed", false);
yield deferred.promise;
// Reset the input.
state = yield msg("reset");
checkState(state, "", [], -1);
// Type an X again. The form history entry should still be gone.
state = yield msg("key", { key: "x", waitForSuggestions: true });
checkState(state, "x", ["xfoo", "xbar"], -1);
yield msg("reset");
});
let gDidInitialSetUp = false;
function setUp() {
return Task.spawn(function* () {
if (!gDidInitialSetUp) {
yield promiseNewEngine(TEST_ENGINE_BASENAME);
yield promiseTab();
gDidInitialSetUp = true;
}
yield msg("focus");
});
}
function msg(type, data=null) {
gMsgMan.sendAsyncMessage(TEST_MSG, {
type: type,
data: data,
});
let deferred = Promise.defer();
gMsgMan.addMessageListener(TEST_MSG, function onMsg(msg) {
gMsgMan.removeMessageListener(TEST_MSG, onMsg);
deferred.resolve(msg.data);
});
return deferred.promise;
}
function checkState(actualState, expectedInputVal, expectedSuggestions,
expectedSelectedIdx) {
expectedSuggestions = expectedSuggestions.map(sugg => {
return typeof(sugg) == "object" ? sugg : {
str: sugg,
type: "remote",
};
});
let expectedState = {
selectedIndex: expectedSelectedIdx,
numSuggestions: expectedSuggestions.length,
suggestionAtIndex: expectedSuggestions.map(s => s.str),
isFormHistorySuggestionAtIndex:
expectedSuggestions.map(s => s.type == "formHistory"),
tableHidden: expectedSuggestions.length == 0,
tableChildrenLength: expectedSuggestions.length,
tableChildren: expectedSuggestions.map((s, i) => {
let expectedClasses = new Set([s.type]);
if (i == expectedSelectedIdx) {
expectedClasses.add("selected");
}
return {
textContent: s.str,
classes: expectedClasses,
};
}),
inputValue: expectedInputVal,
ariaExpanded: expectedSuggestions.length == 0 ? "false" : "true",
};
SimpleTest.isDeeply(actualState, expectedState, "State");
}
var gMsgMan;
function promiseTab() {
let deferred = Promise.defer();
let tab = gBrowser.addTab();
registerCleanupFunction(() => gBrowser.removeTab(tab));
gBrowser.selectedTab = tab;
let pageURL = getRootDirectory(gTestPath) + TEST_PAGE_BASENAME;
tab.linkedBrowser.addEventListener("load", function onLoad(event) {
tab.linkedBrowser.removeEventListener("load", onLoad, true);
gMsgMan = tab.linkedBrowser.messageManager;
gMsgMan.sendAsyncMessage("ContentSearch", {
type: "AddToWhitelist",
data: [pageURL],
});
promiseMsg("ContentSearch", "AddToWhitelistAck", gMsgMan).then(() => {
let jsURL = getRootDirectory(gTestPath) + TEST_CONTENT_SCRIPT_BASENAME;
gMsgMan.loadFrameScript(jsURL, false);
deferred.resolve();
});
}, true, true);
openUILinkIn(pageURL, "current");
return deferred.promise;
}
function promiseMsg(name, type, msgMan) {
let deferred = Promise.defer();
info("Waiting for " + name + " message " + type + "...");
msgMan.addMessageListener(name, function onMsg(msg) {
info("Received " + name + " message " + msg.data.type + "\n");
if (msg.data.type == type) {
msgMan.removeMessageListener(name, onMsg);
deferred.resolve(msg);
}
});
return deferred.promise;
}
function promiseNewEngine(basename) {
info("Waiting for engine to be added: " + basename);
let addDeferred = Promise.defer();
let url = getRootDirectory(gTestPath) + basename;
Services.search.addEngine(url, Ci.nsISearchEngine.TYPE_MOZSEARCH, "", false, {
onSuccess: function (engine) {
info("Search engine added: " + basename);
registerCleanupFunction(() => Services.search.removeEngine(engine));
addDeferred.resolve(engine);
},
onError: function (errCode) {
ok(false, "addEngine failed with error code " + errCode);
addDeferred.reject();
},
});
return addDeferred.promise;
}