/* eslint-disable no-restricted-globals, no-restricted-syntax */ import { translateToNewGQLCollection, translateToNewRESTCollection, } from "@hoppscotch/data" import { watchDebounced } from "@vueuse/core" import { TestContainer } from "dioc/testing" import { cloneDeep } from "lodash-es" import { afterAll, describe, expect, it, vi } from "vitest" import { MQTTRequest$, setMQTTRequest } from "~/newstore/MQTTSession" import { SSERequest$, setSSERequest } from "~/newstore/SSESession" import { SIORequest$, setSIORequest } from "~/newstore/SocketIOSession" import { WSRequest$, setWSRequest } from "~/newstore/WebSocketSession" import { graphqlCollectionStore, restCollectionStore, setGraphqlCollections, setRESTCollections, } from "~/newstore/collections" import { addGlobalEnvVariable, environments$, globalEnv$, replaceEnvironments, selectedEnvironmentIndex$, setGlobalEnvVariables, setSelectedEnvironmentIndex, } from "~/newstore/environments" import { graphqlHistoryStore, restHistoryStore, setGraphqlHistoryEntries, setRESTHistoryEntries, translateToNewGQLHistory, translateToNewRESTHistory, } from "~/newstore/history" import { bulkApplyLocalState, localStateStore } from "~/newstore/localstate" import { applySetting, bulkApplySettings, performSettingsDataMigrations, settingsStore, } from "~/newstore/settings" import { GQLTabService } from "~/services/tab/graphql" import { RESTTabService } from "~/services/tab/rest" import { PersistenceService } from "../../persistence" import { ENVIRONMENTS_MOCK, GLOBAL_ENV_MOCK, GQL_COLLECTIONS_MOCK, GQL_HISTORY_MOCK, GQL_TAB_STATE_MOCK, MQTT_REQUEST_MOCK, REST_COLLECTIONS_MOCK, REST_HISTORY_MOCK, REST_TAB_STATE_MOCK, SELECTED_ENV_INDEX_MOCK, SOCKET_IO_REQUEST_MOCK, SSE_REQUEST_MOCK, VUEX_DATA_MOCK, WEBSOCKET_REQUEST_MOCK, } from "./__mocks__" vi.mock("~/modules/i18n", () => { return { __esModule: true, getI18n: vi.fn().mockImplementation(() => () => "test"), } }) // Define modules that are shared across methods here vi.mock("@vueuse/core", async (importOriginal) => { const actualModule: Record = await importOriginal() return { ...actualModule, watchDebounced: vi.fn(), } }) vi.mock("~/newstore/environments", () => { return { addGlobalEnvVariable: vi.fn(), setGlobalEnvVariables: vi.fn(), replaceEnvironments: vi.fn(), setSelectedEnvironmentIndex: vi.fn(), environments$: { subscribe: vi.fn(), }, globalEnv$: { subscribe: vi.fn(), }, selectedEnvironmentIndex$: { subscribe: vi.fn(), }, } }) vi.mock("~/newstore/settings", () => { return { applySetting: vi.fn(), } }) const toastErrorFn = vi.fn() vi.mock("~/composables/toast", () => { return { useToast: () => ({ error: toastErrorFn }), } }) /** * Helper functions */ const spyOnGetItem = () => vi.spyOn(Storage.prototype, "getItem") const spyOnRemoveItem = () => vi.spyOn(Storage.prototype, "removeItem") const spyOnSetItem = () => vi.spyOn(Storage.prototype, "setItem") const bindPersistenceService = ({ mockGQLTabService = false, mockRESTTabService = false, mock = {}, }: { mockGQLTabService?: boolean mockRESTTabService?: boolean mock?: Record } = {}) => { const container = new TestContainer() if (mockGQLTabService) { container.bindMock(GQLTabService, mock) } if (mockRESTTabService) { container.bindMock(RESTTabService, mock) } container.bind(PersistenceService) const service = container.bind(PersistenceService) return service } const invokeSetupLocalPersistence = ( serviceBindMock?: Record ) => { const service = bindPersistenceService(serviceBindMock) service.setupLocalPersistence() } describe("PersistenceService", () => { afterAll(() => { // Clear all mocks vi.clearAllMocks() // Restore the original implementation for any spied functions vi.restoreAllMocks() }) describe("setupLocalPersistence", () => { describe("Check and migrate old settings", () => { // Set of keys read from localStorage across test cases const bgColorKey = "BG_COLOR" const nuxtColorModeKey = "nuxt-color-mode" const selectedEnvIndexKey = "selectedEnvIndex" const themeColorKey = "THEME_COLOR" const vuexKey = "vuex" it(`sets the selected environment index type as "NO_ENV" in localStorage if the value retrieved for ${selectedEnvIndexKey} is "-1"`, () => { window.localStorage.setItem(selectedEnvIndexKey, "-1") const getItemSpy = spyOnGetItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence() expect(getItemSpy).toHaveBeenCalledWith(selectedEnvIndexKey) expect(setItemSpy).toHaveBeenCalledWith( selectedEnvIndexKey, JSON.stringify({ type: "NO_ENV_SELECTED", }) ) }) it(`sets the selected environment index type as "MY_ENV" in localStorage if the value retrieved for "${selectedEnvIndexKey}" is greater than "0"`, () => { window.localStorage.setItem(selectedEnvIndexKey, "1") const getItemSpy = spyOnGetItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence() expect(getItemSpy).toHaveBeenCalledWith(selectedEnvIndexKey) expect(setItemSpy).toHaveBeenCalledWith( selectedEnvIndexKey, JSON.stringify({ type: "MY_ENV", index: 1, }) ) }) it(`skips schema parsing and setting other properties if ${vuexKey} read from localStorage is an empty entity`, () => { window.localStorage.setItem(vuexKey, JSON.stringify({})) const getItemSpy = spyOnGetItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence() expect(getItemSpy).toHaveBeenCalledWith(vuexKey) expect(toastErrorFn).not.toHaveBeenCalled() expect(setItemSpy).not.toHaveBeenCalled() }) it(`shows an error and sets the entry as a backup in localStorage if "${vuexKey}" read from localStorage doesn't match the schema`, () => { // Invalid shape for `vuex` // `postwoman.settings.CURRENT_INTERCEPTOR_ID` -> `string` const vuexData = { ...VUEX_DATA_MOCK, postwoman: { ...VUEX_DATA_MOCK.postwoman, settings: { ...VUEX_DATA_MOCK.postwoman.settings, CURRENT_INTERCEPTOR_ID: 1234, }, }, } window.localStorage.setItem(vuexKey, JSON.stringify(vuexData)) const getItemSpy = spyOnGetItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence() expect(getItemSpy).toHaveBeenCalledWith(vuexKey) expect(toastErrorFn).toHaveBeenCalledWith( expect.stringContaining(vuexKey) ) expect(setItemSpy).toHaveBeenCalledWith( `${vuexKey}-backup`, JSON.stringify(vuexData) ) }) it(`shows an error and sets the entry as a backup in localStorage if "${themeColorKey}" read from localStorage doesn't match the schema`, () => { const vuexData = cloneDeep(VUEX_DATA_MOCK) window.localStorage.setItem(vuexKey, JSON.stringify(vuexData)) const themeColorValue = "invalid-color" window.localStorage.setItem(themeColorKey, themeColorValue) const getItemSpy = spyOnGetItem() const removeItemSpy = spyOnRemoveItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence() expect(getItemSpy).toHaveBeenCalledWith(vuexKey) expect(toastErrorFn).toHaveBeenCalledWith( expect.stringContaining(themeColorKey) ) expect(setItemSpy).toHaveBeenCalledWith( `${themeColorKey}-backup`, themeColorValue ) expect(applySetting).toHaveBeenCalledWith( themeColorKey, themeColorValue ) expect(removeItemSpy).toHaveBeenCalledWith(themeColorKey) }) it(`shows an error and sets the entry as a backup in localStorage if "${nuxtColorModeKey}" read from localStorage doesn't match the schema`, () => { const vuexData = cloneDeep(VUEX_DATA_MOCK) window.localStorage.setItem(vuexKey, JSON.stringify(vuexData)) const nuxtColorModeValue = "invalid-color" window.localStorage.setItem(nuxtColorModeKey, nuxtColorModeValue) const getItemSpy = spyOnGetItem() const removeItemSpy = spyOnRemoveItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence() expect(getItemSpy).toHaveBeenCalledWith(vuexKey) expect(toastErrorFn).toHaveBeenCalledWith( expect.stringContaining(nuxtColorModeKey) ) expect(setItemSpy).toHaveBeenCalledWith( `${nuxtColorModeKey}-backup`, nuxtColorModeValue ) expect(applySetting).toHaveBeenCalledWith( bgColorKey, nuxtColorModeValue ) expect(removeItemSpy).toHaveBeenCalledWith(nuxtColorModeKey) }) it(`extracts individual properties from the key "${vuexKey}" and sets them in localStorage`, () => { const vuexData = cloneDeep(VUEX_DATA_MOCK) window.localStorage.setItem(vuexKey, JSON.stringify(vuexData)) const themeColor = "red" const nuxtColorMode = "dark" window.localStorage.setItem(themeColorKey, themeColor) window.localStorage.setItem(nuxtColorModeKey, nuxtColorMode) const getItemSpy = spyOnGetItem() const removeItemSpy = spyOnRemoveItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence() expect(getItemSpy).toHaveBeenCalledWith(vuexKey) expect(toastErrorFn).not.toHaveBeenCalledWith(nuxtColorModeKey) expect(setItemSpy).not.toHaveBeenCalledWith( `${nuxtColorModeKey}-backup` ) expect(setItemSpy).toHaveBeenCalledWith( "settings", JSON.stringify(vuexData.postwoman.settings) ) const { postwoman } = vuexData delete postwoman.settings expect(setItemSpy).toHaveBeenCalledWith( vuexKey, JSON.stringify(vuexData) ) // Excluding `settings` expect(setItemSpy).toHaveBeenCalledWith( "collections", JSON.stringify(postwoman.collections) ) delete postwoman.collections // Excluding `settings` & `collections` expect(setItemSpy).toHaveBeenCalledWith( vuexKey, JSON.stringify(vuexData) ) expect(setItemSpy).toHaveBeenCalledWith( "collectionsGraphql", JSON.stringify(postwoman.collectionsGraphql) ) delete postwoman.collectionsGraphql // Excluding `settings, `collections` & `collectionsGraphql` expect(setItemSpy).toHaveBeenCalledWith( vuexKey, JSON.stringify(vuexData) ) expect(setItemSpy).toHaveBeenCalledWith( "environments", JSON.stringify(postwoman.environments) ) delete postwoman.environments // Excluding `settings, `collections`, `collectionsGraphql` & `environments` expect(setItemSpy).toHaveBeenCalledWith( vuexKey, JSON.stringify(vuexData) ) expect(getItemSpy).toHaveBeenCalledWith(themeColorKey) expect(applySetting).toHaveBeenCalledWith(themeColorKey, themeColor) expect(removeItemSpy).toHaveBeenCalledWith(themeColorKey) expect(window.localStorage.getItem(themeColorKey)).toBe(null) expect(getItemSpy).toHaveBeenCalledWith(nuxtColorModeKey) expect(applySetting).toHaveBeenCalledWith(bgColorKey, nuxtColorMode) expect(removeItemSpy).toHaveBeenCalledWith(nuxtColorModeKey) expect(window.localStorage.getItem(nuxtColorModeKey)).toBe(null) }) }) describe("Setup local state persistence", () => { // Key read from localStorage across test cases const localStateKey = "localState" it(`shows an error and sets the entry as a backup in localStorage if "${localStateKey}" read from localStorage has a value which is not a "string" or "undefined"`, () => { const localStateData = { REMEMBERED_TEAM_ID: null, } window.localStorage.setItem( localStateKey, JSON.stringify(localStateData) ) const getItemSpy = spyOnGetItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence() expect(getItemSpy).toHaveBeenCalledWith(localStateKey) expect(toastErrorFn).toHaveBeenCalledWith( expect.stringContaining(localStateKey) ) expect(setItemSpy).toHaveBeenCalledWith( `${localStateKey}-backup`, JSON.stringify(localStateData) ) }) it(`shows an error and sets the entry as a backup in localStorage if "${localStateKey}" read from localStorage has an invalid key`, () => { const localStateData = { INVALID_KEY: null, } window.localStorage.setItem( localStateKey, JSON.stringify(localStateData) ) const getItemSpy = spyOnGetItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence() expect(getItemSpy).toHaveBeenCalledWith(localStateKey) expect(toastErrorFn).toHaveBeenCalledWith( expect.stringContaining(localStateKey) ) expect(setItemSpy).toHaveBeenCalledWith( `${localStateKey}-backup`, JSON.stringify(localStateData) ) }) it(`schema parsing succeeds if there is no "${localStateKey}" key present in localStorage where the fallback of "{}" is chosen`, () => { window.localStorage.removeItem(localStateKey) const getItemSpy = spyOnGetItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence() expect(getItemSpy).toHaveBeenCalledWith(localStateKey) expect(toastErrorFn).not.toHaveBeenCalledWith(localStateKey) expect(setItemSpy).not.toHaveBeenCalled() }) it(`reads the value for "${localStateKey}" key from localStorage, invokes "bulkApplyLocalState" function if a value is yielded and subscribes to "localStateStore" updates`, () => { vi.mock("~/newstore/localstate", () => { return { bulkApplyLocalState: vi.fn(), localStateStore: { subject$: { subscribe: vi.fn(), }, }, } }) const localStateData = { REMEMBERED_TEAM_ID: "test-id", } window.localStorage.setItem( localStateKey, JSON.stringify(localStateData) ) const getItemSpy = spyOnGetItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence() expect(getItemSpy).toHaveBeenCalledWith(localStateKey) expect(toastErrorFn).not.toHaveBeenCalledWith(localStateKey) expect(setItemSpy).not.toHaveBeenCalled() expect(bulkApplyLocalState).toHaveBeenCalledWith(localStateData) expect(localStateStore.subject$.subscribe).toHaveBeenCalledWith( expect.any(Function) ) }) }) describe("Setup settings persistence", () => { // Key read from localStorage across test cases const settingsKey = "settings" it(`shows an error and sets the entry as a backup in localStorage if "${settingsKey}" read from localStorage doesn't match the schema`, () => { // Invalid shape for `settings` // Expected value are booleans const settings = { EXTENSIONS_ENABLED: "true", PROXY_ENABLED: "true", } window.localStorage.setItem(settingsKey, JSON.stringify(settings)) const getItemSpy = spyOnGetItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence() expect(getItemSpy).toHaveBeenCalledWith(settingsKey) expect(toastErrorFn).toHaveBeenCalledWith( expect.stringContaining(settingsKey) ) expect(setItemSpy).toHaveBeenCalledWith( `${settingsKey}-backup`, JSON.stringify(settings) ) }) it(`schema parsing succeeds if there is no "${settingsKey}" key present in localStorage where the fallback of "{}" is chosen`, () => { window.localStorage.removeItem(settingsKey) const getItemSpy = spyOnGetItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence() expect(getItemSpy).toHaveBeenCalledWith(settingsKey) expect(toastErrorFn).not.toHaveBeenCalledWith(settingsKey) expect(setItemSpy).not.toHaveBeenCalled() }) it(`reads the value for "${settingsKey}" from localStorage, invokes "performSettingsDataMigrations" and "bulkApplySettings" functions as required and subscribes to "settingsStore" updates`, () => { vi.mock("~/newstore/settings", async (importOriginal) => { const actualModule: Record = await importOriginal() return { ...actualModule, applySetting: vi.fn(), bulkApplySettings: vi.fn(), performSettingsDataMigrations: vi .fn() .mockImplementation((data: any) => data), settingsStore: { subject$: { subscribe: vi.fn(), }, }, } }) const { settings } = VUEX_DATA_MOCK.postwoman window.localStorage.setItem(settingsKey, JSON.stringify(settings)) const getItemSpy = spyOnGetItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence() // toastErrorFn = vi.fn() expect(getItemSpy).toHaveBeenCalledWith(settingsKey) expect(toastErrorFn).not.toHaveBeenCalledWith(settingsKey) expect(setItemSpy).not.toHaveBeenCalled() expect(performSettingsDataMigrations).toHaveBeenCalledWith(settings) expect(bulkApplySettings).toHaveBeenCalledWith(settings) expect(settingsStore.subject$.subscribe).toHaveBeenCalledWith( expect.any(Function) ) }) }) describe("Setup history persistence", () => { // Keys read from localStorage across test cases const historyKey = "history" const graphqlHistoryKey = "graphqlHistory" it(`shows an error and sets the entry as a backup in localStorage if "${historyKey}" read from localStorage doesn't match the schema`, () => { // Invalid shape for `history` // `v` -> `number` const restHistoryData = [{ ...REST_HISTORY_MOCK, v: "1" }] window.localStorage.setItem(historyKey, JSON.stringify(restHistoryData)) const getItemSpy = spyOnGetItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence() expect(getItemSpy).toHaveBeenCalledWith(historyKey) expect(toastErrorFn).toHaveBeenCalledWith( expect.stringContaining(historyKey) ) expect(setItemSpy).toHaveBeenCalledWith( `${historyKey}-backup`, JSON.stringify(restHistoryData) ) }) it(`REST history schema parsing succeeds if there is no "${historyKey}" key present in localStorage where the fallback of "[]" is chosen`, () => { window.localStorage.removeItem(historyKey) const getItemSpy = spyOnGetItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence() expect(getItemSpy).toHaveBeenCalledWith(historyKey) expect(toastErrorFn).not.toHaveBeenCalledWith(historyKey) expect(setItemSpy).not.toHaveBeenCalled() }) it(`shows an error and sets the entry as a backup in localStorage if "${graphqlHistoryKey}" read from localStorage doesn't match the schema`, () => { // Invalid shape for `graphqlHistory` // `v` -> `number` const graphqlHistoryData = [{ ...GQL_HISTORY_MOCK, v: "1" }] window.localStorage.setItem( graphqlHistoryKey, JSON.stringify(graphqlHistoryData) ) const getItemSpy = spyOnGetItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence() expect(getItemSpy).toHaveBeenCalledWith(graphqlHistoryKey) expect(setItemSpy).toHaveBeenCalledWith( `${graphqlHistoryKey}-backup`, JSON.stringify(graphqlHistoryData) ) }) it(`GQL history schema parsing succeeds if there is no "${graphqlHistoryKey}" key present in localStorage where the fallback of "[]" is chosen`, () => { window.localStorage.removeItem(graphqlHistoryKey) const getItemSpy = spyOnGetItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence() expect(getItemSpy).toHaveBeenCalledWith(graphqlHistoryKey) expect(toastErrorFn).not.toHaveBeenCalledWith(graphqlHistoryKey) expect(setItemSpy).not.toHaveBeenCalled() }) it("reads REST and GQL history entries from localStorage, translates them to the new format, writes back the updates and subscribes to the respective store for updates", () => { vi.mock("~/newstore/history", () => { return { setGraphqlHistoryEntries: vi.fn(), setRESTHistoryEntries: vi.fn(), translateToNewGQLHistory: vi .fn() .mockImplementation((data: any) => data), translateToNewRESTHistory: vi .fn() .mockImplementation((data: any) => data), graphqlHistoryStore: { subject$: { subscribe: vi.fn(), }, }, restHistoryStore: { subject$: { subscribe: vi.fn(), }, }, } }) const stringifiedRestHistory = JSON.stringify(REST_HISTORY_MOCK) const stringifiedGqlHistory = JSON.stringify(GQL_HISTORY_MOCK) window.localStorage.setItem(historyKey, stringifiedRestHistory) window.localStorage.setItem(graphqlHistoryKey, stringifiedGqlHistory) const getItemSpy = spyOnGetItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence() expect(getItemSpy).toHaveBeenCalledWith(historyKey) expect(getItemSpy).toHaveBeenCalledWith(graphqlHistoryKey) expect(toastErrorFn).not.toHaveBeenCalledWith( historyKey, graphqlHistoryKey ) expect(setItemSpy).not.toHaveBeenCalled() expect(translateToNewRESTHistory).toHaveBeenCalled() expect(translateToNewGQLHistory).toHaveBeenCalled() // This ensures `updatedOn` field is treated as a `string` const parsedRestHistory = JSON.parse(stringifiedRestHistory) const parsedGqlHistory = JSON.parse(stringifiedGqlHistory) expect(setRESTHistoryEntries).toHaveBeenCalledWith(parsedRestHistory) expect(setGraphqlHistoryEntries).toHaveBeenCalledWith(parsedGqlHistory) expect(restHistoryStore.subject$.subscribe).toHaveBeenCalledWith( expect.any(Function) ) expect(graphqlHistoryStore.subject$.subscribe).toHaveBeenCalledWith( expect.any(Function) ) }) }) describe("Setup collections persistence", () => { // Keys read from localStorage across test cases const collectionsKey = "collections" const collectionsGraphqlKey = "collectionsGraphql" it(`shows an error and sets the entry as a backup in localStorage if "${collectionsKey}" read from localStorage doesn't match the schema`, () => { // Invalid shape for `collections` // `v` -> `number` const restCollectionsData = [{ ...REST_COLLECTIONS_MOCK, v: "1" }] window.localStorage.setItem( collectionsKey, JSON.stringify(restCollectionsData) ) const getItemSpy = spyOnGetItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence() expect(getItemSpy).toHaveBeenCalledWith(collectionsKey) expect(toastErrorFn).toHaveBeenCalledWith( expect.stringContaining(collectionsKey) ) expect(setItemSpy).toHaveBeenCalledWith( `${collectionsKey}-backup`, JSON.stringify(restCollectionsData) ) }) it(`REST collections schema parsing succeeds if there is no "${collectionsKey}" key present in localStorage where the fallback of "[]" is chosen`, () => { window.localStorage.removeItem(collectionsKey) const getItemSpy = spyOnGetItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence() expect(getItemSpy).toHaveBeenCalledWith(collectionsKey) expect(toastErrorFn).not.toHaveBeenCalledWith(collectionsKey) expect(setItemSpy).not.toHaveBeenCalled() }) it(`shows an error and sets the entry as a backup in localStorage if "${collectionsGraphqlKey}" read from localStorage doesn't match the schema`, () => { // Invalid shape for `collectionsGraphql` // `v` -> `number` const graphqlCollectionsData = [{ ...GQL_COLLECTIONS_MOCK, v: "1" }] window.localStorage.setItem( collectionsGraphqlKey, JSON.stringify(graphqlCollectionsData) ) const getItemSpy = spyOnGetItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence() expect(getItemSpy).toHaveBeenCalledWith(collectionsGraphqlKey) expect(toastErrorFn).toHaveBeenCalledWith( expect.stringContaining(collectionsGraphqlKey) ) expect(setItemSpy).toHaveBeenCalledWith( `${collectionsGraphqlKey}-backup`, JSON.stringify(graphqlCollectionsData) ) }) it(`GQL history schema parsing succeeds if there is no "${collectionsGraphqlKey}" key present in localStorage where the fallback of "[]" is chosen`, () => { window.localStorage.removeItem(collectionsGraphqlKey) const getItemSpy = spyOnGetItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence() expect(getItemSpy).toHaveBeenCalledWith(collectionsGraphqlKey) expect(toastErrorFn).not.toHaveBeenCalledWith(collectionsGraphqlKey) expect(setItemSpy).not.toHaveBeenCalled() }) it("reads REST and GQL collection entries from localStorage, translates them to the new format, writes back the updates and subscribes to the respective store for updates", () => { vi.mock("@hoppscotch/data", async (importOriginal) => { const actualModule: Record = await importOriginal() return { ...actualModule, translateToNewGQLCollection: vi .fn() .mockImplementation((data: any) => data), translateToNewRESTCollection: vi .fn() .mockImplementation((data: any) => data), } }) vi.mock("~/newstore/collections", () => { return { setGraphqlCollections: vi.fn(), setRESTCollections: vi.fn(), graphqlCollectionStore: { subject$: { subscribe: vi.fn(), }, }, restCollectionStore: { subject$: { subscribe: vi.fn(), }, }, } }) const restCollections = REST_COLLECTIONS_MOCK const gqlCollections = GQL_COLLECTIONS_MOCK window.localStorage.setItem( collectionsKey, JSON.stringify(restCollections) ) window.localStorage.setItem( collectionsGraphqlKey, JSON.stringify(gqlCollections) ) const getItemSpy = spyOnGetItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence() expect(getItemSpy).toHaveBeenCalledWith(collectionsKey) expect(getItemSpy).toHaveBeenCalledWith(collectionsGraphqlKey) expect(toastErrorFn).not.toHaveBeenCalledWith( collectionsKey, collectionsGraphqlKey ) expect(setItemSpy).not.toHaveBeenCalled() expect(translateToNewGQLCollection).toHaveBeenCalled() expect(translateToNewRESTCollection).toHaveBeenCalled() expect(setRESTCollections).toHaveBeenCalledWith(restCollections) expect(setGraphqlCollections).toHaveBeenCalledWith(gqlCollections) expect(graphqlCollectionStore.subject$.subscribe).toHaveBeenCalledWith( expect.any(Function) ) expect(restCollectionStore.subject$.subscribe).toHaveBeenCalledWith( expect.any(Function) ) }) }) describe("Setup environments persistence", () => { // Key read from localStorage across test cases const environmentsKey = "environments" it(`shows an error and sets the entry as a backup in localStorage if "${environmentsKey}" read from localStorage doesn't match the schema`, () => { // Invalid shape for `environments` const environments = [ // `entries` -> `variables` { name: "Test", entries: [{ key: "test-key", value: "test-value" }] }, ] window.localStorage.setItem( environmentsKey, JSON.stringify(environments) ) const getItemSpy = spyOnGetItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence() expect(getItemSpy).toHaveBeenCalledWith(environmentsKey) expect(toastErrorFn).toHaveBeenCalledWith( expect.stringContaining(environmentsKey) ) expect(setItemSpy).toHaveBeenCalledWith( `${environmentsKey}-backup`, JSON.stringify(environments) ) }) it(`separates "globals" entries from "${environmentsKey}", subscribes to the "environmentStore" and updates localStorage entries`, () => { const environments = cloneDeep(ENVIRONMENTS_MOCK) window.localStorage.setItem( "environments", JSON.stringify(environments) ) const getItemSpy = spyOnGetItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence() expect(getItemSpy).toHaveBeenCalledWith(environmentsKey) expect(toastErrorFn).not.toHaveBeenCalledWith(environmentsKey) expect(setItemSpy).not.toHaveBeenCalledWith(`${environmentsKey}-backup`) expect(addGlobalEnvVariable).toHaveBeenCalledWith( environments[0].variables[0] ) // Removes `globals` from environments environments.splice(0, 1) expect(setItemSpy).toHaveBeenCalledWith( environmentsKey, JSON.stringify(environments) ) expect(replaceEnvironments).toBeCalledWith(environments) expect(environments$.subscribe).toHaveBeenCalledWith( expect.any(Function) ) }) }) describe("Setup selected environment persistence", () => { // Key read from localStorage across test cases const selectedEnvIndexKey = "selectedEnvIndex" it(`shows an error and sets the entry as a backup in localStorage if "${selectedEnvIndexKey}" read from localStorage doesn't match the schema`, () => { // Invalid shape for `selectedEnvIndex` // `index` -> `number` const selectedEnvIndex = { ...SELECTED_ENV_INDEX_MOCK, index: "1" } window.localStorage.setItem( selectedEnvIndexKey, JSON.stringify(selectedEnvIndex) ) const getItemSpy = spyOnGetItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence() expect(getItemSpy).toHaveBeenCalledWith(selectedEnvIndexKey) expect(toastErrorFn).toHaveBeenCalledWith( expect.stringContaining(selectedEnvIndexKey) ) expect(setItemSpy).toHaveBeenCalledWith( `${selectedEnvIndexKey}-backup`, JSON.stringify(selectedEnvIndex) ) }) it(`schema parsing succeeds if there is no "${selectedEnvIndexKey}" key present in localStorage where the fallback of "null" is chosen`, () => { window.localStorage.removeItem(selectedEnvIndexKey) const getItemSpy = spyOnGetItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence() expect(getItemSpy).toHaveBeenCalledWith(selectedEnvIndexKey) expect(toastErrorFn).not.toHaveBeenCalledWith(selectedEnvIndexKey) expect(setItemSpy).not.toHaveBeenCalled() }) it(`sets it to the store if there is a value associated with the "${selectedEnvIndexKey}" key in localStorage`, () => { const selectedEnvIndex = SELECTED_ENV_INDEX_MOCK window.localStorage.setItem( selectedEnvIndexKey, JSON.stringify(selectedEnvIndex) ) const getItemSpy = spyOnGetItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence() expect(getItemSpy).toHaveBeenCalledWith(selectedEnvIndexKey) expect(toastErrorFn).not.toHaveBeenCalledWith(selectedEnvIndexKey) expect(setItemSpy).not.toHaveBeenCalled() expect(setSelectedEnvironmentIndex).toHaveBeenCalledWith( selectedEnvIndex ) expect(selectedEnvironmentIndex$.subscribe).toHaveBeenCalledWith( expect.any(Function) ) }) it(`sets it to "NO_ENV_SELECTED" if there is no value associated with the "${selectedEnvIndexKey}" in localStorage`, () => { window.localStorage.removeItem(selectedEnvIndexKey) const getItemSpy = spyOnGetItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence() expect(getItemSpy).toHaveBeenCalledWith(selectedEnvIndexKey) expect(toastErrorFn).not.toHaveBeenCalledWith(selectedEnvIndexKey) expect(setItemSpy).not.toHaveBeenCalled() expect(setSelectedEnvironmentIndex).toHaveBeenCalledWith({ type: "NO_ENV_SELECTED", }) expect(selectedEnvironmentIndex$.subscribe).toHaveBeenCalledWith( expect.any(Function) ) }) }) describe("setup WebSocket persistence", () => { // Key read from localStorage across test cases const wsRequestKey = "WebsocketRequest" it(`shows an error and sets the entry as a backup in localStorage if "${wsRequestKey}" read from localStorage doesn't match the schema`, () => { // Invalid shape for `WebsocketRequest` const request = { ...WEBSOCKET_REQUEST_MOCK, // `protocols` -> `[]` protocols: {}, } window.localStorage.setItem(wsRequestKey, JSON.stringify(request)) const getItemSpy = spyOnGetItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence() expect(getItemSpy).toHaveBeenCalledWith(wsRequestKey) expect(toastErrorFn).toHaveBeenCalledWith( expect.stringContaining(wsRequestKey) ) expect(setItemSpy).toHaveBeenCalledWith( `${wsRequestKey}-backup`, JSON.stringify(request) ) }) it(`schema parsing succeeds if there is no "${wsRequestKey}" key present in localStorage where the fallback of "null" is chosen`, () => { window.localStorage.removeItem(wsRequestKey) const getItemSpy = spyOnGetItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence() expect(getItemSpy).toHaveBeenCalledWith(wsRequestKey) expect(toastErrorFn).not.toHaveBeenCalledWith(wsRequestKey) expect(setItemSpy).not.toHaveBeenCalled() }) it(`reads the "${wsRequestKey}" entry from localStorage, sets it as the new request, subscribes to the "WSSessionStore" and updates localStorage entries`, () => { vi.mock("~/newstore/WebSocketSession", () => { return { setWSRequest: vi.fn(), WSRequest$: { subscribe: vi.fn(), }, } }) const request = WEBSOCKET_REQUEST_MOCK window.localStorage.setItem(wsRequestKey, JSON.stringify(request)) const getItemSpy = spyOnGetItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence() expect(getItemSpy).toHaveBeenCalledWith(wsRequestKey) expect(toastErrorFn).not.toHaveBeenCalledWith(wsRequestKey) expect(setItemSpy).not.toHaveBeenCalled() expect(setWSRequest).toHaveBeenCalledWith(request) expect(WSRequest$.subscribe).toHaveBeenCalledWith(expect.any(Function)) }) }) describe("setup Socket.IO persistence", () => { // Key read from localStorage across test cases const sioRequestKey = "SocketIORequest" it(`shows an error and sets the entry as a backup in localStorage if "${sioRequestKey}" read from localStorage doesn't match the schema`, () => { // Invalid shape for `SocketIORequest` const request = { ...SOCKET_IO_REQUEST_MOCK, // `v` -> `version: v4` v: "4", } window.localStorage.setItem(sioRequestKey, JSON.stringify(request)) const getItemSpy = spyOnGetItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence() expect(getItemSpy).toHaveBeenCalledWith(sioRequestKey) expect(toastErrorFn).toHaveBeenCalledWith( expect.stringContaining(sioRequestKey) ) expect(setItemSpy).toHaveBeenCalledWith( `${sioRequestKey}-backup`, JSON.stringify(request) ) }) it(`schema parsing succeeds if there is no "${sioRequestKey}" key present in localStorage where the fallback of "null" is chosen`, () => { window.localStorage.removeItem(sioRequestKey) const getItemSpy = spyOnGetItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence() expect(getItemSpy).toHaveBeenCalledWith(sioRequestKey) expect(toastErrorFn).not.toHaveBeenCalledWith(sioRequestKey) expect(setItemSpy).not.toHaveBeenCalled() }) it(`reads the "${sioRequestKey}" entry from localStorage, sets it as the new request, subscribes to the "SIOSessionStore" and updates localStorage entries`, () => { vi.mock("~/newstore/SocketIOSession", () => { return { setSIORequest: vi.fn(), SIORequest$: { subscribe: vi.fn(), }, } }) const request = SOCKET_IO_REQUEST_MOCK window.localStorage.setItem(sioRequestKey, JSON.stringify(request)) const getItemSpy = spyOnGetItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence() expect(getItemSpy).toHaveBeenCalledWith(sioRequestKey) expect(toastErrorFn).not.toHaveBeenCalledWith(sioRequestKey) expect(setItemSpy).not.toHaveBeenCalled() expect(setSIORequest).toHaveBeenCalledWith(request) expect(SIORequest$.subscribe).toHaveBeenCalledWith(expect.any(Function)) }) }) describe("setup SSE Persistence", () => { // Key read from localStorage across test cases const sseRequestKey = "SSERequest" it(`shows an error and sets the entry as a backup in localStorage if "${sseRequestKey}" read from localStorage doesn't match the versioned schema`, () => { // Invalid shape for `SSERequest` const request = { ...SSE_REQUEST_MOCK, // `url` -> `endpoint` url: "https://express-eventsource.herokuapp.com/events", } window.localStorage.setItem(sseRequestKey, JSON.stringify(request)) const getItemSpy = spyOnGetItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence() expect(getItemSpy).toHaveBeenCalledWith(sseRequestKey) expect(toastErrorFn).toHaveBeenCalledWith( expect.stringContaining(sseRequestKey) ) expect(setItemSpy).toHaveBeenCalledWith( `${sseRequestKey}-backup`, JSON.stringify(request) ) }) it(`schema parsing succeeds if there is no "${sseRequestKey}" key present in localStorage where the fallback of "null" is chosen`, () => { window.localStorage.removeItem(sseRequestKey) const getItemSpy = spyOnGetItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence() expect(getItemSpy).toHaveBeenCalledWith(sseRequestKey) expect(toastErrorFn).not.toHaveBeenCalledWith(sseRequestKey) expect(setItemSpy).not.toHaveBeenCalled() }) it(`reads the "${sseRequestKey}" entry from localStorage, sets it as the new request, subscribes to the "SSESessionStore" and updates localStorage entries`, () => { vi.mock("~/newstore/SSESession", () => { return { setSSERequest: vi.fn(), SSERequest$: { subscribe: vi.fn(), }, } }) const request = SSE_REQUEST_MOCK window.localStorage.setItem(sseRequestKey, JSON.stringify(request)) const getItemSpy = spyOnGetItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence() expect(getItemSpy).toHaveBeenCalledWith(sseRequestKey) expect(toastErrorFn).not.toHaveBeenCalledWith(sseRequestKey) expect(setItemSpy).not.toHaveBeenCalled() expect(setSSERequest).toHaveBeenCalledWith(request) expect(SSERequest$.subscribe).toHaveBeenCalledWith(expect.any(Function)) }) }) describe("setup MQTT Persistence", () => { // Key read from localStorage across test cases const mqttRequestKey = "MQTTRequest" it(`shows an error and sets the entry as a backup in localStorage if "${mqttRequestKey}" read from localStorage doesn't match the schema`, () => { // Invalid shape for `MQTTRequest` const request = { ...MQTT_REQUEST_MOCK, // `url` -> `endpoint` url: "wss://test.mosquitto.org:8081", } window.localStorage.setItem(mqttRequestKey, JSON.stringify(request)) const getItemSpy = spyOnGetItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence() expect(getItemSpy).toHaveBeenCalledWith(mqttRequestKey) expect(toastErrorFn).toHaveBeenCalledWith( expect.stringContaining(mqttRequestKey) ) expect(setItemSpy).toHaveBeenCalledWith( `${mqttRequestKey}-backup`, JSON.stringify(request) ) }) it(`schema parsing succeeds if there is no "${mqttRequestKey}" key present in localStorage where the fallback of "null" is chosen`, () => { window.localStorage.removeItem(mqttRequestKey) const getItemSpy = spyOnGetItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence() expect(getItemSpy).toHaveBeenCalledWith(mqttRequestKey) expect(toastErrorFn).not.toHaveBeenCalledWith(mqttRequestKey) expect(setItemSpy).not.toHaveBeenCalled() }) it(`reads the ${mqttRequestKey}" entry from localStorage, sets it as the new request, subscribes to the "MQTTSessionStore" and updates localStorage entries`, () => { vi.mock("~/newstore/MQTTSession", () => { return { setMQTTRequest: vi.fn(), MQTTRequest$: { subscribe: vi.fn(), }, } }) const request = MQTT_REQUEST_MOCK window.localStorage.setItem(mqttRequestKey, JSON.stringify(request)) const getItemSpy = spyOnGetItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence() expect(getItemSpy).toHaveBeenCalledWith(mqttRequestKey) expect(toastErrorFn).not.toHaveBeenCalledWith(mqttRequestKey) expect(setItemSpy).not.toHaveBeenCalled() expect(setMQTTRequest).toHaveBeenCalledWith(request) expect(MQTTRequest$.subscribe).toHaveBeenCalledWith( expect.any(Function) ) }) }) describe("setup global environments persistence", () => { // Key read from localStorage across test cases const globalEnvKey = "globalEnv" it(`shows an error and sets the entry as a backup in localStorage if "${globalEnvKey}" read from localStorage doesn't match the schema`, () => { // Invalid shape for `globalEnv` const globalEnv = [ { ...GLOBAL_ENV_MOCK[0], // `key` -> `string` key: 1, }, ] window.localStorage.setItem(globalEnvKey, JSON.stringify(globalEnv)) const getItemSpy = spyOnGetItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence() expect(getItemSpy).toHaveBeenCalledWith(globalEnvKey) expect(toastErrorFn).toHaveBeenCalledWith( expect.stringContaining(globalEnvKey) ) expect(setItemSpy).toHaveBeenCalledWith( `${globalEnvKey}-backup`, JSON.stringify(globalEnv) ) }) it(`schema parsing succeeds if there is no "${globalEnvKey}" key present in localStorage where the fallback of "[]" is chosen`, () => { window.localStorage.removeItem(globalEnvKey) const getItemSpy = spyOnGetItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence() expect(getItemSpy).toHaveBeenCalledWith(globalEnvKey) expect(toastErrorFn).not.toHaveBeenCalledWith(globalEnvKey) expect(setItemSpy).not.toHaveBeenCalled() }) it(`reads the "globalEnv" entry from localStorage, dispatches the new value, subscribes to the "environmentsStore" and updates localStorage entries`, () => { const globalEnv = GLOBAL_ENV_MOCK window.localStorage.setItem(globalEnvKey, JSON.stringify(globalEnv)) const getItemSpy = spyOnGetItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence() expect(getItemSpy).toHaveBeenCalledWith(globalEnvKey) expect(toastErrorFn).not.toHaveBeenCalledWith(globalEnvKey) expect(setItemSpy).not.toHaveBeenCalled() expect(setGlobalEnvVariables).toHaveBeenCalledWith(globalEnv) expect(globalEnv$.subscribe).toHaveBeenCalledWith(expect.any(Function)) }) }) describe("setup GQL tabs persistence", () => { // Key read from localStorage across test cases const gqlTabStateKey = "gqlTabState" const loadTabsFromPersistedStateFn = vi.fn() const mock = { loadTabsFromPersistedState: loadTabsFromPersistedStateFn } it(`shows an error and sets the entry as a backup in localStorage if "${gqlTabStateKey}" read from localStorage doesn't match the schema`, () => { // Invalid shape for `gqlTabState` // `lastActiveTabID` -> `string` const gqlTabState = { ...GQL_TAB_STATE_MOCK, lastActiveTabID: 1234 } window.localStorage.setItem(gqlTabStateKey, JSON.stringify(gqlTabState)) const getItemSpy = spyOnGetItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence() expect(getItemSpy).toHaveBeenCalledWith(gqlTabStateKey) expect(toastErrorFn).toHaveBeenCalledWith( expect.stringContaining(gqlTabStateKey) ) expect(setItemSpy).toHaveBeenCalledWith( `${gqlTabStateKey}-backup`, JSON.stringify(gqlTabState) ) }) it(`skips schema parsing and the loading of persisted tabs if there is no "${gqlTabStateKey}" key present in localStorage`, () => { window.localStorage.removeItem(gqlTabStateKey) const getItemSpy = spyOnGetItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence({ mockGQLTabService: true, mock }) expect(getItemSpy).toHaveBeenCalledWith(gqlTabStateKey) expect(toastErrorFn).not.toHaveBeenCalledWith(gqlTabStateKey) expect(setItemSpy).not.toHaveBeenCalled() expect(loadTabsFromPersistedStateFn).not.toHaveBeenCalled() expect(watchDebounced).toHaveBeenCalled() }) it("loads tabs from the state persisted in localStorage and sets watcher for `persistableTabState`", () => { const tabState = GQL_TAB_STATE_MOCK window.localStorage.setItem(gqlTabStateKey, JSON.stringify(tabState)) const getItemSpy = spyOnGetItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence({ mockGQLTabService: true, mock }) expect(getItemSpy).toHaveBeenCalledWith(gqlTabStateKey) expect(toastErrorFn).not.toHaveBeenCalledWith(gqlTabStateKey) expect(setItemSpy).not.toHaveBeenCalled() expect(loadTabsFromPersistedStateFn).toHaveBeenCalledWith(tabState) expect(watchDebounced).toHaveBeenCalledWith( expect.any(Object), expect.any(Function), { debounce: 500, deep: true } ) }) it("logs an error to the console on failing to parse persisted tab state", () => { window.localStorage.setItem(gqlTabStateKey, "invalid-json") console.error = vi.fn() const getItemSpy = spyOnGetItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence() expect(getItemSpy).toHaveBeenCalledWith(gqlTabStateKey) expect(toastErrorFn).not.toHaveBeenCalledWith(gqlTabStateKey) expect(setItemSpy).not.toHaveBeenCalled() expect(console.error).toHaveBeenCalledWith( `Failed parsing persisted tab state, state:`, window.localStorage.getItem(gqlTabStateKey) ) expect(watchDebounced).toHaveBeenCalledWith( expect.any(Object), expect.any(Function), { debounce: 500, deep: true } ) }) }) describe("setup REST tabs persistence", () => { // Key read from localStorage across test cases const restTabStateKey = "restTabState" const loadTabsFromPersistedStateFn = vi.fn() const mock = { loadTabsFromPersistedState: loadTabsFromPersistedStateFn } it(`shows an error and sets the entry as a backup in localStorage if "${restTabStateKey}" read from localStorage doesn't match the schema`, () => { // Invalid shape for `restTabState` // `lastActiveTabID` -> `string` const restTabState = { ...REST_TAB_STATE_MOCK, lastActiveTabID: 1234 } window.localStorage.setItem( restTabStateKey, JSON.stringify(restTabState) ) const getItemSpy = spyOnGetItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence() expect(getItemSpy).toHaveBeenCalledWith(restTabStateKey) expect(toastErrorFn).toHaveBeenCalledWith( expect.stringContaining(restTabStateKey) ) expect(setItemSpy).toHaveBeenCalledWith( `${restTabStateKey}-backup`, JSON.stringify(restTabState) ) }) it(`skips schema parsing and the loading of persisted tabs if there is no "${restTabStateKey}" key present in localStorage`, () => { window.localStorage.removeItem(restTabStateKey) const getItemSpy = spyOnGetItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence({ mockRESTTabService: true, mock }) expect(getItemSpy).toHaveBeenCalledWith(restTabStateKey) expect(toastErrorFn).not.toHaveBeenCalledWith(restTabStateKey) expect(setItemSpy).not.toHaveBeenCalled() expect(loadTabsFromPersistedStateFn).not.toHaveBeenCalled() expect(watchDebounced).toHaveBeenCalled() }) it("loads tabs from the state persisted in localStorage and sets watcher for `persistableTabState`", () => { const tabState = REST_TAB_STATE_MOCK window.localStorage.setItem(restTabStateKey, JSON.stringify(tabState)) const getItemSpy = spyOnGetItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence({ mockRESTTabService: true, mock }) expect(getItemSpy).toHaveBeenCalledWith(restTabStateKey) expect(toastErrorFn).not.toHaveBeenCalledWith(restTabStateKey) expect(setItemSpy).not.toHaveBeenCalled() expect(loadTabsFromPersistedStateFn).toHaveBeenCalledWith(tabState) expect(watchDebounced).toHaveBeenCalledWith( expect.any(Object), expect.any(Function), { debounce: 500, deep: true } ) }) it("logs an error to the console on failing to parse persisted tab state", () => { window.localStorage.setItem(restTabStateKey, "invalid-json") console.error = vi.fn() const getItemSpy = spyOnGetItem() const setItemSpy = spyOnSetItem() invokeSetupLocalPersistence() expect(getItemSpy).toHaveBeenCalledWith(restTabStateKey) expect(toastErrorFn).not.toHaveBeenCalledWith(restTabStateKey) expect(setItemSpy).not.toHaveBeenCalled() expect(console.error).toHaveBeenCalledWith( `Failed parsing persisted tab state, state:`, window.localStorage.getItem(restTabStateKey) ) expect(watchDebounced).toHaveBeenCalledWith( expect.any(Object), expect.any(Function), { debounce: 500, deep: true } ) }) }) }) it("`setLocalConfig` method sets a value in localStorage", () => { const testKey = "test-key" const testValue = "test-value" const setItemSpy = spyOnSetItem() const service = bindPersistenceService() service.setLocalConfig(testKey, testValue) expect(setItemSpy).toHaveBeenCalledWith(testKey, testValue) }) it("`getLocalConfig` method gets a value from localStorage", () => { const testKey = "test-key" const testValue = "test-value" const setItemSpy = spyOnSetItem() const getItemSpy = spyOnGetItem() const service = bindPersistenceService() service.setLocalConfig(testKey, testValue) const retrievedValue = service.getLocalConfig(testKey) expect(setItemSpy).toHaveBeenCalledWith(testKey, testValue) expect(getItemSpy).toHaveBeenCalledWith(testKey) expect(retrievedValue).toBe(testValue) }) it("`removeLocalConfig` method clears a value in localStorage", () => { const testKey = "test-key" const testValue = "test-value" const setItemSpy = spyOnSetItem() const removeItemSpy = spyOnRemoveItem() const service = bindPersistenceService() service.setLocalConfig(testKey, testValue) service.removeLocalConfig(testKey) expect(setItemSpy).toHaveBeenCalledWith(testKey, testValue) expect(removeItemSpy).toHaveBeenCalledWith(testKey) }) })