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:
@@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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", () => {});
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user