1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-15 15:53:27 +00:00

[PM-5189] Implementing jest tests for the OverlayBackground

This commit is contained in:
Cesar Gonzalez
2024-06-05 10:24:20 -05:00
parent 81e2cd3e1f
commit d3ba598a45
3 changed files with 220 additions and 14 deletions

View File

@@ -30,6 +30,13 @@ export type WebsiteIconData = {
icon: string; icon: string;
}; };
export type FocusedFieldData = {
focusedFieldStyles: Partial<CSSStyleDeclaration>;
focusedFieldRects: Partial<DOMRect>;
tabId?: number;
frameId?: number;
};
export type OverlayAddNewItemMessage = { export type OverlayAddNewItemMessage = {
login?: { login?: {
uri?: string; uri?: string;
@@ -39,11 +46,9 @@ export type OverlayAddNewItemMessage = {
}; };
}; };
export type FocusedFieldData = { export type CloseInlineMenuMessage = {
focusedFieldStyles: Partial<CSSStyleDeclaration>; forceCloseAutofillInlineMenu?: boolean;
focusedFieldRects: Partial<DOMRect>; overlayElement?: string;
tabId?: number;
frameId?: number;
}; };
export type OverlayBackgroundExtensionMessage = { export type OverlayBackgroundExtensionMessage = {
@@ -52,8 +57,6 @@ export type OverlayBackgroundExtensionMessage = {
tab?: chrome.tabs.Tab; tab?: chrome.tabs.Tab;
sender?: string; sender?: string;
details?: AutofillPageDetails; details?: AutofillPageDetails;
overlayElement?: string;
forceCloseAutofillInlineMenu?: boolean;
isAutofillInlineMenuHidden?: boolean; isAutofillInlineMenuHidden?: boolean;
setTransparentInlineMenu?: boolean; setTransparentInlineMenu?: boolean;
isFieldCurrentlyFocused?: boolean; isFieldCurrentlyFocused?: boolean;
@@ -62,7 +65,8 @@ export type OverlayBackgroundExtensionMessage = {
focusedFieldData?: FocusedFieldData; focusedFieldData?: FocusedFieldData;
styles?: Partial<CSSStyleDeclaration>; styles?: Partial<CSSStyleDeclaration>;
data?: LockedVaultPendingNotificationsData; data?: LockedVaultPendingNotificationsData;
} & OverlayAddNewItemMessage; } & OverlayAddNewItemMessage &
CloseInlineMenuMessage;
export type OverlayPortMessage = { export type OverlayPortMessage = {
[key: string]: any; [key: string]: any;
@@ -116,7 +120,9 @@ export type OverlayBackgroundExtensionMessageHandlers = {
rebuildSubFrameOffsets: ({ sender }: BackgroundSenderParam) => void; rebuildSubFrameOffsets: ({ sender }: BackgroundSenderParam) => void;
collectPageDetailsResponse: ({ message, sender }: BackgroundOnMessageHandlerParams) => void; collectPageDetailsResponse: ({ message, sender }: BackgroundOnMessageHandlerParams) => void;
unlockCompleted: ({ message }: BackgroundMessageParam) => void; unlockCompleted: ({ message }: BackgroundMessageParam) => void;
addedCipher: () => void;
addEditCipherSubmitted: () => void; addEditCipherSubmitted: () => void;
editedCipher: () => void;
deletedCipher: () => void; deletedCipher: () => void;
}; };

View File

@@ -742,7 +742,7 @@ describe("OverlayBackground", () => {
}); });
}); });
describe("openAutofillInlineMenu", () => { describe("openAutofillInlineMenu message handler", () => {
let sender: chrome.runtime.MessageSender; let sender: chrome.runtime.MessageSender;
beforeEach(() => { beforeEach(() => {
@@ -787,6 +787,161 @@ describe("OverlayBackground", () => {
); );
}); });
}); });
describe("closeAutofillInlineMenu", () => {
let sender: chrome.runtime.MessageSender;
beforeEach(() => {
sender = mock<chrome.runtime.MessageSender>({ tab: { id: 1 } });
sendMockExtensionMessage({
command: "updateIsFieldCurrentlyFilling",
isFieldCurrentlyFilling: false,
});
sendMockExtensionMessage({
command: "updateIsFieldCurrentlyFocused",
isFieldCurrentlyFocused: false,
});
});
it("sends a message to close the inline menu without checking field focus state if forcing the closure", async () => {
sendMockExtensionMessage({
command: "updateIsFieldCurrentlyFocused",
isFieldCurrentlyFocused: true,
});
await flushPromises();
sendMockExtensionMessage(
{
command: "closeAutofillInlineMenu",
forceCloseAutofillInlineMenu: true,
overlayElement: AutofillOverlayElement.Button,
},
sender,
);
await flushPromises();
expect(tabsSendMessageSpy).toHaveBeenCalledWith(
sender.tab,
{
command: "closeInlineMenu",
overlayElement: AutofillOverlayElement.Button,
},
{ frameId: 0 },
);
});
it("skips sending a message to close the inline menu if a form field is currently focused", async () => {
sendMockExtensionMessage({
command: "updateIsFieldCurrentlyFocused",
isFieldCurrentlyFocused: true,
});
await flushPromises();
sendMockExtensionMessage(
{
command: "closeAutofillInlineMenu",
forceCloseAutofillInlineMenu: false,
overlayElement: AutofillOverlayElement.Button,
},
sender,
);
await flushPromises();
expect(tabsSendMessageSpy).not.toHaveBeenCalled();
});
it("sends a message to close the inline menu list only if the field is currently filling", async () => {
sendMockExtensionMessage({
command: "updateIsFieldCurrentlyFilling",
isFieldCurrentlyFilling: true,
});
await flushPromises();
sendMockExtensionMessage({ command: "closeAutofillInlineMenu" }, sender);
await flushPromises();
expect(tabsSendMessageSpy).toHaveBeenCalledWith(
sender.tab,
{
command: "closeInlineMenu",
overlayElement: AutofillOverlayElement.List,
},
{ frameId: 0 },
);
expect(tabsSendMessageSpy).not.toHaveBeenCalledWith(
sender.tab,
{
command: "closeInlineMenu",
overlayElement: AutofillOverlayElement.Button,
},
{ frameId: 0 },
);
});
it("sends a message to close the inline menu if the form field is not focused and not filling", async () => {
sendMockExtensionMessage({ command: "closeAutofillInlineMenu" }, sender);
await flushPromises();
expect(tabsSendMessageSpy).toHaveBeenCalledWith(
sender.tab,
{
command: "closeInlineMenu",
overlayElement: undefined,
},
{ frameId: 0 },
);
});
});
describe("checkAutofillInlineMenuFocused message handler", () => {
beforeEach(async () => {
await initOverlayElementPorts();
});
it("will check if the inline menu list is focused if the list port is open", () => {
sendMockExtensionMessage({ command: "checkAutofillInlineMenuFocused" });
expect(listPortSpy.postMessage).toHaveBeenCalledWith({
command: "checkAutofillInlineMenuListFocused",
});
expect(buttonPortSpy.postMessage).not.toHaveBeenCalledWith({
command: "checkAutofillInlineMenuButtonFocused",
});
});
it("will check if the overlay button is focused if the list port is not open", () => {
overlayBackground["inlineMenuListPort"] = undefined;
sendMockExtensionMessage({ command: "checkAutofillInlineMenuFocused" });
expect(buttonPortSpy.postMessage).toHaveBeenCalledWith({
command: "checkAutofillInlineMenuButtonFocused",
});
expect(listPortSpy.postMessage).not.toHaveBeenCalledWith({
command: "checkAutofillInlineMenuListFocused",
});
});
});
describe("extension messages that trigger an update of the inline menu ciphers", () => {
const extensionMessages = [
"addedCipher",
"addEditCipherSubmitted",
"editedCipher",
"deletedCipher",
];
beforeEach(() => {
jest.spyOn(overlayBackground, "updateOverlayCiphers").mockImplementation();
});
extensionMessages.forEach((message) => {
it(`triggers an update of the overlay ciphers when the ${message} message is received`, () => {
sendMockExtensionMessage({ command: message });
expect(overlayBackground.updateOverlayCiphers).toHaveBeenCalled();
});
});
});
}); });
describe("inline menu button message handlers", () => {}); describe("inline menu button message handlers", () => {});

