1
0
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:
Cesar Gonzalez
2024-06-04 12:28:08 -05:00
parent f3c0a24a1d
commit b9d257046c
3 changed files with 158 additions and 37 deletions

View File

@@ -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>();
}); environmentService.environment$ = environmentMock$;
stateService = mock<DefaultBrowserStateService>(); inlineMenuVisibilityMock$ = new BehaviorSubject(AutofillOverlayVisibility.OnFieldFocus);
autofillSettingsService = mock<AutofillSettingsService>({ autofillSettingsService = mock<AutofillSettingsService>();
inlineMenuVisibility$: of(AutofillOverlayVisibility.OnFieldFocus), 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", () => {
const tabId = 1;
describe("removePageDetails", () => {
it("removes the page details for a specific tab from the pageDetailsForTab object", () => {
const tabId = 1;
const frameId = 2;
beforeEach(() => {
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: 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); 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 { 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,

View File

@@ -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,