diff --git a/packages/devtools/src/context/devtools-context.test.ts b/packages/devtools/src/context/devtools-context.test.ts new file mode 100644 index 00000000..5ebb4520 --- /dev/null +++ b/packages/devtools/src/context/devtools-context.test.ts @@ -0,0 +1,59 @@ +import { beforeEach, describe, expect, it } from 'vitest' +import { TANSTACK_DEVTOOLS_STATE } from '../utils/storage' +import { getStateFromLocalStorage } from './devtools-context' + +describe('getStateFromLocalStorage', () => { + beforeEach(() => { + localStorage.clear() + }) + it('should return undefined when no data in localStorage', () => { + const state = getStateFromLocalStorage() + expect(state).toEqual(undefined) + }) + it('should return parsed state from localStorage and not remove valid plugins', () => { + const mockState = { + activePlugins: ['plugin1'], + settings: { + theme: 'dark', + }, + } + localStorage.setItem(TANSTACK_DEVTOOLS_STATE, JSON.stringify(mockState)) + const state = getStateFromLocalStorage([ + { + id: 'plugin1', + render: () => {}, + name: 'Plugin 1', + }, + ]) + expect(state).toEqual(mockState) + }) + it('should filter out inactive plugins', () => { + const mockState = { + activePlugins: ['plugin1', 'plugin2'], + settings: { + theme: 'dark', + }, + } + localStorage.setItem(TANSTACK_DEVTOOLS_STATE, JSON.stringify(mockState)) + const plugins = [{ id: 'plugin1', render: () => {}, name: 'Plugin 1' }] + const state = getStateFromLocalStorage(plugins) + expect(state?.activePlugins).toEqual(['plugin1']) + }) + it('should return empty plugin state if all active plugins are invalid', () => { + const mockState = { + activePlugins: ['plugin1', 'plugin2'], + settings: { + theme: 'dark', + }, + } + localStorage.setItem(TANSTACK_DEVTOOLS_STATE, JSON.stringify(mockState)) + const plugins = [{ id: 'plugin3', render: () => {}, name: 'Plugin 3' }] + const state = getStateFromLocalStorage(plugins) + expect(state?.activePlugins).toEqual([]) + }) + it('should handle invalid JSON in localStorage gracefully', () => { + localStorage.setItem(TANSTACK_DEVTOOLS_STATE, 'invalid json') + const state = getStateFromLocalStorage() + expect(state).toEqual(undefined) + }) +}) diff --git a/packages/devtools/src/context/devtools-context.tsx b/packages/devtools/src/context/devtools-context.tsx index b3da5a53..efb7ecb8 100644 --- a/packages/devtools/src/context/devtools-context.tsx +++ b/packages/devtools/src/context/devtools-context.tsx @@ -99,11 +99,35 @@ const generatePluginId = (plugin: TanStackDevtoolsPlugin, index: number) => { return index.toString() } +export function getStateFromLocalStorage( + plugins?: Array, +) { + const existingStateString = getStorageItem(TANSTACK_DEVTOOLS_STATE) + const existingState = + tryParseJson(existingStateString) + const pluginIds = + plugins?.map((plugin, i) => generatePluginId(plugin, i)) || [] + if (existingState?.activePlugins) { + const originalLength = existingState.activePlugins.length + // Filter out any active plugins that are no longer available + existingState.activePlugins = existingState.activePlugins.filter((id) => + pluginIds.includes(id), + ) + + if (existingState.activePlugins.length !== originalLength) { + // If any active plugins were removed, update local storage + setStorageItem(TANSTACK_DEVTOOLS_STATE, JSON.stringify(existingState)) + } + } + + return existingState +} + const getExistingStateFromStorage = ( config?: TanStackDevtoolsConfig, plugins?: Array, ) => { - const existingState = getStorageItem(TANSTACK_DEVTOOLS_STATE) + const existingState = getStateFromLocalStorage() const settings = getSettings() const state: DevtoolsStore = { @@ -118,7 +142,7 @@ const getExistingStateFromStorage = ( }) || [], state: { ...initialState.state, - ...(existingState ? JSON.parse(existingState) : {}), + ...existingState, }, settings: { ...initialState.settings, diff --git a/packages/devtools/src/context/devtools-store.ts b/packages/devtools/src/context/devtools-store.ts index ddc5ebb2..e7e077ab 100644 --- a/packages/devtools/src/context/devtools-store.ts +++ b/packages/devtools/src/context/devtools-store.ts @@ -82,6 +82,7 @@ export const initialState: DevtoolsStore = { urlFlag: 'tanstack-devtools', theme: typeof window !== 'undefined' && + typeof window.matchMedia !== 'undefined' && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light', diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b9f11877..96c694db 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -359,6 +359,8 @@ importers: specifier: ^4.2.4 version: 4.2.4 + examples/react/start/generated/prisma: {} + examples/react/time-travel: dependencies: '@tanstack/devtools-event-client':