View File

@@ -43,6 +43,7 @@ import {
PageDetailsForTab, PageDetailsForTab,
SubFrameOffsetData, SubFrameOffsetData,
SubFrameOffsetsForTab, SubFrameOffsetsForTab,
CloseInlineMenuMessage,
} from "./abstractions/overlay.background"; } from "./abstractions/overlay.background";
export class OverlayBackground implements OverlayBackgroundInterface { export class OverlayBackground implements OverlayBackgroundInterface {
@@ -91,7 +92,9 @@ export class OverlayBackground implements OverlayBackgroundInterface {
rebuildSubFrameOffsets: ({ sender }) => this.rebuildSubFrameOffsets(sender), rebuildSubFrameOffsets: ({ sender }) => this.rebuildSubFrameOffsets(sender),
collectPageDetailsResponse: ({ message, sender }) => this.storePageDetails(message, sender), collectPageDetailsResponse: ({ message, sender }) => this.storePageDetails(message, sender),
unlockCompleted: ({ message }) => this.unlockCompleted(message), unlockCompleted: ({ message }) => this.unlockCompleted(message),
addedCipher: () => this.updateOverlayCiphers(),
addEditCipherSubmitted: () => this.updateOverlayCiphers(), addEditCipherSubmitted: () => this.updateOverlayCiphers(),
editedCipher: () => this.updateOverlayCiphers(),
deletedCipher: () => this.updateOverlayCiphers(), deletedCipher: () => this.updateOverlayCiphers(),
}; };
private readonly inlineMenuButtonPortMessageHandlers: InlineMenuButtonPortMessageHandlers = { private readonly inlineMenuButtonPortMessageHandlers: InlineMenuButtonPortMessageHandlers = {
@@ -470,10 +473,7 @@ export class OverlayBackground implements OverlayBackgroundInterface {
*/ */
private closeInlineMenu( private closeInlineMenu(
sender: chrome.runtime.MessageSender, sender: chrome.runtime.MessageSender,
{ { forceCloseAutofillInlineMenu, overlayElement }: CloseInlineMenuMessage = {},
forceCloseAutofillInlineMenu,
overlayElement,
}: { forceCloseAutofillInlineMenu?: boolean; overlayElement?: string } = {},
) { ) {
if (forceCloseAutofillInlineMenu) { if (forceCloseAutofillInlineMenu) {
void BrowserApi.tabSendMessage( void BrowserApi.tabSendMessage(
@@ -507,6 +507,13 @@ export class OverlayBackground implements OverlayBackgroundInterface {
); );
} }
/**
* Sends a message to the sender tab to trigger a delayed closure of the inline menu.
* This is used to ensure that we capture click events on the inline menu in the case
* that some on page programmatic method attempts to force focus redirection.
*
* @param sender - The sender of the port message
*/
private triggerDelayedInlineMenuClosure(sender: chrome.runtime.MessageSender) { private triggerDelayedInlineMenuClosure(sender: chrome.runtime.MessageSender) {
if (this.isFieldCurrentlyFocused) { if (this.isFieldCurrentlyFocused) {
return; return;
@@ -589,6 +596,10 @@ export class OverlayBackground implements OverlayBackgroundInterface {
}); });
} }
/**
* Handles updating the opacity of both the inline menu button and list.
* This is used to simultaneously fade in the inline menu elements.
*/
private setInlineMenuFadeInTimeout() { private setInlineMenuFadeInTimeout() {
if (this.inlineMenuFadeInTimeout) { if (this.inlineMenuFadeInTimeout) {
globalThis.clearTimeout(this.inlineMenuFadeInTimeout); globalThis.clearTimeout(this.inlineMenuFadeInTimeout);
@@ -598,7 +609,7 @@ export class OverlayBackground implements OverlayBackgroundInterface {
this.inlineMenuFadeInTimeout = globalThis.setTimeout(() => { this.inlineMenuFadeInTimeout = globalThis.setTimeout(() => {
this.inlineMenuButtonPort?.postMessage(message); this.inlineMenuButtonPort?.postMessage(message);
this.inlineMenuListPort?.postMessage(message); this.inlineMenuListPort?.postMessage(message);
}, 75); }, 50);
} }
/** /**
@@ -977,6 +988,11 @@ export class OverlayBackground implements OverlayBackgroundInterface {
return this.isFieldCurrentlyFilling; return this.isFieldCurrentlyFilling;
} }
/**
* Sends a message to the top level frame of the sender to check if the inline menu button is visible.
*
* @param sender - The sender of the message
*/
private async checkIsAutofillInlineMenuButtonVisible(sender: chrome.runtime.MessageSender) { private async checkIsAutofillInlineMenuButtonVisible(sender: chrome.runtime.MessageSender) {
return await BrowserApi.tabSendMessage( return await BrowserApi.tabSendMessage(
sender.tab, sender.tab,
@@ -985,6 +1001,11 @@ export class OverlayBackground implements OverlayBackgroundInterface {
); );
} }
/**
* Sends a message to the top level frame of the sender to check if the inline menu list is visible.
*
* @param sender - The sender of the message
*/
private async checkIsAutofillInlineMenuListVisible(sender: chrome.runtime.MessageSender) { private async checkIsAutofillInlineMenuListVisible(sender: chrome.runtime.MessageSender) {
return await BrowserApi.tabSendMessage( return await BrowserApi.tabSendMessage(
sender.tab, sender.tab,
@@ -993,16 +1014,34 @@ export class OverlayBackground implements OverlayBackgroundInterface {
); );
} }
/**
* Responds to the content script's request to check if the inline menu ciphers are populated.
* This will return true only if the sender is the focused field's tab and the inline menu
* ciphers are populated.
*
* @param sender - The sender of the message
*/
private checkIsInlineMenuCiphersPopulated(sender: chrome.runtime.MessageSender) { private checkIsInlineMenuCiphersPopulated(sender: chrome.runtime.MessageSender) {
return sender.tab.id === this.focusedFieldData.tabId && this.inlineMenuCiphers.size > 0; return sender.tab.id === this.focusedFieldData.tabId && this.inlineMenuCiphers.size > 0;
} }
/**
* Triggers an update in the meta "color-scheme" value within the inline menu button.
* This is done to ensure that the button element has a transparent background, which
* is accomplished by setting the "color-scheme" meta value of the button iframe to
* the same value as the page's meta "color-scheme" value.
*/
private updateInlineMenuButtonColorScheme() { private updateInlineMenuButtonColorScheme() {
this.inlineMenuButtonPort?.postMessage({ this.inlineMenuButtonPort?.postMessage({
command: "updateAutofillInlineMenuColorScheme", command: "updateAutofillInlineMenuColorScheme",
}); });
} }
/**
* Triggers an update in the inline menu list's height.
*
* @param message - Contains the dimensions of the inline menu list
*/
private updateInlineMenuListHeight(message: OverlayBackgroundExtensionMessage) { private updateInlineMenuListHeight(message: OverlayBackgroundExtensionMessage) {
this.inlineMenuListPort?.postMessage({ this.inlineMenuListPort?.postMessage({
command: "updateInlineMenuIframePosition", command: "updateInlineMenuIframePosition",
@@ -1160,6 +1199,12 @@ export class OverlayBackground implements OverlayBackgroundInterface {
handler({ message, port }); handler({ message, port });
}; };
/**
* Ensures that the inline menu list and button port
* references are reset when they are disconnected.
*
* @param port - The port that was disconnected
*/
private handlePortOnDisconnect = (port: chrome.runtime.Port) => { private handlePortOnDisconnect = (port: chrome.runtime.Port) => {
if (port.name === AutofillOverlayPort.List) { if (port.name === AutofillOverlayPort.List) {
this.inlineMenuListPort = null; this.inlineMenuListPort = null;