mirror of
https://github.com/bitwarden/browser
synced 2025-12-16 16:23:44 +00:00
[PM-5189] Implementing jest tests for the OverlayBackground
This commit is contained in:
@@ -1,14 +1,15 @@
|
|||||||
import { mock, MockProxy, mockReset } from "jest-mock-extended";
|
import { mock, MockProxy, mockReset } from "jest-mock-extended";
|
||||||
import { BehaviorSubject, of } from "rxjs";
|
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 } from "@bitwarden/common/autofill/constants";
|
||||||
import { 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,
|
||||||
DomainSettingsService,
|
DomainSettingsService,
|
||||||
} from "@bitwarden/common/autofill/services/domain-settings.service";
|
} from "@bitwarden/common/autofill/services/domain-settings.service";
|
||||||
|
import { InlineMenuVisibilitySetting } from "@bitwarden/common/autofill/types";
|
||||||
import {
|
import {
|
||||||
EnvironmentService,
|
EnvironmentService,
|
||||||
Region,
|
Region,
|
||||||
@@ -27,60 +28,74 @@ import {
|
|||||||
import { UserId } from "@bitwarden/common/types/guid";
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
|
|
||||||
import { DefaultBrowserStateService } from "../../platform/services/default-browser-state.service";
|
import { BrowserApi } from "../../platform/browser/browser-api";
|
||||||
import { BrowserPlatformUtilsService } from "../../platform/services/platform-utils/browser-platform-utils.service";
|
import { BrowserPlatformUtilsService } from "../../platform/services/platform-utils/browser-platform-utils.service";
|
||||||
import { AutofillService } from "../services/abstractions/autofill.service";
|
import { AutofillService } from "../services/abstractions/autofill.service";
|
||||||
import { createChromeTabMock, createAutofillPageDetailsMock } from "../spec/autofill-mocks";
|
import { createChromeTabMock, createAutofillPageDetailsMock } from "../spec/autofill-mocks";
|
||||||
import { sendMockExtensionMessage } from "../spec/testing-utils";
|
import { flushPromises, sendMockExtensionMessage } from "../spec/testing-utils";
|
||||||
|
|
||||||
|
import {
|
||||||
|
PageDetailsForTab,
|
||||||
|
SubFrameOffsetData,
|
||||||
|
SubFrameOffsetsForTab,
|
||||||
|
} from "./abstractions/overlay.background";
|
||||||
import { OverlayBackground } from "./overlay.background";
|
import { OverlayBackground } from "./overlay.background";
|
||||||
|
|
||||||
describe("OverlayBackground", () => {
|
describe("OverlayBackground", () => {
|
||||||
const mockUserId = Utils.newGuid() as UserId;
|
const mockUserId = Utils.newGuid() as UserId;
|
||||||
let accountService: FakeAccountService;
|
let accountService: FakeAccountService;
|
||||||
let fakeStateProvider: FakeStateProvider;
|
let fakeStateProvider: FakeStateProvider;
|
||||||
|
let showFaviconsMock$: BehaviorSubject<boolean>;
|
||||||
let domainSettingsService: DomainSettingsService;
|
let domainSettingsService: DomainSettingsService;
|
||||||
let logService: MockProxy<LogService>;
|
let logService: MockProxy<LogService>;
|
||||||
let cipherService: MockProxy<CipherService>;
|
let cipherService: MockProxy<CipherService>;
|
||||||
let autofillService: MockProxy<AutofillService>;
|
let autofillService: MockProxy<AutofillService>;
|
||||||
|
let activeAccountStatusMock$: BehaviorSubject<AuthenticationStatus>;
|
||||||
let authService: MockProxy<AuthService>;
|
let authService: MockProxy<AuthService>;
|
||||||
|
let environmentMock$: BehaviorSubject<CloudEnvironment>;
|
||||||
let environmentService: MockProxy<EnvironmentService>;
|
let environmentService: MockProxy<EnvironmentService>;
|
||||||
let stateService: MockProxy<DefaultBrowserStateService>;
|
let inlineMenuVisibilityMock$: BehaviorSubject<InlineMenuVisibilitySetting>;
|
||||||
let autofillSettingsService: MockProxy<AutofillSettingsService>;
|
let autofillSettingsService: MockProxy<AutofillSettingsService>;
|
||||||
let i18nService: MockProxy<I18nService>;
|
let i18nService: MockProxy<I18nService>;
|
||||||
let platformUtilsService: MockProxy<BrowserPlatformUtilsService>;
|
let platformUtilsService: MockProxy<BrowserPlatformUtilsService>;
|
||||||
|
let selectedThemeMock$: BehaviorSubject<ThemeType>;
|
||||||
let themeStateService: MockProxy<ThemeStateService>;
|
let themeStateService: MockProxy<ThemeStateService>;
|
||||||
let overlayBackground: OverlayBackground;
|
let overlayBackground: OverlayBackground;
|
||||||
|
let portKeyForTabSpy: Record<number, string>;
|
||||||
|
let pageDetailsForTabSpy: PageDetailsForTab;
|
||||||
|
let subFrameOffsetsSpy: SubFrameOffsetsForTab;
|
||||||
|
let getFrameDetailsSpy: jest.SpyInstance;
|
||||||
|
let tabsSendMessageSpy: jest.SpyInstance;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
accountService = mockAccountServiceWith(mockUserId);
|
accountService = mockAccountServiceWith(mockUserId);
|
||||||
fakeStateProvider = new FakeStateProvider(accountService);
|
fakeStateProvider = new FakeStateProvider(accountService);
|
||||||
|
showFaviconsMock$ = new BehaviorSubject(true);
|
||||||
domainSettingsService = new DefaultDomainSettingsService(fakeStateProvider);
|
domainSettingsService = new DefaultDomainSettingsService(fakeStateProvider);
|
||||||
domainSettingsService.showFavicons$ = of(true);
|
domainSettingsService.showFavicons$ = showFaviconsMock$;
|
||||||
logService = mock<LogService>();
|
logService = mock<LogService>();
|
||||||
cipherService = mock<CipherService>();
|
cipherService = mock<CipherService>();
|
||||||
autofillService = mock<AutofillService>();
|
autofillService = mock<AutofillService>();
|
||||||
authService = mock<AuthService>({
|
activeAccountStatusMock$ = new BehaviorSubject(AuthenticationStatus.Unlocked);
|
||||||
activeAccountStatus$: new BehaviorSubject(AuthenticationStatus.Unlocked),
|
authService = mock<AuthService>();
|
||||||
});
|
authService.activeAccountStatus$ = activeAccountStatusMock$;
|
||||||
environmentService = mock<EnvironmentService>({
|
environmentMock$ = new BehaviorSubject(
|
||||||
environment$: new BehaviorSubject(
|
|
||||||
new CloudEnvironment({
|
new CloudEnvironment({
|
||||||
key: Region.US,
|
key: Region.US,
|
||||||
domain: "bitwarden.com",
|
domain: "bitwarden.com",
|
||||||
urls: { icons: "https://icons.bitwarden.com/" },
|
urls: { icons: "https://icons.bitwarden.com/" },
|
||||||
}),
|
}),
|
||||||
),
|
);
|
||||||
});
|
environmentService = mock<EnvironmentService>();
|
||||||
stateService = mock<DefaultBrowserStateService>();
|
environmentService.environment$ = environmentMock$;
|
||||||
autofillSettingsService = mock<AutofillSettingsService>({
|
inlineMenuVisibilityMock$ = new BehaviorSubject(AutofillOverlayVisibility.OnFieldFocus);
|
||||||
inlineMenuVisibility$: of(AutofillOverlayVisibility.OnFieldFocus),
|
autofillSettingsService = mock<AutofillSettingsService>();
|
||||||
});
|
autofillSettingsService.inlineMenuVisibility$ = inlineMenuVisibilityMock$;
|
||||||
i18nService = mock<I18nService>();
|
i18nService = mock<I18nService>();
|
||||||
platformUtilsService = mock<BrowserPlatformUtilsService>();
|
platformUtilsService = mock<BrowserPlatformUtilsService>();
|
||||||
themeStateService = mock<ThemeStateService>({
|
selectedThemeMock$ = new BehaviorSubject(ThemeType.Light);
|
||||||
selectedTheme$: of(ThemeType.Light),
|
themeStateService = mock<ThemeStateService>();
|
||||||
});
|
themeStateService.selectedTheme$ = selectedThemeMock$;
|
||||||
overlayBackground = new OverlayBackground(
|
overlayBackground = new OverlayBackground(
|
||||||
logService,
|
logService,
|
||||||
cipherService,
|
cipherService,
|
||||||
@@ -88,12 +103,16 @@ describe("OverlayBackground", () => {
|
|||||||
authService,
|
authService,
|
||||||
environmentService,
|
environmentService,
|
||||||
domainSettingsService,
|
domainSettingsService,
|
||||||
stateService,
|
|
||||||
autofillSettingsService,
|
autofillSettingsService,
|
||||||
i18nService,
|
i18nService,
|
||||||
platformUtilsService,
|
platformUtilsService,
|
||||||
themeStateService,
|
themeStateService,
|
||||||
);
|
);
|
||||||
|
portKeyForTabSpy = overlayBackground["portKeyForTab"];
|
||||||
|
pageDetailsForTabSpy = overlayBackground["pageDetailsForTab"];
|
||||||
|
subFrameOffsetsSpy = overlayBackground["subFrameOffsetsForTab"];
|
||||||
|
getFrameDetailsSpy = jest.spyOn(BrowserApi, "getFrameDetails");
|
||||||
|
tabsSendMessageSpy = jest.spyOn(BrowserApi, "tabSendMessage");
|
||||||
|
|
||||||
void overlayBackground.init();
|
void overlayBackground.init();
|
||||||
});
|
});
|
||||||
@@ -103,21 +122,119 @@ describe("OverlayBackground", () => {
|
|||||||
mockReset(cipherService);
|
mockReset(cipherService);
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: describe init
|
describe("storing pageDetails", () => {
|
||||||
|
|
||||||
describe("removePageDetails", () => {
|
|
||||||
it("removes the page details for a specific tab from the pageDetailsForTab object", () => {
|
|
||||||
const tabId = 1;
|
const tabId = 1;
|
||||||
const frameId = 2;
|
|
||||||
|
beforeEach(() => {
|
||||||
|
sendMockExtensionMessage(
|
||||||
|
{ command: "collectPageDetailsResponse", details: createAutofillPageDetailsMock() },
|
||||||
|
mock<chrome.runtime.MessageSender>({ tab: createChromeTabMock({ id: tabId }), frameId: 0 }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("generates a random 12 character string used to validate port messages from the tab", () => {
|
||||||
|
expect(portKeyForTabSpy[tabId]).toHaveLength(12);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("stores the page details for the tab", () => {
|
||||||
|
expect(pageDetailsForTabSpy[tabId]).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("building sub frame offsets", () => {
|
||||||
|
let getFrameCounter: number = 2;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
getFrameDetailsSpy.mockImplementation((_details: chrome.webNavigation.GetFrameDetails) => {
|
||||||
|
getFrameCounter--;
|
||||||
|
return mock<chrome.webNavigation.GetFrameResultDetails>({
|
||||||
|
parentFrameId: getFrameCounter,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
tabsSendMessageSpy.mockResolvedValue(mock<SubFrameOffsetData>());
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
getFrameCounter = 2;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("builds the offset values for a sub frame within the tab", () => {
|
||||||
|
sendMockExtensionMessage(
|
||||||
|
{ command: "collectPageDetailsResponse", details: createAutofillPageDetailsMock() },
|
||||||
|
mock<chrome.runtime.MessageSender>({
|
||||||
|
tab: createChromeTabMock({ id: tabId }),
|
||||||
|
frameId: 1,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(subFrameOffsetsSpy[tabId]).toBeDefined();
|
||||||
|
expect(pageDetailsForTabSpy[tabId].size).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("skips building offset values for a previously calculated sub frame", async () => {
|
||||||
|
getFrameCounter = 0;
|
||||||
|
sendMockExtensionMessage(
|
||||||
|
{ command: "collectPageDetailsResponse", details: createAutofillPageDetailsMock() },
|
||||||
|
mock<chrome.runtime.MessageSender>({
|
||||||
|
tab: createChromeTabMock({ id: tabId }),
|
||||||
|
frameId: 1,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
sendMockExtensionMessage(
|
sendMockExtensionMessage(
|
||||||
{ command: "collectPageDetailsResponse", details: createAutofillPageDetailsMock() },
|
{ command: "collectPageDetailsResponse", details: createAutofillPageDetailsMock() },
|
||||||
mock<chrome.runtime.MessageSender>({ tab: createChromeTabMock({ id: tabId }), frameId }),
|
mock<chrome.runtime.MessageSender>({
|
||||||
|
tab: createChromeTabMock({ id: tabId }),
|
||||||
|
frameId: 1,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
expect(getFrameDetailsSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(subFrameOffsetsSpy[tabId]).toStrictEqual(
|
||||||
|
new Map([[1, { left: 0, top: 0, url: "url" }]]),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("will attempt to build the sub frame offsets by posting window messages if a set of offsets is not returned", async () => {
|
||||||
|
const tab = createChromeTabMock({ id: tabId });
|
||||||
|
const frameId = 1;
|
||||||
|
tabsSendMessageSpy.mockResolvedValueOnce(null);
|
||||||
|
sendMockExtensionMessage(
|
||||||
|
{ command: "collectPageDetailsResponse", details: createAutofillPageDetailsMock() },
|
||||||
|
mock<chrome.runtime.MessageSender>({
|
||||||
|
tab,
|
||||||
|
frameId,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
expect(tabsSendMessageSpy).toHaveBeenCalledWith(
|
||||||
|
tab,
|
||||||
|
{
|
||||||
|
command: "getSubFrameOffsetsFromWindowMessage",
|
||||||
|
subFrameId: frameId,
|
||||||
|
},
|
||||||
|
{ frameId },
|
||||||
|
);
|
||||||
|
expect(subFrameOffsetsSpy[tabId]).toStrictEqual(new Map([[frameId, null]]));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("removing pageDetails", () => {
|
||||||
|
it("removes the page details, sub frame details, and port key for a specific tab from the pageDetailsForTab object", () => {
|
||||||
|
const tabId = 1;
|
||||||
|
sendMockExtensionMessage(
|
||||||
|
{ command: "collectPageDetailsResponse", details: createAutofillPageDetailsMock() },
|
||||||
|
mock<chrome.runtime.MessageSender>({ tab: createChromeTabMock({ id: tabId }), frameId: 1 }),
|
||||||
);
|
);
|
||||||
|
|
||||||
overlayBackground.removePageDetails(tabId);
|
overlayBackground.removePageDetails(tabId);
|
||||||
|
|
||||||
expect(overlayBackground["pageDetailsForTab"][tabId]).toBeUndefined();
|
expect(pageDetailsForTabSpy[tabId]).toBeUndefined();
|
||||||
|
expect(subFrameOffsetsSpy[tabId]).toBeUndefined();
|
||||||
|
expect(portKeyForTabSpy[tabId]).toBeUndefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi
|
|||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
|
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
@@ -128,7 +127,6 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
|||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
private environmentService: EnvironmentService,
|
private environmentService: EnvironmentService,
|
||||||
private domainSettingsService: DomainSettingsService,
|
private domainSettingsService: DomainSettingsService,
|
||||||
private stateService: StateService,
|
|
||||||
private autofillSettingsService: AutofillSettingsServiceAbstraction,
|
private autofillSettingsService: AutofillSettingsServiceAbstraction,
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
private platformUtilsService: PlatformUtilsService,
|
private platformUtilsService: PlatformUtilsService,
|
||||||
@@ -261,6 +259,13 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
|||||||
pageDetailsMap.set(sender.frameId, pageDetails);
|
pageDetailsMap.set(sender.frameId, pageDetails);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles sub frame offset calculations for the given tab and frame id.
|
||||||
|
* Is used in setting the position of the inline menu list and button.
|
||||||
|
*
|
||||||
|
* @param message - The message received from the `updateSubFrameData` command
|
||||||
|
* @param sender - The sender of the message
|
||||||
|
*/
|
||||||
private updateSubFrameData(
|
private updateSubFrameData(
|
||||||
message: OverlayBackgroundExtensionMessage,
|
message: OverlayBackgroundExtensionMessage,
|
||||||
sender: chrome.runtime.MessageSender,
|
sender: chrome.runtime.MessageSender,
|
||||||
|
|||||||
@@ -1049,7 +1049,6 @@ export default class MainBackground {
|
|||||||
this.authService,
|
this.authService,
|
||||||
this.environmentService,
|
this.environmentService,
|
||||||
this.domainSettingsService,
|
this.domainSettingsService,
|
||||||
this.stateService,
|
|
||||||
this.autofillSettingsService,
|
this.autofillSettingsService,
|
||||||
this.i18nService,
|
this.i18nService,
|
||||||
this.platformUtilsService,
|
this.platformUtilsService,
|
||||||
|
|||||||
Reference in New Issue
Block a user