summaryrefslogtreecommitdiffstats
path: root/browser/components/aboutlogins/tests/chrome/test_login_item.html
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/aboutlogins/tests/chrome/test_login_item.html')
-rw-r--r--browser/components/aboutlogins/tests/chrome/test_login_item.html481
1 files changed, 481 insertions, 0 deletions
diff --git a/browser/components/aboutlogins/tests/chrome/test_login_item.html b/browser/components/aboutlogins/tests/chrome/test_login_item.html
new file mode 100644
index 0000000000..a7946a0618
--- /dev/null
+++ b/browser/components/aboutlogins/tests/chrome/test_login_item.html
@@ -0,0 +1,481 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test the login-item component
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test the login-item component</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+ <script type="module" src="chrome://browser/content/aboutlogins/components/login-item.mjs"></script>
+ <script type="module" src="chrome://browser/content/aboutlogins/components/confirmation-dialog.mjs"></script>
+ <script type="module" src="chrome://browser/content/aboutlogins/components/login-timeline.mjs"></script>
+ <script src="aboutlogins_common.js"></script>
+
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ <p id="display">
+ </p>
+<div id="content" style="display: none">
+ <iframe id="templateFrame" src="chrome://browser/content/aboutlogins/aboutLogins.html"
+ sandbox="allow-same-origin"></iframe>
+</div>
+<pre id="test">
+</pre>
+<script type="module">
+
+import { CONCEALED_PASSWORD_TEXT } from "chrome://browser/content/aboutlogins/aboutLoginsUtils.mjs";
+
+/** Test the login-item component **/
+
+let gLoginItem, gConfirmationDialog;
+const TEST_LOGIN_1 = {
+ guid: "123456789",
+ origin: "https://example.com",
+ username: "user1",
+ password: "pass1",
+ timeCreated: "1000",
+ timePasswordChanged: "2000",
+ timeLastUsed: "4000",
+};
+
+const TEST_LOGIN_2 = {
+ guid: "987654321",
+ origin: "https://example.com",
+ username: "user2",
+ password: "pass2",
+ timeCreated: "2000",
+ timePasswordChanged: "4000",
+ timeLastUsed: "8000",
+};
+
+const TEST_BREACH = {
+ Name: "Test-Breach",
+ breachAlertURL: "https://monitor.firefox.com/breach-details/Test-Breach",
+};
+
+const TEST_BREACHES_MAP = new Map();
+TEST_BREACHES_MAP.set(TEST_LOGIN_1.guid, TEST_BREACH);
+
+const TEST_VULNERABLE_MAP = new Map();
+TEST_VULNERABLE_MAP.set(TEST_LOGIN_2.guid, true);
+
+const getLoginTimeline = loginItem =>
+ loginItem.shadowRoot.querySelector("login-timeline");
+
+const verifyTimelineActions = (actions, expectedActions) => {
+ is(
+ actions.length,
+ expectedActions.length,
+ `Number timeline actions length is correct. Actual: ${actions.length}. Expected: ${expectedActions.length}`
+ );
+
+ actions.forEach((point, index) => {
+ let actionId = document.l10n.getAttributes(point).id;
+ let expectedAction = expectedActions[index];
+
+ is(
+ actionId,
+ expectedAction,
+ `Rendered action is correct. Actual: ${actionId}. Expected: ${expectedAction}`
+ );
+ });
+};
+
+add_setup(async () => {
+ let templateFrame = document.getElementById("templateFrame");
+ let displayEl = document.getElementById("display");
+ await importDependencies(templateFrame, displayEl);
+
+ gLoginItem = document.createElement("login-item");
+ displayEl.appendChild(gLoginItem);
+
+ gConfirmationDialog = document.createElement("confirmation-dialog");
+ gConfirmationDialog.hidden = true;
+ displayEl.appendChild(gConfirmationDialog);
+});
+
+add_task(async function test_empty_item() {
+ ok(gLoginItem, "loginItem exists");
+ is(gLoginItem.shadowRoot.querySelector("a[name='origin']").getAttribute("href"), "", "origin should be blank");
+ is(gLoginItem.shadowRoot.querySelector("input[name='username']").value, "", "username should be blank");
+ is(gLoginItem._passwordInput.value, "", "password should be blank");
+ ok(!gLoginItem._passwordInput.isConnected, "Real password input should be disconnected");
+ is(gLoginItem._passwordDisplayInput.value, "", "password display should be blank");
+ ok(!isHidden(gLoginItem._passwordDisplayInput), "Password display input should be visible")
+ ok(isHidden(getLoginTimeline(gLoginItem)), "Timeline should be hidden");
+});
+
+add_task(async function test_set_login() {
+ gLoginItem.setLogin(TEST_LOGIN_1);
+ await asyncElementRendered();
+
+ ok(!gLoginItem.dataset.editing, "loginItem should not be in 'edit' mode");
+ ok(!gLoginItem.dataset.isNewLogin, "loginItem should not be in 'isNewLogin' mode");
+ ok(isHidden(gLoginItem._originInput), "Origin input should be hidden when not in edit mode");
+ ok(!isHidden(gLoginItem._originDisplayInput), "Origin display link should be visible when not in edit mode");
+ let originLink = gLoginItem.shadowRoot.querySelector("a[name='origin']");
+ is(originLink.getAttribute("href"), TEST_LOGIN_1.origin, "origin should be populated");
+ let usernameInput = gLoginItem.shadowRoot.querySelector("input[name='username']");
+ is(usernameInput.value, TEST_LOGIN_1.username, "username should be populated");
+ is(document.l10n.getAttributes(usernameInput).id, "about-logins-login-item-username", "username field should have default placeholder when not editing");
+
+ let passwordInput = gLoginItem._passwordInput;
+ is(passwordInput.value, TEST_LOGIN_1.password, "password should be populated");
+ ok(!passwordInput.hasAttribute("value"), "Password shouldn't be exposed in @value");
+ ok(!gLoginItem._passwordInput.isConnected, "Real password input should be disconnected");
+ let passwordDisplayInput = gLoginItem._passwordDisplayInput;
+ is(passwordDisplayInput.value, CONCEALED_PASSWORD_TEXT, "password display should be populated");
+ ok(!isHidden(passwordDisplayInput), "Password display input should be visible");
+
+ let timeline = getLoginTimeline(gLoginItem);
+ ok(!isHidden(timeline), "Timeline should be visible");
+ let actions = timeline.shadowRoot.querySelectorAll(".action");
+ verifyTimelineActions(actions, [
+ "login-item-timeline-action-created",
+ "login-item-timeline-action-updated",
+ "login-item-timeline-action-used",
+ ]);
+
+ let copyButtons = [...gLoginItem.shadowRoot.querySelectorAll(".copy-button")];
+ ok(copyButtons.every(button => !isHidden(button)), "The copy buttons should be visible when viewing a login");
+
+ let loginNoUsername = Object.assign({}, TEST_LOGIN_1, {username: ""});
+ gLoginItem.setLogin(loginNoUsername);
+ ok(!gLoginItem.dataset.editing, "loginItem should not be in 'edit' mode");
+ is(document.l10n.getAttributes(usernameInput).id, "about-logins-login-item-username", "username field should have default placeholder when username is not present and not editing");
+ let copyUsernameButton = gLoginItem.shadowRoot.querySelector(".copy-username-button");
+ ok(copyUsernameButton.disabled, "The copy-username-button should be disabled if there is no username");
+
+ usernameInput.placeholder = "dummy placeholder";
+ gLoginItem.shadowRoot.querySelector(".edit-button").click();
+ await asyncElementRendered();
+ is(
+ document.l10n.getAttributes(usernameInput).id,
+ null,
+ "there should be no placeholder id on the username input in edit mode"
+ );
+ is(usernameInput.placeholder, "", "there should be no placeholder on the username input in edit mode");
+});
+
+add_task(async function test_update_breaches() {
+ gLoginItem.setLogin(TEST_LOGIN_1);
+ gLoginItem.setBreaches(TEST_BREACHES_MAP);
+ await asyncElementRendered();
+
+ let breachAlert = gLoginItem.shadowRoot.querySelector(".breach-alert");
+ ok(!isHidden(breachAlert), "Breach alert should be visible");
+ is(breachAlert.querySelector(".alert-link").href, TEST_LOGIN_1.origin + "/", "Link in the text should point to the login origin");
+ let vulernableAlert = gLoginItem.shadowRoot.querySelector(".vulnerable-alert");
+ ok(isHidden(vulernableAlert), "Vulnerable alert should not be visible on a non-vulnerable login.");
+});
+
+add_task(async function test_breach_alert_is_correctly_hidden() {
+ gLoginItem.setLogin(TEST_LOGIN_2);
+ gLoginItem.setBreaches(TEST_BREACHES_MAP);
+ await asyncElementRendered();
+
+ let breachAlert = gLoginItem.shadowRoot.querySelector(".breach-alert");
+ ok(isHidden(breachAlert), "Breach alert should not be visible on login without an associated breach.");
+ let vulernableAlert = gLoginItem.shadowRoot.querySelector(".vulnerable-alert");
+ ok(isHidden(vulernableAlert), "Vulnerable alert should not be visible on a non-vulnerable login.");
+});
+
+add_task(async function test_update_vulnerable() {
+ gLoginItem.setLogin(TEST_LOGIN_2);
+ gLoginItem.setVulnerableLogins(TEST_VULNERABLE_MAP);
+ await asyncElementRendered();
+
+ let breachAlert = gLoginItem.shadowRoot.querySelector(".breach-alert");
+ ok(isHidden(breachAlert), "Breach alert should not be visible on login without an associated breach.");
+ let vulernableAlert = gLoginItem.shadowRoot.querySelector(".vulnerable-alert");
+ ok(!isHidden(vulernableAlert), "Vulnerable alert should be visible");
+ is(vulernableAlert.querySelector(".alert-link").href, TEST_LOGIN_2.origin + "/", "Link in the text should point to the login origin");
+});
+
+add_task(async function test_vulnerable_alert_is_correctly_hidden() {
+ gLoginItem.setLogin(TEST_LOGIN_1);
+ gLoginItem.setVulnerableLogins(TEST_VULNERABLE_MAP);
+ gLoginItem.setBreaches(new Map());
+ await asyncElementRendered();
+
+ let breachAlert = gLoginItem.shadowRoot.querySelector(".breach-alert");
+ ok(isHidden(breachAlert), "Breach alert should not be visible on login without an associated breach.");
+ let vulernableAlert = gLoginItem.shadowRoot.querySelector(".vulnerable-alert");
+ ok(isHidden(vulernableAlert), "Vulnerable alert should not be visible on a non-vulnerable login.");
+});
+
+add_task(async function test_edit_login() {
+ gLoginItem.setLogin(TEST_LOGIN_1);
+ let usernameInput = gLoginItem.shadowRoot.querySelector("input[name='username']");
+ usernameInput.placeholder = "dummy placeholder";
+ gLoginItem.shadowRoot.querySelector(".edit-button").click();
+ await asyncElementRendered();
+ await asyncElementRendered();
+
+ ok(gLoginItem.dataset.editing, "loginItem should be in 'edit' mode");
+ ok(isHidden(gLoginItem.shadowRoot.querySelector(".edit-button")), "edit button should be hidden in 'edit' mode");
+ ok(!gLoginItem.dataset.isNewLogin, "loginItem should not be in 'isNewLogin' mode");
+ let deleteButton = gLoginItem.shadowRoot.querySelector(".delete-button");
+ ok(!deleteButton.disabled, "Delete button should be enabled when editing a login");
+ ok(isHidden(gLoginItem._originInput), "Origin input should be hidden in edit mode");
+ ok(!isHidden(gLoginItem._originDisplayInput), "Origin display link should be visible in edit mode");
+ is(gLoginItem.shadowRoot.querySelector("a[name='origin']").getAttribute("href"), TEST_LOGIN_1.origin, "origin should be populated");
+ is(usernameInput.value, TEST_LOGIN_1.username, "username should be populated");
+ is(usernameInput, document.activeElement?.shadowRoot?.activeElement, "username is focused");
+ is(usernameInput.selectionStart, 0, "username value is selected from start");
+ is(usernameInput.selectionEnd, usernameInput.value.length, "username value is selected to the end");
+ is(
+ document.l10n.getAttributes(usernameInput).id,
+ null,
+ "there should be no placeholder id on the username input in edit mode"
+ );
+ is(usernameInput.placeholder, "", "there should be no placeholder on the username input in edit mode");
+ is(gLoginItem._passwordInput.value, TEST_LOGIN_1.password, "password should be populated");
+ is(gLoginItem._passwordDisplayInput.value, CONCEALED_PASSWORD_TEXT, "password display should be populated");
+
+ let timeline = getLoginTimeline(gLoginItem);
+ ok(!isHidden(timeline), "Timeline should be visible");
+
+ let copyButtons = [...gLoginItem.shadowRoot.querySelectorAll(".copy-button")];
+ ok(copyButtons.every(button => isHidden(button)), "The copy buttons should be hidden when editing a login");
+
+ usernameInput.value = "newUsername";
+ gLoginItem._passwordInput.value = "newPassword";
+
+ let updateEventDispatched = false;
+ document.addEventListener("AboutLoginsUpdateLogin", event => {
+ is(event.detail.guid, TEST_LOGIN_1.guid, "event should include guid");
+ is(event.detail.origin, TEST_LOGIN_1.origin, "event should include origin");
+ is(event.detail.username, "newUsername", "event should include new username");
+ is(event.detail.password, "newPassword", "event should include new password");
+ updateEventDispatched = true;
+ }, {once: true});
+ gLoginItem.shadowRoot.querySelector(".save-changes-button").click();
+ ok(updateEventDispatched, "Clicking the .save-changes-button should dispatch the AboutLoginsUpdateLogin event");
+});
+
+add_task(async function test_edit_login_cancel() {
+ gLoginItem.setLogin(TEST_LOGIN_1);
+ gLoginItem.shadowRoot.querySelector(".edit-button").click();
+ await asyncElementRendered();
+
+ ok(gLoginItem.dataset.editing, "loginItem should be in 'edit' mode");
+ is(!!gLoginItem.dataset.isNewLogin, false,
+ "loginItem should not be in 'isNewLogin' mode");
+
+ gLoginItem.shadowRoot.querySelector(".cancel-button").click();
+ gConfirmationDialog.shadowRoot.querySelector(".confirm-button").click();
+
+ await SimpleTest.promiseWaitForCondition(
+ () => gConfirmationDialog.hidden,
+ "waiting for confirmation dialog to hide"
+ );
+
+ ok(!gLoginItem.dataset.editing, "loginItem should not be in 'edit' mode");
+ ok(!gLoginItem.dataset.isNewLogin, "loginItem should not be in 'isNewLogin' mode");
+});
+
+add_task(async function test_reveal_password_change_selected_login() {
+ gLoginItem.setLogin(TEST_LOGIN_1);
+ let revealCheckbox = gLoginItem.shadowRoot.querySelector(".reveal-password-checkbox");
+ let passwordInput = gLoginItem._passwordInput;
+
+ ok(!revealCheckbox.checked, "reveal-checkbox should not be checked by default");
+ is(passwordInput.type, "password", "Password should be masked by default");
+ revealCheckbox.click();
+ ok(revealCheckbox.checked, "reveal-checkbox should be checked after clicking");
+ await SimpleTest.promiseWaitForCondition(() => passwordInput.type == "text",
+ "waiting for password input type to change after checking for primary password");
+ is(passwordInput.type, "text", "Password should be unmasked when checkbox is clicked");
+ ok(!isHidden(passwordInput), "Password input should be visible");
+
+ let editButton = gLoginItem.shadowRoot.querySelector(".edit-button");
+ editButton.click();
+ await asyncElementRendered();
+ ok(!isHidden(passwordInput), "Password input should still be visible");
+ ok(revealCheckbox.checked, "reveal-checkbox should remain checked when entering 'edit' mode");
+ gLoginItem.shadowRoot.querySelector(".cancel-button").click();
+ ok(!revealCheckbox.checked, "reveal-checkbox should be unchecked after canceling 'edit' mode");
+ revealCheckbox.click();
+ ok(isHidden(passwordInput), "Password input should be hidden");
+ ok(!isHidden(gLoginItem._passwordDisplayInput), "Password display should be visible");
+ gLoginItem.setLogin(TEST_LOGIN_2);
+ ok(!revealCheckbox.checked, "reveal-checkbox should be unchecked when changing logins");
+ is(passwordInput.type, "password", "Password should be masked by default when switching logins");
+ ok(isHidden(passwordInput), "Password input should be hidden");
+ ok(!isHidden(gLoginItem._passwordDisplayInput), "Password display should be visible");
+});
+
+add_task(async function test_set_login_empty() {
+ gLoginItem.setLogin({});
+ await asyncElementRendered();
+
+ ok(gLoginItem.dataset.editing, "loginItem should be in 'edit' mode");
+ ok(isHidden(gLoginItem.shadowRoot.querySelector(".edit-button")), "edit button should be hidden in 'edit' mode");
+ ok(gLoginItem.dataset.isNewLogin, "loginItem should be in 'isNewLogin' mode");
+ let deleteButton = gLoginItem.shadowRoot.querySelector(".delete-button");
+ ok(deleteButton.disabled, "Delete button should be disabled when creating a login");
+ ok(!isHidden(gLoginItem._originInput), "Origin input should be visible in new login edit mode");
+ ok(isHidden(gLoginItem._originDisplayInput), "Origin display should be hidden in new login edit mode");
+ is(gLoginItem.shadowRoot.querySelector("input[name='origin']").value, "", "origin should be empty");
+ is(gLoginItem.shadowRoot.querySelector("input[name='username']").value, "", "username should be empty");
+ is(gLoginItem._passwordInput.value, "", "password should be empty");
+ ok(!isHidden(gLoginItem._passwordInput), "Real password input should be visible in edit mode");
+ ok(isHidden(gLoginItem._passwordDisplayInput), "Password display should be hidden in edit mode");
+ ok(!gLoginItem._passwordInput.hasAttribute("value"), "Password shouldn't be exposed in @value");
+
+ let timeline = getLoginTimeline(gLoginItem);
+ ok(isHidden(timeline), "Timeline should be visible");
+
+ let copyButtons = [...gLoginItem.shadowRoot.querySelectorAll(".copy-button")];
+ ok(copyButtons.every(button => isHidden(button)), "The copy buttons should be hidden when creating a login");
+
+ let createEventDispatched = false;
+ document.addEventListener("AboutLoginsCreateLogin", event => {
+ createEventDispatched = true;
+ }, {once: true});
+ gLoginItem.shadowRoot.querySelector(".save-changes-button").click();
+ ok(!createEventDispatched, "Clicking the .save-changes-button shouldn't dispatch the event when fields are invalid");
+ let originInput = gLoginItem.shadowRoot.querySelector("input[name='origin']");
+ ok(originInput.matches(":invalid"), "origin value is required");
+ is(originInput.value, "", "origin input should be blank at start");
+
+ for (let originTuple of [
+ ["ftp://ftp.example.com/", "ftp://ftp.example.com/"],
+ ["https://example.com/", "https://example.com/"],
+ ["http://example.com/", "http://example.com/"],
+ ["www.example.com/bar", "https://www.example.com/bar"],
+ ["example.com/foo", "https://example.com/foo"],
+ ]) {
+ originInput.value = originTuple[0];
+ sendKey("TAB");
+ is(originInput.value, originTuple[1],
+ "origin input should have https:// prefix when not provided by user");
+ // Return focus back to the origin input
+ synthesizeKey("VK_TAB", { shiftKey: true });
+ }
+
+ gLoginItem.shadowRoot.querySelector("input[name='username']").value = "user1";
+ gLoginItem._passwordInput.value = "pass1";
+
+ document.addEventListener("AboutLoginsCreateLogin", event => {
+ is(event.detail.guid, undefined, "event should not include guid");
+ is(event.detail.origin, "https://example.com/foo", "event should include origin");
+ is(event.detail.username, "user1", "event should include new username");
+ is(event.detail.password, "pass1", "event should include new password");
+ createEventDispatched = true;
+ }, {once: true});
+ gLoginItem.shadowRoot.querySelector(".save-changes-button").click();
+ ok(createEventDispatched, "Clicking the .save-changes-button should dispatch the AboutLoginsCreateLogin event");
+});
+
+add_task(async function test_different_login_modified() {
+ gLoginItem.setLogin(TEST_LOGIN_1);
+ let otherLogin = Object.assign({}, TEST_LOGIN_1, {username: "fakeuser", guid: "fakeguid"});
+ gLoginItem.loginModified(otherLogin);
+ await asyncElementRendered();
+
+ is(gLoginItem.shadowRoot.querySelector("a[name='origin']").getAttribute("href"), TEST_LOGIN_1.origin, "origin should be unchanged");
+ is(gLoginItem.shadowRoot.querySelector("input[name='username']").value, TEST_LOGIN_1.username, "username should be unchanged");
+ is(gLoginItem._passwordInput.value, TEST_LOGIN_1.password, "password should be unchanged");
+ ok(!gLoginItem._passwordInput.hasAttribute("value"), "Password shouldn't be exposed in @value");
+ ok(!gLoginItem._passwordInput.isConnected, "Real password input should be disconnected in masked non-edit mode");
+ ok(!isHidden(gLoginItem._passwordDisplayInput), "Password display should be visible in masked non-edit mode");
+});
+
+add_task(async function test_different_login_removed() {
+ gLoginItem.setLogin(TEST_LOGIN_1);
+ let otherLogin = Object.assign({}, TEST_LOGIN_1, {username: "fakeuser", guid: "fakeguid"});
+ gLoginItem.loginRemoved(otherLogin);
+ await asyncElementRendered();
+
+ is(gLoginItem.shadowRoot.querySelector("a[name='origin']").getAttribute("href"), TEST_LOGIN_1.origin, "origin should be unchanged");
+ is(gLoginItem.shadowRoot.querySelector("input[name='username']").value, TEST_LOGIN_1.username, "username should be unchanged");
+ is(gLoginItem._passwordInput.value, TEST_LOGIN_1.password, "password should be unchanged");
+ ok(!gLoginItem._passwordInput.hasAttribute("value"), "Password shouldn't be exposed in @value");
+ ok(!gLoginItem._passwordInput.isConnected, "Real password input should be disconnected in masked non-edit mode");
+ ok(!isHidden(gLoginItem._passwordDisplayInput), "Password display should be visible in masked non-edit mode");
+});
+
+add_task(async function test_login_modified() {
+ gLoginItem.setLogin(TEST_LOGIN_1);
+ let modifiedLogin = Object.assign({}, TEST_LOGIN_1, {username: "updateduser"});
+ gLoginItem.loginModified(modifiedLogin);
+ await asyncElementRendered();
+
+ is(gLoginItem.shadowRoot.querySelector("a[name='origin']").getAttribute("href"), modifiedLogin.origin, "origin should be updated");
+ is(gLoginItem.shadowRoot.querySelector("input[name='username']").value, modifiedLogin.username, "username should be updated");
+ is(gLoginItem._passwordInput.value, modifiedLogin.password, "password should be updated");
+ ok(!gLoginItem._passwordInput.hasAttribute("value"), "Password shouldn't be exposed in @value");
+ ok(!gLoginItem._passwordInput.isConnected, "Real password input should be disconnected in masked non-edit mode");
+ ok(!isHidden(gLoginItem._passwordDisplayInput), "Password display should be visible in masked non-edit mode");
+});
+
+add_task(async function test_login_removed() {
+ gLoginItem.setLogin(TEST_LOGIN_1);
+ gLoginItem.loginRemoved(TEST_LOGIN_1);
+ await asyncElementRendered();
+
+ is(gLoginItem.shadowRoot.querySelector("input[name='origin']").value, "", "origin should be cleared");
+ is(gLoginItem.shadowRoot.querySelector("input[name='username']").value, "", "username should be cleared");
+ is(gLoginItem._passwordInput.value, "", "password should be cleared");
+ ok(!gLoginItem._passwordInput.hasAttribute("value"), "Password shouldn't be exposed in @value");
+
+ let timeline = getLoginTimeline(gLoginItem);
+ ok(isHidden(timeline), "Timeline should be visible");
+});
+
+add_task(async function test_login_long_username_scrollLeft_reset() {
+ let loginLongUsername = Object.assign({}, TEST_LOGIN_1, {username: "user2longnamelongnamelongnamelongnamelongname"});
+ gLoginItem.setLogin(loginLongUsername);
+ gLoginItem.shadowRoot.querySelector(".edit-button").click();
+ await asyncElementRendered();
+ await asyncElementRendered();
+ let usernameInput = gLoginItem.shadowRoot.querySelector("input[name='username']");
+ usernameInput.scrollLeft = usernameInput.scrollLeftMax;
+ gLoginItem.shadowRoot.querySelector(".cancel-button").click();
+ is(usernameInput.scrollLeft, 0, "username input should be scrolled horizontally to the beginning");
+});
+
+add_task(async function test_copy_button_state() {
+ gLoginItem.setLogin(TEST_LOGIN_1);
+ await asyncElementRendered();
+
+ let copyUsernameButton = gLoginItem.shadowRoot.querySelector(".copy-username-button");
+ ok(!copyUsernameButton.disabled, "The copy-username-button should be enabled");
+
+ let copyPasswordButton = gLoginItem.shadowRoot.querySelector(".copy-password-button");
+ ok(!copyPasswordButton.disabled, "The copy-passwoed-button should be enabled");
+
+ copyUsernameButton.click();
+ ok(copyUsernameButton.disabled, "The copy-username-button should be disabled when it is clicked");
+ ok(!copyPasswordButton.disabled, "The copy-passwoed-button should be enabled when the copy-username-button is clicked");
+
+ copyPasswordButton.click();
+ await SimpleTest.promiseWaitForCondition(() => copyPasswordButton.disabled,
+ "waiting for copy-password-button to become disabled after checking for primary password");
+
+ ok(copyPasswordButton.disabled, "The copy-passwoed-button should be disabled when it is clicked");
+ ok(!copyUsernameButton.disabled, "The copy-username-button should be enabled when the copy-password-button is clicked");
+
+ let loginNoUsername = Object.assign({}, TEST_LOGIN_2, {username: ""});
+ gLoginItem.setLogin(loginNoUsername);
+
+ ok(copyUsernameButton.disabled, "The copy-username-button should be disabled when the username is empty");
+ ok(!copyPasswordButton.disabled, "The copy-passwoed-button should be enabled");
+
+ copyPasswordButton.click();
+ await SimpleTest.promiseWaitForCondition(() => copyPasswordButton.disabled,
+ "waiting for copy-password-button to become disabled after checking for primary password");
+
+ ok(copyPasswordButton.disabled, "The copy-passwoed-button should be disabled when it is clicked");
+ ok(copyUsernameButton.disabled, "The copy-username-button should still be disabled after clicking the password button when the username is empty");
+});
+
+</script>
+
+</body>
+</html>