1642 lines
54 KiB
TypeScript
1642 lines
54 KiB
TypeScript
/* 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<string, unknown> = 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<string, unknown>
|
|
} = {}) => {
|
|
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<string, unknown>
|
|
) => {
|
|
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<string, unknown> = 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<string, unknown> = 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)
|
|
})
|
|
})
|