| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| /** @fileoverview Runs the Polymer Password Settings tests. */ |
| |
| // clang-format off |
| import {isChromeOS, webUIListenerCallback} from 'chrome://resources/js/cr.m.js'; |
| import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js'; |
| import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; |
| import {MultiStoreExceptionEntry, MultiStorePasswordUiEntry, PasswordManagerImpl, PasswordManagerProxy, ProfileInfoBrowserProxyImpl, Router, routes, SettingsPluralStringProxyImpl} from 'chrome://settings/settings.js'; |
| import {createExceptionEntry, createMultiStoreExceptionEntry, createMultiStorePasswordEntry, createPasswordEntry, makeCompromisedCredential, makePasswordCheckStatus, PasswordSectionElementFactory} from 'chrome://test/settings/passwords_and_autofill_fake_data.js'; |
| import {runCancelExportTest, runExportFlowErrorRetryTest, runExportFlowErrorTest, runExportFlowFastTest, runExportFlowSlowTest, runFireCloseEventAfterExportCompleteTest,runStartExportTest} from 'chrome://test/settings/passwords_export_test.js'; |
| import {getSyncAllPrefs, simulateStoredAccounts, simulateSyncStatus} from 'chrome://test/settings/sync_test_util.m.js'; |
| import {TestPasswordManagerProxy} from 'chrome://test/settings/test_password_manager_proxy.js'; |
| import {TestProfileInfoBrowserProxy} from 'chrome://test/settings/test_profile_info_browser_proxy.m.js'; |
| import {TestPluralStringProxy} from 'chrome://test/test_plural_string_proxy.js'; |
| import {eventToPromise} from 'chrome://test/test_util.m.js'; |
| |
| // clang-format on |
| |
| const PasswordCheckState = chrome.passwordsPrivate.PasswordCheckState; |
| |
| /** |
| * Helper method that validates a that elements in the password list match |
| * the expected data. |
| * @param {!Element} passwordsSection The passwords section element that will |
| * be checked. |
| * @param {!Array<!MultiStorePasswordUiEntry>} expectedPasswords The expected |
| * data. |
| * @private |
| */ |
| function validateMultiStorePasswordList(passwordsSection, expectedPasswords) { |
| assertDeepEquals(expectedPasswords, passwordsSection.$.passwordList.items); |
| const listItems = |
| passwordsSection.shadowRoot.querySelectorAll('password-list-item'); |
| for (let index = 0; index < expectedPasswords.length; ++index) { |
| const expected = expectedPasswords[index]; |
| const listItem = listItems[index]; |
| assertTrue(!!listItem); |
| assertEquals(expected.urls.shown, listItem.$.originUrl.textContent.trim()); |
| assertEquals(expected.urls.link, listItem.$.originUrl.href); |
| assertEquals(expected.username, listItem.$.username.value); |
| } |
| } |
| |
| /** |
| * Convenience version of validateMultiStorePasswordList() for when store |
| * duplicates don't exist. |
| * @param {!Element} passwordsSection The passwords section element that will |
| * be checked. |
| * @param {!Array<!chrome.passwordsPrivate.PasswordUiEntry>} passwordList The |
| * expected data. |
| * @private |
| */ |
| function validatePasswordList(passwordsSection, passwordList) { |
| validateMultiStorePasswordList( |
| passwordsSection, |
| passwordList.map(entry => new MultiStorePasswordUiEntry(entry))); |
| } |
| |
| |
| /** |
| * Helper method that validates a that elements in the exception list match |
| * the expected data. |
| * @param {!Array<!Element>} nodes The nodes that will be checked. |
| * @param {!Array<!MultiStoreExceptionEntry>} exceptionList The expected data. |
| * @private |
| */ |
| function validateMultiStoreExceptionList(nodes, exceptionList) { |
| assertEquals(exceptionList.length, nodes.length); |
| for (let index = 0; index < exceptionList.length; ++index) { |
| const node = nodes[index]; |
| const exception = exceptionList[index]; |
| assertEquals( |
| exception.urls.shown, |
| node.querySelector('#exception').textContent.trim()); |
| assertEquals( |
| exception.urls.link.toLowerCase(), |
| node.querySelector('#exception').href); |
| } |
| } |
| |
| /** |
| * Convenience version of validateMultiStoreExceptionList() for when store |
| * duplicates do not exist. |
| * @param {!Array<!Element>} nodes The nodes that will be checked. |
| * @param {!Array<!chrome.passwordsPrivate.ExceptionEntry>} exceptionList The |
| * expected data. |
| * @private |
| */ |
| function validateExceptionList(nodes, exceptionList) { |
| validateMultiStoreExceptionList( |
| nodes, exceptionList.map(entry => new MultiStoreExceptionEntry(entry))); |
| } |
| |
| /** |
| * Returns all children of an element that has children added by a dom-repeat. |
| * @param {!Element} element |
| * @return {!Array<!Element>} |
| * @private |
| */ |
| function getDomRepeatChildren(element) { |
| const nodes = element.querySelectorAll('.list-item:not([id])'); |
| return nodes; |
| } |
| |
| /** |
| * Extracts the first password-list-item in the a password-section element. |
| * @param {!Element} passwordsSection |
| */ |
| function getFirstPasswordListItem(passwordsSection) { |
| // The first child is a template, skip and get the real 'first child'. |
| return passwordsSection.$$('password-list-item'); |
| } |
| |
| /** |
| * Helper method used to test for a url in a list of passwords. |
| * @param {!Array<!chrome.passwordsPrivate.PasswordUiEntry>} passwordList |
| * @param {string} url The URL that is being searched for. |
| */ |
| function listContainsUrl(passwordList, url) { |
| for (let i = 0; i < passwordList.length; ++i) { |
| if (passwordList[i].urls.origin === url) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Helper method used to test for a url in a list of passwords. |
| * @param {!Array<!chrome.passwordsPrivate.ExceptionEntry>} exceptionList |
| * @param {string} url The URL that is being searched for. |
| */ |
| function exceptionsListContainsUrl(exceptionList, url) { |
| for (let i = 0; i < exceptionList.length; ++i) { |
| if (exceptionList[i].urls.orginUrl === url) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Helper function to test for an element is visible. |
| */ |
| function isElementVisible(element) { |
| return element && !element.hidden; |
| } |
| |
| /** |
| * Helper function to test if all components of edit dialog are shown correctly. |
| */ |
| function editDialogPartsAreShownCorrectly(passwordDialog) { |
| assertEquals( |
| passwordDialog.i18n('editPasswordTitle'), |
| passwordDialog.$.title.textContent.trim()); |
| assertFalse(passwordDialog.$.passwordInput.readonly); |
| assertTrue(passwordDialog.$.passwordInput.required); |
| assertTrue(isElementVisible(passwordDialog.$.footnote)); |
| assertTrue(isElementVisible(passwordDialog.$.cancel)); |
| assertEquals( |
| passwordDialog.i18n('save'), |
| passwordDialog.$.actionButton.textContent.trim()); |
| } |
| |
| /** |
| * Helper function to test if all components of details dialog are shown |
| * correctly. |
| */ |
| function detailsDialogPartsAreShownCorrectly(passwordDialog) { |
| assertEquals( |
| passwordDialog.i18n('passwordDetailsTitle'), |
| passwordDialog.$.title.textContent.trim()); |
| assertTrue(passwordDialog.$.passwordInput.readonly); |
| assertFalse(passwordDialog.$.passwordInput.required); |
| assertFalse(isElementVisible(passwordDialog.$.footnote)); |
| assertFalse(isElementVisible(passwordDialog.$.cancel)); |
| assertEquals( |
| passwordDialog.i18n('done'), |
| passwordDialog.$.actionButton.textContent.trim()); |
| } |
| |
| /** |
| * Simulates user who is eligible and opted-in for account storage. Should be |
| * called after the PasswordsSection element is created. The load time value for |
| * enableAccountStorage must be overridden separately. |
| * @param {TestPasswordManagerProxy} passwordManager |
| */ |
| function simulateAccountStorageUser(passwordManager) { |
| simulateSyncStatus({signedIn: false}); |
| simulateStoredAccounts([{email: 'john@gmail.com'}]); |
| passwordManager.setIsOptedInForAccountStorageAndNotify(true); |
| |
| flush(); |
| } |
| |
| suite('PasswordsSection', function() { |
| /** @type {TestPasswordManagerProxy} */ |
| let passwordManager = null; |
| |
| /** @type {PasswordSectionElementFactory} */ |
| let elementFactory = null; |
| |
| /** @type {TestPluralStringProxy} */ |
| let pluralString = null; |
| |
| suiteSetup(function() { |
| loadTimeData.overrideValues({enablePasswordCheck: true}); |
| }); |
| |
| setup(function() { |
| PolymerTest.clearBody(); |
| // Override the PasswordManagerImpl for testing. |
| passwordManager = new TestPasswordManagerProxy(); |
| pluralString = new TestPluralStringProxy(); |
| SettingsPluralStringProxyImpl.instance_ = pluralString; |
| |
| PasswordManagerImpl.instance_ = passwordManager; |
| elementFactory = new PasswordSectionElementFactory(document); |
| }); |
| |
| test('testPasswordsExtensionIndicator', function() { |
| // Initialize with dummy prefs. |
| const element = document.createElement('passwords-section'); |
| element.prefs = { |
| credentials_enable_service: {}, |
| }; |
| document.body.appendChild(element); |
| |
| assertFalse(!!element.$$('#passwordsExtensionIndicator')); |
| element.set('prefs.credentials_enable_service.extensionId', 'test-id'); |
| flush(); |
| |
| assertTrue(!!element.$$('#passwordsExtensionIndicator')); |
| }); |
| |
| test('verifyNoSavedPasswords', function() { |
| const passwordsSection = |
| elementFactory.createPasswordsSection(passwordManager, [], []); |
| |
| validatePasswordList(passwordsSection, []); |
| |
| assertFalse(passwordsSection.$.noPasswordsLabel.hidden); |
| assertTrue(passwordsSection.$.savedPasswordsHeaders.hidden); |
| }); |
| |
| test('verifySavedPasswordEntries', function() { |
| const passwordList = [ |
| createPasswordEntry({url: 'site1.com', username: 'luigi', id: 0}), |
| createPasswordEntry({url: 'longwebsite.com', username: 'peach', id: 1}), |
| createPasswordEntry({url: 'site2.com', username: 'mario', id: 2}), |
| createPasswordEntry({url: 'site1.com', username: 'peach', id: 3}), |
| createPasswordEntry({url: 'google.com', username: 'mario', id: 4}), |
| createPasswordEntry({url: 'site2.com', username: 'luigi', id: 5}), |
| ]; |
| |
| const passwordsSection = elementFactory.createPasswordsSection( |
| passwordManager, passwordList, []); |
| |
| // Assert that the data is passed into the iron list. If this fails, |
| // then other expectations will also fail. |
| assertDeepEquals( |
| passwordList.map(entry => new MultiStorePasswordUiEntry(entry)), |
| passwordsSection.$.passwordList.items); |
| |
| validatePasswordList(passwordsSection, passwordList); |
| |
| assertTrue(passwordsSection.$.noPasswordsLabel.hidden); |
| assertFalse(passwordsSection.$.savedPasswordsHeaders.hidden); |
| }); |
| |
| // Test verifies that passwords duplicated across stores get properly merged |
| // in the UI. |
| test('verifySavedPasswordEntriesWithMultiStore', function() { |
| // Entries with duplicates. |
| const accountPassword1 = createPasswordEntry( |
| {username: 'user1', frontendId: 1, id: 10, fromAccountStore: true}); |
| const devicePassword1 = createPasswordEntry( |
| {username: 'user1', frontendId: 1, id: 11, fromAccountStore: false}); |
| const accountPassword2 = createPasswordEntry( |
| {username: 'user2', frontendId: 2, id: 20, fromAccountStore: true}); |
| const devicePassword2 = createPasswordEntry( |
| {username: 'user2', frontendId: 2, id: 21, fromAccountStore: false}); |
| // Entries without duplicate. |
| const devicePassword3 = createPasswordEntry( |
| {username: 'user3', frontendId: 3, id: 3, fromAccountStore: false}); |
| const accountPassword4 = createPasswordEntry( |
| {username: 'user4', frontendId: 4, id: 4, fromAccountStore: true}); |
| |
| // Shuffle entries a little. |
| const passwordsSection = elementFactory.createPasswordsSection( |
| passwordManager, |
| [ |
| devicePassword3, accountPassword1, devicePassword2, accountPassword4, |
| devicePassword1, accountPassword2 |
| ], |
| []); |
| |
| // Expected list keeping relative order. |
| const expectedList = [ |
| createMultiStorePasswordEntry({username: 'user3', deviceId: 3}), |
| createMultiStorePasswordEntry( |
| {username: 'user1', accountId: 10, deviceId: 11}), |
| createMultiStorePasswordEntry( |
| {username: 'user2', accountId: 20, deviceId: 21}), |
| createMultiStorePasswordEntry({username: 'user4', accountId: 4}), |
| ]; |
| |
| validateMultiStorePasswordList(passwordsSection, expectedList); |
| }); |
| |
| // Test verifies that removing a password will update the elements. |
| test('verifyPasswordListRemove', function() { |
| const passwordList = [ |
| createPasswordEntry( |
| {url: 'anotherwebsite.com', username: 'luigi', id: 0}), |
| createPasswordEntry({url: 'longwebsite.com', username: 'peach', id: 1}), |
| createPasswordEntry({url: 'website.com', username: 'mario', id: 2}) |
| ]; |
| |
| const passwordsSection = elementFactory.createPasswordsSection( |
| passwordManager, passwordList, []); |
| |
| validatePasswordList(passwordsSection, passwordList); |
| // Simulate 'longwebsite.com' being removed from the list. |
| passwordList.splice(1, 1); |
| passwordManager.lastCallback.addSavedPasswordListChangedListener( |
| passwordList); |
| flush(); |
| |
| assertFalse( |
| listContainsUrl(passwordsSection.savedPasswords, 'longwebsite.com')); |
| assertFalse(listContainsUrl(passwordList, 'longwebsite.com')); |
| |
| validatePasswordList(passwordsSection, passwordList); |
| }); |
| |
| // Regression test for crbug.com/1110290. |
| // Test verifies that if the password list is updated, all the plaintext |
| // passwords are hidden. |
| test('updatingPasswordListHidesPlaintextPasswords', function() { |
| const passwordList = [ |
| createPasswordEntry({username: 'user0', id: 0}), |
| createPasswordEntry({username: 'user1', id: 1}), |
| ]; |
| const passwordsSection = elementFactory.createPasswordsSection( |
| passwordManager, passwordList, []); |
| |
| // Make passwords visible. |
| const passwordListItems = |
| passwordsSection.root.querySelectorAll('password-list-item'); |
| assertEquals(2, passwordListItems.length); |
| passwordListItems[0].password = 'pwd0'; |
| passwordListItems[1].password = 'pwd1'; |
| flush(); |
| |
| // Remove first row and verify that the remaining password is hidden. |
| passwordList.splice(0, 1); |
| passwordManager.lastCallback.addSavedPasswordListChangedListener( |
| passwordList); |
| flush(); |
| assertEquals('', getFirstPasswordListItem(passwordsSection).password); |
| assertEquals( |
| 'user1', getFirstPasswordListItem(passwordsSection).entry.username); |
| }); |
| |
| // Test verifies that removing the account copy of a duplicated password will |
| // still leave the device copy present. |
| test('verifyPasswordListRemoveAccountCopy', function() { |
| const passwordList = [ |
| createPasswordEntry({frontendId: 0, id: 0, fromAccountStore: true}), |
| createPasswordEntry({frontendId: 0, id: 1, fromAccountStore: false}), |
| ]; |
| |
| const passwordsSection = elementFactory.createPasswordsSection( |
| passwordManager, passwordList, []); |
| |
| validateMultiStorePasswordList( |
| passwordsSection, |
| [createMultiStorePasswordEntry({accountId: 0, deviceId: 1})]); |
| // Simulate account copy being removed from the list. |
| passwordList.splice(0, 1); |
| passwordManager.lastCallback.addSavedPasswordListChangedListener( |
| passwordList); |
| flush(); |
| |
| validateMultiStorePasswordList( |
| passwordsSection, [createMultiStorePasswordEntry({deviceId: 1})]); |
| }); |
| |
| // Test verifies that removing the device copy of a duplicated password will |
| // still leave the account copy present. |
| test('verifyPasswordListRemoveDeviceCopy', function() { |
| const passwordList = [ |
| createPasswordEntry({frontendId: 0, id: 0, fromAccountStore: true}), |
| createPasswordEntry({frontendId: 0, id: 1, fromAccountStore: false}), |
| ]; |
| |
| const passwordsSection = elementFactory.createPasswordsSection( |
| passwordManager, passwordList, []); |
| |
| validateMultiStorePasswordList( |
| passwordsSection, |
| [createMultiStorePasswordEntry({accountId: 0, deviceId: 1})]); |
| // Simulate device copy being removed from the list. |
| passwordList.splice(1, 1); |
| passwordManager.lastCallback.addSavedPasswordListChangedListener( |
| passwordList); |
| flush(); |
| |
| validateMultiStorePasswordList( |
| passwordsSection, [createMultiStorePasswordEntry({accountId: 0})]); |
| }); |
| |
| // Test verifies that removing both copies of a duplicated password will |
| // cause no password to be displayed. |
| test('verifyPasswordListRemoveBothCopies', function() { |
| const passwordList = [ |
| createPasswordEntry({frontendId: 0, id: 0, fromAccountStore: true}), |
| createPasswordEntry({frontendId: 0, id: 1, fromAccountStore: false}), |
| ]; |
| |
| const passwordsSection = elementFactory.createPasswordsSection( |
| passwordManager, passwordList, []); |
| |
| validateMultiStorePasswordList( |
| passwordsSection, |
| [createMultiStorePasswordEntry({accountId: 0, deviceId: 1})]); |
| // Simulate both copies being removed from the list. |
| passwordList.splice(0, 2); |
| passwordManager.lastCallback.addSavedPasswordListChangedListener( |
| passwordList); |
| flush(); |
| |
| validateMultiStorePasswordList(passwordsSection, []); |
| }); |
| |
| // Test verifies that adding a password will update the elements. |
| test('verifyPasswordListAdd', function() { |
| const passwordList = [ |
| createPasswordEntry( |
| {url: 'anotherwebsite.com', username: 'luigi', id: 0}), |
| createPasswordEntry({url: 'longwebsite.com', username: 'peach', id: 1}), |
| ]; |
| |
| const passwordsSection = elementFactory.createPasswordsSection( |
| passwordManager, passwordList, []); |
| |
| validatePasswordList(passwordsSection, passwordList); |
| // Simulate 'website.com' being added to the list. |
| passwordList.unshift( |
| createPasswordEntry({url: 'website.com', username: 'mario', id: 2})); |
| passwordManager.lastCallback.addSavedPasswordListChangedListener( |
| passwordList); |
| flush(); |
| |
| validatePasswordList(passwordsSection, passwordList); |
| }); |
| |
| // Test verifies that adding an account copy of an existing password will |
| // merge it with the one already in the list. |
| test('verifyPasswordListAddAccountCopy', function() { |
| const passwordList = [ |
| createPasswordEntry({frontendId: 0, fromAccountStore: false, id: 0}), |
| ]; |
| |
| const passwordsSection = elementFactory.createPasswordsSection( |
| passwordManager, passwordList, []); |
| |
| validatePasswordList(passwordsSection, passwordList); |
| // Simulate account copy being added to the list. |
| passwordList.unshift( |
| createPasswordEntry({frontendId: 0, fromAccountStore: true, id: 1})); |
| |
| passwordManager.lastCallback.addSavedPasswordListChangedListener( |
| passwordList); |
| flush(); |
| |
| validateMultiStorePasswordList( |
| passwordsSection, |
| [createMultiStorePasswordEntry({deviceId: 0, accountId: 1})]); |
| }); |
| |
| // Test verifies that adding a device copy of an existing password will |
| // merge it with the one already in the list. |
| test('verifyPasswordListAddDeviceCopy', function() { |
| const passwordList = [ |
| createPasswordEntry({frontendId: 0, fromAccountStore: true, id: 0}), |
| ]; |
| |
| const passwordsSection = elementFactory.createPasswordsSection( |
| passwordManager, passwordList, []); |
| |
| validatePasswordList(passwordsSection, passwordList); |
| // Simulate device copy being added to the list. |
| passwordList.unshift( |
| createPasswordEntry({frontendId: 0, fromAccountStore: false, id: 1})); |
| |
| passwordManager.lastCallback.addSavedPasswordListChangedListener( |
| passwordList); |
| flush(); |
| |
| validateMultiStorePasswordList( |
| passwordsSection, |
| [createMultiStorePasswordEntry({accountId: 0, deviceId: 1})]); |
| }); |
| |
| // Test verifies that removing one out of two passwords for the same website |
| // will update the elements. |
| test('verifyPasswordListRemoveSameWebsite', function() { |
| const passwordsSection = |
| elementFactory.createPasswordsSection(passwordManager, [], []); |
| |
| // Set-up initial list. |
| let passwordList = [ |
| createPasswordEntry({url: 'website.com', username: 'mario', id: 0}), |
| createPasswordEntry({url: 'website.com', username: 'luigi', id: 1}) |
| ]; |
| |
| passwordManager.lastCallback.addSavedPasswordListChangedListener( |
| passwordList); |
| flush(); |
| validatePasswordList(passwordsSection, passwordList); |
| |
| // Simulate '(website.com, mario)' being removed from the list. |
| passwordList.shift(); |
| passwordManager.lastCallback.addSavedPasswordListChangedListener( |
| passwordList); |
| flush(); |
| validatePasswordList(passwordsSection, passwordList); |
| |
| // Simulate '(website.com, luigi)' being removed from the list as well. |
| passwordList = []; |
| passwordManager.lastCallback.addSavedPasswordListChangedListener( |
| passwordList); |
| flush(); |
| validatePasswordList(passwordsSection, passwordList); |
| }); |
| |
| // Test verifies that pressing the 'remove' button will trigger a remove |
| // event. Does not actually remove any passwords. |
| test('verifyPasswordItemRemoveButton', async function() { |
| const passwordList = [ |
| createPasswordEntry({url: 'one', username: 'six', id: 0}), |
| createPasswordEntry({url: 'two', username: 'five', id: 1}), |
| createPasswordEntry({url: 'three', username: 'four', id: 2}), |
| createPasswordEntry({url: 'four', username: 'three', id: 3}), |
| createPasswordEntry({url: 'five', username: 'two', id: 4}), |
| createPasswordEntry({url: 'six', username: 'one', id: 5}), |
| ]; |
| |
| const passwordsSection = elementFactory.createPasswordsSection( |
| passwordManager, passwordList, []); |
| |
| const firstNode = getFirstPasswordListItem(passwordsSection); |
| assertTrue(!!firstNode); |
| const firstPassword = passwordList[0]; |
| |
| // Click the remove button on the first password. |
| firstNode.$.moreActionsButton.click(); |
| passwordsSection.$.passwordsListHandler.$.menuRemovePassword.click(); |
| |
| const id = await passwordManager.whenCalled('removeSavedPassword'); |
| // Verify that the expected value was passed to the proxy. |
| assertEquals(firstPassword.id, id); |
| assertEquals( |
| passwordsSection.i18n('passwordDeleted'), |
| passwordsSection.$.passwordsListHandler.$.removalNotification |
| .textContent); |
| }); |
| |
| // Test verifies that 'Copy password' button is hidden for Federated |
| // (passwordless) credentials. Does not test Copy button. |
| test('verifyCopyAbsentForFederatedPasswordInMenu', function() { |
| const passwordList = [ |
| createPasswordEntry({federationText: 'with chromium.org'}), |
| ]; |
| |
| const passwordsSection = elementFactory.createPasswordsSection( |
| passwordManager, passwordList, []); |
| |
| getFirstPasswordListItem(passwordsSection).$.moreActionsButton.click(); |
| flush(); |
| assertTrue( |
| passwordsSection.$.passwordsListHandler.$$('#menuCopyPassword').hidden); |
| }); |
| |
| // Test verifies that 'Copy password' button is not hidden for common |
| // credentials. Does not test Copy button. |
| test('verifyCopyPresentInMenu', function() { |
| const passwordList = [ |
| createPasswordEntry({url: 'one.com', username: 'hey'}), |
| ]; |
| const passwordsSection = elementFactory.createPasswordsSection( |
| passwordManager, passwordList, []); |
| |
| getFirstPasswordListItem(passwordsSection).$.moreActionsButton.click(); |
| flush(); |
| assertFalse( |
| passwordsSection.$.passwordsListHandler.$$('#menuCopyPassword').hidden); |
| }); |
| |
| // Test verifies that 'Edit' button is replaced to 'Details' for Federated |
| // (passwordless) credentials. Does not test Details and Edit button. |
| test('verifyEditReplacedToDetailsForFederatedPasswordInMenu', function() { |
| const passwordList = [ |
| createPasswordEntry({federationText: 'with chromium.org'}), |
| ]; |
| const passwordsSection = elementFactory.createPasswordsSection( |
| passwordManager, passwordList, []); |
| |
| getFirstPasswordListItem(passwordsSection).$.moreActionsButton.click(); |
| flush(); |
| assertEquals( |
| passwordsSection.i18n('passwordViewDetails'), |
| passwordsSection.$.passwordsListHandler.$$('#menuEditPassword') |
| .textContent.trim()); |
| }); |
| |
| // Test verifies that 'Edit' button is replaced to 'Details' for Federated |
| // (passwordless) credentials when EditPasswordsInSettings flag is enabled. |
| // Does not test Details and Edit button. |
| test( |
| 'verifyDetailsForFederatedPasswordInMenuEnabledEditPasswordsInSettings', |
| function() { |
| const passwordList = [ |
| createPasswordEntry({federationText: 'with chromium.org'}), |
| ]; |
| loadTimeData.overrideValues({editPasswordsInSettings: true}); |
| const passwordsSection = elementFactory.createPasswordsSection( |
| passwordManager, passwordList, []); |
| |
| getFirstPasswordListItem(passwordsSection).$.moreActionsButton.click(); |
| flush(); |
| assertEquals( |
| passwordsSection.i18n('passwordViewDetails'), |
| passwordsSection.$.passwordsListHandler.$$('#menuEditPassword') |
| .textContent.trim()); |
| }); |
| |
| // Test verifies that 'Edit' button is shown instead of 'Details' for |
| // common credentials when the flag editPasswordsInSettings is enabled. |
| // Does not test Details and Edit button. |
| test('verifyEditButtonInMenuEnabledEditPasswordsInSettings', function() { |
| const passwordList = [ |
| createPasswordEntry({url: 'one.com', username: 'hey'}), |
| ]; |
| loadTimeData.overrideValues({editPasswordsInSettings: true}); |
| const passwordsSection = elementFactory.createPasswordsSection( |
| passwordManager, passwordList, []); |
| |
| getFirstPasswordListItem(passwordsSection).$.moreActionsButton.click(); |
| flush(); |
| assertEquals( |
| passwordsSection.i18n('editPassword'), |
| passwordsSection.$.passwordsListHandler.$$('#menuEditPassword') |
| .textContent.trim()); |
| }); |
| |
| // Test verifies that 'Details' button is shown instead of 'Edit' for |
| // non-federated credentials when the flag editPasswordsInSettings is |
| // disabled. Does not test Details and Edit button. |
| test('verifyDetailsButtonInMenuDisabledEditPasswordsInSettings', function() { |
| const passwordList = [ |
| createPasswordEntry({url: 'one.com', username: 'hey'}), |
| ]; |
| loadTimeData.overrideValues({editPasswordsInSettings: false}); |
| const passwordsSection = elementFactory.createPasswordsSection( |
| passwordManager, passwordList, []); |
| |
| getFirstPasswordListItem(passwordsSection).$.moreActionsButton.click(); |
| flush(); |
| assertEquals( |
| passwordsSection.i18n('passwordViewDetails'), |
| passwordsSection.$.passwordsListHandler.$$('#menuEditPassword') |
| .textContent.trim()); |
| }); |
| |
| test('verifyFilterPasswords', function() { |
| const passwordList = [ |
| createPasswordEntry({url: 'one.com', username: 'SHOW', id: 0}), |
| createPasswordEntry({url: 'two.com', username: 'shower', id: 1}), |
| createPasswordEntry({url: 'three.com/show', username: 'four', id: 2}), |
| createPasswordEntry({url: 'four.com', username: 'three', id: 3}), |
| createPasswordEntry({url: 'five.com', username: 'two', id: 4}), |
| createPasswordEntry({url: 'six-show.com', username: 'one', id: 5}), |
| ]; |
| |
| const passwordsSection = elementFactory.createPasswordsSection( |
| passwordManager, passwordList, []); |
| passwordsSection.filter = 'SHow'; |
| flush(); |
| |
| const expectedList = [ |
| createPasswordEntry({url: 'one.com', username: 'SHOW', id: 0}), |
| createPasswordEntry({url: 'two.com', username: 'shower', id: 1}), |
| createPasswordEntry({url: 'three.com/show', username: 'four', id: 2}), |
| createPasswordEntry({url: 'six-show.com', username: 'one', id: 5}), |
| ]; |
| |
| validatePasswordList(passwordsSection, expectedList); |
| }); |
| |
| test('verifyFilterPasswordsWithRemoval', function() { |
| const passwordList = [ |
| createPasswordEntry({url: 'one.com', username: 'SHOW', id: 0}), |
| createPasswordEntry({url: 'two.com', username: 'shower', id: 1}), |
| createPasswordEntry({url: 'three.com/show', username: 'four', id: 2}), |
| createPasswordEntry({url: 'four.com', username: 'three', id: 3}), |
| createPasswordEntry({url: 'five.com', username: 'two', id: 4}), |
| createPasswordEntry({url: 'six-show.com', username: 'one', id: 5}), |
| ]; |
| |
| const passwordsSection = elementFactory.createPasswordsSection( |
| passwordManager, passwordList, []); |
| passwordsSection.filter = 'SHow'; |
| flush(); |
| |
| let expectedList = [ |
| createPasswordEntry({url: 'one.com', username: 'SHOW', id: 0}), |
| createPasswordEntry({url: 'two.com', username: 'shower', id: 1}), |
| createPasswordEntry({url: 'three.com/show', username: 'four', id: 2}), |
| createPasswordEntry({url: 'six-show.com', username: 'one', id: 5}), |
| ]; |
| |
| validatePasswordList(passwordsSection, expectedList); |
| |
| // Simulate removal of three.com/show |
| passwordList.splice(2, 1); |
| flush(); |
| |
| expectedList = [ |
| createPasswordEntry({url: 'one.com', username: 'SHOW', id: 0}), |
| createPasswordEntry({url: 'two.com', username: 'shower', id: 1}), |
| createPasswordEntry({url: 'six-show.com', username: 'one', id: 5}), |
| ]; |
| |
| passwordManager.lastCallback.addSavedPasswordListChangedListener( |
| passwordList); |
| flush(); |
| validatePasswordList(passwordsSection, expectedList); |
| }); |
| |
| test('verifyFilterPasswordExceptions', function() { |
| const exceptionList = [ |
| createExceptionEntry({url: 'docsshoW.google.com', id: 0}), |
| createExceptionEntry({url: 'showmail.com', id: 1}), |
| createExceptionEntry({url: 'google.com', id: 2}), |
| createExceptionEntry({url: 'inbox.google.com', id: 3}), |
| createExceptionEntry({url: 'mapsshow.google.com', id: 4}), |
| createExceptionEntry({url: 'plus.google.comshow', id: 5}), |
| ]; |
| |
| const passwordsSection = elementFactory.createPasswordsSection( |
| passwordManager, [], exceptionList); |
| passwordsSection.filter = 'shOW'; |
| flush(); |
| |
| const expectedExceptionList = [ |
| createExceptionEntry({url: 'docsshoW.google.com', id: 0}), |
| createExceptionEntry({url: 'showmail.com', id: 1}), |
| createExceptionEntry({url: 'mapsshow.google.com', id: 4}), |
| createExceptionEntry({url: 'plus.google.comshow', id: 5}), |
| ]; |
| |
| validateExceptionList( |
| getDomRepeatChildren(passwordsSection.$.passwordExceptionsList), |
| expectedExceptionList); |
| }); |
| |
| test('verifyNoPasswordExceptions', function() { |
| const passwordsSection = |
| elementFactory.createPasswordsSection(passwordManager, [], []); |
| |
| validateExceptionList( |
| getDomRepeatChildren(passwordsSection.$.passwordExceptionsList), []); |
| |
| assertFalse(passwordsSection.$.noExceptionsLabel.hidden); |
| }); |
| |
| test('verifyPasswordExceptions', function() { |
| const exceptionList = [ |
| createExceptionEntry({url: 'docs.google.com', id: 0}), |
| createExceptionEntry({url: 'mail.com', id: 1}), |
| createExceptionEntry({url: 'google.com', id: 2}), |
| createExceptionEntry({url: 'inbox.google.com', id: 3}), |
| createExceptionEntry({url: 'maps.google.com', id: 4}), |
| createExceptionEntry({url: 'plus.google.com', id: 5}), |
| ]; |
| |
| const passwordsSection = elementFactory.createPasswordsSection( |
| passwordManager, [], exceptionList); |
| |
| validateExceptionList( |
| getDomRepeatChildren(passwordsSection.$.passwordExceptionsList), |
| exceptionList); |
| |
| assertTrue(passwordsSection.$.noExceptionsLabel.hidden); |
| }); |
| |
| // Test verifies that exceptions duplicated across stores get properly merged |
| // in the UI. |
| test('verifyPasswordExceptionsWithMultiStore', function() { |
| // Entries with duplicates. |
| const accountException1 = createExceptionEntry( |
| {url: '1.com', frontendId: 1, id: 10, fromAccountStore: true}); |
| const deviceException1 = createExceptionEntry( |
| {url: '1.com', frontendId: 1, id: 11, fromAccountStore: false}); |
| const accountException2 = createExceptionEntry( |
| {url: '2.com', frontendId: 2, id: 20, fromAccountStore: true}); |
| const deviceException2 = createExceptionEntry( |
| {url: '2.com', frontendId: 2, id: 21, fromAccountStore: false}); |
| // Entries without duplicate. |
| const deviceException3 = createExceptionEntry( |
| {url: '3.com', frontendId: 3, id: 3, fromAccountStore: false}); |
| const accountException4 = createExceptionEntry( |
| {url: '4.com', frontendId: 4, id: 4, fromAccountStore: true}); |
| |
| // Shuffle entries a little. |
| const passwordsSection = |
| elementFactory.createPasswordsSection(passwordManager, [], [ |
| deviceException3, accountException1, deviceException2, |
| accountException4, deviceException1, accountException2 |
| ]); |
| |
| // Expected list keeping relative order. |
| const expectedList = [ |
| createMultiStoreExceptionEntry({url: '3.com', deviceId: 3}), |
| createMultiStoreExceptionEntry( |
| {url: '1.com', accountId: 10, deviceId: 11}), |
| createMultiStoreExceptionEntry( |
| {url: '2.com', accountId: 20, deviceId: 21}), |
| createMultiStoreExceptionEntry({url: '4.com', accountId: 4}), |
| ]; |
| |
| validateMultiStoreExceptionList( |
| getDomRepeatChildren(passwordsSection.$.passwordExceptionsList), |
| expectedList); |
| |
| assertTrue(passwordsSection.$.noExceptionsLabel.hidden); |
| }); |
| |
| // Test verifies that removing an exception will update the elements. |
| test('verifyPasswordExceptionRemove', function() { |
| const exceptionList = [ |
| createExceptionEntry({url: 'docs.google.com', id: 0}), |
| createExceptionEntry({url: 'mail.com', id: 1}), |
| createExceptionEntry({url: 'google.com', id: 2}), |
| createExceptionEntry({url: 'inbox.google.com', id: 3}), |
| createExceptionEntry({url: 'maps.google.com', id: 4}), |
| createExceptionEntry({url: 'plus.google.com', id: 5}), |
| ]; |
| |
| const passwordsSection = elementFactory.createPasswordsSection( |
| passwordManager, [], exceptionList); |
| |
| validateExceptionList( |
| getDomRepeatChildren(passwordsSection.$.passwordExceptionsList), |
| exceptionList); |
| |
| // Simulate 'mail.com' being removed from the list. |
| passwordsSection.splice('passwordExceptions', 1, 1); |
| flush(); |
| assertFalse(exceptionsListContainsUrl( |
| passwordsSection.passwordExceptions, 'mail.com')); |
| assertFalse(exceptionsListContainsUrl(exceptionList, 'mail.com')); |
| |
| const expectedExceptionList = [ |
| createExceptionEntry({url: 'docs.google.com', id: 0}), |
| createExceptionEntry({url: 'google.com', id: 2}), |
| createExceptionEntry({url: 'inbox.google.com', id: 3}), |
| createExceptionEntry({url: 'maps.google.com', id: 4}), |
| createExceptionEntry({url: 'plus.google.com', id: 5}) |
| ]; |
| validateExceptionList( |
| getDomRepeatChildren(passwordsSection.$.passwordExceptionsList), |
| expectedExceptionList); |
| }); |
| |
| // Test verifies that pressing the 'remove' button will trigger a remove |
| // event. Does not actually remove any exceptions. |
| test('verifyPasswordExceptionRemoveButton', function() { |
| const exceptionList = [ |
| createExceptionEntry({url: 'docs.google.com', id: 0}), |
| createExceptionEntry({url: 'mail.com', id: 1}), |
| createExceptionEntry({url: 'google.com', id: 2}), |
| createExceptionEntry({url: 'inbox.google.com', id: 3}), |
| createExceptionEntry({url: 'maps.google.com', id: 4}), |
| createExceptionEntry({url: 'plus.google.com', id: 5}), |
| ]; |
| |
| const passwordsSection = elementFactory.createPasswordsSection( |
| passwordManager, [], exceptionList); |
| |
| const exceptions = |
| getDomRepeatChildren(passwordsSection.$.passwordExceptionsList); |
| |
| // The index of the button currently being checked. |
| let item = 0; |
| |
| const clickRemoveButton = function() { |
| exceptions[item].querySelector('#removeExceptionButton').click(); |
| }; |
| |
| // Removes the next exception item, verifies that the expected method was |
| // called on |passwordManager| and continues recursively until no more items |
| // exist. |
| function removeNextRecursive() { |
| passwordManager.resetResolver('removeExceptions'); |
| clickRemoveButton(); |
| return passwordManager.whenCalled('removeExceptions').then(ids => { |
| // Verify that the event matches the expected value. |
| assertTrue(item < exceptionList.length); |
| assertDeepEquals(ids, [exceptionList[item].id]); |
| |
| if (++item < exceptionList.length) { |
| return removeNextRecursive(); |
| } |
| }); |
| } |
| |
| // Click 'remove' on all passwords, one by one. |
| return removeNextRecursive(); |
| }); |
| |
| // Test verifies that pressing the 'remove' button for a duplicated exception |
| // will remove both the device and account copies. |
| test('verifyDuplicatedExceptionRemoveButton', async function() { |
| // Create a duplicated exception that will be merged into a single entry. |
| const deviceCopy = |
| createPasswordEntry({frontendId: 42, id: 0, fromAccountStore: false}); |
| const accountCopy = |
| createPasswordEntry({frontendId: 42, id: 1, fromAccountStore: true}); |
| |
| const passwordsSection = elementFactory.createPasswordsSection( |
| passwordManager, [], [deviceCopy, accountCopy]); |
| |
| const [mergedEntry] = |
| getDomRepeatChildren(passwordsSection.$.passwordExceptionsList); |
| mergedEntry.querySelector('#removeExceptionButton').click(); |
| |
| // Verify both ids get passed to the proxy. |
| const ids = await passwordManager.whenCalled('removeExceptions'); |
| assertTrue(ids.includes(deviceCopy.id)); |
| assertTrue(ids.includes(accountCopy.id)); |
| }); |
| |
| test('verifyFederatedPassword', function() { |
| const federationEntry = createMultiStorePasswordEntry( |
| {federationText: 'with chromium.org', username: 'bart', deviceId: 42}); |
| const passwordDialog = |
| elementFactory.createPasswordEditDialog(federationEntry); |
| |
| assertEquals( |
| federationEntry.federationText, passwordDialog.$.passwordInput.value); |
| // Text should be readable. |
| assertEquals('text', passwordDialog.$.passwordInput.type); |
| assertTrue(passwordDialog.$.showPasswordButton.hidden); |
| detailsDialogPartsAreShownCorrectly(passwordDialog); |
| }); |
| |
| test('verifyDetailsDialogDisabledEditPasswordsInSettings', function() { |
| const federationEntry = createMultiStorePasswordEntry( |
| {federationText: 'with chromium.org', username: 'bart', deviceId: 42}); |
| loadTimeData.overrideValues({editPasswordsInSettings: false}); |
| const passwordDialogFederation = |
| elementFactory.createPasswordEditDialog(federationEntry); |
| detailsDialogPartsAreShownCorrectly(passwordDialogFederation); |
| |
| const commonEntry = createMultiStorePasswordEntry( |
| {url: 'goo.gl', username: 'bart', accountId: 42}); |
| const passwordDialogCommon = |
| elementFactory.createPasswordEditDialog(commonEntry); |
| detailsDialogPartsAreShownCorrectly(passwordDialogCommon); |
| }); |
| |
| test('verifyEditOrDetailsDialogEnabledEditPasswordsInSettings', function() { |
| const federationEntry = createMultiStorePasswordEntry( |
| {federationText: 'with chromium.org', username: 'bart', deviceId: 42}); |
| loadTimeData.overrideValues({editPasswordsInSettings: true}); |
| const passwordDialogFederation = |
| elementFactory.createPasswordEditDialog(federationEntry); |
| detailsDialogPartsAreShownCorrectly(passwordDialogFederation); |
| |
| const commonEntry = createMultiStorePasswordEntry( |
| {url: 'goo.gl', username: 'bart', accountId: 42}); |
| const passwordDialogCommon = |
| elementFactory.createPasswordEditDialog(commonEntry); |
| // Should show edit dialog for common credetial when editPasswordsInSettings |
| // flag is enabled. |
| editDialogPartsAreShownCorrectly(passwordDialogCommon); |
| }); |
| |
| test('editDialogChangePassword', async function() { |
| loadTimeData.overrideValues({editPasswordsInSettings: true}); |
| |
| const PASSWORD1 = 'hello_world'; |
| const commonEntry = createMultiStorePasswordEntry( |
| {url: 'goo.gl', username: 'bart', accountId: 42}); |
| |
| const editDialog = elementFactory.createPasswordEditDialog(commonEntry); |
| |
| editDialog.password = PASSWORD1; |
| flush(); |
| |
| assertEquals(PASSWORD1, editDialog.$.passwordInput.value); |
| |
| // Empty password should be consider invalid and disables the save button. |
| editDialog.$.passwordInput.value = ''; |
| assertTrue(editDialog.$.passwordInput.invalid); |
| assertTrue(editDialog.$.actionButton.disabled); |
| |
| const PASSWORD2 = 'hello_world_2'; |
| editDialog.$.passwordInput.value = PASSWORD2; |
| assertFalse(editDialog.$.passwordInput.invalid); |
| assertFalse(editDialog.$.actionButton.disabled); |
| |
| editDialog.$.actionButton.click(); |
| |
| // Check that the changeSavedPassword is called with the right arguments. |
| const {newPassword} = |
| await passwordManager.whenCalled('changeSavedPassword'); |
| assertEquals(PASSWORD2, newPassword); |
| }); |
| |
| // Test verifies that the edit dialog informs the password is stored in the |
| // account. |
| test('verifyStorageDetailsInEditDialogForAccountPassword', function() { |
| const accountPassword = createMultiStorePasswordEntry( |
| {url: 'goo.gl', username: 'bart', accountId: 42}); |
| const accountPasswordDialog = |
| elementFactory.createPasswordEditDialog(accountPassword); |
| |
| // By default no message is displayed. |
| assertTrue(accountPasswordDialog.$.storageDetails.hidden); |
| |
| // Display the message. |
| accountPasswordDialog.shouldShowStorageDetails = true; |
| flush(); |
| assertFalse(accountPasswordDialog.$.storageDetails.hidden); |
| assertEquals( |
| accountPasswordDialog.i18n('passwordStoredInAccount'), |
| accountPasswordDialog.$.storageDetails.innerText); |
| }); |
| |
| // Test verifies that the edit dialog informs the password is stored on the |
| // device. |
| test('verifyStorageDetailsInEditDialogForDevicePassword', function() { |
| const devicePassword = createMultiStorePasswordEntry( |
| {url: 'goo.gl', username: 'bart', deviceId: 42}); |
| const devicePasswordDialog = |
| elementFactory.createPasswordEditDialog(devicePassword); |
| |
| // By default no message is displayed. |
| assertTrue(devicePasswordDialog.$.storageDetails.hidden); |
| |
| // Display the message. |
| devicePasswordDialog.shouldShowStorageDetails = true; |
| flush(); |
| assertFalse(devicePasswordDialog.$.storageDetails.hidden); |
| assertEquals( |
| devicePasswordDialog.i18n('passwordStoredOnDevice'), |
| devicePasswordDialog.$.storageDetails.innerText); |
| }); |
| |
| // Test verifies that the edit dialog informs the password is stored both on |
| // the device and in the account. |
| test( |
| 'verifyStorageDetailsInEditDialogForPasswordInBothLocations', function() { |
| const accountAndDevicePassword = createMultiStorePasswordEntry( |
| {url: 'goo.gl', username: 'bart', deviceId: 42, accountId: 43}); |
| const accountAndDevicePasswordDialog = |
| elementFactory.createPasswordEditDialog(accountAndDevicePassword); |
| |
| // By default no message is displayed. |
| assertTrue(accountAndDevicePasswordDialog.$.storageDetails.hidden); |
| |
| // Display the message. |
| accountAndDevicePasswordDialog.shouldShowStorageDetails = true; |
| flush(); |
| assertFalse(accountAndDevicePasswordDialog.$.storageDetails.hidden); |
| assertEquals( |
| accountAndDevicePasswordDialog.i18n( |
| 'passwordStoredInAccountAndOnDevice'), |
| accountAndDevicePasswordDialog.$.storageDetails.innerText); |
| }); |
| |
| test('showSavedPasswordEditDialog', function() { |
| const PASSWORD = 'bAn@n@5'; |
| const item = createMultiStorePasswordEntry( |
| {url: 'goo.gl', username: 'bart', deviceId: 42}); |
| const passwordDialog = elementFactory.createPasswordEditDialog(item); |
| |
| assertFalse(passwordDialog.$.showPasswordButton.hidden); |
| |
| passwordDialog.password = PASSWORD; |
| flush(); |
| |
| assertEquals(PASSWORD, passwordDialog.$.passwordInput.value); |
| // Password should be visible. |
| assertEquals('text', passwordDialog.$.passwordInput.type); |
| assertFalse(passwordDialog.$.showPasswordButton.hidden); |
| }); |
| |
| test('showSavedPasswordListItem', function() { |
| const PASSWORD = 'bAn@n@5'; |
| const item = createPasswordEntry({url: 'goo.gl', username: 'bart'}); |
| const passwordListItem = elementFactory.createPasswordListItem(item); |
| // Hidden passwords should be disabled. |
| assertTrue(passwordListItem.$$('#password').disabled); |
| |
| passwordListItem.password = PASSWORD; |
| flush(); |
| |
| assertEquals(PASSWORD, passwordListItem.$$('#password').value); |
| // Password should be visible. |
| assertEquals('text', passwordListItem.$$('#password').type); |
| // Visible passwords should not be disabled. |
| assertFalse(passwordListItem.$$('#password').disabled); |
| |
| // Hide Password Button should be shown. |
| assertTrue(passwordListItem.$$('#showPasswordButton') |
| .classList.contains('icon-visibility-off')); |
| }); |
| |
| // Tests that invoking the plaintext password sets the corresponding |
| // password. |
| test('onShowSavedPasswordEditDialog', function() { |
| const expectedItem = createMultiStorePasswordEntry( |
| {url: 'goo.gl', username: 'bart', deviceId: 1}); |
| const passwordDialog = |
| elementFactory.createPasswordEditDialog(expectedItem); |
| assertEquals('', passwordDialog.password); |
| |
| passwordManager.setPlaintextPassword('password'); |
| passwordDialog.$.showPasswordButton.click(); |
| return passwordManager.whenCalled('requestPlaintextPassword') |
| .then(({id, reason}) => { |
| assertEquals(1, id); |
| assertEquals('VIEW', reason); |
| assertEquals('password', passwordDialog.password); |
| }); |
| }); |
| |
| test('onShowSavedPasswordListItem', function() { |
| const expectedItem = |
| createPasswordEntry({url: 'goo.gl', username: 'bart', id: 1}); |
| const passwordListItem = |
| elementFactory.createPasswordListItem(expectedItem); |
| assertEquals('', passwordListItem.password); |
| |
| passwordManager.setPlaintextPassword('password'); |
| passwordListItem.$$('#showPasswordButton').click(); |
| return passwordManager.whenCalled('requestPlaintextPassword') |
| .then(({id, reason}) => { |
| assertEquals(1, id); |
| assertEquals('VIEW', reason); |
| assertEquals('password', passwordListItem.password); |
| }); |
| }); |
| |
| test('onCopyPasswordListItem', function() { |
| const expectedItem = |
| createPasswordEntry({url: 'goo.gl', username: 'bart', id: 1}); |
| const passwordsSection = elementFactory.createPasswordsSection( |
| passwordManager, [expectedItem], []); |
| |
| getFirstPasswordListItem(passwordsSection).$.moreActionsButton.click(); |
| passwordsSection.$.passwordsListHandler.$$('#menuCopyPassword').click(); |
| |
| return passwordManager.whenCalled('requestPlaintextPassword') |
| .then(({id, reason}) => { |
| assertEquals(1, id); |
| assertEquals('COPY', reason); |
| }); |
| }); |
| |
| test('closingPasswordsSectionHidesUndoToast', function() { |
| const passwordEntry = |
| createPasswordEntry({url: 'goo.gl', username: 'bart'}); |
| const passwordsSection = elementFactory.createPasswordsSection( |
| passwordManager, [passwordEntry], []); |
| const toastManager = passwordsSection.$.passwordsListHandler.$.toast; |
| |
| // Click the remove button on the first password and assert that an undo |
| // toast is shown. |
| getFirstPasswordListItem(passwordsSection).$.moreActionsButton.click(); |
| passwordsSection.$.passwordsListHandler.$.menuRemovePassword.click(); |
| flush(); |
| assertTrue(toastManager.open); |
| |
| // Remove the passwords section from the DOM and check that this closes |
| // the undo toast. |
| document.body.removeChild(passwordsSection); |
| flush(); |
| assertFalse(toastManager.open); |
| }); |
| |
| // Chrome offers the export option when there are passwords. |
| test('offerExportWhenPasswords', function() { |
| const passwordList = [ |
| createPasswordEntry({url: 'googoo.com', username: 'Larry'}), |
| ]; |
| const passwordsSection = elementFactory.createPasswordsSection( |
| passwordManager, passwordList, []); |
| |
| validatePasswordList(passwordsSection, passwordList); |
| assertFalse(passwordsSection.$.menuExportPassword.hidden); |
| }); |
| |
| // Chrome shouldn't offer the option to export passwords if there are no |
| // passwords. |
| test('noExportIfNoPasswords', function() { |
| const passwordList = []; |
| const passwordsSection = elementFactory.createPasswordsSection( |
| passwordManager, passwordList, []); |
| |
| validatePasswordList(passwordsSection, passwordList); |
| assertTrue(passwordsSection.$.menuExportPassword.hidden); |
| }); |
| |
| // Test that clicking the Export Passwords menu item opens the export |
| // dialog. |
| test('exportOpen', function(done) { |
| const passwordList = [ |
| createPasswordEntry({url: 'googoo.com', username: 'Larry'}), |
| ]; |
| const passwordsSection = elementFactory.createPasswordsSection( |
| passwordManager, passwordList, []); |
| |
| // The export dialog calls requestExportProgressStatus() when opening. |
| passwordManager.requestExportProgressStatus = (callback) => { |
| callback(chrome.passwordsPrivate.ExportProgressStatus.NOT_STARTED); |
| done(); |
| }; |
| passwordManager.addPasswordsFileExportProgressListener = () => {}; |
| passwordsSection.$.menuExportPassword.click(); |
| }); |
| |
| if (!isChromeOS) { |
| // Test that tapping "Export passwords..." notifies the browser. |
| test('startExport', function(done) { |
| const exportDialog = |
| elementFactory.createExportPasswordsDialog(passwordManager); |
| runStartExportTest(exportDialog, passwordManager, done); |
| }); |
| |
| // Test the export flow. If exporting is fast, we should skip the |
| // in-progress view altogether. |
| test('exportFlowFast', function(done) { |
| const exportDialog = |
| elementFactory.createExportPasswordsDialog(passwordManager); |
| runExportFlowFastTest(exportDialog, passwordManager, done); |
| }); |
| |
| // The error view is shown when an error occurs. |
| test('exportFlowError', function(done) { |
| const exportDialog = |
| elementFactory.createExportPasswordsDialog(passwordManager); |
| runExportFlowErrorTest(exportDialog, passwordManager, done); |
| }); |
| |
| // The error view allows to retry. |
| test('exportFlowErrorRetry', function(done) { |
| const exportDialog = |
| elementFactory.createExportPasswordsDialog(passwordManager); |
| runExportFlowErrorRetryTest(exportDialog, passwordManager, done); |
| }); |
| |
| // Test the export flow. If exporting is slow, Chrome should show the |
| // in-progress dialog for at least 1000ms. |
| test('exportFlowSlow', function(done) { |
| const exportDialog = |
| elementFactory.createExportPasswordsDialog(passwordManager); |
| runExportFlowSlowTest(exportDialog, passwordManager, done); |
| }); |
| |
| // Test that canceling the dialog while exporting will also cancel the |
| // export on the browser. |
| test('cancelExport', function(done) { |
| const exportDialog = |
| elementFactory.createExportPasswordsDialog(passwordManager); |
| runCancelExportTest(exportDialog, passwordManager, done); |
| }); |
| |
| test('fires close event after export complete', () => { |
| const exportDialog = |
| elementFactory.createExportPasswordsDialog(passwordManager); |
| return runFireCloseEventAfterExportCompleteTest( |
| exportDialog, passwordManager); |
| }); |
| |
| // Test verifies that the overflow menu does not offer an option to move a |
| // password to the account. |
| test('noMoveToAccountOption', function() { |
| const passwordsSection = |
| elementFactory.createPasswordsSection(passwordManager, [], []); |
| assertFalse(!!passwordsSection.$.passwordsListHandler.$$( |
| '#menuMovePasswordToAccount')); |
| }); |
| |
| // Tests that the opt-in/opt-out buttons appear for signed-in (non-sync) |
| // users and that the text content changes accordingly. |
| test('changeOptInButtonsBasedOnSignInAndAccountStorageOptIn', function() { |
| // Feature flag enabled. |
| loadTimeData.overrideValues({enableAccountStorage: true}); |
| |
| const passwordsSection = |
| elementFactory.createPasswordsSection(passwordManager, [], []); |
| |
| // Sync is disabled and the user is initially signed out. |
| simulateSyncStatus({signedIn: false}); |
| const isDisplayed = element => !!element && !element.hidden; |
| assertFalse( |
| isDisplayed(passwordsSection.$.accountStorageButtonsContainer)); |
| |
| // User signs in but is not opted in yet. |
| simulateStoredAccounts([{email: 'john@gmail.com'}]); |
| passwordManager.setIsOptedInForAccountStorageAndNotify(false); |
| flush(); |
| assertTrue( |
| isDisplayed(passwordsSection.$.accountStorageButtonsContainer)); |
| assertTrue(isDisplayed(passwordsSection.$.optInToAccountStorageButton)); |
| assertFalse(isDisplayed(passwordsSection.$.optOutOfAccountStorageButton)); |
| assertTrue(isDisplayed(passwordsSection.$.accountStorageOptInBody)); |
| assertFalse(isDisplayed(passwordsSection.$.accountStorageOptOutBody)); |
| |
| // Opt in. |
| passwordManager.setIsOptedInForAccountStorageAndNotify(true); |
| flush(); |
| assertTrue( |
| isDisplayed(passwordsSection.$.accountStorageButtonsContainer)); |
| assertFalse(isDisplayed(passwordsSection.$.optInToAccountStorageButton)); |
| assertTrue(isDisplayed(passwordsSection.$.optOutOfAccountStorageButton)); |
| assertTrue(isDisplayed(passwordsSection.$.accountStorageOptOutBody)); |
| assertFalse(isDisplayed(passwordsSection.$.accountStorageOptInBody)); |
| assertEquals('john@gmail.com', passwordsSection.$.accountEmail.innerText); |
| |
| // Sign out |
| simulateStoredAccounts([]); |
| assertFalse( |
| isDisplayed(passwordsSection.$.accountStorageButtonsContainer)); |
| }); |
| |
| // Test verifies the the account storage buttons are not shown for custom |
| // passphrase users. |
| test('accountStorageButonsNotShownForCustomPassphraseUser', function() { |
| loadTimeData.overrideValues({enableAccountStorage: true}); |
| |
| const passwordsSection = |
| elementFactory.createPasswordsSection(passwordManager, [], []); |
| |
| simulateSyncStatus({signedIn: false}); |
| simulateStoredAccounts([{email: 'john@gmail.com'}]); |
| // Simulate custom passphrase. |
| const syncPrefs = getSyncAllPrefs(); |
| syncPrefs.encryptAllData = true; |
| webUIListenerCallback('sync-prefs-changed', syncPrefs); |
| flush(); |
| |
| assertTrue( |
| !passwordsSection.$.accountStorageButtonsContainer || |
| passwordsSection.$.accountStorageButtonsContainer.hidden); |
| }); |
| |
| // Test verifies that enabling sync hides the buttons for account storage |
| // opt-in/out and the 'device passwords' page. |
| test('enablingSyncHidesAccountStorageButtons', function() { |
| // Feature flag enabled. |
| loadTimeData.overrideValues({enableAccountStorage: true}); |
| |
| const passwordsSection = |
| elementFactory.createPasswordsSection(passwordManager, [], []); |
| |
| simulateAccountStorageUser(passwordManager); |
| const isDisplayed = element => !!element && !element.hidden; |
| assertTrue( |
| isDisplayed(passwordsSection.$.accountStorageButtonsContainer)); |
| |
| // Enable sync. |
| simulateSyncStatus({signedIn: true}); |
| assertFalse( |
| isDisplayed(passwordsSection.$.accountStorageButtonsContainer)); |
| }); |
| |
| // Test verifies that the button linking to the 'device passwords' page is |
| // only visible when there is at least one device password, and that it has |
| // the appropriate text. |
| test('verifyDevicePasswordsButtonVisibility', function() { |
| // Set up user eligible to the account-scoped password storage, not |
| // opted in and with no device passwords. Button should be hidden. |
| loadTimeData.overrideValues({enableAccountStorage: true}); |
| const passwordList = |
| [createPasswordEntry({fromAccountStore: true, id: 10})]; |
| const passwordsSection = elementFactory.createPasswordsSection( |
| passwordManager, passwordList, []); |
| simulateSyncStatus({signedIn: false}); |
| simulateStoredAccounts([{email: 'john@gmail.com'}]); |
| assertTrue(passwordsSection.$.devicePasswordsLink.hidden); |
| |
| // Opting in still doesn't display it because the user has no device |
| // passwords yet. |
| passwordManager.setIsOptedInForAccountStorageAndNotify(true); |
| flush(); |
| assertTrue(passwordsSection.$.devicePasswordsLink.hidden); |
| |
| // Add a device password. The button shows up, with the text in singular |
| // form. |
| passwordList.unshift( |
| createPasswordEntry({fromAccountStore: false, id: 20})); |
| passwordManager.lastCallback.addSavedPasswordListChangedListener( |
| passwordList); |
| flush(); |
| assertFalse(passwordsSection.$.devicePasswordsLink.hidden); |
| assertEquals( |
| passwordsSection.i18n('devicePasswordsLinkLabelSingular'), |
| passwordsSection.$.devicePasswordsLinkLabel.innerText); |
| |
| // Add a second device password. The text nows says '2 passwords'. |
| passwordList.unshift( |
| createPasswordEntry({fromAccountStore: false, id: 30})); |
| passwordManager.lastCallback.addSavedPasswordListChangedListener( |
| passwordList); |
| flush(); |
| assertFalse(passwordsSection.$.devicePasswordsLink.hidden); |
| assertEquals( |
| passwordsSection.i18n('devicePasswordsLinkLabelPlural', 2), |
| passwordsSection.$.devicePasswordsLinkLabel.innerText); |
| }); |
| |
| // Test verifies that, for account-scoped password storage users, removing |
| // a password stored in a single location indicates the location in the |
| // toast manager message. |
| test( |
| 'passwordRemovalMessageSpecifiesStoreForAccountStorageUsers', |
| function() { |
| loadTimeData.overrideValues({enableAccountStorage: true}); |
| |
| const passwordList = [ |
| createPasswordEntry( |
| {username: 'account', id: 0, fromAccountStore: true}), |
| createPasswordEntry( |
| {username: 'local', id: 1, fromAccountStore: false}), |
| ]; |
| const passwordsSection = elementFactory.createPasswordsSection( |
| passwordManager, passwordList, []); |
| |
| simulateAccountStorageUser(passwordManager); |
| |
| // No removal actually happens, so all passwords keep their position. |
| const passwordListItems = |
| passwordsSection.root.querySelectorAll('password-list-item'); |
| passwordListItems[0].$.moreActionsButton.click(); |
| passwordsSection.$.passwordsListHandler.$.menuRemovePassword.click(); |
| flush(); |
| assertEquals( |
| passwordsSection.i18n('passwordDeletedFromAccount'), |
| passwordsSection.$.passwordsListHandler.$.removalNotification |
| .textContent); |
| |
| passwordListItems[1].$.moreActionsButton.click(); |
| passwordsSection.$.passwordsListHandler.$.menuRemovePassword.click(); |
| flush(); |
| assertEquals( |
| passwordsSection.i18n('passwordDeletedFromDevice'), |
| passwordsSection.$.passwordsListHandler.$.removalNotification |
| .textContent); |
| }); |
| |
| // Test verifies that if the user attempts to remove a password stored |
| // both on the device and in the account, the PasswordRemoveDialog shows up. |
| // Clicking the button in the dialog then removes both versions of the |
| // password. |
| test('verifyPasswordRemoveDialogRemoveBothCopies', async function() { |
| loadTimeData.overrideValues({enableAccountStorage: true}); |
| |
| const accountCopy = |
| createPasswordEntry({frontendId: 42, id: 0, fromAccountStore: true}); |
| const deviceCopy = |
| createPasswordEntry({frontendId: 42, id: 1, fromAccountStore: false}); |
| const passwordsSection = elementFactory.createPasswordsSection( |
| passwordManager, [accountCopy, deviceCopy], []); |
| |
| simulateAccountStorageUser(passwordManager); |
| |
| // At first the dialog is not shown. |
| assertTrue( |
| !passwordsSection.$.passwordsListHandler.$$('#passwordRemoveDialog')); |
| |
| // Clicking remove in the overflow menu shows the dialog. |
| getFirstPasswordListItem(passwordsSection).$.moreActionsButton.click(); |
| passwordsSection.$.passwordsListHandler.$.menuRemovePassword.click(); |
| flush(); |
| const removeDialog = |
| passwordsSection.$.passwordsListHandler.$$('#passwordRemoveDialog'); |
| assertTrue(!!removeDialog); |
| |
| // Both checkboxes are selected by default. Confirming removes from both |
| // account and device. |
| assertTrue( |
| removeDialog.$.removeFromAccountCheckbox.checked && |
| removeDialog.$.removeFromDeviceCheckbox.checked); |
| removeDialog.$.removeButton.click(); |
| const removedIds = |
| await passwordManager.whenCalled('removeSavedPasswords'); |
| assertTrue(removedIds.includes(accountCopy.id)); |
| assertTrue(removedIds.includes(deviceCopy.id)); |
| }); |
| |
| // Test verifies that if the user attempts to remove a password stored |
| // both on the device and in the account, the PasswordRemoveDialog shows up. |
| // The user then chooses to remove only of the copies. |
| test('verifyPasswordRemoveDialogRemoveSingleCopy', async function() { |
| loadTimeData.overrideValues({enableAccountStorage: true}); |
| |
| const accountCopy = |
| createPasswordEntry({frontendId: 42, id: 0, fromAccountStore: true}); |
| const deviceCopy = |
| createPasswordEntry({frontendId: 42, id: 1, fromAccountStore: false}); |
| const passwordsSection = elementFactory.createPasswordsSection( |
| passwordManager, [accountCopy, deviceCopy], []); |
| |
| simulateAccountStorageUser(passwordManager); |
| |
| // At first the dialog is not shown. |
| assertTrue( |
| !passwordsSection.$.passwordsListHandler.$$('#passwordRemoveDialog')); |
| |
| // Clicking remove in the overflow menu shows the dialog. |
| getFirstPasswordListItem(passwordsSection).$.moreActionsButton.click(); |
| passwordsSection.$.passwordsListHandler.$.menuRemovePassword.click(); |
| flush(); |
| const removeDialog = |
| passwordsSection.$.passwordsListHandler.$$('#passwordRemoveDialog'); |
| assertTrue(!!removeDialog); |
| |
| // Uncheck the account checkboxes then confirm. Only the device copy is |
| // removed. |
| removeDialog.$.removeFromAccountCheckbox.click(); |
| flush(); |
| assertTrue( |
| !removeDialog.$.removeFromAccountCheckbox.checked && |
| removeDialog.$.removeFromDeviceCheckbox.checked); |
| removeDialog.$.removeButton.click(); |
| const removedIds = |
| await passwordManager.whenCalled('removeSavedPasswords'); |
| assertTrue(removedIds.includes(deviceCopy.id)); |
| }); |
| } |
| |
| // The export dialog is dismissable. |
| test('exportDismissable', function() { |
| const exportDialog = |
| elementFactory.createExportPasswordsDialog(passwordManager); |
| |
| assertTrue(exportDialog.$$('#dialog_start').open); |
| exportDialog.$$('#cancelButton').click(); |
| flush(); |
| assertFalse(!!exportDialog.$$('#dialog_start')); |
| }); |
| |
| test('fires close event when canceled', () => { |
| const exportDialog = |
| elementFactory.createExportPasswordsDialog(passwordManager); |
| const wait = eventToPromise('passwords-export-dialog-close', exportDialog); |
| exportDialog.$$('#cancelButton').click(); |
| return wait; |
| }); |
| |
| test('hideLinkToPasswordManagerWhenEncrypted', function() { |
| const passwordsSection = |
| elementFactory.createPasswordsSection(passwordManager, [], []); |
| const syncPrefs = getSyncAllPrefs(); |
| syncPrefs.encryptAllData = true; |
| webUIListenerCallback('sync-prefs-changed', syncPrefs); |
| simulateSyncStatus({signedIn: true}); |
| flush(); |
| assertTrue(passwordsSection.$.manageLink.hidden); |
| }); |
| |
| test('showLinkToPasswordManagerWhenNotEncrypted', function() { |
| const passwordsSection = |
| elementFactory.createPasswordsSection(passwordManager, [], []); |
| const syncPrefs = getSyncAllPrefs(); |
| syncPrefs.encryptAllData = false; |
| webUIListenerCallback('sync-prefs-changed', syncPrefs); |
| flush(); |
| assertFalse(passwordsSection.$.manageLink.hidden); |
| }); |
| |
| test('showLinkToPasswordManagerWhenNotSignedIn', function() { |
| const passwordsSection = |
| elementFactory.createPasswordsSection(passwordManager, [], []); |
| const syncPrefs = getSyncAllPrefs(); |
| simulateSyncStatus({signedIn: false}); |
| webUIListenerCallback('sync-prefs-changed', syncPrefs); |
| flush(); |
| assertFalse(passwordsSection.$.manageLink.hidden); |
| }); |
| |
| test( |
| 'showPasswordCheckBannerWhenNotCheckedBeforeAndSignedInAndHavePasswords', |
| function() { |
| // Suppose no check done initially, non-empty list of passwords, |
| // signed in. |
| assertEquals( |
| passwordManager.data.checkStatus.elapsedTimeSinceLastCheck, |
| undefined); |
| const passwordList = [ |
| createPasswordEntry({url: 'site1.com', username: 'luigi'}), |
| ]; |
| const passwordsSection = elementFactory.createPasswordsSection( |
| passwordManager, passwordList, []); |
| return passwordManager.whenCalled('getPasswordCheckStatus').then(() => { |
| flush(); |
| assertFalse( |
| passwordsSection.$$('#checkPasswordsBannerContainer').hidden); |
| assertFalse(passwordsSection.$$('#checkPasswordsButtonRow').hidden); |
| assertTrue(passwordsSection.$$('#checkPasswordsLinkRow').hidden); |
| }); |
| }); |
| |
| test( |
| 'showPasswordCheckBannerWhenCanceledCheckedBeforeAndSignedInAndHavePasswords', |
| async function() { |
| // Suppose initial check was canceled, non-empty list of passwords, |
| // signed in. |
| assertEquals( |
| passwordManager.data.checkStatus.elapsedTimeSinceLastCheck, |
| undefined); |
| const passwordList = [ |
| createPasswordEntry({url: 'site1.com', username: 'luigi', id: 0}), |
| createPasswordEntry({url: 'site2.com', username: 'luigi', id: 1}), |
| ]; |
| passwordManager.data.checkStatus.state = PasswordCheckState.CANCELED; |
| passwordManager.data.leakedCredentials = [ |
| makeCompromisedCredential('site1.com', 'luigi', 'LEAKED'), |
| ]; |
| pluralString.text = '1 compromised password'; |
| |
| const passwordsSection = elementFactory.createPasswordsSection( |
| passwordManager, passwordList, []); |
| |
| await passwordManager.whenCalled('getCompromisedCredentials'); |
| await pluralString.whenCalled('getPluralString'); |
| |
| flush(); |
| assertTrue( |
| passwordsSection.$$('#checkPasswordsBannerContainer').hidden); |
| assertTrue(passwordsSection.$$('#checkPasswordsButtonRow').hidden); |
| assertFalse(passwordsSection.$$('#checkPasswordsLinkRow').hidden); |
| assertEquals( |
| pluralString.text, |
| passwordsSection.$$('#checkPasswordLeakCount').innerText.trim()); |
| }); |
| |
| test('showPasswordCheckLinkButtonWithoutWarningWhenNotSignedIn', function() { |
| // Suppose no check done initially, non-empty list of passwords, |
| // signed out. |
| assertEquals( |
| passwordManager.data.checkStatus.elapsedTimeSinceLastCheck, undefined); |
| const passwordList = [ |
| createPasswordEntry({url: 'site1.com', username: 'luigi'}), |
| ]; |
| const passwordsSection = elementFactory.createPasswordsSection( |
| passwordManager, passwordList, []); |
| simulateSyncStatus({signedIn: false}); |
| return passwordManager.whenCalled('getPasswordCheckStatus').then(() => { |
| flush(); |
| assertTrue(passwordsSection.$$('#checkPasswordsBannerContainer').hidden); |
| assertTrue(passwordsSection.$$('#checkPasswordsButtonRow').hidden); |
| assertFalse(passwordsSection.$$('#checkPasswordsLinkRow').hidden); |
| }); |
| }); |
| |
| test('showPasswordCheckLinkButtonWithoutWarningWhenNoPasswords', function() { |
| // Suppose no check done initially, empty list of passwords, signed |
| // in. |
| assertEquals( |
| passwordManager.data.checkStatus.elapsedTimeSinceLastCheck, undefined); |
| const passwordsSection = |
| elementFactory.createPasswordsSection(passwordManager, [], []); |
| return passwordManager.whenCalled('getPasswordCheckStatus').then(() => { |
| flush(); |
| assertTrue(passwordsSection.$$('#checkPasswordsBannerContainer').hidden); |
| assertTrue(passwordsSection.$$('#checkPasswordsButtonRow').hidden); |
| assertFalse(passwordsSection.$$('#checkPasswordsLinkRow').hidden); |
| }); |
| }); |
| |
| test( |
| 'showPasswordCheckLinkButtonWithoutWarningWhenNoCredentialsLeaked', |
| function() { |
| // Suppose no leaks initially, non-empty list of passwords, signed in. |
| passwordManager.data.leakedCredentials = []; |
| passwordManager.data.checkStatus.elapsedTimeSinceLastCheck = |
| '5 min ago'; |
| const passwordList = [ |
| createPasswordEntry({url: 'site1.com', username: 'luigi'}), |
| ]; |
| const passwordsSection = elementFactory.createPasswordsSection( |
| passwordManager, passwordList, []); |
| return passwordManager.whenCalled('getPasswordCheckStatus').then(() => { |
| flush(); |
| assertTrue( |
| passwordsSection.$$('#checkPasswordsBannerContainer').hidden); |
| assertTrue(passwordsSection.$$('#checkPasswordsButtonRow').hidden); |
| assertFalse(passwordsSection.$$('#checkPasswordsLinkRow').hidden); |
| assertFalse( |
| passwordsSection.$$('#checkPasswordLeakDescription').hidden); |
| assertTrue(passwordsSection.$$('#checkPasswordWarningIcon').hidden); |
| assertTrue(passwordsSection.$$('#checkPasswordLeakCount').hidden); |
| }); |
| }); |
| |
| test( |
| 'showPasswordCheckLinkButtonWithWarningWhenSomeCredentialsLeaked', |
| function() { |
| // Suppose no leaks initially, non-empty list of passwords, signed in. |
| passwordManager.data.leakedCredentials = [ |
| makeCompromisedCredential('one.com', 'test4', 'LEAKED'), |
| makeCompromisedCredential('two.com', 'test3', 'PHISHED'), |
| ]; |
| passwordManager.data.checkStatus.elapsedTimeSinceLastCheck = |
| '5 min ago'; |
| const passwordList = [ |
| createPasswordEntry({url: 'site1.com', username: 'luigi'}), |
| ]; |
| const passwordsSection = elementFactory.createPasswordsSection( |
| passwordManager, passwordList, []); |
| return passwordManager.whenCalled('getPasswordCheckStatus').then(() => { |
| flush(); |
| assertTrue( |
| passwordsSection.$$('#checkPasswordsBannerContainer').hidden); |
| assertTrue(passwordsSection.$$('#checkPasswordsButtonRow').hidden); |
| assertFalse(passwordsSection.$$('#checkPasswordsLinkRow').hidden); |
| assertTrue( |
| passwordsSection.$$('#checkPasswordLeakDescription').hidden); |
| assertFalse(passwordsSection.$$('#checkPasswordWarningIcon').hidden); |
| assertFalse(passwordsSection.$$('#checkPasswordLeakCount').hidden); |
| }); |
| }); |
| |
| test('makeWarningAppearWhenLeaksDetected', function() { |
| // Suppose no leaks detected initially, non-empty list of passwords, |
| // signed in. |
| assertEquals( |
| passwordManager.data.checkStatus.elapsedTimeSinceLastCheck, undefined); |
| passwordManager.data.leakedCredentials = []; |
| passwordManager.data.checkStatus.elapsedTimeSinceLastCheck = '5 min ago'; |
| const passwordList = [ |
| createPasswordEntry({url: 'one.com', username: 'test4', id: 0}), |
| createPasswordEntry({url: 'two.com', username: 'test3', id: 1}), |
| ]; |
| const passwordsSection = elementFactory.createPasswordsSection( |
| passwordManager, passwordList, []); |
| return passwordManager.whenCalled('getPasswordCheckStatus').then(() => { |
| flush(); |
| assertTrue(passwordsSection.$$('#checkPasswordsBannerContainer').hidden); |
| assertTrue(passwordsSection.$$('#checkPasswordsButtonRow').hidden); |
| assertFalse(passwordsSection.$$('#checkPasswordsLinkRow').hidden); |
| assertFalse(passwordsSection.$$('#checkPasswordLeakDescription').hidden); |
| assertTrue(passwordsSection.$$('#checkPasswordWarningIcon').hidden); |
| assertTrue(passwordsSection.$$('#checkPasswordLeakCount').hidden); |
| // Suppose two newly detected leaks come in. |
| const leakedCredentials = [ |
| makeCompromisedCredential('one.com', 'test4', 'LEAKED'), |
| makeCompromisedCredential('two.com', 'test3', 'PHISHED'), |
| ]; |
| const elapsedTimeSinceLastCheck = 'just now'; |
| passwordManager.data.leakedCredentials = leakedCredentials; |
| passwordManager.data.checkStatus.elapsedTimeSinceLastCheck = |
| elapsedTimeSinceLastCheck; |
| passwordManager.lastCallback.addCompromisedCredentialsListener( |
| leakedCredentials); |
| passwordManager.lastCallback.addPasswordCheckStatusListener( |
| makePasswordCheckStatus( |
| /*state=*/ PasswordCheckState.RUNNING, |
| /*checked=*/ 2, |
| /*remaining=*/ 0, |
| /*elapsedTime=*/ elapsedTimeSinceLastCheck)); |
| flush(); |
| assertTrue(passwordsSection.$$('#checkPasswordsBannerContainer').hidden); |
| assertTrue(passwordsSection.$$('#checkPasswordsButtonRow').hidden); |
| assertFalse(passwordsSection.$$('#checkPasswordsLinkRow').hidden); |
| assertTrue(passwordsSection.$$('#checkPasswordLeakDescription').hidden); |
| assertFalse(passwordsSection.$$('#checkPasswordWarningIcon').hidden); |
| assertFalse(passwordsSection.$$('#checkPasswordLeakCount').hidden); |
| }); |
| }); |
| |
| test('makeBannerDisappearWhenSignedOut', function() { |
| // Suppose no leaks detected initially, non-empty list of passwords, |
| // signed in. |
| const passwordList = [ |
| createPasswordEntry({url: 'one.com', username: 'test4', id: 0}), |
| createPasswordEntry({url: 'two.com', username: 'test3', id: 1}), |
| ]; |
| const passwordsSection = elementFactory.createPasswordsSection( |
| passwordManager, passwordList, []); |
| return passwordManager.whenCalled('getPasswordCheckStatus').then(() => { |
| flush(); |
| assertFalse(passwordsSection.$$('#checkPasswordsBannerContainer').hidden); |
| assertFalse(passwordsSection.$$('#checkPasswordsButtonRow').hidden); |
| assertTrue(passwordsSection.$$('#checkPasswordsLinkRow').hidden); |
| |
| simulateSyncStatus({signedIn: false}); |
| assertTrue(passwordsSection.$$('#checkPasswordsBannerContainer').hidden); |
| assertTrue(passwordsSection.$$('#checkPasswordsButtonRow').hidden); |
| assertFalse(passwordsSection.$$('#checkPasswordsLinkRow').hidden); |
| }); |
| }); |
| |
| test('clickingCheckPasswordsButtonStartsCheck', async function() { |
| const passwordsSection = |
| elementFactory.createPasswordsSection(passwordManager, [], []); |
| passwordsSection.$$('#checkPasswordsButton').click(); |
| flush(); |
| const router = Router.getInstance(); |
| assertEquals(routes.CHECK_PASSWORDS, router.currentRoute); |
| assertEquals('true', router.getQueryParameters().get('start')); |
| const referrer = |
| await passwordManager.whenCalled('recordPasswordCheckReferrer'); |
| assertEquals( |
| PasswordManagerProxy.PasswordCheckReferrer.PASSWORD_SETTINGS, referrer); |
| }); |
| |
| test('clickingCheckPasswordsRowStartsCheck', async function() { |
| const passwordsSection = |
| elementFactory.createPasswordsSection(passwordManager, [], []); |
| passwordsSection.$$('#checkPasswordsLinkRow').click(); |
| flush(); |
| const router = Router.getInstance(); |
| assertEquals(routes.CHECK_PASSWORDS, router.currentRoute); |
| assertEquals('true', router.getQueryParameters().get('start')); |
| const referrer = |
| await passwordManager.whenCalled('recordPasswordCheckReferrer'); |
| assertEquals( |
| PasswordManagerProxy.PasswordCheckReferrer.PASSWORD_SETTINGS, referrer); |
| }); |
| }); |