Source code

Revision control

Copy as Markdown

Other Tools

Test Info:

<!DOCTYPE HTML>
<html>
<!--
Test the login-item component
-->
<head>
<meta charset="utf-8">
<title>Test the login-item component</title>
<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">
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_LOGIN_3 = {
guid: "987654321",
origin: "https://example.com",
username: "user2",
password: "pass2",
timeCreated: "4000",
timePasswordChanged: "4000",
timeLastUsed: "4000",
};
const TEST_BREACH = {
Name: "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 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);
await asyncElementRendered();
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("login-breach-alert");
ok(!isHidden(breachAlert), "Breach alert should be visible");
is(breachAlert.hostname, TEST_LOGIN_1.origin, "Link in the text should point to the login origin");
let vulernableAlert = gLoginItem.shadowRoot.querySelector("login-vulnerable-password-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("login-breach-alert");
ok(isHidden(breachAlert), "Breach alert should not be visible on login without an associated breach.");
let vulernableAlert = gLoginItem.shadowRoot.querySelector("login-vulnerable-password-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("login-breach-alert");
ok(isHidden(breachAlert), "Breach alert should not be visible on login without an associated breach.");
let vulernableAlert = gLoginItem.shadowRoot.querySelector("login-vulnerable-password-alert");
ok(!isHidden(vulernableAlert), "Vulnerable alert should be visible");
is(vulernableAlert.shadowRoot.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("login-breach-alert");
ok(isHidden(breachAlert), "Breach alert should not be visible on login without an associated breach.");
let vulernableAlert = gLoginItem.shadowRoot.querySelector("login-vulnerable-password-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_keyboard_shortcut() {
gLoginItem.setLogin(TEST_LOGIN_1);
let usernameInput = gLoginItem.shadowRoot.querySelector("input[name='username']");
usernameInput.placeholder = "dummy placeholder";
const ev = new KeyboardEvent("keydown", { altKey:true, key:"Enter" });
window.dispatchEvent(ev);
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_edit_login_cancel_keyboard_shortcut() {
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");
const ev = new KeyboardEvent("keydown", { key: "Escape" });
window.dispatchEvent(ev);
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", (_e) => {
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 [
["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-password-button should be enabled");
copyUsernameButton.click();
await asyncElementRendered();
copyPasswordButton.click();
await asyncElementRendered();
let loginNoUsername = Object.assign({}, TEST_LOGIN_2, {username: ""});
gLoginItem.setLogin(loginNoUsername);
await asyncElementRendered();
ok(copyUsernameButton.disabled, "The copy-username-button should be disabled when the username is empty");
ok(!copyPasswordButton.disabled, "The copy-password-button should be enabled");
copyPasswordButton.click();
});
add_task(async function test_login_timeline_state() {
gLoginItem.setLogin(TEST_LOGIN_1);
await asyncElementRendered();
let timeline = getLoginTimeline(gLoginItem);
ok(!isHidden(timeline), "Timeline should be visible");
is(timeline.history.length, 3, "All 3 timestamps (created, updated, used) must be shown")
let actions = timeline.shadowRoot.querySelectorAll(".action");
verifyTimelineActions(actions, [
"login-item-timeline-action-created",
"login-item-timeline-action-updated",
"login-item-timeline-action-used",
]);
gLoginItem.setLogin(TEST_LOGIN_3);
await asyncElementRendered();
timeline = getLoginTimeline(gLoginItem);
ok(!isHidden(timeline), "Timeline should be visible");
is(timeline.history.length, 2, "Created and Last Used must be shown only")
actions = timeline.shadowRoot.querySelectorAll(".action");
verifyTimelineActions(actions, [
"login-item-timeline-action-created",
"login-item-timeline-action-used",
]);
});
</script>
</body>
</html>