mirror of
https://github.com/bitwarden/browser.git
synced 2025-01-26 22:01:32 +01:00
[PM-5189] Implementing jest tests for the OverlayBackground
This commit is contained in:
parent
2efbc5ce51
commit
5e70d042b4
@ -3,7 +3,10 @@ import { BehaviorSubject } from "rxjs";
|
|||||||
|
|
||||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||||
import { AutofillOverlayVisibility } from "@bitwarden/common/autofill/constants";
|
import {
|
||||||
|
AutofillOverlayVisibility,
|
||||||
|
SHOW_AUTOFILL_BUTTON,
|
||||||
|
} from "@bitwarden/common/autofill/constants";
|
||||||
import { AutofillSettingsServiceAbstraction as AutofillSettingsService } from "@bitwarden/common/autofill/services/autofill-settings.service";
|
import { AutofillSettingsServiceAbstraction as AutofillSettingsService } from "@bitwarden/common/autofill/services/autofill-settings.service";
|
||||||
import {
|
import {
|
||||||
DefaultDomainSettingsService,
|
DefaultDomainSettingsService,
|
||||||
@ -44,7 +47,14 @@ import {
|
|||||||
createPortSpyMock,
|
createPortSpyMock,
|
||||||
createFocusedFieldDataMock,
|
createFocusedFieldDataMock,
|
||||||
} from "../spec/autofill-mocks";
|
} from "../spec/autofill-mocks";
|
||||||
import { flushPromises, sendMockExtensionMessage, sendPortMessage } from "../spec/testing-utils";
|
import {
|
||||||
|
flushPromises,
|
||||||
|
sendMockExtensionMessage,
|
||||||
|
sendPortMessage,
|
||||||
|
triggerPortOnConnectEvent,
|
||||||
|
triggerPortOnDisconnectEvent,
|
||||||
|
triggerPortOnMessageEvent,
|
||||||
|
} from "../spec/testing-utils";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
FocusedFieldData,
|
FocusedFieldData,
|
||||||
@ -93,19 +103,19 @@ describe("OverlayBackground", () => {
|
|||||||
async function initOverlayElementPorts(options = { initList: true, initButton: true }) {
|
async function initOverlayElementPorts(options = { initList: true, initButton: true }) {
|
||||||
const { initList, initButton } = options;
|
const { initList, initButton } = options;
|
||||||
if (initButton) {
|
if (initButton) {
|
||||||
await overlayBackground["handlePortOnConnect"](createPortSpyMock(AutofillOverlayPort.Button));
|
triggerPortOnConnectEvent(createPortSpyMock(AutofillOverlayPort.Button));
|
||||||
buttonPortSpy = overlayBackground["inlineMenuButtonPort"];
|
buttonPortSpy = overlayBackground["inlineMenuButtonPort"];
|
||||||
|
|
||||||
buttonMessageConnectorSpy = createPortSpyMock(AutofillOverlayPort.ButtonMessageConnector);
|
buttonMessageConnectorSpy = createPortSpyMock(AutofillOverlayPort.ButtonMessageConnector);
|
||||||
await overlayBackground["handlePortOnConnect"](buttonMessageConnectorSpy);
|
triggerPortOnConnectEvent(buttonMessageConnectorSpy);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (initList) {
|
if (initList) {
|
||||||
await overlayBackground["handlePortOnConnect"](createPortSpyMock(AutofillOverlayPort.List));
|
triggerPortOnConnectEvent(createPortSpyMock(AutofillOverlayPort.List));
|
||||||
listPortSpy = overlayBackground["inlineMenuListPort"];
|
listPortSpy = overlayBackground["inlineMenuListPort"];
|
||||||
|
|
||||||
listMessageConnectorSpy = createPortSpyMock(AutofillOverlayPort.ListMessageConnector);
|
listMessageConnectorSpy = createPortSpyMock(AutofillOverlayPort.ListMessageConnector);
|
||||||
await overlayBackground["handlePortOnConnect"](listMessageConnectorSpy);
|
triggerPortOnConnectEvent(listMessageConnectorSpy);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { buttonPortSpy, listPortSpy };
|
return { buttonPortSpy, listPortSpy };
|
||||||
@ -1269,6 +1279,25 @@ describe("OverlayBackground", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("handle extension onMessage", () => {
|
||||||
|
it("will return early if the message command is not present within the extensionMessageHandlers", () => {
|
||||||
|
const message = {
|
||||||
|
command: "not-a-command",
|
||||||
|
};
|
||||||
|
const sender = mock<chrome.runtime.MessageSender>({ tab: { id: 1 } });
|
||||||
|
const sendResponse = jest.fn();
|
||||||
|
|
||||||
|
const returnValue = overlayBackground["handleExtensionMessage"](
|
||||||
|
message,
|
||||||
|
sender,
|
||||||
|
sendResponse,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(returnValue).toBe(undefined);
|
||||||
|
expect(sendResponse).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("inline menu button message handlers", () => {
|
describe("inline menu button message handlers", () => {
|
||||||
let sender: chrome.runtime.MessageSender;
|
let sender: chrome.runtime.MessageSender;
|
||||||
const portKey = "inlineMenuButtonPort";
|
const portKey = "inlineMenuButtonPort";
|
||||||
@ -1613,5 +1642,152 @@ describe("OverlayBackground", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("viewSelectedCipher message handler", () => {
|
||||||
|
let openViewVaultItemPopoutSpy: jest.SpyInstance;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
openViewVaultItemPopoutSpy = jest
|
||||||
|
.spyOn(overlayBackground as any, "openViewVaultItemPopout")
|
||||||
|
.mockImplementation();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns early if the passed cipher ID does not match one of the inline menu ciphers", async () => {
|
||||||
|
overlayBackground["inlineMenuCiphers"] = new Map([
|
||||||
|
["overlay-cipher-0", mock<CipherView>({ id: "overlay-cipher-0" })],
|
||||||
|
]);
|
||||||
|
|
||||||
|
sendPortMessage(listMessageConnectorSpy, {
|
||||||
|
command: "viewSelectedCipher",
|
||||||
|
overlayCipherId: "overlay-cipher-1",
|
||||||
|
portKey,
|
||||||
|
});
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
expect(openViewVaultItemPopoutSpy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("will open the view vault item popout with the selected cipher", async () => {
|
||||||
|
const cipher = mock<CipherView>({ id: "overlay-cipher-1" });
|
||||||
|
overlayBackground["inlineMenuCiphers"] = new Map([
|
||||||
|
["overlay-cipher-0", mock<CipherView>({ id: "overlay-cipher-0" })],
|
||||||
|
["overlay-cipher-1", cipher],
|
||||||
|
]);
|
||||||
|
|
||||||
|
sendPortMessage(listMessageConnectorSpy, {
|
||||||
|
command: "viewSelectedCipher",
|
||||||
|
overlayCipherId: "overlay-cipher-1",
|
||||||
|
portKey,
|
||||||
|
});
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
expect(openViewVaultItemPopoutSpy).toHaveBeenCalledWith(sender.tab, {
|
||||||
|
cipherId: cipher.id,
|
||||||
|
action: SHOW_AUTOFILL_BUTTON,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("redirectAutofillInlineMenuFocusOut message handler", () => {
|
||||||
|
it("redirects focus out of the inline menu list", async () => {
|
||||||
|
sendPortMessage(listMessageConnectorSpy, {
|
||||||
|
command: "redirectAutofillInlineMenuFocusOut",
|
||||||
|
direction: RedirectFocusDirection.Next,
|
||||||
|
portKey,
|
||||||
|
});
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
expect(tabSendMessageDataSpy).toHaveBeenCalledWith(
|
||||||
|
sender.tab,
|
||||||
|
"redirectAutofillInlineMenuFocusOut",
|
||||||
|
{ direction: RedirectFocusDirection.Next },
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("updateAutofillInlineMenuListHeight message handler", () => {
|
||||||
|
it("sends a message to the list port to update the menu iframe position", () => {
|
||||||
|
sendPortMessage(listMessageConnectorSpy, {
|
||||||
|
command: "updateAutofillInlineMenuListHeight",
|
||||||
|
styles: { height: "100px" },
|
||||||
|
portKey,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(listPortSpy.postMessage).toHaveBeenCalledWith({
|
||||||
|
command: "updateInlineMenuIframePosition",
|
||||||
|
styles: { height: "100px" },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("handle port onConnect", () => {
|
||||||
|
it("skips setting up the overlay port if the port connection is not for an overlay element", async () => {
|
||||||
|
const port = createPortSpyMock("not-an-overlay-element");
|
||||||
|
|
||||||
|
triggerPortOnConnectEvent(port);
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
expect(port.onMessage.addListener).not.toHaveBeenCalled();
|
||||||
|
expect(port.postMessage).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("stores an existing overlay port so that it can be disconnected at a later time", async () => {
|
||||||
|
overlayBackground["inlineMenuButtonPort"] = mock<chrome.runtime.Port>();
|
||||||
|
|
||||||
|
await initOverlayElementPorts({ initList: false, initButton: true });
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
expect(overlayBackground["expiredPorts"].length).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("handle overlay element port onMessage", () => {
|
||||||
|
let sender: chrome.runtime.MessageSender;
|
||||||
|
const portKey = "inlineMenuListPort";
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
sender = mock<chrome.runtime.MessageSender>({ tab: { id: 1 } });
|
||||||
|
portKeyForTabSpy[sender.tab.id] = portKey;
|
||||||
|
activeAccountStatusMock$.next(AuthenticationStatus.Unlocked);
|
||||||
|
await initOverlayElementPorts();
|
||||||
|
listMessageConnectorSpy.sender = sender;
|
||||||
|
openUnlockPopoutSpy.mockImplementation();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("ignores messages that do not contain a valid portKey", async () => {
|
||||||
|
triggerPortOnMessageEvent(buttonMessageConnectorSpy, {
|
||||||
|
command: "autofillInlineMenuBlurred",
|
||||||
|
});
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
expect(listPortSpy.postMessage).not.toHaveBeenCalledWith({
|
||||||
|
command: "checkAutofillInlineMenuListFocused",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("ignores messages from ports that are not listened to", () => {
|
||||||
|
triggerPortOnMessageEvent(buttonPortSpy, {
|
||||||
|
command: "autofillInlineMenuBlurred",
|
||||||
|
portKey,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(listPortSpy.postMessage).not.toHaveBeenCalledWith({
|
||||||
|
command: "checkAutofillInlineMenuListFocused",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("handle port onDisconnect", () => {
|
||||||
|
it("sets the disconnected port to a `null` value", async () => {
|
||||||
|
await initOverlayElementPorts();
|
||||||
|
|
||||||
|
triggerPortOnDisconnectEvent(buttonPortSpy);
|
||||||
|
triggerPortOnDisconnectEvent(listPortSpy);
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
expect(overlayBackground["inlineMenuListPort"]).toBeNull();
|
||||||
|
expect(overlayBackground["inlineMenuButtonPort"]).toBeNull();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1170,7 +1170,8 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
|||||||
message: OverlayBackgroundExtensionMessage,
|
message: OverlayBackgroundExtensionMessage,
|
||||||
port: chrome.runtime.Port,
|
port: chrome.runtime.Port,
|
||||||
) => {
|
) => {
|
||||||
if (this.portKeyForTab[port.sender.tab.id] !== message?.portKey) {
|
const tabPortKey = this.portKeyForTab[port.sender.tab.id];
|
||||||
|
if (!tabPortKey || tabPortKey !== message?.portKey) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,21 +1,21 @@
|
|||||||
import { mock } from "jest-mock-extended";
|
import { mock } from "jest-mock-extended";
|
||||||
|
|
||||||
function triggerTestFailure() {
|
export function triggerTestFailure() {
|
||||||
expect(true).toBe("Test has failed.");
|
expect(true).toBe("Test has failed.");
|
||||||
}
|
}
|
||||||
|
|
||||||
const scheduler = typeof setImmediate === "function" ? setImmediate : setTimeout;
|
const scheduler = typeof setImmediate === "function" ? setImmediate : setTimeout;
|
||||||
function flushPromises() {
|
export function flushPromises() {
|
||||||
return new Promise(function (resolve) {
|
return new Promise(function (resolve) {
|
||||||
scheduler(resolve);
|
scheduler(resolve);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function postWindowMessage(data: any, origin = "https://localhost/", source = window) {
|
export function postWindowMessage(data: any, origin = "https://localhost/", source = window) {
|
||||||
globalThis.dispatchEvent(new MessageEvent("message", { data, origin, source }));
|
globalThis.dispatchEvent(new MessageEvent("message", { data, origin, source }));
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendMockExtensionMessage(
|
export function sendMockExtensionMessage(
|
||||||
message: any,
|
message: any,
|
||||||
sender?: chrome.runtime.MessageSender,
|
sender?: chrome.runtime.MessageSender,
|
||||||
sendResponse?: CallableFunction,
|
sendResponse?: CallableFunction,
|
||||||
@ -32,7 +32,7 @@ function sendMockExtensionMessage(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function triggerRuntimeOnConnectEvent(port: chrome.runtime.Port) {
|
export function triggerRuntimeOnConnectEvent(port: chrome.runtime.Port) {
|
||||||
(chrome.runtime.onConnect.addListener as unknown as jest.SpyInstance).mock.calls.forEach(
|
(chrome.runtime.onConnect.addListener as unknown as jest.SpyInstance).mock.calls.forEach(
|
||||||
(call) => {
|
(call) => {
|
||||||
const callback = call[0];
|
const callback = call[0];
|
||||||
@ -41,21 +41,37 @@ function triggerRuntimeOnConnectEvent(port: chrome.runtime.Port) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendPortMessage(port: chrome.runtime.Port, message: any) {
|
export function sendPortMessage(port: chrome.runtime.Port, message: any) {
|
||||||
(port.onMessage.addListener as unknown as jest.SpyInstance).mock.calls.forEach((call) => {
|
(port.onMessage.addListener as unknown as jest.SpyInstance).mock.calls.forEach((call) => {
|
||||||
const callback = call[0];
|
const callback = call[0];
|
||||||
callback(message || {}, port);
|
callback(message || {}, port);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function triggerPortOnDisconnectEvent(port: chrome.runtime.Port) {
|
export function triggerPortOnConnectEvent(port: chrome.runtime.Port) {
|
||||||
|
(chrome.runtime.onConnect.addListener as unknown as jest.SpyInstance).mock.calls.forEach(
|
||||||
|
(call) => {
|
||||||
|
const callback = call[0];
|
||||||
|
callback(port);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function triggerPortOnMessageEvent(port: chrome.runtime.Port, message: any) {
|
||||||
|
(port.onMessage.addListener as unknown as jest.SpyInstance).mock.calls.forEach((call) => {
|
||||||
|
const callback = call[0];
|
||||||
|
callback(message, port);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function triggerPortOnDisconnectEvent(port: chrome.runtime.Port) {
|
||||||
(port.onDisconnect.addListener as unknown as jest.SpyInstance).mock.calls.forEach((call) => {
|
(port.onDisconnect.addListener as unknown as jest.SpyInstance).mock.calls.forEach((call) => {
|
||||||
const callback = call[0];
|
const callback = call[0];
|
||||||
callback(port);
|
callback(port);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function triggerWindowOnFocusedChangedEvent(windowId: number) {
|
export function triggerWindowOnFocusedChangedEvent(windowId: number) {
|
||||||
(chrome.windows.onFocusChanged.addListener as unknown as jest.SpyInstance).mock.calls.forEach(
|
(chrome.windows.onFocusChanged.addListener as unknown as jest.SpyInstance).mock.calls.forEach(
|
||||||
(call) => {
|
(call) => {
|
||||||
const callback = call[0];
|
const callback = call[0];
|
||||||
@ -64,7 +80,7 @@ function triggerWindowOnFocusedChangedEvent(windowId: number) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function triggerTabOnActivatedEvent(activeInfo: chrome.tabs.TabActiveInfo) {
|
export function triggerTabOnActivatedEvent(activeInfo: chrome.tabs.TabActiveInfo) {
|
||||||
(chrome.tabs.onActivated.addListener as unknown as jest.SpyInstance).mock.calls.forEach(
|
(chrome.tabs.onActivated.addListener as unknown as jest.SpyInstance).mock.calls.forEach(
|
||||||
(call) => {
|
(call) => {
|
||||||
const callback = call[0];
|
const callback = call[0];
|
||||||
@ -73,14 +89,14 @@ function triggerTabOnActivatedEvent(activeInfo: chrome.tabs.TabActiveInfo) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function triggerTabOnReplacedEvent(addedTabId: number, removedTabId: number) {
|
export function triggerTabOnReplacedEvent(addedTabId: number, removedTabId: number) {
|
||||||
(chrome.tabs.onReplaced.addListener as unknown as jest.SpyInstance).mock.calls.forEach((call) => {
|
(chrome.tabs.onReplaced.addListener as unknown as jest.SpyInstance).mock.calls.forEach((call) => {
|
||||||
const callback = call[0];
|
const callback = call[0];
|
||||||
callback(addedTabId, removedTabId);
|
callback(addedTabId, removedTabId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function triggerTabOnUpdatedEvent(
|
export function triggerTabOnUpdatedEvent(
|
||||||
tabId: number,
|
tabId: number,
|
||||||
changeInfo: chrome.tabs.TabChangeInfo,
|
changeInfo: chrome.tabs.TabChangeInfo,
|
||||||
tab: chrome.tabs.Tab,
|
tab: chrome.tabs.Tab,
|
||||||
@ -91,14 +107,14 @@ function triggerTabOnUpdatedEvent(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function triggerTabOnRemovedEvent(tabId: number, removeInfo: chrome.tabs.TabRemoveInfo) {
|
export function triggerTabOnRemovedEvent(tabId: number, removeInfo: chrome.tabs.TabRemoveInfo) {
|
||||||
(chrome.tabs.onRemoved.addListener as unknown as jest.SpyInstance).mock.calls.forEach((call) => {
|
(chrome.tabs.onRemoved.addListener as unknown as jest.SpyInstance).mock.calls.forEach((call) => {
|
||||||
const callback = call[0];
|
const callback = call[0];
|
||||||
callback(tabId, removeInfo);
|
callback(tabId, removeInfo);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function mockQuerySelectorAllDefinedCall() {
|
export function mockQuerySelectorAllDefinedCall() {
|
||||||
const originalDocumentQuerySelectorAll = document.querySelectorAll;
|
const originalDocumentQuerySelectorAll = document.querySelectorAll;
|
||||||
document.querySelectorAll = function (selector: string) {
|
document.querySelectorAll = function (selector: string) {
|
||||||
return originalDocumentQuerySelectorAll.call(
|
return originalDocumentQuerySelectorAll.call(
|
||||||
@ -125,19 +141,3 @@ function mockQuerySelectorAllDefinedCall() {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
|
||||||
triggerTestFailure,
|
|
||||||
flushPromises,
|
|
||||||
postWindowMessage,
|
|
||||||
sendMockExtensionMessage,
|
|
||||||
triggerRuntimeOnConnectEvent,
|
|
||||||
sendPortMessage,
|
|
||||||
triggerPortOnDisconnectEvent,
|
|
||||||
triggerWindowOnFocusedChangedEvent,
|
|
||||||
triggerTabOnActivatedEvent,
|
|
||||||
triggerTabOnReplacedEvent,
|
|
||||||
triggerTabOnUpdatedEvent,
|
|
||||||
triggerTabOnRemovedEvent,
|
|
||||||
mockQuerySelectorAllDefinedCall,
|
|
||||||
};
|
|
||||||
|
Loading…
Reference in New Issue
Block a user