Bug 1576271 - Logins with no username should show '(no username)' in the login-item view when not editing. r=MattN,fluent-reviewers,flod
authorJared Wein <jwein@mozilla.com>
Thu, 29 Aug 2019 15:44:22 +0000
changeset 551163 8f4bcd61ad953f6a08447ef40f08b037c22e5140
parent 551162 696c78a89f827c1345bed59fdc786c9b412d01cd
child 551164 d01e6a3eea5ca1c9e91ca4440a2edc35804f481f
push id11865
push userbtara@mozilla.com
push dateMon, 02 Sep 2019 08:54:37 +0000
treeherdermozilla-beta@37f59c4671b3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersMattN, fluent-reviewers, flod
bugs1576271
milestone70.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1576271 - Logins with no username should show '(no username)' in the login-item view when not editing. r=MattN,fluent-reviewers,flod Differential Revision: https://phabricator.services.mozilla.com/D43334
browser/components/aboutlogins/content/components/login-item.js
browser/components/aboutlogins/tests/chrome/test_login_item.html
browser/locales/en-US/browser/aboutLogins.ftl
--- a/browser/components/aboutlogins/content/components/login-item.js
+++ b/browser/components/aboutlogins/content/components/login-item.js
@@ -123,16 +123,25 @@ export default class LoginItem extends H
       this._favicon.hidden = true;
       this._faviconWrapper.classList.remove("hide-default-favicon");
     }
 
     this._title.textContent = this._login.title;
     this._originInput.defaultValue = this._login.origin || "";
     this._usernameInput.defaultValue = this._login.username || "";
     this._passwordInput.defaultValue = this._login.password || "";
+    if (this.dataset.editing) {
+      this._usernameInput.removeAttribute("data-l10n-id");
+    } else {
+      document.l10n.setAttributes(
+        this._usernameInput,
+        "about-logins-login-item-username"
+      );
+    }
+    this._copyUsernameButton.disabled = !this._login.username;
     document.l10n.setAttributes(
       this._saveChangesButton,
       this.dataset.isNewLogin
         ? "login-item-save-new-button"
         : "login-item-save-changes-button"
     );
     await this._updatePasswordRevealState();
   }
@@ -274,16 +283,17 @@ export default class LoginItem extends H
           return;
         }
         if (classList.contains("dismiss-breach-alert")) {
           this.dismissBreachAlert();
           return;
         }
         if (classList.contains("edit-button")) {
           this._toggleEditing();
+          this.render();
 
           recordTelemetryEvent({ object: "existing_login", method: "edit" });
           return;
         }
         if (classList.contains("origin-input")) {
           this._handleOriginClick();
         }
         break;
--- a/browser/components/aboutlogins/tests/chrome/test_login_item.html
+++ b/browser/components/aboutlogins/tests/chrome/test_login_item.html
@@ -81,17 +81,19 @@ add_task(async function test_empty_item(
 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");
   let originInput = gLoginItem.shadowRoot.querySelector("input[name='origin']");
   is(originInput.value, TEST_LOGIN_1.origin, "origin should be populated");
-  is(gLoginItem.shadowRoot.querySelector("input[name='username']").value, TEST_LOGIN_1.username, "username 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");
   is(gLoginItem.shadowRoot.querySelector("input[name='password']").value, TEST_LOGIN_1.password, "password should be populated");
   is(document.l10n.getAttributes(gLoginItem.shadowRoot.querySelector(".time-created")).args.timeCreated, TEST_LOGIN_1.timeCreated, "time-created should be populated");
   is(document.l10n.getAttributes(gLoginItem.shadowRoot.querySelector(".time-changed")).args.timeChanged, TEST_LOGIN_1.timePasswordChanged, "time-changed should be populated");
   is(document.l10n.getAttributes(gLoginItem.shadowRoot.querySelector(".time-used")).args.timeUsed, TEST_LOGIN_1.timeLastUsed, "time-used should be populated");
   let copyButtons = [...gLoginItem.shadowRoot.querySelectorAll(".copy-button")];
   ok(copyButtons.every(button => !isHidden(button)), "The copy buttons should be visible when viewing a login");
 
   let openSiteEventDispatched = false;
@@ -109,16 +111,26 @@ add_task(async function test_set_login()
   }, {once: true});
   synthesizeMouseAtCenter(originInput, {button: 1});
   ok(openSiteEventDispatched, "Middle-clicking the .origin-input should dispatch the AboutLoginsOpenSite event");
 
   document.addEventListener("AboutLoginsOpenSite", event => {
     ok(false, "right-clicking the .origin-input should not trigger the AboutLoginsOpenSite event");
   }, {once: true});
   synthesizeMouseAtCenter(originInput, {button: 2});
+
+  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");
+
+  gLoginItem.shadowRoot.querySelector(".edit-button").click();
+  is(document.l10n.getAttributes(usernameInput).id, null, "username field should have no placeholder when editing");
 });
 
 add_task(async function test_update_breaches() {
   gLoginItem.setLogin(TEST_LOGIN_1);
   gLoginItem.updateBreaches(TEST_BREACHES_MAP);
   await asyncElementRendered();
 
   let correspondingBreach = TEST_BREACHES_MAP.get(gLoginItem._login.guid);
@@ -142,25 +154,27 @@ add_task(async function test_edit_login(
   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");
   is(gLoginItem.shadowRoot.querySelector("input[name='origin']").value, TEST_LOGIN_1.origin, "origin should be populated");
-  is(gLoginItem.shadowRoot.querySelector("input[name='username']").value, TEST_LOGIN_1.username, "username 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, null, "username field should have no placeholder in edit mode");
   is(gLoginItem.shadowRoot.querySelector("input[name='password']").value, TEST_LOGIN_1.password, "password should be populated");
   is(document.l10n.getAttributes(gLoginItem.shadowRoot.querySelector(".time-created")).args.timeCreated, TEST_LOGIN_1.timeCreated, "time-created should be populated");
   is(document.l10n.getAttributes(gLoginItem.shadowRoot.querySelector(".time-changed")).args.timeChanged, TEST_LOGIN_1.timePasswordChanged, "time-changed should be populated");
   is(document.l10n.getAttributes(gLoginItem.shadowRoot.querySelector(".time-used")).args.timeUsed, TEST_LOGIN_1.timeLastUsed, "time-used should be populated");
   let copyButtons = [...gLoginItem.shadowRoot.querySelectorAll(".copy-button")];
   ok(copyButtons.every(button => isHidden(button)), "The copy buttons should be hidden when editing a login");
 
-  gLoginItem.shadowRoot.querySelector("input[name='username']").value = "newUsername";
+  usernameInput.value = "newUsername";
   gLoginItem.shadowRoot.querySelector("input[name='password']").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");
--- a/browser/locales/en-US/browser/aboutLogins.ftl
+++ b/browser/locales/en-US/browser/aboutLogins.ftl
@@ -78,18 +78,18 @@ about-logins-intro-instruction-help = Vi
 
 login-item-new-login-title = Create New Login
 login-item-edit-button = Edit
 login-item-delete-button = Delete
 login-item-origin-label = Website Address
 login-item-origin =
   .placeholder = https://www.example.com
 login-item-username-label = Username
-login-item-username =
-  .placeholder = name@example.com
+about-logins-login-item-username =
+  .placeholder = (no username)
 login-item-copy-username-button-text = Copy
 login-item-copied-username-button-text = Copied!
 login-item-password-label = Password
 login-item-password-reveal-checkbox-show =
   .title = Show password
 login-item-password-reveal-checkbox-hide =
   .title = Hide password
 login-item-copy-password-button-text = Copy