From 07713511e483473c14ec8686d4240b69d11ab270 Mon Sep 17 00:00:00 2001 From: Cesar Gonzalez Date: Tue, 4 Jun 2024 16:54:14 -0500 Subject: [PATCH] [PM-5189] Working through jest tests for OverlayBackground --- .../background/overlay.background.spec.ts | 135 +++++++++++++++--- .../autofill/background/overlay.background.ts | 23 ++- 2 files changed, 136 insertions(+), 22 deletions(-) diff --git a/apps/browser/src/autofill/background/overlay.background.spec.ts b/apps/browser/src/autofill/background/overlay.background.spec.ts index f240a1cba3e..7f746abcbc8 100644 --- a/apps/browser/src/autofill/background/overlay.background.spec.ts +++ b/apps/browser/src/autofill/background/overlay.background.spec.ts @@ -35,6 +35,7 @@ import { createChromeTabMock, createAutofillPageDetailsMock } from "../spec/auto import { flushPromises, sendMockExtensionMessage } from "../spec/testing-utils"; import { + FocusedFieldData, PageDetailsForTab, SubFrameOffsetData, SubFrameOffsetsForTab, @@ -66,6 +67,7 @@ describe("OverlayBackground", () => { let subFrameOffsetsSpy: SubFrameOffsetsForTab; let getFrameDetailsSpy: jest.SpyInstance; let tabsSendMessageSpy: jest.SpyInstance; + let getFrameCounter: number = 2; beforeEach(() => { accountService = mockAccountServiceWith(mockUserId); @@ -112,13 +114,21 @@ describe("OverlayBackground", () => { pageDetailsForTabSpy = overlayBackground["pageDetailsForTab"]; subFrameOffsetsSpy = overlayBackground["subFrameOffsetsForTab"]; getFrameDetailsSpy = jest.spyOn(BrowserApi, "getFrameDetails"); + getFrameDetailsSpy.mockImplementation((_details: chrome.webNavigation.GetFrameDetails) => { + getFrameCounter--; + return mock({ + parentFrameId: getFrameCounter, + }); + }); tabsSendMessageSpy = jest.spyOn(BrowserApi, "tabSendMessage"); void overlayBackground.init(); }); afterEach(() => { + getFrameCounter = 2; jest.clearAllMocks(); + jest.useRealTimers(); mockReset(cipherService); }); @@ -141,23 +151,17 @@ describe("OverlayBackground", () => { }); describe("building sub frame offsets", () => { - let getFrameCounter: number = 2; - beforeEach(() => { - getFrameDetailsSpy.mockImplementation((_details: chrome.webNavigation.GetFrameDetails) => { - getFrameCounter--; - return mock({ - parentFrameId: getFrameCounter, - }); - }); - tabsSendMessageSpy.mockResolvedValue(mock()); + tabsSendMessageSpy.mockResolvedValue( + mock({ + left: getFrameCounter, + top: getFrameCounter, + url: "url", + }), + ); }); - afterEach(() => { - getFrameCounter = 2; - }); - - it("builds the offset values for a sub frame within the tab", () => { + it("builds the offset values for a sub frame within the tab", async () => { sendMockExtensionMessage( { command: "collectPageDetailsResponse", details: createAutofillPageDetailsMock() }, mock({ @@ -165,8 +169,11 @@ describe("OverlayBackground", () => { frameId: 1, }), ); + await flushPromises(); - expect(subFrameOffsetsSpy[tabId]).toBeDefined(); + expect(subFrameOffsetsSpy[tabId]).toStrictEqual( + new Map([[1, { left: 4, top: 4, url: "url" }]]), + ); expect(pageDetailsForTabSpy[tabId].size).toBe(2); }); @@ -202,10 +209,7 @@ describe("OverlayBackground", () => { tabsSendMessageSpy.mockResolvedValueOnce(null); sendMockExtensionMessage( { command: "collectPageDetailsResponse", details: createAutofillPageDetailsMock() }, - mock({ - tab, - frameId, - }), + mock({ tab, frameId }), ); await flushPromises(); @@ -219,6 +223,22 @@ describe("OverlayBackground", () => { ); expect(subFrameOffsetsSpy[tabId]).toStrictEqual(new Map([[frameId, null]])); }); + + it("updates sub frame data that has been calculated using window messages", async () => { + const tab = createChromeTabMock({ id: tabId }); + const frameId = 1; + const subFrameData = mock({ frameId, left: 10, top: 10, url: "url" }); + tabsSendMessageSpy.mockResolvedValueOnce(null); + subFrameOffsetsSpy[tabId] = new Map([[frameId, null]]); + + sendMockExtensionMessage( + { command: "updateSubFrameData", subFrameData }, + mock({ tab, frameId }), + ); + await flushPromises(); + + expect(subFrameOffsetsSpy[tabId]).toStrictEqual(new Map([[frameId, subFrameData]])); + }); }); }); @@ -237,4 +257,81 @@ describe("OverlayBackground", () => { expect(portKeyForTabSpy[tabId]).toBeUndefined(); }); }); + + describe("re-positioning the inline menu within sub frames", () => { + describe("rebuildSubFrameOffsets", () => { + const tabId = 1; + const topFrameId = 0; + const middleFrameId = 10; + const bottomFrameId = 20; + let tab: chrome.tabs.Tab; + + beforeEach(() => { + tab = createChromeTabMock({ id: tabId }); + overlayBackground["focusedFieldData"] = mock({ + tabId, + frameId: bottomFrameId, + }); + subFrameOffsetsSpy[tabId] = new Map([ + [topFrameId, { left: 1, top: 1, url: "https://top-frame.com" }], + [middleFrameId, { left: 2, top: 2, url: "https://middle-frame.com" }], + [bottomFrameId, { left: 3, top: 3, url: "https://bottom-frame.com" }], + ]); + tabsSendMessageSpy.mockResolvedValue( + mock({ + left: getFrameCounter, + top: getFrameCounter, + url: "url", + }), + ); + }); + + it("skips rebuilding sub frame offsets if the sender contains the currently focused field", () => { + const sender = mock({ tab, frameId: bottomFrameId }); + + sendMockExtensionMessage({ command: "rebuildSubFrameOffsets" }, sender); + + expect(getFrameDetailsSpy).not.toHaveBeenCalled(); + }); + + it("skips rebuilding sub frame offsets if the tab does not contain sub frames", () => { + const sender = mock({ + tab: createChromeTabMock({ id: 15 }), + frameId: 0, + }); + + sendMockExtensionMessage({ command: "rebuildSubFrameOffsets" }, sender); + + expect(getFrameDetailsSpy).not.toHaveBeenCalled(); + }); + + it("rebuilds the sub frame offsets for a given tab", async () => { + const sender = mock({ tab, frameId: middleFrameId }); + + sendMockExtensionMessage({ command: "rebuildSubFrameOffsets" }, sender); + await flushPromises(); + + expect(getFrameDetailsSpy).toHaveBeenCalledWith({ tabId, frameId: topFrameId }); + expect(getFrameDetailsSpy).toHaveBeenCalledWith({ tabId, frameId: bottomFrameId }); + expect(getFrameDetailsSpy).not.toHaveBeenCalledWith({ tabId, frameId: middleFrameId }); + }); + + it("triggers an update of the inline menu position after rebuilding sub frames", async () => { + jest.useFakeTimers(); + overlayBackground["updateInlineMenuPositionTimeout"] = 1; + const sender = mock({ tab, frameId: middleFrameId }); + jest.spyOn(overlayBackground as any, "updateInlineMenuPositionAfterSubFrameRebuild"); + + sendMockExtensionMessage({ command: "rebuildSubFrameOffsets" }, sender); + await flushPromises(); + jest.advanceTimersByTime(650); + + expect( + overlayBackground["updateInlineMenuPositionAfterSubFrameRebuild"], + ).toHaveBeenCalled(); + }); + }); + + describe("updateInlineMenuPositionAfterSubFrameRebuild", () => {}); + }); }); diff --git a/apps/browser/src/autofill/background/overlay.background.ts b/apps/browser/src/autofill/background/overlay.background.ts index c8fd2007ea4..8eb5389a4bd 100644 --- a/apps/browser/src/autofill/background/overlay.background.ts +++ b/apps/browser/src/autofill/background/overlay.background.ts @@ -276,6 +276,14 @@ export class OverlayBackground implements OverlayBackgroundInterface { } } + /** + * Builds the offset data for a sub frame of a tab. The offset data is used + * to calculate the position of the inline menu list and button. + * + * @param tab - The tab that the sub frame is associated with + * @param frameId - The frame ID of the sub frame + * @param url - The URL of the sub frame + */ private async buildSubFrameOffsets(tab: chrome.tabs.Tab, frameId: number, url: string) { const tabId = tab.id; let subFrameOffsetsForTab = this.subFrameOffsetsForTab[tabId]; @@ -291,7 +299,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { const subFrameData = { url, top: 0, left: 0 }; let frameDetails = await BrowserApi.getFrameDetails({ tabId, frameId }); - while (frameDetails && frameDetails.parentFrameId !== -1) { + while (frameDetails && frameDetails.parentFrameId > -1) { const subFrameOffset: SubFrameOffsetData = await BrowserApi.tabSendMessage( tab, { @@ -324,6 +332,15 @@ export class OverlayBackground implements OverlayBackgroundInterface { subFrameOffsetsForTab.set(frameId, subFrameData); } + /** + * Handles rebuilding the sub frame offsets when the tab is repositioned or scrolled. + * Will trigger a re-positioning of the inline menu list and button. Note that we + * do not trigger an update to sub frame data if the sender is the frame that has + * the field currently focused. We trigger a re-calculation of the field's position + * and as a result, the sub frame offsets of that frame will be updated. + * + * @param sender - The sender of the message + */ private async rebuildSubFrameOffsets(sender: chrome.runtime.MessageSender) { if (sender.frameId === this.focusedFieldData?.frameId) { return; @@ -338,8 +355,8 @@ export class OverlayBackground implements OverlayBackgroundInterface { clearTimeout(this.updateInlineMenuPositionTimeout); } - const frameTabs = Array.from(subFrameOffsetsForTab.keys()); - for (const frameId of frameTabs) { + const tabFrameIds = Array.from(subFrameOffsetsForTab.keys()); + for (const frameId of tabFrameIds) { if (frameId === sender.frameId) { continue; }