1
0
mirror of https://github.com/bitwarden/browser.git synced 2025-02-10 00:21:27 +01:00

[PM-5189] Implementing jest tests for the OverlayBackground

This commit is contained in:
Cesar Gonzalez 2024-06-04 12:28:08 -05:00
parent f3c0a24a1d
commit b9d257046c
No known key found for this signature in database
GPG Key ID: 3381A5457F8CCECF
3 changed files with 158 additions and 37 deletions

View File

@ -1,14 +1,15 @@
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 { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
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 {
DefaultDomainSettingsService,
DomainSettingsService,
} from "@bitwarden/common/autofill/services/domain-settings.service";
import { InlineMenuVisibilitySetting } from "@bitwarden/common/autofill/types";
import {
EnvironmentService,
Region,
@ -27,60 +28,74 @@ import {
import { UserId } from "@bitwarden/common/types/guid";
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 { AutofillService } from "../services/abstractions/autofill.service";
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";
describe("OverlayBackground", () => {
const mockUserId = Utils.newGuid() as UserId;
let accountService: FakeAccountService;
let fakeStateProvider: FakeStateProvider;
let showFaviconsMock$: BehaviorSubject<boolean>;
let domainSettingsService: DomainSettingsService;
let logService: MockProxy<LogService>;
let cipherService: MockProxy<CipherService>;
let autofillService: MockProxy<AutofillService>;
let activeAccountStatusMock$: BehaviorSubject<AuthenticationStatus>;
let authService: MockProxy<AuthService>;
let environmentMock$: BehaviorSubject<CloudEnvironment>;
let environmentService: MockProxy<EnvironmentService>;
let stateService: MockProxy<DefaultBrowserStateService>;
let inlineMenuVisibilityMock$: BehaviorSubject<InlineMenuVisibilitySetting>;
let autofillSettingsService: MockProxy<AutofillSettingsService>;
let i18nService: MockProxy<I18nService>;
let platformUtilsService: MockProxy<BrowserPlatformUtilsService>;
let selectedThemeMock$: BehaviorSubject<ThemeType>;
let themeStateService: MockProxy<ThemeStateService>;
let overlayBackground: OverlayBackground;
let portKeyForTabSpy: Record<number, string>;
let pageDetailsForTabSpy: PageDetailsForTab;
let subFrameOffsetsSpy: SubFrameOffsetsForTab;
let getFrameDetailsSpy: jest.SpyInstance;
let tabsSendMessageSpy: jest.SpyInstance;
beforeEach(() => {
accountService = mockAccountServiceWith(mockUserId);
fakeStateProvider = new FakeStateProvider(accountService);
showFaviconsMock$ = new BehaviorSubject(true);
domainSettingsService = new DefaultDomainSettingsService(fakeStateProvider);
domainSettingsService.showFavicons$ = of(true);
domainSettingsService.showFavicons$ = showFaviconsMock$;
logService = mock<LogService>();
cipherService = mock<CipherService>();
autofillService = mock<AutofillService>();
authService = mock<AuthService>({
activeAccountStatus$: new BehaviorSubject(AuthenticationStatus.Unlocked),
});
environmentService = mock<EnvironmentService>({
environment$: new BehaviorSubject(
new CloudEnvironment({
key: Region.US,
domain: "bitwarden.com",
urls: { icons: "https://icons.bitwarden.com/" },
}),
),
});
stateService = mock<DefaultBrowserStateService>();
autofillSettingsService = mock<AutofillSettingsService>({
inlineMenuVisibility$: of(AutofillOverlayVisibility.OnFieldFocus),
});
activeAccountStatusMock$ = new BehaviorSubject(AuthenticationStatus.Unlocked);
authService = mock<AuthService>();
authService.activeAccountStatus$ = activeAccountStatusMock$;
environmentMock$ = new BehaviorSubject(
new CloudEnvironment({
key: Region.US,
domain: "bitwarden.com",
urls: { icons: "https://icons.bitwarden.com/" },
}),
);
environmentService = mock<EnvironmentService>();
environmentService.environment$ = environmentMock$;
inlineMenuVisibilityMock$ = new BehaviorSubject(AutofillOverlayVisibility.OnFieldFocus);
autofillSettingsService = mock<AutofillSettingsService>();
autofillSettingsService.inlineMenuVisibility$ = inlineMenuVisibilityMock$;
i18nService = mock<I18nService>();
platformUtilsService = mock<BrowserPlatformUtilsService>();
themeStateService = mock<ThemeStateService>({
selectedTheme$: of(ThemeType.Light),
});
selectedThemeMock$ = new BehaviorSubject(ThemeType.Light);
themeStateService = mock<ThemeStateService>();
themeStateService.selectedTheme$ = selectedThemeMock$;
overlayBackground = new OverlayBackground(
logService,
cipherService,
@ -88,12 +103,16 @@ describe("OverlayBackground", () => {
authService,
environmentService,
domainSettingsService,
stateService,
autofillSettingsService,
i18nService,
platformUtilsService,
themeStateService,
);
portKeyForTabSpy = overlayBackground["portKeyForTab"];
pageDetailsForTabSpy = overlayBackground["pageDetailsForTab"];
subFrameOffsetsSpy = overlayBackground["subFrameOffsetsForTab"];
getFrameDetailsSpy = jest.spyOn(BrowserApi, "getFrameDetails");
tabsSendMessageSpy = jest.spyOn(BrowserApi, "tabSendMessage");
void overlayBackground.init();
});
@ -103,21 +122,119 @@ describe("OverlayBackground", () => {
mockReset(cipherService);
});
// TODO: describe init
describe("removePageDetails", () => {
it("removes the page details for a specific tab from the pageDetailsForTab object", () => {
const tabId = 1;
const frameId = 2;
describe("storing pageDetails", () => {
const tabId = 1;
beforeEach(() => {
sendMockExtensionMessage(
{ command: "collectPageDetailsResponse", details: createAutofillPageDetailsMock() },
mock<chrome.runtime.MessageSender>({ tab: createChromeTabMock({ id: tabId }), frameId }),
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(
{ command: "collectPageDetailsResponse", details: createAutofillPageDetailsMock() },
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);
expect(overlayBackground["pageDetailsForTab"][tabId]).toBeUndefined();
expect(pageDetailsForTabSpy[tabId]).toBeUndefined();
expect(subFrameOffsetsSpy[tabId]).toBeUndefined();
expect(portKeyForTabSpy[tabId]).toBeUndefined();
});
});
});

View File

@ -10,7 +10,6 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.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 { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
@ -128,7 +127,6 @@ export class OverlayBackground implements OverlayBackgroundInterface {
private authService: AuthService,
private environmentService: EnvironmentService,
private domainSettingsService: DomainSettingsService,
private stateService: StateService,
private autofillSettingsService: AutofillSettingsServiceAbstraction,
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService,
@ -261,6 +259,13 @@ export class OverlayBackground implements OverlayBackgroundInterface {
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(
message: OverlayBackgroundExtensionMessage,
sender: chrome.runtime.MessageSender,

View File

@ -1049,7 +1049,6 @@ export default class MainBackground {
this.authService,
this.environmentService,
this.domainSettingsService,
this.stateService,
this.autofillSettingsService,
this.i18nService,
this.platformUtilsService,