mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-05 16:46:26 +00:00
550 lines
20 KiB
JavaScript
550 lines
20 KiB
JavaScript
/* Any copyright is dedicated to the Public Domain.
|
|
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
|
|
|
"use strict";
|
|
|
|
/**
|
|
* TO TEST:
|
|
* - test state saved on doorhanger dismissal
|
|
* - links to switch steps
|
|
* - TOS and PP link clicks
|
|
* - identityList is populated correctly
|
|
*/
|
|
|
|
Services.prefs.setBoolPref("toolkit.identity.debug", true);
|
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "IdentityService",
|
|
"resource://gre/modules/identity/Identity.jsm");
|
|
|
|
const TEST_ORIGIN = "https://example.com";
|
|
const TEST_EMAIL = "user@example.com";
|
|
|
|
let gTestIndex = 0;
|
|
let outerWinId = gBrowser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
|
|
.getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
|
|
|
|
function NotificationBase(aNotId) {
|
|
this.id = aNotId;
|
|
}
|
|
NotificationBase.prototype = {
|
|
message: TEST_ORIGIN,
|
|
mainAction: {
|
|
label: "",
|
|
callback: function() {
|
|
this.mainActionClicked = true;
|
|
}.bind(this),
|
|
},
|
|
secondaryActions: [],
|
|
options: {
|
|
"identity": {
|
|
origin: TEST_ORIGIN,
|
|
rpId: outerWinId,
|
|
},
|
|
},
|
|
};
|
|
|
|
let tests = [
|
|
{
|
|
name: "test_request_required_typed",
|
|
|
|
run: function() {
|
|
setupRPFlow();
|
|
this.notifyOptions = {
|
|
rpId: outerWinId,
|
|
origin: TEST_ORIGIN,
|
|
};
|
|
this.notifyObj = new NotificationBase("identity-request");
|
|
Services.obs.notifyObservers({wrappedJSObject: this.notifyOptions},
|
|
"identity-request", null);
|
|
},
|
|
|
|
onShown: function(popup) {
|
|
checkPopup(popup, this.notifyObj);
|
|
let notification = popup.childNodes[0];
|
|
|
|
// Check identity popup state
|
|
let state = notification.identity;
|
|
ok(!state.typedEmail, "Nothing should be typed yet");
|
|
ok(!state.selected, "Identity should not be selected yet");
|
|
ok(!state.termsOfService, "No TOS specified");
|
|
ok(!state.privacyPolicy, "No PP specified");
|
|
is(state.step, 0, "Step should be persisted with default value");
|
|
is(state.rpId, outerWinId, "Check rpId");
|
|
is(state.origin, TEST_ORIGIN, "Check origin");
|
|
|
|
is(notification.step, 0, "Should be on the new email step");
|
|
is(notification.chooseEmailLink.hidden, true, "Identity list is empty so link to list view should be hidden");
|
|
is(notification.addEmailLink.parentElement.hidden, true, "We are already on the email input step so choose email pane should be hidden");
|
|
is(notification.emailField.value, "", "Email field should default to empty on a new notification");
|
|
let notifDoc = notification.ownerDocument;
|
|
ok(notifDoc.getAnonymousElementByAttribute(notification, "anonid", "tos").hidden,
|
|
"TOS link should be hidden");
|
|
ok(notifDoc.getAnonymousElementByAttribute(notification, "anonid", "privacypolicy").hidden,
|
|
"PP link should be hidden");
|
|
|
|
// Try to continue with a missing email address
|
|
triggerMainCommand(popup);
|
|
is(notification.throbber.style.visibility, "hidden", "is throbber visible");
|
|
ok(!notification.button.disabled, "Button should not be disabled");
|
|
is(window.gIdentitySelected, null, "Check no identity selected");
|
|
|
|
// Fill in an invalid email address and try again
|
|
notification.emailField.value = "foo";
|
|
triggerMainCommand(popup);
|
|
is(notification.throbber.style.visibility, "hidden", "is throbber visible");
|
|
ok(!notification.button.disabled, "Button should not be disabled");
|
|
is(window.gIdentitySelected, null, "Check no identity selected");
|
|
|
|
// Fill in an email address and try again
|
|
notification.emailField.value = TEST_EMAIL;
|
|
triggerMainCommand(popup);
|
|
is(window.gIdentitySelected.rpId, outerWinId, "Check identity selected rpId");
|
|
is(window.gIdentitySelected.identity, TEST_EMAIL, "Check identity selected email");
|
|
is(notification.identity.selected, TEST_EMAIL, "Check persisted email");
|
|
is(notification.throbber.style.visibility, "visible", "is throbber visible");
|
|
ok(notification.button.disabled, "Button should be disabled");
|
|
ok(notification.emailField.disabled, "Email field should be disabled");
|
|
ok(notification.identityList.disabled, "Identity list should be disabled");
|
|
|
|
PopupNotifications.getNotification("identity-request").remove();
|
|
},
|
|
|
|
onHidden: function(popup) { },
|
|
},
|
|
{
|
|
name: "test_request_optional",
|
|
|
|
run: function() {
|
|
this.notifyOptions = {
|
|
rpId: outerWinId,
|
|
origin: TEST_ORIGIN,
|
|
privacyPolicy: TEST_ORIGIN + "/pp.txt",
|
|
termsOfService: TEST_ORIGIN + "/tos.tzt",
|
|
};
|
|
this.notifyObj = new NotificationBase("identity-request");
|
|
Services.obs.notifyObservers({ wrappedJSObject: this.notifyOptions },
|
|
"identity-request", null);
|
|
},
|
|
|
|
onShown: function(popup) {
|
|
checkPopup(popup, this.notifyObj);
|
|
let notification = popup.childNodes[0];
|
|
|
|
// Check identity popup state
|
|
let state = notification.identity;
|
|
ok(!state.typedEmail, "Nothing should be typed yet");
|
|
ok(!state.selected, "Identity should not be selected yet");
|
|
is(state.termsOfService, this.notifyOptions.termsOfService, "Check TOS URL");
|
|
is(state.privacyPolicy, this.notifyOptions.privacyPolicy, "Check PP URL");
|
|
is(state.step, 0, "Step should be persisted with default value");
|
|
is(state.rpId, outerWinId, "Check rpId");
|
|
is(state.origin, TEST_ORIGIN, "Check origin");
|
|
|
|
is(notification.step, 0, "Should be on the new email step");
|
|
is(notification.chooseEmailLink.hidden, true, "Identity list is empty so link to list view should be hidden");
|
|
is(notification.addEmailLink.parentElement.hidden, true, "We are already on the email input step so choose email pane should be hidden");
|
|
is(notification.emailField.value, "", "Email field should default to empty on a new notification");
|
|
let notifDoc = notification.ownerDocument;
|
|
let tosLink = notifDoc.getAnonymousElementByAttribute(notification, "anonid", "tos");
|
|
ok(!tosLink.hidden, "TOS link should be visible");
|
|
is(tosLink.href, this.notifyOptions.termsOfService, "Check TOS link URL");
|
|
let ppLink = notifDoc.getAnonymousElementByAttribute(notification, "anonid", "privacypolicy");
|
|
ok(!ppLink.hidden, "PP link should be visible");
|
|
is(ppLink.href, this.notifyOptions.privacyPolicy, "Check PP link URL");
|
|
|
|
// Try to continue with a missing email address
|
|
triggerMainCommand(popup);
|
|
is(notification.throbber.style.visibility, "hidden", "is throbber visible");
|
|
ok(!notification.button.disabled, "Button should not be disabled");
|
|
is(window.gIdentitySelected, null, "Check no identity selected");
|
|
|
|
// Fill in an invalid email address and try again
|
|
notification.emailField.value = "foo";
|
|
triggerMainCommand(popup);
|
|
is(notification.throbber.style.visibility, "hidden", "is throbber visible");
|
|
ok(!notification.button.disabled, "Button should not be disabled");
|
|
is(window.gIdentitySelected, null, "Check no identity selected");
|
|
|
|
// Fill in an email address and try again
|
|
notification.emailField.value = TEST_EMAIL;
|
|
triggerMainCommand(popup);
|
|
is(window.gIdentitySelected.rpId, outerWinId, "Check identity selected rpId");
|
|
is(window.gIdentitySelected.identity, TEST_EMAIL, "Check identity selected email");
|
|
is(notification.identity.selected, TEST_EMAIL, "Check persisted email");
|
|
is(notification.throbber.style.visibility, "visible", "is throbber visible");
|
|
ok(notification.button.disabled, "Button should be disabled");
|
|
ok(notification.emailField.disabled, "Email field should be disabled");
|
|
ok(notification.identityList.disabled, "Identity list should be disabled");
|
|
|
|
PopupNotifications.getNotification("identity-request").remove();
|
|
},
|
|
|
|
onHidden: function(popup) {},
|
|
},
|
|
{
|
|
name: "test_login_state_changed",
|
|
run: function () {
|
|
this.notifyOptions = {
|
|
rpId: outerWinId,
|
|
};
|
|
this.notifyObj = new NotificationBase("identity-logged-in");
|
|
this.notifyObj.message = "Signed in as: user@example.com";
|
|
this.notifyObj.mainAction.label = "Sign Out";
|
|
this.notifyObj.mainAction.accessKey = "O";
|
|
Services.obs.notifyObservers({ wrappedJSObject: this.notifyOptions },
|
|
"identity-login-state-changed", TEST_EMAIL);
|
|
executeSoon(function() {
|
|
PopupNotifications.getNotification("identity-logged-in").anchorElement.click();
|
|
});
|
|
},
|
|
|
|
onShown: function(popup) {
|
|
checkPopup(popup, this.notifyObj);
|
|
|
|
// Fire the notification that the user is no longer logged-in to close the UI.
|
|
Services.obs.notifyObservers({ wrappedJSObject: this.notifyOptions },
|
|
"identity-login-state-changed", null);
|
|
},
|
|
|
|
onHidden: function(popup) {},
|
|
},
|
|
{
|
|
name: "test_login_state_changed_logout",
|
|
run: function () {
|
|
this.notifyOptions = {
|
|
rpId: outerWinId,
|
|
};
|
|
this.notifyObj = new NotificationBase("identity-logged-in");
|
|
this.notifyObj.message = "Signed in as: user@example.com";
|
|
this.notifyObj.mainAction.label = "Sign Out";
|
|
this.notifyObj.mainAction.accessKey = "O";
|
|
Services.obs.notifyObservers({ wrappedJSObject: this.notifyOptions },
|
|
"identity-login-state-changed", TEST_EMAIL);
|
|
executeSoon(function() {
|
|
PopupNotifications.getNotification("identity-logged-in").anchorElement.click();
|
|
});
|
|
},
|
|
|
|
onShown: function(popup) {
|
|
checkPopup(popup, this.notifyObj);
|
|
|
|
// This time trigger the Sign Out button and make sure the UI goes away.
|
|
triggerMainCommand(popup);
|
|
},
|
|
|
|
onHidden: function(popup) {},
|
|
},
|
|
];
|
|
|
|
function test_auth() {
|
|
let notifyOptions = {
|
|
provId: outerWinId,
|
|
origin: TEST_ORIGIN,
|
|
};
|
|
|
|
Services.obs.addObserver(function() {
|
|
// prepare to send auth-complete and close the window
|
|
let winCloseObs = new WindowObserver(function(closedWin) {
|
|
info("closed window");
|
|
finish();
|
|
}, "domwindowclosed");
|
|
Services.ww.registerNotification(winCloseObs);
|
|
Services.obs.notifyObservers(null, "identity-auth-complete", IdentityService.IDP.authenticationFlowSet.authId);
|
|
|
|
}, "test-identity-auth-window", false);
|
|
|
|
let winObs = new WindowObserver(function(authWin) {
|
|
ok(authWin, "Authentication window opened");
|
|
ok(authWin.contentWindow.location);
|
|
});
|
|
|
|
Services.ww.registerNotification(winObs);
|
|
|
|
Services.obs.notifyObservers({ wrappedJSObject: notifyOptions },
|
|
"identity-auth", TEST_ORIGIN + "/auth");
|
|
}
|
|
|
|
function test() {
|
|
waitForExplicitFinish();
|
|
|
|
registerCleanupFunction(cleanUp);
|
|
|
|
let sitw = {};
|
|
Components.utils.import("resource:///modules/SignInToWebsite.jsm", sitw);
|
|
|
|
ok(sitw.SignInToWebsiteUX, "SignInToWebsiteUX object exists");
|
|
|
|
// Replace implementation of ID Service functions for testing
|
|
window.selectIdentity = sitw.SignInToWebsiteUX.selectIdentity;
|
|
sitw.SignInToWebsiteUX.selectIdentity = function(aRpId, aIdentity) {
|
|
info("Identity selected: " + aIdentity);
|
|
window.gIdentitySelected = {rpId: aRpId, identity: aIdentity};
|
|
};
|
|
|
|
window.setAuthenticationFlow = IdentityService.IDP.setAuthenticationFlow;
|
|
IdentityService.IDP.setAuthenticationFlow = function(aAuthId, aProvId) {
|
|
info("setAuthenticationFlow: " + aAuthId + " : " + aProvId);
|
|
this.authenticationFlowSet = { authId: aAuthId, provId: aProvId };
|
|
Services.obs.notifyObservers(null, "test-identity-auth-window", aAuthId);
|
|
};
|
|
|
|
runNextTest();
|
|
}
|
|
|
|
// Cleanup between tests
|
|
function resetState() {
|
|
delete window.gIdentitySelected;
|
|
delete IdentityService.IDP.authenticationFlowSet;
|
|
IdentityService.reset();
|
|
}
|
|
|
|
// Cleanup after all tests
|
|
function cleanUp() {
|
|
info("cleanup");
|
|
resetState();
|
|
|
|
for (let topic in gActiveObservers)
|
|
Services.obs.removeObserver(gActiveObservers[topic], topic);
|
|
for (let eventName in gActiveListeners)
|
|
PopupNotifications.panel.removeEventListener(eventName, gActiveListeners[eventName], false);
|
|
delete IdentityService.RP._rpFlows[outerWinId];
|
|
|
|
// Put the JSM functions back to how they were
|
|
IdentityService.IDP.setAuthenticationFlow = window.setAuthenticationFlow;
|
|
delete window.setAuthenticationFlow;
|
|
|
|
let sitw = {};
|
|
Components.utils.import("resource:///modules/SignInToWebsite.jsm", sitw);
|
|
sitw.SignInToWebsiteUX.selectIdentity = window.selectIdentity;
|
|
delete window.selectIdentity;
|
|
|
|
Services.prefs.clearUserPref("toolkit.identity.debug");
|
|
}
|
|
|
|
let gActiveListeners = {};
|
|
let gActiveObservers = {};
|
|
let gShownState = {};
|
|
|
|
function runNextTest() {
|
|
let nextTest = tests[gTestIndex];
|
|
|
|
function goNext() {
|
|
resetState();
|
|
if (++gTestIndex == tests.length)
|
|
executeSoon(test_auth);
|
|
else
|
|
executeSoon(runNextTest);
|
|
}
|
|
|
|
function addObserver(topic) {
|
|
function observer() {
|
|
Services.obs.removeObserver(observer, "PopupNotifications-" + topic);
|
|
delete gActiveObservers["PopupNotifications-" + topic];
|
|
|
|
info("[Test #" + gTestIndex + "] observer for " + topic + " called");
|
|
nextTest[topic]();
|
|
goNext();
|
|
}
|
|
Services.obs.addObserver(observer, "PopupNotifications-" + topic, false);
|
|
gActiveObservers["PopupNotifications-" + topic] = observer;
|
|
}
|
|
|
|
if (nextTest.backgroundShow) {
|
|
addObserver("backgroundShow");
|
|
} else if (nextTest.updateNotShowing) {
|
|
addObserver("updateNotShowing");
|
|
} else {
|
|
doOnPopupEvent("popupshowing", function () {
|
|
info("[Test #" + gTestIndex + "] popup showing");
|
|
});
|
|
doOnPopupEvent("popupshown", function () {
|
|
gShownState[gTestIndex] = true;
|
|
info("[Test #" + gTestIndex + "] popup shown");
|
|
nextTest.onShown(this);
|
|
});
|
|
|
|
// We allow multiple onHidden functions to be defined in an array. They're
|
|
// called in the order they appear.
|
|
let onHiddenArray = nextTest.onHidden instanceof Array ?
|
|
nextTest.onHidden :
|
|
[nextTest.onHidden];
|
|
doOnPopupEvent("popuphidden", function () {
|
|
if (!gShownState[gTestIndex]) {
|
|
// TODO: needed?
|
|
info("Popup from test " + gTestIndex + " was hidden before its popupshown fired");
|
|
}
|
|
|
|
let onHidden = onHiddenArray.shift();
|
|
info("[Test #" + gTestIndex + "] popup hidden (" + onHiddenArray.length + " hides remaining)");
|
|
executeSoon(function () {
|
|
onHidden.call(nextTest, this);
|
|
if (!onHiddenArray.length)
|
|
goNext();
|
|
}.bind(this));
|
|
}, onHiddenArray.length);
|
|
info("[Test #" + gTestIndex + "] added listeners; panel state: " + PopupNotifications.isPanelOpen);
|
|
}
|
|
|
|
info("[Test #" + gTestIndex + "] running test");
|
|
nextTest.run();
|
|
}
|
|
|
|
function doOnPopupEvent(eventName, callback, numExpected) {
|
|
gActiveListeners[eventName] = function (event) {
|
|
if (event.target != PopupNotifications.panel)
|
|
return;
|
|
if (typeof(numExpected) === "number")
|
|
numExpected--;
|
|
if (!numExpected) {
|
|
PopupNotifications.panel.removeEventListener(eventName, gActiveListeners[eventName], false);
|
|
delete gActiveListeners[eventName];
|
|
}
|
|
|
|
callback.call(PopupNotifications.panel);
|
|
};
|
|
PopupNotifications.panel.addEventListener(eventName, gActiveListeners[eventName], false);
|
|
}
|
|
|
|
function checkPopup(popup, notificationObj) {
|
|
info("[Test #" + gTestIndex + "] checking popup");
|
|
|
|
let notifications = popup.childNodes;
|
|
is(notifications.length, 1, "only one notification displayed");
|
|
let notification = notifications[0];
|
|
let icon = document.getAnonymousElementByAttribute(notification, "class", "popup-notification-icon");
|
|
is(notification.getAttribute("label"), notificationObj.message, "message matches");
|
|
is(notification.id, notificationObj.id + "-notification", "id matches");
|
|
if (notificationObj.id != "identity-request" && notificationObj.mainAction) {
|
|
is(notification.getAttribute("buttonlabel"), notificationObj.mainAction.label, "main action label matches");
|
|
is(notification.getAttribute("buttonaccesskey"), notificationObj.mainAction.accessKey, "main action accesskey matches");
|
|
}
|
|
let actualSecondaryActions = notification.childNodes;
|
|
let secondaryActions = notificationObj.secondaryActions || [];
|
|
let actualSecondaryActionsCount = actualSecondaryActions.length;
|
|
if (secondaryActions.length) {
|
|
let lastChild = actualSecondaryActions.item(actualSecondaryActions.length - 1);
|
|
is(lastChild.tagName, "menuseparator", "menuseparator exists");
|
|
actualSecondaryActionsCount--;
|
|
}
|
|
is(actualSecondaryActionsCount, secondaryActions.length, actualSecondaryActions.length + " secondary actions");
|
|
secondaryActions.forEach(function (a, i) {
|
|
is(actualSecondaryActions[i].getAttribute("label"), a.label, "label for secondary action " + i + " matches");
|
|
is(actualSecondaryActions[i].getAttribute("accesskey"), a.accessKey, "accessKey for secondary action " + i + " matches");
|
|
});
|
|
}
|
|
|
|
function triggerMainCommand(popup) {
|
|
info("[Test #" + gTestIndex + "] triggering main command");
|
|
let notifications = popup.childNodes;
|
|
ok(notifications.length > 0, "at least one notification displayed");
|
|
let notification = notifications[0];
|
|
|
|
// 20, 10 so that the inner button is hit
|
|
EventUtils.synthesizeMouse(notification.button, 20, 10, {});
|
|
}
|
|
|
|
function triggerSecondaryCommand(popup, index) {
|
|
info("[Test #" + gTestIndex + "] triggering secondary command");
|
|
let notifications = popup.childNodes;
|
|
ok(notifications.length > 0, "at least one notification displayed");
|
|
let notification = notifications[0];
|
|
|
|
notification.button.focus();
|
|
|
|
popup.addEventListener("popupshown", function () {
|
|
popup.removeEventListener("popupshown", arguments.callee, false);
|
|
|
|
// Press down until the desired command is selected
|
|
for (let i = 0; i <= index; i++)
|
|
EventUtils.synthesizeKey("VK_DOWN", {});
|
|
|
|
// Activate
|
|
EventUtils.synthesizeKey("VK_RETURN", {});
|
|
}, false);
|
|
|
|
// One down event to open the popup
|
|
EventUtils.synthesizeKey("VK_DOWN", { altKey: (navigator.platform.indexOf("Mac") == -1) });
|
|
}
|
|
|
|
function dismissNotification(popup) {
|
|
info("[Test #" + gTestIndex + "] dismissing notification");
|
|
executeSoon(function () {
|
|
EventUtils.synthesizeKey("VK_ESCAPE", {});
|
|
});
|
|
}
|
|
|
|
function partial(fn) {
|
|
let args = Array.prototype.slice.call(arguments, 1);
|
|
return function() {
|
|
return fn.apply(this, args.concat(Array.prototype.slice.call(arguments)));
|
|
};
|
|
}
|
|
|
|
// create a mock "doc" object, which the Identity Service
|
|
// uses as a pointer back into the doc object
|
|
function mock_doc(aIdentity, aOrigin, aDoFunc) {
|
|
let mockedDoc = {};
|
|
mockedDoc.id = outerWinId;
|
|
mockedDoc.loggedInEmail = aIdentity;
|
|
mockedDoc.origin = aOrigin;
|
|
mockedDoc['do'] = aDoFunc;
|
|
mockedDoc.doReady = partial(aDoFunc, 'ready');
|
|
mockedDoc.doLogin = partial(aDoFunc, 'login');
|
|
mockedDoc.doLogout = partial(aDoFunc, 'logout');
|
|
mockedDoc.doError = partial(aDoFunc, 'error');
|
|
mockedDoc.doCancel = partial(aDoFunc, 'cancel');
|
|
mockedDoc.doCoffee = partial(aDoFunc, 'coffee');
|
|
|
|
return mockedDoc;
|
|
}
|
|
|
|
// takes a list of functions and returns a function that
|
|
// when called the first time, calls the first func,
|
|
// then the next time the second, etc.
|
|
function call_sequentially() {
|
|
let numCalls = 0;
|
|
let funcs = arguments;
|
|
|
|
return function() {
|
|
if (!funcs[numCalls]) {
|
|
let argString = Array.prototype.slice.call(arguments).join(",");
|
|
ok(false, "Too many calls: " + argString);
|
|
return;
|
|
}
|
|
funcs[numCalls].apply(funcs[numCalls], arguments);
|
|
numCalls += 1;
|
|
};
|
|
}
|
|
|
|
function setupRPFlow(aIdentity) {
|
|
IdentityService.RP.watch(mock_doc(aIdentity, TEST_ORIGIN, call_sequentially(
|
|
function(action, params) {
|
|
is(action, "ready", "1st callback");
|
|
is(params, null);
|
|
},
|
|
function(action, params) {
|
|
is(action, "logout", "2nd callback");
|
|
is(params, null);
|
|
},
|
|
function(action, params) {
|
|
is(action, "ready", "3rd callback");
|
|
is(params, null);
|
|
}
|
|
)));
|
|
}
|
|
|
|
function WindowObserver(aCallback, aObserveTopic = "domwindowopened") {
|
|
this.observe = function(aSubject, aTopic, aData) {
|
|
if (aTopic != aObserveTopic) {
|
|
return;
|
|
}
|
|
info(aObserveTopic);
|
|
Services.ww.unregisterNotification(this);
|
|
|
|
SimpleTest.executeSoon(function() {
|
|
let domWin = aSubject.QueryInterface(Ci.nsIDOMWindow);
|
|
aCallback(domWin);
|
|
});
|
|
};
|
|
}
|