mirror of
https://github.com/bitwarden/browser
synced 2025-12-19 09:43:23 +00:00
[PM-5189] Reworking project structure to ensure we can better differentiate the inline menu feature from other features
This commit is contained in:
@@ -9,13 +9,3 @@ exports[`AutofillInlineMenuIframeService initMenuIframe sets up the iframe's att
|
|||||||
title="title"
|
title="title"
|
||||||
/>
|
/>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`AutofillOverlayIframeService initMenuIframe sets up the iframe's attributes 1`] = `
|
|
||||||
<iframe
|
|
||||||
allowtransparency="true"
|
|
||||||
src="chrome-extension://id/overlay/menu.html"
|
|
||||||
style="all: initial !important; position: fixed !important; display: block !important; z-index: 2147483647 !important; line-height: 0 !important; overflow: hidden !important; transition: opacity 125ms ease-out 0s !important; visibility: visible !important; clip-path: none !important; pointer-events: auto !important; margin: 0px !important; padding: 0px !important; color-scheme: normal !important; opacity: 0 !important; height: 0px;"
|
|
||||||
tabindex="-1"
|
|
||||||
title="title"
|
|
||||||
/>
|
|
||||||
`;
|
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`AutofillOverlayIframeService initMenuIframe sets up the iframe's attributes 1`] = `
|
|
||||||
<iframe
|
|
||||||
allowtransparency="true"
|
|
||||||
src="chrome-extension://id/overlay/menu.html"
|
|
||||||
style="all: initial !important; position: fixed !important; display: block !important; z-index: 2147483647 !important; line-height: 0 !important; overflow: hidden !important; transition: opacity 125ms ease-out 0s !important; visibility: visible !important; clip-path: none !important; pointer-events: auto !important; margin: 0px !important; padding: 0px !important; color-scheme: normal !important; opacity: 0 !important; height: 0px;"
|
|
||||||
tabindex="-1"
|
|
||||||
title="title"
|
|
||||||
/>
|
|
||||||
`;
|
|
||||||
@@ -81,85 +81,3 @@ exports[`AutofillInlineMenuButton initAutofillInlineMenuButton creates the butto
|
|||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`AutofillOverlayButton initAutofillInlineMenuButton creates the button element with the locked icon when the user's auth status is not Unlocked 1`] = `
|
|
||||||
<button
|
|
||||||
aria-label="toggleBitwardenVaultOverlay"
|
|
||||||
class="inline-menu-button"
|
|
||||||
tabindex="-1"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
class="inline-menu-button-svg-icon logo-locked-icon"
|
|
||||||
fill="none"
|
|
||||||
height="16"
|
|
||||||
viewBox="0 0 16 16"
|
|
||||||
width="16"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<g
|
|
||||||
clip-path="url(#a)"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M12.66.175A.566.566 0 0 0 12.25 0H1.75a.559.559 0 0 0-.409.175.561.561 0 0 0-.175.41v7c.002.532.105 1.06.305 1.554.189.488.444.948.756 1.368.322.42.682.81 1.076 1.163.365.335.75.649 1.152.939.35.248.718.483 1.103.706.385.222.656.372.815.45.16.08.29.141.386.182A.53.53 0 0 0 7 14a.509.509 0 0 0 .238-.055c.098-.043.225-.104.387-.182.162-.079.438-.23.816-.45.378-.222.75-.459 1.102-.707.403-.29.788-.604 1.154-.939a8.435 8.435 0 0 0 1.076-1.163c.312-.42.567-.88.757-1.367a4.19 4.19 0 0 0 .304-1.555v-7a.55.55 0 0 0-.174-.407Z"
|
|
||||||
fill="#175DDC"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M7 12.365s4.306-2.18 4.306-4.717V1.5H7v10.865Z"
|
|
||||||
fill="#fff"
|
|
||||||
/>
|
|
||||||
<circle
|
|
||||||
cx="12.889"
|
|
||||||
cy="12.889"
|
|
||||||
fill="#F8F9FA"
|
|
||||||
r="4.889"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M11.26 11.717h2.37v-.848c0-.313-.116-.58-.348-.8a1.17 1.17 0 0 0-.838-.332c-.327 0-.606.11-.838.332a1.066 1.066 0 0 0-.347.8v.848Zm3.851.424v2.546a.4.4 0 0 1-.13.3.44.44 0 0 1-.314.124h-4.445a.44.44 0 0 1-.315-.124.4.4 0 0 1-.13-.3V12.14a.4.4 0 0 1 .13-.3.44.44 0 0 1 .315-.124h.148v-.848c0-.542.204-1.008.612-1.397a2.044 2.044 0 0 1 1.462-.583c.568 0 1.056.194 1.463.583.408.39.611.855.611 1.397v.848h.149a.44.44 0 0 1 .315.124.4.4 0 0 1 .13.3Z"
|
|
||||||
fill="#555"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
<defs>
|
|
||||||
<clippath
|
|
||||||
id="a"
|
|
||||||
>
|
|
||||||
<rect
|
|
||||||
fill="#fff"
|
|
||||||
height="16"
|
|
||||||
rx="2"
|
|
||||||
width="16"
|
|
||||||
/>
|
|
||||||
</clippath>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`AutofillOverlayButton initAutofillInlineMenuButton creates the button element with the normal icon when the user's auth status is Unlocked 1`] = `
|
|
||||||
<button
|
|
||||||
aria-label="toggleBitwardenVaultOverlay"
|
|
||||||
class="inline-menu-button"
|
|
||||||
tabindex="-1"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
class="inline-menu-button-svg-icon logo-icon"
|
|
||||||
fill="none"
|
|
||||||
height="14"
|
|
||||||
viewBox="0 0 14 14"
|
|
||||||
width="14"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M12.66.175A.566.566 0 0 0 12.25 0H1.75a.559.559 0 0 0-.409.175.561.561 0 0 0-.175.41v7c.002.532.105 1.06.305 1.554.189.488.444.948.756 1.368.322.42.682.81 1.076 1.163.365.335.75.649 1.152.939.35.248.718.483 1.103.706.385.222.656.372.815.45.16.08.29.141.386.182A.53.53 0 0 0 7 14a.509.509 0 0 0 .238-.055c.098-.043.225-.104.387-.182.162-.079.438-.23.816-.45.378-.222.75-.459 1.102-.707.403-.29.788-.604 1.154-.939a8.435 8.435 0 0 0 1.076-1.163c.312-.42.567-.88.757-1.367a4.19 4.19 0 0 0 .304-1.555v-7a.55.55 0 0 0-.174-.407Z"
|
|
||||||
fill="#175DDC"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M7 12.365s4.306-2.18 4.306-4.717V1.5H7v10.865Z"
|
|
||||||
fill="#fff"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
`;
|
|
||||||
|
|||||||
@@ -1,83 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`AutofillOverlayButton initAutofillOverlayButton creates the button element with the locked icon when the user's auth status is not Unlocked 1`] = `
|
|
||||||
<button
|
|
||||||
aria-label="toggleBitwardenVaultOverlay"
|
|
||||||
class="inline-menu-button"
|
|
||||||
tabindex="-1"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
class="inline-menu-button-svg-icon logo-locked-icon"
|
|
||||||
fill="none"
|
|
||||||
height="16"
|
|
||||||
viewBox="0 0 16 16"
|
|
||||||
width="16"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<g
|
|
||||||
clip-path="url(#a)"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M12.66.175A.566.566 0 0 0 12.25 0H1.75a.559.559 0 0 0-.409.175.561.561 0 0 0-.175.41v7c.002.532.105 1.06.305 1.554.189.488.444.948.756 1.368.322.42.682.81 1.076 1.163.365.335.75.649 1.152.939.35.248.718.483 1.103.706.385.222.656.372.815.45.16.08.29.141.386.182A.53.53 0 0 0 7 14a.509.509 0 0 0 .238-.055c.098-.043.225-.104.387-.182.162-.079.438-.23.816-.45.378-.222.75-.459 1.102-.707.403-.29.788-.604 1.154-.939a8.435 8.435 0 0 0 1.076-1.163c.312-.42.567-.88.757-1.367a4.19 4.19 0 0 0 .304-1.555v-7a.55.55 0 0 0-.174-.407Z"
|
|
||||||
fill="#175DDC"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M7 12.365s4.306-2.18 4.306-4.717V1.5H7v10.865Z"
|
|
||||||
fill="#fff"
|
|
||||||
/>
|
|
||||||
<circle
|
|
||||||
cx="12.889"
|
|
||||||
cy="12.889"
|
|
||||||
fill="#F8F9FA"
|
|
||||||
r="4.889"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M11.26 11.717h2.37v-.848c0-.313-.116-.58-.348-.8a1.17 1.17 0 0 0-.838-.332c-.327 0-.606.11-.838.332a1.066 1.066 0 0 0-.347.8v.848Zm3.851.424v2.546a.4.4 0 0 1-.13.3.44.44 0 0 1-.314.124h-4.445a.44.44 0 0 1-.315-.124.4.4 0 0 1-.13-.3V12.14a.4.4 0 0 1 .13-.3.44.44 0 0 1 .315-.124h.148v-.848c0-.542.204-1.008.612-1.397a2.044 2.044 0 0 1 1.462-.583c.568 0 1.056.194 1.463.583.408.39.611.855.611 1.397v.848h.149a.44.44 0 0 1 .315.124.4.4 0 0 1 .13.3Z"
|
|
||||||
fill="#555"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
<defs>
|
|
||||||
<clippath
|
|
||||||
id="a"
|
|
||||||
>
|
|
||||||
<rect
|
|
||||||
fill="#fff"
|
|
||||||
height="16"
|
|
||||||
rx="2"
|
|
||||||
width="16"
|
|
||||||
/>
|
|
||||||
</clippath>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`AutofillOverlayButton initAutofillOverlayButton creates the button element with the normal icon when the user's auth status is Unlocked 1`] = `
|
|
||||||
<button
|
|
||||||
aria-label="toggleBitwardenVaultOverlay"
|
|
||||||
class="inline-menu-button"
|
|
||||||
tabindex="-1"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
class="inline-menu-button-svg-icon logo-icon"
|
|
||||||
fill="none"
|
|
||||||
height="14"
|
|
||||||
viewBox="0 0 14 14"
|
|
||||||
width="14"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M12.66.175A.566.566 0 0 0 12.25 0H1.75a.559.559 0 0 0-.409.175.561.561 0 0 0-.175.41v7c.002.532.105 1.06.305 1.554.189.488.444.948.756 1.368.322.42.682.81 1.076 1.163.365.335.75.649 1.152.939.35.248.718.483 1.103.706.385.222.656.372.815.45.16.08.29.141.386.182A.53.53 0 0 0 7 14a.509.509 0 0 0 .238-.055c.098-.043.225-.104.387-.182.162-.079.438-.23.816-.45.378-.222.75-.459 1.102-.707.403-.29.788-.604 1.154-.939a8.435 8.435 0 0 0 1.076-1.163c.312-.42.567-.88.757-1.367a4.19 4.19 0 0 0 .304-1.555v-7a.55.55 0 0 0-.174-.407Z"
|
|
||||||
fill="#175DDC"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M7 12.365s4.306-2.18 4.306-4.717V1.5H7v10.865Z"
|
|
||||||
fill="#fff"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
`;
|
|
||||||
@@ -41,7 +41,7 @@ class AutofillInlineMenuButton extends AutofillInlineMenuPageElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the overlay button. Facilitates ensuring that the page
|
* Initializes the inline menu button. Facilitates ensuring that the page
|
||||||
* is set up with the expected styles and translations.
|
* is set up with the expected styles and translations.
|
||||||
*
|
*
|
||||||
* @param authStatus - The authentication status of the user
|
* @param authStatus - The authentication status of the user
|
||||||
@@ -114,7 +114,7 @@ class AutofillInlineMenuButton extends AutofillInlineMenuPageElement {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the button is focused. If it is not, then it posts a message
|
* Checks if the button is focused. If it is not, then it posts a message
|
||||||
* to the parent window indicating that the overlay should be closed.
|
* to the parent window indicating that the inline menu should be closed.
|
||||||
*/
|
*/
|
||||||
private checkButtonFocused() {
|
private checkButtonFocused() {
|
||||||
if (globalThis.document.hasFocus()) {
|
if (globalThis.document.hasFocus()) {
|
||||||
|
|||||||
@@ -1,15 +1,66 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`AutofillOverlayList initAutofillInlineMenuList the list of ciphers for an authenticated user creates the view for a list of ciphers 1`] = `
|
exports[`AutofillInlineMenuList initAutofillInlineMenuList the inline menu with an empty list of ciphers creates the views for the no results inline menu 1`] = `
|
||||||
<div
|
<div
|
||||||
class="overlay-list-container theme_light"
|
class="inline-menu-list-container theme_light"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="no-items inline-menu-list-message"
|
||||||
|
>
|
||||||
|
noItemsToShow
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="inline-menu-list-button-container"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
aria-label="addNewVaultItem, opensInANewWindow"
|
||||||
|
class="add-new-item-button inline-menu-list-button"
|
||||||
|
id="new-item-button"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
fill="none"
|
||||||
|
height="17"
|
||||||
|
viewBox="0 0 16 17"
|
||||||
|
width="16"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<g
|
||||||
|
clip-path="url(#a)"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M15.222 7.914H8.963a.471.471 0 0 1-.34-.147.512.512 0 0 1-.142-.353V.99c0-.133-.05-.26-.14-.354a.471.471 0 0 0-.68 0 .51.51 0 0 0-.142.354v6.424c0 .132-.051.26-.142.353a.474.474 0 0 1-.34.147H.777a.47.47 0 0 0-.34.146.5.5 0 0 0-.14.354.522.522 0 0 0 .14.353.48.48 0 0 0 .34.147h6.26c.128 0 .25.052.34.146.09.094.142.221.142.354v6.576c0 .132.05.26.14.353a.471.471 0 0 0 .68 0 .512.512 0 0 0 .142-.353V9.414c0-.133.051-.26.142-.354a.474.474 0 0 1 .34-.146h6.26c.127 0 .25-.053.34-.147a.511.511 0 0 0 0-.707.472.472 0 0 0-.34-.146Z"
|
||||||
|
fill="#175DDC"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clippath
|
||||||
|
id="a"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M0 .49h16v16H0z"
|
||||||
|
fill="#fff"
|
||||||
|
/>
|
||||||
|
</clippath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
newItem
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers for an authenticated user creates the view for a list of ciphers 1`] = `
|
||||||
|
<div
|
||||||
|
class="inline-menu-list-container theme_light"
|
||||||
>
|
>
|
||||||
<ul
|
<ul
|
||||||
class="overlay-actions-list"
|
class="inline-menu-list-actions"
|
||||||
role="list"
|
role="list"
|
||||||
>
|
>
|
||||||
<li
|
<li
|
||||||
class="overlay-actions-list-item"
|
class="inline-menu-list-actions-item"
|
||||||
role="listitem"
|
role="listitem"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -79,7 +130,7 @@ exports[`AutofillOverlayList initAutofillInlineMenuList the list of ciphers for
|
|||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
class="overlay-actions-list-item"
|
class="inline-menu-list-actions-item"
|
||||||
role="listitem"
|
role="listitem"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -148,7 +199,7 @@ exports[`AutofillOverlayList initAutofillInlineMenuList the list of ciphers for
|
|||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
class="overlay-actions-list-item"
|
class="inline-menu-list-actions-item"
|
||||||
role="listitem"
|
role="listitem"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -204,7 +255,7 @@ exports[`AutofillOverlayList initAutofillInlineMenuList the list of ciphers for
|
|||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
class="overlay-actions-list-item"
|
class="inline-menu-list-actions-item"
|
||||||
role="listitem"
|
role="listitem"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -289,7 +340,7 @@ exports[`AutofillOverlayList initAutofillInlineMenuList the list of ciphers for
|
|||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
class="overlay-actions-list-item"
|
class="inline-menu-list-actions-item"
|
||||||
role="listitem"
|
role="listitem"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -359,7 +410,7 @@ exports[`AutofillOverlayList initAutofillInlineMenuList the list of ciphers for
|
|||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
class="overlay-actions-list-item"
|
class="inline-menu-list-actions-item"
|
||||||
role="listitem"
|
role="listitem"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -432,22 +483,22 @@ exports[`AutofillOverlayList initAutofillInlineMenuList the list of ciphers for
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`AutofillOverlayList initAutofillInlineMenuList the locked overlay for an unauthenticated user creates the views for the locked overlay 1`] = `
|
exports[`AutofillInlineMenuList initAutofillInlineMenuList the locked inline menu for an unauthenticated user creates the views for the locked inline menu 1`] = `
|
||||||
<div
|
<div
|
||||||
class="overlay-list-container theme_light"
|
class="inline-menu-list-container theme_light"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="locked-overlay overlay-list-message"
|
class="locked-inline-menu inline-menu-list-message"
|
||||||
id="locked-overlay-description"
|
id="locked-inline-menu-description"
|
||||||
>
|
>
|
||||||
unlockYourAccount
|
unlockYourAccount
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="overlay-list-button-container"
|
class="inline-menu-list-button-container"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
aria-label="unlockAccount, opensInANewWindow"
|
aria-label="unlockAccount, opensInANewWindow"
|
||||||
class="unlock-button overlay-list-button"
|
class="unlock-button inline-menu-list-button"
|
||||||
id="unlock-button"
|
id="unlock-button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
@@ -483,54 +534,3 @@ exports[`AutofillOverlayList initAutofillInlineMenuList the locked overlay for a
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`AutofillOverlayList initAutofillInlineMenuList the overlay with an empty list of ciphers creates the views for the no results overlay 1`] = `
|
|
||||||
<div
|
|
||||||
class="overlay-list-container theme_light"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="no-items overlay-list-message"
|
|
||||||
>
|
|
||||||
noItemsToShow
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="overlay-list-button-container"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
aria-label="addNewVaultItem, opensInANewWindow"
|
|
||||||
class="add-new-item-button overlay-list-button"
|
|
||||||
id="new-item-button"
|
|
||||||
tabindex="-1"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
fill="none"
|
|
||||||
height="17"
|
|
||||||
viewBox="0 0 16 17"
|
|
||||||
width="16"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<g
|
|
||||||
clip-path="url(#a)"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M15.222 7.914H8.963a.471.471 0 0 1-.34-.147.512.512 0 0 1-.142-.353V.99c0-.133-.05-.26-.14-.354a.471.471 0 0 0-.68 0 .51.51 0 0 0-.142.354v6.424c0 .132-.051.26-.142.353a.474.474 0 0 1-.34.147H.777a.47.47 0 0 0-.34.146.5.5 0 0 0-.14.354.522.522 0 0 0 .14.353.48.48 0 0 0 .34.147h6.26c.128 0 .25.052.34.146.09.094.142.221.142.354v6.576c0 .132.05.26.14.353a.471.471 0 0 0 .68 0 .512.512 0 0 0 .142-.353V9.414c0-.133.051-.26.142-.354a.474.474 0 0 1 .34-.146h6.26c.127 0 .25-.053.34-.147a.511.511 0 0 0 0-.707.472.472 0 0 0-.34-.146Z"
|
|
||||||
fill="#175DDC"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
<defs>
|
|
||||||
<clippath
|
|
||||||
id="a"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M0 .49h16v16H0z"
|
|
||||||
fill="#fff"
|
|
||||||
/>
|
|
||||||
</clippath>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
||||||
newItem
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -2,12 +2,12 @@ import { mock } from "jest-mock-extended";
|
|||||||
|
|
||||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||||
|
|
||||||
import { createInitAutofillOverlayListMessageMock } from "../../../../spec/autofill-mocks";
|
import { createInitAutofillInlineMenuListMessageMock } from "../../../../spec/autofill-mocks";
|
||||||
import { flushPromises, postWindowMessage } from "../../../../spec/testing-utils";
|
import { flushPromises, postWindowMessage } from "../../../../spec/testing-utils";
|
||||||
|
|
||||||
import { AutofillInlineMenuList } from "./autofill-inline-menu-list";
|
import { AutofillInlineMenuList } from "./autofill-inline-menu-list";
|
||||||
|
|
||||||
describe("AutofillOverlayList", () => {
|
describe("AutofillInlineMenuList", () => {
|
||||||
globalThis.customElements.define("autofill-inline-menu-list", AutofillInlineMenuList);
|
globalThis.customElements.define("autofill-inline-menu-list", AutofillInlineMenuList);
|
||||||
global.ResizeObserver = jest.fn().mockImplementation(() => ({
|
global.ResizeObserver = jest.fn().mockImplementation(() => ({
|
||||||
observe: jest.fn(),
|
observe: jest.fn(),
|
||||||
@@ -15,12 +15,12 @@ describe("AutofillOverlayList", () => {
|
|||||||
disconnect: jest.fn(),
|
disconnect: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let autofillOverlayList: AutofillInlineMenuList;
|
let autofillInlineMenuList: AutofillInlineMenuList;
|
||||||
const portKey: string = "overlayListPortKey";
|
const portKey: string = "inlineMenuListPortKey";
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
document.body.innerHTML = `<autofill-inline-menu-list></autofill-inline-menu-list>`;
|
document.body.innerHTML = `<autofill-inline-menu-list></autofill-inline-menu-list>`;
|
||||||
autofillOverlayList = document.querySelector("autofill-inline-menu-list");
|
autofillInlineMenuList = document.querySelector("autofill-inline-menu-list");
|
||||||
jest.spyOn(globalThis.document, "createElement");
|
jest.spyOn(globalThis.document, "createElement");
|
||||||
jest.spyOn(globalThis.parent, "postMessage");
|
jest.spyOn(globalThis.parent, "postMessage");
|
||||||
});
|
});
|
||||||
@@ -30,10 +30,10 @@ describe("AutofillOverlayList", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("initAutofillInlineMenuList", () => {
|
describe("initAutofillInlineMenuList", () => {
|
||||||
describe("the locked overlay for an unauthenticated user", () => {
|
describe("the locked inline menu for an unauthenticated user", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
postWindowMessage(
|
postWindowMessage(
|
||||||
createInitAutofillOverlayListMessageMock({
|
createInitAutofillInlineMenuListMessageMock({
|
||||||
authStatus: AuthenticationStatus.Locked,
|
authStatus: AuthenticationStatus.Locked,
|
||||||
cipherList: [],
|
cipherList: [],
|
||||||
portKey,
|
portKey,
|
||||||
@@ -41,13 +41,13 @@ describe("AutofillOverlayList", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("creates the views for the locked overlay", () => {
|
it("creates the views for the locked inline menu", () => {
|
||||||
expect(autofillOverlayList["overlayListContainer"]).toMatchSnapshot();
|
expect(autofillInlineMenuList["inlineMenuListContainer"]).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("allows the user to unlock the vault", () => {
|
it("allows the user to unlock the vault", () => {
|
||||||
const unlockButton =
|
const unlockButton =
|
||||||
autofillOverlayList["overlayListContainer"].querySelector("#unlock-button");
|
autofillInlineMenuList["inlineMenuListContainer"].querySelector("#unlock-button");
|
||||||
|
|
||||||
unlockButton.dispatchEvent(new Event("click"));
|
unlockButton.dispatchEvent(new Event("click"));
|
||||||
|
|
||||||
@@ -58,10 +58,10 @@ describe("AutofillOverlayList", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("the overlay with an empty list of ciphers", () => {
|
describe("the inline menu with an empty list of ciphers", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
postWindowMessage(
|
postWindowMessage(
|
||||||
createInitAutofillOverlayListMessageMock({
|
createInitAutofillInlineMenuListMessageMock({
|
||||||
authStatus: AuthenticationStatus.Unlocked,
|
authStatus: AuthenticationStatus.Unlocked,
|
||||||
ciphers: [],
|
ciphers: [],
|
||||||
portKey,
|
portKey,
|
||||||
@@ -69,13 +69,13 @@ describe("AutofillOverlayList", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("creates the views for the no results overlay", () => {
|
it("creates the views for the no results inline menu", () => {
|
||||||
expect(autofillOverlayList["overlayListContainer"]).toMatchSnapshot();
|
expect(autofillInlineMenuList["inlineMenuListContainer"]).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("allows the user to add a vault item", () => {
|
it("allows the user to add a vault item", () => {
|
||||||
const addVaultItemButton =
|
const addVaultItemButton =
|
||||||
autofillOverlayList["overlayListContainer"].querySelector("#new-item-button");
|
autofillInlineMenuList["inlineMenuListContainer"].querySelector("#new-item-button");
|
||||||
|
|
||||||
addVaultItemButton.dispatchEvent(new Event("click"));
|
addVaultItemButton.dispatchEvent(new Event("click"));
|
||||||
|
|
||||||
@@ -88,23 +88,23 @@ describe("AutofillOverlayList", () => {
|
|||||||
|
|
||||||
describe("the list of ciphers for an authenticated user", () => {
|
describe("the list of ciphers for an authenticated user", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
postWindowMessage(createInitAutofillOverlayListMessageMock());
|
postWindowMessage(createInitAutofillInlineMenuListMessageMock());
|
||||||
});
|
});
|
||||||
|
|
||||||
it("creates the view for a list of ciphers", () => {
|
it("creates the view for a list of ciphers", () => {
|
||||||
expect(autofillOverlayList["overlayListContainer"]).toMatchSnapshot();
|
expect(autofillInlineMenuList["inlineMenuListContainer"]).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("loads ciphers on scroll one page at a time", () => {
|
it("loads ciphers on scroll one page at a time", () => {
|
||||||
jest.useFakeTimers();
|
jest.useFakeTimers();
|
||||||
const originalListOfElements =
|
const originalListOfElements =
|
||||||
autofillOverlayList["overlayListContainer"].querySelectorAll(".cipher-container");
|
autofillInlineMenuList["inlineMenuListContainer"].querySelectorAll(".cipher-container");
|
||||||
|
|
||||||
window.dispatchEvent(new Event("scroll"));
|
window.dispatchEvent(new Event("scroll"));
|
||||||
jest.runAllTimers();
|
jest.runAllTimers();
|
||||||
|
|
||||||
const updatedListOfElements =
|
const updatedListOfElements =
|
||||||
autofillOverlayList["overlayListContainer"].querySelectorAll(".cipher-container");
|
autofillInlineMenuList["inlineMenuListContainer"].querySelectorAll(".cipher-container");
|
||||||
|
|
||||||
expect(originalListOfElements.length).toBe(6);
|
expect(originalListOfElements.length).toBe(6);
|
||||||
expect(updatedListOfElements.length).toBe(8);
|
expect(updatedListOfElements.length).toBe(8);
|
||||||
@@ -112,9 +112,9 @@ describe("AutofillOverlayList", () => {
|
|||||||
|
|
||||||
it("debounces the ciphers scroll handler", () => {
|
it("debounces the ciphers scroll handler", () => {
|
||||||
jest.useFakeTimers();
|
jest.useFakeTimers();
|
||||||
autofillOverlayList["cipherListScrollDebounceTimeout"] = setTimeout(jest.fn, 0);
|
autofillInlineMenuList["cipherListScrollDebounceTimeout"] = setTimeout(jest.fn, 0);
|
||||||
const handleDebouncedScrollEventSpy = jest.spyOn(
|
const handleDebouncedScrollEventSpy = jest.spyOn(
|
||||||
autofillOverlayList as any,
|
autofillInlineMenuList as any,
|
||||||
"handleDebouncedScrollEvent",
|
"handleDebouncedScrollEvent",
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -130,12 +130,12 @@ describe("AutofillOverlayList", () => {
|
|||||||
|
|
||||||
describe("fill cipher button event listeners", () => {
|
describe("fill cipher button event listeners", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
postWindowMessage(createInitAutofillOverlayListMessageMock({ portKey }));
|
postWindowMessage(createInitAutofillInlineMenuListMessageMock({ portKey }));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("allows the user to fill a cipher on click", () => {
|
it("allows the user to fill a cipher on click", () => {
|
||||||
const fillCipherButton =
|
const fillCipherButton =
|
||||||
autofillOverlayList["overlayListContainer"].querySelector(".fill-cipher-button");
|
autofillInlineMenuList["inlineMenuListContainer"].querySelector(".fill-cipher-button");
|
||||||
|
|
||||||
fillCipherButton.dispatchEvent(new Event("click"));
|
fillCipherButton.dispatchEvent(new Event("click"));
|
||||||
|
|
||||||
@@ -147,7 +147,9 @@ describe("AutofillOverlayList", () => {
|
|||||||
|
|
||||||
it("allows the user to move keyboard focus to the next cipher element on ArrowDown", () => {
|
it("allows the user to move keyboard focus to the next cipher element on ArrowDown", () => {
|
||||||
const fillCipherElements =
|
const fillCipherElements =
|
||||||
autofillOverlayList["overlayListContainer"].querySelectorAll(".fill-cipher-button");
|
autofillInlineMenuList["inlineMenuListContainer"].querySelectorAll(
|
||||||
|
".fill-cipher-button",
|
||||||
|
);
|
||||||
const firstFillCipherElement = fillCipherElements[0];
|
const firstFillCipherElement = fillCipherElements[0];
|
||||||
const secondFillCipherElement = fillCipherElements[1];
|
const secondFillCipherElement = fillCipherElements[1];
|
||||||
jest.spyOn(secondFillCipherElement as HTMLElement, "focus");
|
jest.spyOn(secondFillCipherElement as HTMLElement, "focus");
|
||||||
@@ -159,7 +161,9 @@ describe("AutofillOverlayList", () => {
|
|||||||
|
|
||||||
it("directs focus to the first item in the cipher list if no cipher is present after the current one when pressing ArrowDown", () => {
|
it("directs focus to the first item in the cipher list if no cipher is present after the current one when pressing ArrowDown", () => {
|
||||||
const fillCipherElements =
|
const fillCipherElements =
|
||||||
autofillOverlayList["overlayListContainer"].querySelectorAll(".fill-cipher-button");
|
autofillInlineMenuList["inlineMenuListContainer"].querySelectorAll(
|
||||||
|
".fill-cipher-button",
|
||||||
|
);
|
||||||
const lastFillCipherElement = fillCipherElements[fillCipherElements.length - 1];
|
const lastFillCipherElement = fillCipherElements[fillCipherElements.length - 1];
|
||||||
const firstFillCipherElement = fillCipherElements[0];
|
const firstFillCipherElement = fillCipherElements[0];
|
||||||
jest.spyOn(firstFillCipherElement as HTMLElement, "focus");
|
jest.spyOn(firstFillCipherElement as HTMLElement, "focus");
|
||||||
@@ -171,7 +175,9 @@ describe("AutofillOverlayList", () => {
|
|||||||
|
|
||||||
it("allows the user to move keyboard focus to the previous cipher element on ArrowUp", () => {
|
it("allows the user to move keyboard focus to the previous cipher element on ArrowUp", () => {
|
||||||
const fillCipherElements =
|
const fillCipherElements =
|
||||||
autofillOverlayList["overlayListContainer"].querySelectorAll(".fill-cipher-button");
|
autofillInlineMenuList["inlineMenuListContainer"].querySelectorAll(
|
||||||
|
".fill-cipher-button",
|
||||||
|
);
|
||||||
const firstFillCipherElement = fillCipherElements[0];
|
const firstFillCipherElement = fillCipherElements[0];
|
||||||
const secondFillCipherElement = fillCipherElements[1];
|
const secondFillCipherElement = fillCipherElements[1];
|
||||||
jest.spyOn(firstFillCipherElement as HTMLElement, "focus");
|
jest.spyOn(firstFillCipherElement as HTMLElement, "focus");
|
||||||
@@ -183,7 +189,9 @@ describe("AutofillOverlayList", () => {
|
|||||||
|
|
||||||
it("directs focus to the last item in the cipher list if no cipher is present before the current one when pressing ArrowUp", () => {
|
it("directs focus to the last item in the cipher list if no cipher is present before the current one when pressing ArrowUp", () => {
|
||||||
const fillCipherElements =
|
const fillCipherElements =
|
||||||
autofillOverlayList["overlayListContainer"].querySelectorAll(".fill-cipher-button");
|
autofillInlineMenuList["inlineMenuListContainer"].querySelectorAll(
|
||||||
|
".fill-cipher-button",
|
||||||
|
);
|
||||||
const firstFillCipherElement = fillCipherElements[0];
|
const firstFillCipherElement = fillCipherElements[0];
|
||||||
const lastFillCipherElement = fillCipherElements[fillCipherElements.length - 1];
|
const lastFillCipherElement = fillCipherElements[fillCipherElements.length - 1];
|
||||||
jest.spyOn(lastFillCipherElement as HTMLElement, "focus");
|
jest.spyOn(lastFillCipherElement as HTMLElement, "focus");
|
||||||
@@ -195,7 +203,7 @@ describe("AutofillOverlayList", () => {
|
|||||||
|
|
||||||
it("allows the user to move keyboard focus to the view cipher button on ArrowRight", () => {
|
it("allows the user to move keyboard focus to the view cipher button on ArrowRight", () => {
|
||||||
const cipherContainerElement =
|
const cipherContainerElement =
|
||||||
autofillOverlayList["overlayListContainer"].querySelector(".cipher-container");
|
autofillInlineMenuList["inlineMenuListContainer"].querySelector(".cipher-container");
|
||||||
const fillCipherElement = cipherContainerElement.querySelector(".fill-cipher-button");
|
const fillCipherElement = cipherContainerElement.querySelector(".fill-cipher-button");
|
||||||
const viewCipherButton = cipherContainerElement.querySelector(".view-cipher-button");
|
const viewCipherButton = cipherContainerElement.querySelector(".view-cipher-button");
|
||||||
jest.spyOn(viewCipherButton as HTMLElement, "focus");
|
jest.spyOn(viewCipherButton as HTMLElement, "focus");
|
||||||
@@ -207,7 +215,7 @@ describe("AutofillOverlayList", () => {
|
|||||||
|
|
||||||
it("ignores keyup events that do not include ArrowUp, ArrowDown, or ArrowRight", () => {
|
it("ignores keyup events that do not include ArrowUp, ArrowDown, or ArrowRight", () => {
|
||||||
const fillCipherElement =
|
const fillCipherElement =
|
||||||
autofillOverlayList["overlayListContainer"].querySelector(".fill-cipher-button");
|
autofillInlineMenuList["inlineMenuListContainer"].querySelector(".fill-cipher-button");
|
||||||
jest.spyOn(fillCipherElement as HTMLElement, "focus");
|
jest.spyOn(fillCipherElement as HTMLElement, "focus");
|
||||||
|
|
||||||
fillCipherElement.dispatchEvent(new KeyboardEvent("keyup", { code: "ArrowLeft" }));
|
fillCipherElement.dispatchEvent(new KeyboardEvent("keyup", { code: "ArrowLeft" }));
|
||||||
@@ -218,12 +226,12 @@ describe("AutofillOverlayList", () => {
|
|||||||
|
|
||||||
describe("view cipher button event listeners", () => {
|
describe("view cipher button event listeners", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
postWindowMessage(createInitAutofillOverlayListMessageMock({ portKey }));
|
postWindowMessage(createInitAutofillInlineMenuListMessageMock({ portKey }));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("allows the user to view a cipher on click", () => {
|
it("allows the user to view a cipher on click", () => {
|
||||||
const viewCipherButton =
|
const viewCipherButton =
|
||||||
autofillOverlayList["overlayListContainer"].querySelector(".view-cipher-button");
|
autofillInlineMenuList["inlineMenuListContainer"].querySelector(".view-cipher-button");
|
||||||
|
|
||||||
viewCipherButton.dispatchEvent(new Event("click"));
|
viewCipherButton.dispatchEvent(new Event("click"));
|
||||||
|
|
||||||
@@ -235,7 +243,7 @@ describe("AutofillOverlayList", () => {
|
|||||||
|
|
||||||
it("allows the user to move keyboard focus to the current cipher element on ArrowLeft", () => {
|
it("allows the user to move keyboard focus to the current cipher element on ArrowLeft", () => {
|
||||||
const cipherContainerElement =
|
const cipherContainerElement =
|
||||||
autofillOverlayList["overlayListContainer"].querySelector(".cipher-container");
|
autofillInlineMenuList["inlineMenuListContainer"].querySelector(".cipher-container");
|
||||||
const fillCipherButton = cipherContainerElement.querySelector(".fill-cipher-button");
|
const fillCipherButton = cipherContainerElement.querySelector(".fill-cipher-button");
|
||||||
const viewCipherButton = cipherContainerElement.querySelector(".view-cipher-button");
|
const viewCipherButton = cipherContainerElement.querySelector(".view-cipher-button");
|
||||||
jest.spyOn(fillCipherButton as HTMLElement, "focus");
|
jest.spyOn(fillCipherButton as HTMLElement, "focus");
|
||||||
@@ -247,7 +255,7 @@ describe("AutofillOverlayList", () => {
|
|||||||
|
|
||||||
it("allows the user to move keyboard to the next cipher element on ArrowDown", () => {
|
it("allows the user to move keyboard to the next cipher element on ArrowDown", () => {
|
||||||
const cipherContainerElements =
|
const cipherContainerElements =
|
||||||
autofillOverlayList["overlayListContainer"].querySelectorAll(".cipher-container");
|
autofillInlineMenuList["inlineMenuListContainer"].querySelectorAll(".cipher-container");
|
||||||
const viewCipherButton = cipherContainerElements[0].querySelector(".view-cipher-button");
|
const viewCipherButton = cipherContainerElements[0].querySelector(".view-cipher-button");
|
||||||
const secondFillCipherButton =
|
const secondFillCipherButton =
|
||||||
cipherContainerElements[1].querySelector(".fill-cipher-button");
|
cipherContainerElements[1].querySelector(".fill-cipher-button");
|
||||||
@@ -260,7 +268,7 @@ describe("AutofillOverlayList", () => {
|
|||||||
|
|
||||||
it("allows the user to move keyboard focus to the previous cipher element on ArrowUp", () => {
|
it("allows the user to move keyboard focus to the previous cipher element on ArrowUp", () => {
|
||||||
const cipherContainerElements =
|
const cipherContainerElements =
|
||||||
autofillOverlayList["overlayListContainer"].querySelectorAll(".cipher-container");
|
autofillInlineMenuList["inlineMenuListContainer"].querySelectorAll(".cipher-container");
|
||||||
const viewCipherButton = cipherContainerElements[1].querySelector(".view-cipher-button");
|
const viewCipherButton = cipherContainerElements[1].querySelector(".view-cipher-button");
|
||||||
const firstFillCipherButton =
|
const firstFillCipherButton =
|
||||||
cipherContainerElements[0].querySelector(".fill-cipher-button");
|
cipherContainerElements[0].querySelector(".fill-cipher-button");
|
||||||
@@ -273,7 +281,7 @@ describe("AutofillOverlayList", () => {
|
|||||||
|
|
||||||
it("ignores keyup events that do not include ArrowUp, ArrowDown, or ArrowRight", () => {
|
it("ignores keyup events that do not include ArrowUp, ArrowDown, or ArrowRight", () => {
|
||||||
const viewCipherButton =
|
const viewCipherButton =
|
||||||
autofillOverlayList["overlayListContainer"].querySelector(".view-cipher-button");
|
autofillInlineMenuList["inlineMenuListContainer"].querySelector(".view-cipher-button");
|
||||||
jest.spyOn(viewCipherButton as HTMLElement, "focus");
|
jest.spyOn(viewCipherButton as HTMLElement, "focus");
|
||||||
|
|
||||||
viewCipherButton.dispatchEvent(new KeyboardEvent("keyup", { code: "ArrowRight" }));
|
viewCipherButton.dispatchEvent(new KeyboardEvent("keyup", { code: "ArrowRight" }));
|
||||||
@@ -286,10 +294,10 @@ describe("AutofillOverlayList", () => {
|
|||||||
|
|
||||||
describe("global event listener handlers", () => {
|
describe("global event listener handlers", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
postWindowMessage(createInitAutofillOverlayListMessageMock({ portKey }));
|
postWindowMessage(createInitAutofillInlineMenuListMessageMock({ portKey }));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not post a `checkAutofillInlineMenuButtonFocused` message to the parent if the overlay is currently focused", () => {
|
it("does not post a `checkAutofillInlineMenuButtonFocused` message to the parent if the inline menu is currently focused", () => {
|
||||||
jest.spyOn(globalThis.document, "hasFocus").mockReturnValue(true);
|
jest.spyOn(globalThis.document, "hasFocus").mockReturnValue(true);
|
||||||
|
|
||||||
postWindowMessage({ command: "checkAutofillInlineMenuListFocused" });
|
postWindowMessage({ command: "checkAutofillInlineMenuListFocused" });
|
||||||
@@ -297,7 +305,7 @@ describe("AutofillOverlayList", () => {
|
|||||||
expect(globalThis.parent.postMessage).not.toHaveBeenCalled();
|
expect(globalThis.parent.postMessage).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("posts a `checkAutofillInlineMenuButtonFocused` message to the parent if the overlay is not currently focused", () => {
|
it("posts a `checkAutofillInlineMenuButtonFocused` message to the parent if the inline menu is not currently focused", () => {
|
||||||
jest.spyOn(globalThis.document, "hasFocus").mockReturnValue(false);
|
jest.spyOn(globalThis.document, "hasFocus").mockReturnValue(false);
|
||||||
|
|
||||||
postWindowMessage({ command: "checkAutofillInlineMenuListFocused" });
|
postWindowMessage({ command: "checkAutofillInlineMenuListFocused" });
|
||||||
@@ -309,43 +317,43 @@ describe("AutofillOverlayList", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("updates the list of ciphers", () => {
|
it("updates the list of ciphers", () => {
|
||||||
postWindowMessage(createInitAutofillOverlayListMessageMock());
|
postWindowMessage(createInitAutofillInlineMenuListMessageMock());
|
||||||
const updateCiphersSpy = jest.spyOn(autofillOverlayList as any, "updateListItems");
|
const updateCiphersSpy = jest.spyOn(autofillInlineMenuList as any, "updateListItems");
|
||||||
|
|
||||||
postWindowMessage({ command: "updateAutofillInlineMenuListCiphers" });
|
postWindowMessage({ command: "updateAutofillInlineMenuListCiphers" });
|
||||||
|
|
||||||
expect(updateCiphersSpy).toHaveBeenCalled();
|
expect(updateCiphersSpy).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("directing user focus into the overlay list", () => {
|
describe("directing user focus into the inline menu list", () => {
|
||||||
it("sets ARIA attributes that define the list as a `dialog` to screen reader users", () => {
|
it("sets ARIA attributes that define the list as a `dialog` to screen reader users", () => {
|
||||||
postWindowMessage(
|
postWindowMessage(
|
||||||
createInitAutofillOverlayListMessageMock({
|
createInitAutofillInlineMenuListMessageMock({
|
||||||
authStatus: AuthenticationStatus.Locked,
|
authStatus: AuthenticationStatus.Locked,
|
||||||
cipherList: [],
|
cipherList: [],
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
const overlayContainerSetAttributeSpy = jest.spyOn(
|
const inlineMenuContainerSetAttributeSpy = jest.spyOn(
|
||||||
autofillOverlayList["overlayListContainer"],
|
autofillInlineMenuList["inlineMenuListContainer"],
|
||||||
"setAttribute",
|
"setAttribute",
|
||||||
);
|
);
|
||||||
|
|
||||||
postWindowMessage({ command: "focusInlineMenuList" });
|
postWindowMessage({ command: "focusInlineMenuList" });
|
||||||
|
|
||||||
expect(overlayContainerSetAttributeSpy).toHaveBeenCalledWith("role", "dialog");
|
expect(inlineMenuContainerSetAttributeSpy).toHaveBeenCalledWith("role", "dialog");
|
||||||
expect(overlayContainerSetAttributeSpy).toHaveBeenCalledWith("aria-modal", "true");
|
expect(inlineMenuContainerSetAttributeSpy).toHaveBeenCalledWith("aria-modal", "true");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("focuses the unlock button element if the user is not authenticated", async () => {
|
it("focuses the unlock button element if the user is not authenticated", async () => {
|
||||||
postWindowMessage(
|
postWindowMessage(
|
||||||
createInitAutofillOverlayListMessageMock({
|
createInitAutofillInlineMenuListMessageMock({
|
||||||
authStatus: AuthenticationStatus.Locked,
|
authStatus: AuthenticationStatus.Locked,
|
||||||
cipherList: [],
|
cipherList: [],
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
await flushPromises();
|
await flushPromises();
|
||||||
const unlockButton =
|
const unlockButton =
|
||||||
autofillOverlayList["overlayListContainer"].querySelector("#unlock-button");
|
autofillInlineMenuList["inlineMenuListContainer"].querySelector("#unlock-button");
|
||||||
jest.spyOn(unlockButton as HTMLElement, "focus");
|
jest.spyOn(unlockButton as HTMLElement, "focus");
|
||||||
|
|
||||||
postWindowMessage({ command: "focusInlineMenuList" });
|
postWindowMessage({ command: "focusInlineMenuList" });
|
||||||
@@ -354,10 +362,10 @@ describe("AutofillOverlayList", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("focuses the new item button element if the cipher list is empty", async () => {
|
it("focuses the new item button element if the cipher list is empty", async () => {
|
||||||
postWindowMessage(createInitAutofillOverlayListMessageMock({ ciphers: [] }));
|
postWindowMessage(createInitAutofillInlineMenuListMessageMock({ ciphers: [] }));
|
||||||
await flushPromises();
|
await flushPromises();
|
||||||
const newItemButton =
|
const newItemButton =
|
||||||
autofillOverlayList["overlayListContainer"].querySelector("#new-item-button");
|
autofillInlineMenuList["inlineMenuListContainer"].querySelector("#new-item-button");
|
||||||
jest.spyOn(newItemButton as HTMLElement, "focus");
|
jest.spyOn(newItemButton as HTMLElement, "focus");
|
||||||
|
|
||||||
postWindowMessage({ command: "focusInlineMenuList" });
|
postWindowMessage({ command: "focusInlineMenuList" });
|
||||||
@@ -366,9 +374,9 @@ describe("AutofillOverlayList", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("focuses the first cipher button element if the cipher list is populated", () => {
|
it("focuses the first cipher button element if the cipher list is populated", () => {
|
||||||
postWindowMessage(createInitAutofillOverlayListMessageMock());
|
postWindowMessage(createInitAutofillInlineMenuListMessageMock());
|
||||||
const firstCipherItem =
|
const firstCipherItem =
|
||||||
autofillOverlayList["overlayListContainer"].querySelector(".fill-cipher-button");
|
autofillInlineMenuList["inlineMenuListContainer"].querySelector(".fill-cipher-button");
|
||||||
jest.spyOn(firstCipherItem as HTMLElement, "focus");
|
jest.spyOn(firstCipherItem as HTMLElement, "focus");
|
||||||
|
|
||||||
postWindowMessage({ command: "focusInlineMenuList" });
|
postWindowMessage({ command: "focusInlineMenuList" });
|
||||||
@@ -378,8 +386,8 @@ describe("AutofillOverlayList", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("blur event", () => {
|
describe("blur event", () => {
|
||||||
it("posts a message to the parent window indicating that the overlay has lost focus", () => {
|
it("posts a message to the parent window indicating that the inline menu has lost focus", () => {
|
||||||
postWindowMessage(createInitAutofillOverlayListMessageMock({ portKey }));
|
postWindowMessage(createInitAutofillInlineMenuListMessageMock({ portKey }));
|
||||||
|
|
||||||
globalThis.dispatchEvent(new Event("blur"));
|
globalThis.dispatchEvent(new Event("blur"));
|
||||||
|
|
||||||
@@ -392,7 +400,7 @@ describe("AutofillOverlayList", () => {
|
|||||||
|
|
||||||
describe("keydown event", () => {
|
describe("keydown event", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
postWindowMessage(createInitAutofillOverlayListMessageMock({ portKey }));
|
postWindowMessage(createInitAutofillInlineMenuListMessageMock({ portKey }));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("skips redirecting keyboard focus when a KeyDown event triggers and the key is not a `Tab` or `Escape` key", () => {
|
it("skips redirecting keyboard focus when a KeyDown event triggers and the key is not a `Tab` or `Escape` key", () => {
|
||||||
@@ -401,7 +409,7 @@ describe("AutofillOverlayList", () => {
|
|||||||
expect(globalThis.parent.postMessage).not.toHaveBeenCalled();
|
expect(globalThis.parent.postMessage).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("redirects the overlay focus out to the previous element on KeyDown of the `Tab+Shift` keys", () => {
|
it("redirects the inline menu focus out to the previous element on KeyDown of the `Tab+Shift` keys", () => {
|
||||||
globalThis.document.dispatchEvent(
|
globalThis.document.dispatchEvent(
|
||||||
new KeyboardEvent("keydown", { code: "Tab", shiftKey: true }),
|
new KeyboardEvent("keydown", { code: "Tab", shiftKey: true }),
|
||||||
);
|
);
|
||||||
@@ -412,7 +420,7 @@ describe("AutofillOverlayList", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("redirects the overlay focus out to the next element on KeyDown of the `Tab` key", () => {
|
it("redirects the inline menu focus out to the next element on KeyDown of the `Tab` key", () => {
|
||||||
globalThis.document.dispatchEvent(new KeyboardEvent("keydown", { code: "Tab" }));
|
globalThis.document.dispatchEvent(new KeyboardEvent("keydown", { code: "Tab" }));
|
||||||
|
|
||||||
expect(globalThis.parent.postMessage).toHaveBeenCalledWith(
|
expect(globalThis.parent.postMessage).toHaveBeenCalledWith(
|
||||||
@@ -421,7 +429,7 @@ describe("AutofillOverlayList", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("redirects the overlay focus out to the current element on KeyDown of the `Escape` key", () => {
|
it("redirects the inline menu focus out to the current element on KeyDown of the `Escape` key", () => {
|
||||||
globalThis.document.dispatchEvent(new KeyboardEvent("keydown", { code: "Escape" }));
|
globalThis.document.dispatchEvent(new KeyboardEvent("keydown", { code: "Escape" }));
|
||||||
|
|
||||||
expect(globalThis.parent.postMessage).toHaveBeenCalledWith(
|
expect(globalThis.parent.postMessage).toHaveBeenCalledWith(
|
||||||
@@ -434,10 +442,10 @@ describe("AutofillOverlayList", () => {
|
|||||||
|
|
||||||
describe("handleResizeObserver", () => {
|
describe("handleResizeObserver", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
postWindowMessage(createInitAutofillOverlayListMessageMock({ portKey }));
|
postWindowMessage(createInitAutofillInlineMenuListMessageMock({ portKey }));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("ignores resize entries whose target is not the overlay list", () => {
|
it("ignores resize entries whose target is not the inline menu list", () => {
|
||||||
const entries = [
|
const entries = [
|
||||||
{
|
{
|
||||||
target: mock<HTMLElement>(),
|
target: mock<HTMLElement>(),
|
||||||
@@ -445,20 +453,20 @@ describe("AutofillOverlayList", () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
autofillOverlayList["handleResizeObserver"](entries as unknown as ResizeObserverEntry[]);
|
autofillInlineMenuList["handleResizeObserver"](entries as unknown as ResizeObserverEntry[]);
|
||||||
|
|
||||||
expect(globalThis.parent.postMessage).not.toHaveBeenCalled();
|
expect(globalThis.parent.postMessage).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("posts a message to update the overlay list height if the list container is resized", () => {
|
it("posts a message to update the inline menu list height if the list container is resized", () => {
|
||||||
const entries = [
|
const entries = [
|
||||||
{
|
{
|
||||||
target: autofillOverlayList["overlayListContainer"],
|
target: autofillInlineMenuList["inlineMenuListContainer"],
|
||||||
contentRect: { height: 300 },
|
contentRect: { height: 300 },
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
autofillOverlayList["handleResizeObserver"](entries as unknown as ResizeObserverEntry[]);
|
autofillInlineMenuList["handleResizeObserver"](entries as unknown as ResizeObserverEntry[]);
|
||||||
|
|
||||||
expect(globalThis.parent.postMessage).toHaveBeenCalledWith(
|
expect(globalThis.parent.postMessage).toHaveBeenCalledWith(
|
||||||
{ command: "updateAutofillInlineMenuListHeight", styles: { height: "300px" }, portKey },
|
{ command: "updateAutofillInlineMenuListHeight", styles: { height: "300px" }, portKey },
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
import { AutofillInlineMenuPageElement } from "../shared/autofill-inline-menu-page-element";
|
import { AutofillInlineMenuPageElement } from "../shared/autofill-inline-menu-page-element";
|
||||||
|
|
||||||
export class AutofillInlineMenuList extends AutofillInlineMenuPageElement {
|
export class AutofillInlineMenuList extends AutofillInlineMenuPageElement {
|
||||||
private overlayListContainer: HTMLDivElement;
|
private inlineMenuListContainer: HTMLDivElement;
|
||||||
private resizeObserver: ResizeObserver;
|
private resizeObserver: ResizeObserver;
|
||||||
private eventHandlersMemo: { [key: string]: EventListener } = {};
|
private eventHandlersMemo: { [key: string]: EventListener } = {};
|
||||||
private ciphers: OverlayCipherData[] = [];
|
private ciphers: OverlayCipherData[] = [];
|
||||||
@@ -22,7 +22,8 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement {
|
|||||||
private cipherListScrollDebounceTimeout: number | NodeJS.Timeout;
|
private cipherListScrollDebounceTimeout: number | NodeJS.Timeout;
|
||||||
private currentCipherIndex = 0;
|
private currentCipherIndex = 0;
|
||||||
private readonly showCiphersPerPage = 6;
|
private readonly showCiphersPerPage = 6;
|
||||||
private readonly overlayListWindowMessageHandlers: AutofillInlineMenuListWindowMessageHandlers = {
|
private readonly inlineMenuListWindowMessageHandlers: AutofillInlineMenuListWindowMessageHandlers =
|
||||||
|
{
|
||||||
initAutofillInlineMenuList: ({ message }) => this.initAutofillInlineMenuList(message),
|
initAutofillInlineMenuList: ({ message }) => this.initAutofillInlineMenuList(message),
|
||||||
checkAutofillInlineMenuListFocused: () => this.checkInlineMenuListFocused(),
|
checkAutofillInlineMenuListFocused: () => this.checkInlineMenuListFocused(),
|
||||||
updateAutofillInlineMenuListCiphers: ({ message }) => this.updateListItems(message.ciphers),
|
updateAutofillInlineMenuListCiphers: ({ message }) => this.updateListItems(message.ciphers),
|
||||||
@@ -32,18 +33,18 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement {
|
|||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.setupOverlayListGlobalListeners();
|
this.setupInlineMenuListGlobalListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the overlay list and updates the list items with the passed ciphers.
|
* Initializes the inline menu list and updates the list items with the passed ciphers.
|
||||||
* If the auth status is not `Unlocked`, the locked overlay is built.
|
* If the auth status is not `Unlocked`, the locked inline menu is built.
|
||||||
*
|
*
|
||||||
* @param translations - The translations to use for the overlay list.
|
* @param translations - The translations to use for the inline menu list.
|
||||||
* @param styleSheetUrl - The URL of the stylesheet to use for the overlay list.
|
* @param styleSheetUrl - The URL of the stylesheet to use for the inline menu list.
|
||||||
* @param theme - The theme to use for the overlay list.
|
* @param theme - The theme to use for the inline menu list.
|
||||||
* @param authStatus - The current authentication status.
|
* @param authStatus - The current authentication status.
|
||||||
* @param ciphers - The ciphers to display in the overlay list.
|
* @param ciphers - The ciphers to display in the inline menu list.
|
||||||
* @param portKey - Background generated key that allows the port to communicate with the background.
|
* @param portKey - Background generated key that allows the port to communicate with the background.
|
||||||
*/
|
*/
|
||||||
private async initAutofillInlineMenuList({
|
private async initAutofillInlineMenuList({
|
||||||
@@ -64,34 +65,34 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement {
|
|||||||
const themeClass = `theme_${theme}`;
|
const themeClass = `theme_${theme}`;
|
||||||
globalThis.document.documentElement.classList.add(themeClass);
|
globalThis.document.documentElement.classList.add(themeClass);
|
||||||
|
|
||||||
this.overlayListContainer = globalThis.document.createElement("div");
|
this.inlineMenuListContainer = globalThis.document.createElement("div");
|
||||||
this.overlayListContainer.classList.add("overlay-list-container", themeClass);
|
this.inlineMenuListContainer.classList.add("inline-menu-list-container", themeClass);
|
||||||
this.resizeObserver.observe(this.overlayListContainer);
|
this.resizeObserver.observe(this.inlineMenuListContainer);
|
||||||
|
|
||||||
this.shadowDom.append(linkElement, this.overlayListContainer);
|
this.shadowDom.append(linkElement, this.inlineMenuListContainer);
|
||||||
|
|
||||||
if (authStatus === AuthenticationStatus.Unlocked) {
|
if (authStatus === AuthenticationStatus.Unlocked) {
|
||||||
this.updateListItems(ciphers);
|
this.updateListItems(ciphers);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.buildLockedOverlay();
|
this.buildLockedInlineMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds the locked overlay, which is displayed when the user is not authenticated.
|
* Builds the locked inline menu, which is displayed when the user is not authenticated.
|
||||||
* Facilitates the ability to unlock the extension from the overlay.
|
* Facilitates the ability to unlock the extension from the inline menu.
|
||||||
*/
|
*/
|
||||||
private buildLockedOverlay() {
|
private buildLockedInlineMenu() {
|
||||||
const lockedOverlay = globalThis.document.createElement("div");
|
const lockedInlineMenu = globalThis.document.createElement("div");
|
||||||
lockedOverlay.id = "locked-overlay-description";
|
lockedInlineMenu.id = "locked-inline-menu-description";
|
||||||
lockedOverlay.classList.add("locked-overlay", "overlay-list-message");
|
lockedInlineMenu.classList.add("locked-inline-menu", "inline-menu-list-message");
|
||||||
lockedOverlay.textContent = this.getTranslation("unlockYourAccount");
|
lockedInlineMenu.textContent = this.getTranslation("unlockYourAccount");
|
||||||
|
|
||||||
const unlockButtonElement = globalThis.document.createElement("button");
|
const unlockButtonElement = globalThis.document.createElement("button");
|
||||||
unlockButtonElement.id = "unlock-button";
|
unlockButtonElement.id = "unlock-button";
|
||||||
unlockButtonElement.tabIndex = -1;
|
unlockButtonElement.tabIndex = -1;
|
||||||
unlockButtonElement.classList.add("unlock-button", "overlay-list-button");
|
unlockButtonElement.classList.add("unlock-button", "inline-menu-list-button");
|
||||||
unlockButtonElement.textContent = this.getTranslation("unlockAccount");
|
unlockButtonElement.textContent = this.getTranslation("unlockAccount");
|
||||||
unlockButtonElement.setAttribute(
|
unlockButtonElement.setAttribute(
|
||||||
"aria-label",
|
"aria-label",
|
||||||
@@ -100,11 +101,11 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement {
|
|||||||
unlockButtonElement.prepend(buildSvgDomElement(lockIcon));
|
unlockButtonElement.prepend(buildSvgDomElement(lockIcon));
|
||||||
unlockButtonElement.addEventListener(EVENTS.CLICK, this.handleUnlockButtonClick);
|
unlockButtonElement.addEventListener(EVENTS.CLICK, this.handleUnlockButtonClick);
|
||||||
|
|
||||||
const overlayListButtonContainer = globalThis.document.createElement("div");
|
const inlineMenuListButtonContainer = globalThis.document.createElement("div");
|
||||||
overlayListButtonContainer.classList.add("overlay-list-button-container");
|
inlineMenuListButtonContainer.classList.add("inline-menu-list-button-container");
|
||||||
overlayListButtonContainer.appendChild(unlockButtonElement);
|
inlineMenuListButtonContainer.appendChild(unlockButtonElement);
|
||||||
|
|
||||||
this.overlayListContainer.append(lockedOverlay, overlayListButtonContainer);
|
this.inlineMenuListContainer.append(lockedInlineMenu, inlineMenuListButtonContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -117,45 +118,45 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the list items with the passed ciphers.
|
* Updates the list items with the passed ciphers.
|
||||||
* If no ciphers are passed, the no results overlay is built.
|
* If no ciphers are passed, the no results inline menu is built.
|
||||||
*
|
*
|
||||||
* @param ciphers - The ciphers to display in the overlay list.
|
* @param ciphers - The ciphers to display in the inline menu list.
|
||||||
*/
|
*/
|
||||||
private updateListItems(ciphers: OverlayCipherData[]) {
|
private updateListItems(ciphers: OverlayCipherData[]) {
|
||||||
this.ciphers = ciphers;
|
this.ciphers = ciphers;
|
||||||
this.currentCipherIndex = 0;
|
this.currentCipherIndex = 0;
|
||||||
if (this.overlayListContainer) {
|
if (this.inlineMenuListContainer) {
|
||||||
this.overlayListContainer.innerHTML = "";
|
this.inlineMenuListContainer.innerHTML = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ciphers?.length) {
|
if (!ciphers?.length) {
|
||||||
this.buildNoResultsOverlayList();
|
this.buildNoResultsInlineMenuList();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.ciphersList = globalThis.document.createElement("ul");
|
this.ciphersList = globalThis.document.createElement("ul");
|
||||||
this.ciphersList.classList.add("overlay-actions-list");
|
this.ciphersList.classList.add("inline-menu-list-actions");
|
||||||
this.ciphersList.setAttribute("role", "list");
|
this.ciphersList.setAttribute("role", "list");
|
||||||
globalThis.addEventListener(EVENTS.SCROLL, this.handleCiphersListScrollEvent);
|
globalThis.addEventListener(EVENTS.SCROLL, this.handleCiphersListScrollEvent);
|
||||||
|
|
||||||
this.loadPageOfCiphers();
|
this.loadPageOfCiphers();
|
||||||
|
|
||||||
this.overlayListContainer.appendChild(this.ciphersList);
|
this.inlineMenuListContainer.appendChild(this.ciphersList);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Overlay view that is presented when no ciphers are found for a given page.
|
* Inline menu view that is presented when no ciphers are found for a given page.
|
||||||
* Facilitates the ability to add a new vault item from the overlay.
|
* Facilitates the ability to add a new vault item from the inline menu.
|
||||||
*/
|
*/
|
||||||
private buildNoResultsOverlayList() {
|
private buildNoResultsInlineMenuList() {
|
||||||
const noItemsMessage = globalThis.document.createElement("div");
|
const noItemsMessage = globalThis.document.createElement("div");
|
||||||
noItemsMessage.classList.add("no-items", "overlay-list-message");
|
noItemsMessage.classList.add("no-items", "inline-menu-list-message");
|
||||||
noItemsMessage.textContent = this.getTranslation("noItemsToShow");
|
noItemsMessage.textContent = this.getTranslation("noItemsToShow");
|
||||||
|
|
||||||
const newItemButton = globalThis.document.createElement("button");
|
const newItemButton = globalThis.document.createElement("button");
|
||||||
newItemButton.tabIndex = -1;
|
newItemButton.tabIndex = -1;
|
||||||
newItemButton.id = "new-item-button";
|
newItemButton.id = "new-item-button";
|
||||||
newItemButton.classList.add("add-new-item-button", "overlay-list-button");
|
newItemButton.classList.add("add-new-item-button", "inline-menu-list-button");
|
||||||
newItemButton.textContent = this.getTranslation("newItem");
|
newItemButton.textContent = this.getTranslation("newItem");
|
||||||
newItemButton.setAttribute(
|
newItemButton.setAttribute(
|
||||||
"aria-label",
|
"aria-label",
|
||||||
@@ -164,11 +165,11 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement {
|
|||||||
newItemButton.prepend(buildSvgDomElement(plusIcon));
|
newItemButton.prepend(buildSvgDomElement(plusIcon));
|
||||||
newItemButton.addEventListener(EVENTS.CLICK, this.handeNewItemButtonClick);
|
newItemButton.addEventListener(EVENTS.CLICK, this.handeNewItemButtonClick);
|
||||||
|
|
||||||
const overlayListButtonContainer = globalThis.document.createElement("div");
|
const inlineMenuListButtonContainer = globalThis.document.createElement("div");
|
||||||
overlayListButtonContainer.classList.add("overlay-list-button-container");
|
inlineMenuListButtonContainer.classList.add("inline-menu-list-button-container");
|
||||||
overlayListButtonContainer.appendChild(newItemButton);
|
inlineMenuListButtonContainer.appendChild(newItemButton);
|
||||||
|
|
||||||
this.overlayListContainer.append(noItemsMessage, overlayListButtonContainer);
|
this.inlineMenuListContainer.append(noItemsMessage, inlineMenuListButtonContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -180,7 +181,7 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads a page of ciphers into the overlay list container.
|
* Loads a page of ciphers into the inline menu list container.
|
||||||
*/
|
*/
|
||||||
private loadPageOfCiphers() {
|
private loadPageOfCiphers() {
|
||||||
const lastIndex = Math.min(
|
const lastIndex = Math.min(
|
||||||
@@ -188,7 +189,7 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement {
|
|||||||
this.ciphers.length,
|
this.ciphers.length,
|
||||||
);
|
);
|
||||||
for (let cipherIndex = this.currentCipherIndex; cipherIndex < lastIndex; cipherIndex++) {
|
for (let cipherIndex = this.currentCipherIndex; cipherIndex < lastIndex; cipherIndex++) {
|
||||||
this.ciphersList.appendChild(this.buildOverlayActionsListItem(this.ciphers[cipherIndex]));
|
this.ciphersList.appendChild(this.buildInlineMenuListActionsItem(this.ciphers[cipherIndex]));
|
||||||
this.currentCipherIndex++;
|
this.currentCipherIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,7 +231,7 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement {
|
|||||||
*
|
*
|
||||||
* @param cipher - The cipher to build the list item for.
|
* @param cipher - The cipher to build the list item for.
|
||||||
*/
|
*/
|
||||||
private buildOverlayActionsListItem(cipher: OverlayCipherData) {
|
private buildInlineMenuListActionsItem(cipher: OverlayCipherData) {
|
||||||
const fillCipherElement = this.buildFillCipherElement(cipher);
|
const fillCipherElement = this.buildFillCipherElement(cipher);
|
||||||
const viewCipherElement = this.buildViewCipherElement(cipher);
|
const viewCipherElement = this.buildViewCipherElement(cipher);
|
||||||
|
|
||||||
@@ -238,12 +239,12 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement {
|
|||||||
cipherContainerElement.classList.add("cipher-container");
|
cipherContainerElement.classList.add("cipher-container");
|
||||||
cipherContainerElement.append(fillCipherElement, viewCipherElement);
|
cipherContainerElement.append(fillCipherElement, viewCipherElement);
|
||||||
|
|
||||||
const overlayActionsListItem = globalThis.document.createElement("li");
|
const inlineMenuListActionsItem = globalThis.document.createElement("li");
|
||||||
overlayActionsListItem.setAttribute("role", "listitem");
|
inlineMenuListActionsItem.setAttribute("role", "listitem");
|
||||||
overlayActionsListItem.classList.add("overlay-actions-list-item");
|
inlineMenuListActionsItem.classList.add("inline-menu-list-actions-item");
|
||||||
overlayActionsListItem.appendChild(cipherContainerElement);
|
inlineMenuListActionsItem.appendChild(cipherContainerElement);
|
||||||
|
|
||||||
return overlayActionsListItem;
|
return inlineMenuListActionsItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -306,7 +307,7 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement {
|
|||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
const currentListItem = event.target.closest(".overlay-actions-list-item") as HTMLElement;
|
const currentListItem = event.target.closest(".inline-menu-list-actions-item") as HTMLElement;
|
||||||
if (event.code === "ArrowDown") {
|
if (event.code === "ArrowDown") {
|
||||||
this.focusNextListItem(currentListItem);
|
this.focusNextListItem(currentListItem);
|
||||||
return;
|
return;
|
||||||
@@ -369,7 +370,7 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement {
|
|||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
const currentListItem = event.target.closest(".overlay-actions-list-item") as HTMLElement;
|
const currentListItem = event.target.closest(".inline-menu-list-actions-item") as HTMLElement;
|
||||||
const cipherContainer = currentListItem.querySelector(".cipher-container") as HTMLElement;
|
const cipherContainer = currentListItem.querySelector(".cipher-container") as HTMLElement;
|
||||||
cipherContainer?.classList.remove("remove-outline");
|
cipherContainer?.classList.remove("remove-outline");
|
||||||
if (event.code === "ArrowDown") {
|
if (event.code === "ArrowDown") {
|
||||||
@@ -484,7 +485,7 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates whether the overlay list iframe is currently focused.
|
* Validates whether the inline menu list iframe is currently focused.
|
||||||
* If not focused, will check if the button element is focused.
|
* If not focused, will check if the button element is focused.
|
||||||
*/
|
*/
|
||||||
private checkInlineMenuListFocused() {
|
private checkInlineMenuListFocused() {
|
||||||
@@ -496,15 +497,15 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Focuses the overlay list iframe. The element that receives focus is
|
* Focuses the inline menu list iframe. The element that receives focus is
|
||||||
* determined by the presence of the unlock button, new item button, or
|
* determined by the presence of the unlock button, new item button, or
|
||||||
* the first cipher button.
|
* the first cipher button.
|
||||||
*/
|
*/
|
||||||
private focusInlineMenuList() {
|
private focusInlineMenuList() {
|
||||||
this.overlayListContainer.setAttribute("role", "dialog");
|
this.inlineMenuListContainer.setAttribute("role", "dialog");
|
||||||
this.overlayListContainer.setAttribute("aria-modal", "true");
|
this.inlineMenuListContainer.setAttribute("aria-modal", "true");
|
||||||
|
|
||||||
const unlockButtonElement = this.overlayListContainer.querySelector(
|
const unlockButtonElement = this.inlineMenuListContainer.querySelector(
|
||||||
"#unlock-button",
|
"#unlock-button",
|
||||||
) as HTMLElement;
|
) as HTMLElement;
|
||||||
if (unlockButtonElement) {
|
if (unlockButtonElement) {
|
||||||
@@ -512,7 +513,7 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newItemButtonElement = this.overlayListContainer.querySelector(
|
const newItemButtonElement = this.inlineMenuListContainer.querySelector(
|
||||||
"#new-item-button",
|
"#new-item-button",
|
||||||
) as HTMLElement;
|
) as HTMLElement;
|
||||||
if (newItemButtonElement) {
|
if (newItemButtonElement) {
|
||||||
@@ -520,31 +521,31 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const firstCipherElement = this.overlayListContainer.querySelector(
|
const firstCipherElement = this.inlineMenuListContainer.querySelector(
|
||||||
".fill-cipher-button",
|
".fill-cipher-button",
|
||||||
) as HTMLElement;
|
) as HTMLElement;
|
||||||
firstCipherElement?.focus();
|
firstCipherElement?.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets up the global listeners for the overlay list iframe.
|
* Sets up the global listeners for the inline menu list iframe.
|
||||||
*/
|
*/
|
||||||
private setupOverlayListGlobalListeners() {
|
private setupInlineMenuListGlobalListeners() {
|
||||||
this.setupGlobalListeners(this.overlayListWindowMessageHandlers);
|
this.setupGlobalListeners(this.inlineMenuListWindowMessageHandlers);
|
||||||
|
|
||||||
this.resizeObserver = new ResizeObserver(this.handleResizeObserver);
|
this.resizeObserver = new ResizeObserver(this.handleResizeObserver);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the resize observer event. Facilitates updating the height of the
|
* Handles the resize observer event. Facilitates updating the height of the
|
||||||
* overlay list iframe when the height of the list changes.
|
* inline menu list iframe when the height of the list changes.
|
||||||
*
|
*
|
||||||
* @param entries - The resize observer entries.
|
* @param entries - The resize observer entries.
|
||||||
*/
|
*/
|
||||||
private handleResizeObserver = (entries: ResizeObserverEntry[]) => {
|
private handleResizeObserver = (entries: ResizeObserverEntry[]) => {
|
||||||
for (let entryIndex = 0; entryIndex < entries.length; entryIndex++) {
|
for (let entryIndex = 0; entryIndex < entries.length; entryIndex++) {
|
||||||
const entry = entries[entryIndex];
|
const entry = entries[entryIndex];
|
||||||
if (entry.target !== this.overlayListContainer) {
|
if (entry.target !== this.inlineMenuListContainer) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -568,7 +569,7 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Focuses the next list item in the overlay list. If the current list item is the last
|
* Focuses the next list item in the inline menu list. If the current list item is the last
|
||||||
* item in the list, the first item is focused.
|
* item in the list, the first item is focused.
|
||||||
*
|
*
|
||||||
* @param currentListItem - The current list item.
|
* @param currentListItem - The current list item.
|
||||||
@@ -587,7 +588,7 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Focuses the previous list item in the overlay list. If the current list item is the first
|
* Focuses the previous list item in the inline menu list. If the current list item is the first
|
||||||
* item in the list, the last item is focused.
|
* item in the list, the last item is focused.
|
||||||
*
|
*
|
||||||
* @param currentListItem - The current list item.
|
* @param currentListItem - The current list item.
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.overlay-list-message {
|
.inline-menu-list-message {
|
||||||
font-family: $font-family-sans-serif;
|
font-family: $font-family-sans-serif;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: 1.4rem;
|
font-size: 1.4rem;
|
||||||
@@ -39,7 +39,7 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.overlay-list-button-container {
|
.inline-menu-list-button-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0.2rem;
|
padding: 0.2rem;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
@@ -58,7 +58,7 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.overlay-list-button {
|
.inline-menu-list-button {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-content: center;
|
align-content: center;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
@@ -116,12 +116,12 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.overlay-actions-list {
|
.inline-menu-list-actions {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.overlay-actions-list-item {
|
.inline-menu-list-actions-item {
|
||||||
transition: background-color 0.2s ease-in-out;
|
transition: background-color 0.2s ease-in-out;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding: 0.2rem;
|
padding: 0.2rem;
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ function createAutofillOverlayCipherDataMock(index: number, customFields = {}):
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function createInitAutofillOverlayListMessageMock(
|
function createInitAutofillInlineMenuListMessageMock(
|
||||||
customFields = {},
|
customFields = {},
|
||||||
): InitAutofillInlineMenuListMessage {
|
): InitAutofillInlineMenuListMessage {
|
||||||
return {
|
return {
|
||||||
@@ -298,7 +298,7 @@ export {
|
|||||||
createGenerateFillScriptOptionsMock,
|
createGenerateFillScriptOptionsMock,
|
||||||
createAutofillScriptMock,
|
createAutofillScriptMock,
|
||||||
createInitAutofillInlineMenuButtonMessageMock,
|
createInitAutofillInlineMenuButtonMessageMock,
|
||||||
createInitAutofillOverlayListMessageMock,
|
createInitAutofillInlineMenuListMessageMock,
|
||||||
createFocusedFieldDataMock,
|
createFocusedFieldDataMock,
|
||||||
createPortSpyMock,
|
createPortSpyMock,
|
||||||
createMutationRecordMock,
|
createMutationRecordMock,
|
||||||
|
|||||||
Reference in New Issue
Block a user