From 31d74379fa07eba6eb36992857b8da2bacd77ba0 Mon Sep 17 00:00:00 2001 From: Ben Brooks Date: Wed, 3 Dec 2025 09:38:05 -0800 Subject: [PATCH] [pm-8458] Register migration; draft tests Signed-off-by: Ben Brooks --- libs/state/src/state-migrations/migrate.ts | 6 +- ...74-clear-clipboard-delay-to-string.spec.ts | 155 ++++++++++++++++++ 2 files changed, 159 insertions(+), 2 deletions(-) create mode 100644 libs/state/src/state-migrations/migrations/74-clear-clipboard-delay-to-string.spec.ts diff --git a/libs/state/src/state-migrations/migrate.ts b/libs/state/src/state-migrations/migrate.ts index bf4cd17adba..52b8d0bce9d 100644 --- a/libs/state/src/state-migrations/migrate.ts +++ b/libs/state/src/state-migrations/migrate.ts @@ -70,12 +70,13 @@ import { RemoveAcBannersDismissed } from "./migrations/70-remove-ac-banner-dismi import { RemoveNewCustomizationOptionsCalloutDismissed } from "./migrations/71-remove-new-customization-options-callout-dismissed"; import { RemoveAccountDeprovisioningBannerDismissed } from "./migrations/72-remove-account-deprovisioning-banner-dismissed"; import { AddMasterPasswordUnlockData } from "./migrations/73-add-master-password-unlock-data"; +import { ClearClipboardDelayToStringMigrator } from "./migrations/74-clear-clipboard-delay-to-string"; import { MoveStateVersionMigrator } from "./migrations/8-move-state-version"; import { MoveBrowserSettingsToGlobal } from "./migrations/9-move-browser-settings-to-global"; import { MinVersionMigrator } from "./migrations/min-version"; export const MIN_VERSION = 3; -export const CURRENT_VERSION = 73; +export const CURRENT_VERSION = 74; export type MinVersion = typeof MIN_VERSION; export function createMigrationBuilder() { @@ -150,7 +151,8 @@ export function createMigrationBuilder() { .with(RemoveAcBannersDismissed, 69, 70) .with(RemoveNewCustomizationOptionsCalloutDismissed, 70, 71) .with(RemoveAccountDeprovisioningBannerDismissed, 71, 72) - .with(AddMasterPasswordUnlockData, 72, CURRENT_VERSION); + .with(AddMasterPasswordUnlockData, 72, 73) + .with(ClearClipboardDelayToStringMigrator, 73, CURRENT_VERSION); } export async function currentVersion( diff --git a/libs/state/src/state-migrations/migrations/74-clear-clipboard-delay-to-string.spec.ts b/libs/state/src/state-migrations/migrations/74-clear-clipboard-delay-to-string.spec.ts new file mode 100644 index 00000000000..1db51009554 --- /dev/null +++ b/libs/state/src/state-migrations/migrations/74-clear-clipboard-delay-to-string.spec.ts @@ -0,0 +1,155 @@ +import { MockProxy } from "jest-mock-extended"; + +import { MigrationHelper } from "../migration-helper"; +import { mockMigrationHelper } from "../migration-helper.spec"; + +import { ClearClipboardDelayToStringMigrator } from "./74-clear-clipboard-delay-to-string"; + +describe("ClearClipboardDelayToStringMigrator", () => { + let helper: MockProxy; + let sut: ClearClipboardDelayToStringMigrator; + + describe("migrate", () => { + beforeEach(() => { + helper = mockMigrationHelper({}, 73); + sut = new ClearClipboardDelayToStringMigrator(73, 74); + }); + + it("should call getAccounts", async () => { + await sut.migrate(helper); + + // This should always be called in any migration + expect(helper.getAccounts).toHaveBeenCalled(); + }); + + it("should handle empty accounts gracefully", async () => { + // Test with no accounts + helper.getAccounts.mockResolvedValue([]); + + await expect(sut.migrate(helper)).resolves.not.toThrow(); + }); + + it("should handle accounts with no clearClipboard settings", async () => { + // Test with accounts but no clearClipboard settings + helper.getAccounts.mockResolvedValue([ + { userId: "user-1", account: {} }, + { userId: "user-2", account: {} }, + ]); + + await expect(sut.migrate(helper)).resolves.not.toThrow(); + + // Should still call getFromUser for each user + expect(helper.getFromUser).toHaveBeenCalledWith( + "user-1", + expect.objectContaining({ + key: "clearClipboardDelay", + stateDefinition: expect.objectContaining({ + name: "autofillSettingsLocal", + }), + }), + ); + + expect(helper.getFromUser).toHaveBeenCalledWith( + "user-2", + expect.objectContaining({ + key: "clearClipboardDelay", + stateDefinition: expect.objectContaining({ + name: "autofillSettingsLocal", + }), + }), + ); + }); + + it("should migrate a single user with an integer value", async () => { + // Mock getAccounts to return one user + helper.getAccounts.mockResolvedValue([{ userId: "user-1", account: {} }]); + + // Mock getFromUser to return an integer value + helper.getFromUser.mockResolvedValue(10); + + await sut.migrate(helper); + + expect(helper.getFromUser).toHaveBeenCalledWith( + "user-1", + expect.objectContaining({ + key: "clearClipboardDelay", + stateDefinition: expect.objectContaining({ + name: "autofillSettingsLocal", + }), + }), + ); + + expect(helper.setToUser).toHaveBeenCalledWith( + "user-1", + expect.objectContaining({ + key: "clearClipboardDelay", + stateDefinition: expect.objectContaining({ + name: "autofillSettingsLocal", + }), + }), + "tenSeconds", + ); + }); + + it("should migrate null to fiveMinutes", async () => { + helper.getAccounts.mockResolvedValue([{ userId: "user-1", account: {} }]); + + helper.getFromUser.mockResolvedValue(null); + + await sut.migrate(helper); + + expect(helper.setToUser).toHaveBeenCalledWith( + "user-1", + expect.objectContaining({ + key: "clearClipboardDelay", + stateDefinition: expect.objectContaining({ + name: "autofillSettingsLocal", + }), + }), + "fiveMinutes", + ); + }); + + it("should not migrate undefined values", async () => { + helper.getAccounts.mockResolvedValue([{ userId: "user-1", account: {} }]); + + helper.getFromUser.mockResolvedValue(undefined); + + await sut.migrate(helper); + + expect(helper.setToUser).not.toHaveBeenCalled(); + }); + }); + + describe("rollback", () => { + beforeEach(() => { + helper = mockMigrationHelper({}, 74); + sut = new ClearClipboardDelayToStringMigrator(73, 74); + }); + + it("should call getAccounts", async () => { + await sut.rollback(helper); + + expect(helper.getAccounts).toHaveBeenCalled(); + }); + + it("should rollback a string value to integer", async () => { + helper.getAccounts.mockResolvedValue([{ userId: "user-1", account: {} }]); + + helper.getFromUser.mockResolvedValue("tenSeconds"); + + await sut.rollback(helper); + + expect(helper.setToUser).toHaveBeenCalledWith( + "user-1", + expect.objectContaining({ + key: "clearClipboardDelay", + stateDefinition: expect.objectContaining({ + name: "autofillSettingsLocal", + }), + }), + 10, + ); + }); + }); +});