-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(mutate): allow revalidate composables via mutate function
- Loading branch information
Showing
11 changed files
with
395 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { computed, ComputedRef, toRefs, unref, watch } from 'vue'; | ||
import { MaybeRef, toReactive } from '@vueuse/core'; | ||
|
||
import { globalState } from '@/config'; | ||
import { CacheProvider, CacheState, ScopeState } from '@/types'; | ||
|
||
const initScopeState = (cacheProvider: CacheProvider<CacheState>) => { | ||
globalState.set(cacheProvider, { revalidateCache: new Map() }); | ||
}; | ||
|
||
export const useScopeState = (_cacheProvider: MaybeRef<CacheProvider<CacheState>>) => { | ||
const cacheProvider = computed(() => unref(_cacheProvider)); | ||
const scopeState = computed(() => globalState.get(cacheProvider.value)); | ||
|
||
const onScopeStateChange = () => { | ||
if (!scopeState.value) initScopeState(cacheProvider.value); | ||
}; | ||
|
||
watch(scopeState, onScopeStateChange, { immediate: true }); | ||
|
||
return { | ||
scopeState: scopeState as ComputedRef<ScopeState>, | ||
...toRefs(toReactive(scopeState as ComputedRef<ScopeState>)), | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import { useInjectedSetup, mockedCache } from '@/utils/test'; | ||
import { globalState } from '@/config'; | ||
|
||
import { useScopeState } from '.'; | ||
|
||
const cacheProvider = mockedCache; | ||
|
||
const useScopeStateWrapped = () => | ||
useInjectedSetup( | ||
() => {}, | ||
() => useScopeState(cacheProvider), | ||
); | ||
|
||
describe('useSWR - mutate', () => { | ||
beforeEach(() => { | ||
vi.resetAllMocks(); | ||
cacheProvider.clear(); | ||
}); | ||
|
||
beforeAll(() => { | ||
vi.useFakeTimers(); | ||
}); | ||
|
||
afterAll(() => { | ||
vi.useRealTimers(); | ||
}); | ||
|
||
it('should init scope in case that the scope does not have values', async () => { | ||
const { scopeState, revalidateCache } = useScopeStateWrapped(); | ||
|
||
expect(scopeState.value).not.toBeUndefined(); | ||
expect(scopeState.value.revalidateCache).toBeInstanceOf(Map); | ||
|
||
// toRefs values | ||
expect(revalidateCache.value).toBeInstanceOf(Map); | ||
}); | ||
|
||
it('should return values for current scope', async () => { | ||
const key = 'key1'; | ||
const revalidateCb = vi.fn(); | ||
|
||
globalState.set(cacheProvider, { | ||
revalidateCache: new Map([[key, [revalidateCb]]]), | ||
}); | ||
|
||
const { scopeState, revalidateCache } = useScopeStateWrapped(); | ||
|
||
expect(scopeState.value.revalidateCache).toBeInstanceOf(Map); | ||
expect(scopeState.value.revalidateCache.size).toBe(1); | ||
expect(scopeState.value.revalidateCache.get(key)).toHaveLength(1); | ||
expect(scopeState.value.revalidateCache.get(key)).toContain(revalidateCb); | ||
|
||
// toRefs values | ||
expect(revalidateCache.value).toBeInstanceOf(Map); | ||
expect(revalidateCache.value.size).toBe(1); | ||
expect(revalidateCache.value.get(key)).toHaveLength(1); | ||
expect(revalidateCache.value.get(key)).toContain(revalidateCb); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
import { nextTick } from 'vue'; | ||
|
||
import { SWRComposableConfig } from '@/types'; | ||
import { useInjectedSetup, setDataToMockedCache, mockedCache } from '@/utils/test'; | ||
import { globalState } from '@/config'; | ||
|
||
import { useSWR } from '.'; | ||
import { configureGlobalSWR, useSWRConfig } from '../global-swr-config'; | ||
|
||
const cacheProvider = mockedCache; | ||
const defaultKey = 'defaultKey'; | ||
const defaultFetcher = vi.fn((key: string) => key); | ||
const defaultOptions: SWRComposableConfig = { dedupingInterval: 0 }; | ||
|
||
const setTimeoutPromise = (ms: number, resolveTo: unknown) => | ||
new Promise((resolve) => { | ||
setTimeout(() => resolve(resolveTo), ms); | ||
}); | ||
|
||
const useSWRWrapped: typeof useSWR = (...params) => { | ||
return useInjectedSetup( | ||
() => configureGlobalSWR({ cacheProvider, dedupingInterval: 0 }), | ||
() => useSWR(...params), | ||
); | ||
}; | ||
|
||
describe('useSWR - mutate', () => { | ||
beforeEach(() => { | ||
vi.resetAllMocks(); | ||
cacheProvider.clear(); | ||
globalState.delete(cacheProvider); | ||
|
||
vi.spyOn(navigator, 'onLine', 'get').mockReturnValue(true); | ||
vi.spyOn(document, 'visibilityState', 'get').mockReturnValue('visible'); | ||
}); | ||
|
||
beforeAll(() => { | ||
vi.useFakeTimers(); | ||
}); | ||
|
||
afterAll(() => { | ||
vi.useRealTimers(); | ||
}); | ||
|
||
it('should change local data variable value when mutate resolves', async () => { | ||
const { mutate, data } = useSWRWrapped(defaultKey, () => 'FetcherResult'); | ||
|
||
await nextTick(); | ||
expect(data.value).toEqual('FetcherResult'); | ||
|
||
await mutate(() => 'newValue', { revalidate: false }); | ||
await nextTick(); | ||
expect(data.value).toEqual('newValue'); | ||
}); | ||
|
||
it('should change local data variable value when mutate is called with `optimistcData`', async () => { | ||
setDataToMockedCache(defaultKey, { data: 'cachedData' }); | ||
|
||
const { mutate, data } = useInjectedSetup( | ||
() => configureGlobalSWR({ cacheProvider }), | ||
() => useSWR(defaultKey, () => 'FetcherResult'), | ||
); | ||
|
||
expect(data.value).toEqual('cachedData'); | ||
|
||
mutate(() => setTimeoutPromise(1000, 'newValue'), { optimisticData: 'optimistcData' }); | ||
await nextTick(); | ||
expect(data.value).toEqual('optimistcData'); | ||
}); | ||
|
||
it('should update all hooks with the same key when call mutates', async () => { | ||
setDataToMockedCache(defaultKey, { data: 'cachedData' }); | ||
|
||
const { datas, mutate, differentData } = useInjectedSetup( | ||
() => configureGlobalSWR({ cacheProvider }), | ||
() => { | ||
const { data: data1, mutate: localMutate } = useSWR(defaultKey, defaultFetcher); | ||
const { data: data2 } = useSWR(defaultKey, defaultFetcher); | ||
const { data: data3 } = useSWR(defaultKey, defaultFetcher); | ||
const { data: data4 } = useSWR(defaultKey, defaultFetcher); | ||
const { data: differentData1 } = useSWR('key-2', () => 'should not change'); | ||
|
||
return { | ||
differentData: differentData1, | ||
datas: [data1, data2, data3, data4], | ||
mutate: localMutate, | ||
}; | ||
}, | ||
); | ||
|
||
expect(datas.map((data) => data.value)).toEqual([ | ||
'cachedData', | ||
'cachedData', | ||
'cachedData', | ||
'cachedData', | ||
]); | ||
|
||
await mutate(() => 'mutated value'); | ||
expect(datas.map((data) => data.value)).toEqual([ | ||
'mutated value', | ||
'mutated value', | ||
'mutated value', | ||
'mutated value', | ||
]); | ||
|
||
expect(differentData.value).toEqual('should not change'); | ||
}); | ||
|
||
it('should trigger revalidation programmatically', async () => { | ||
let value = 0; | ||
|
||
const { mutate, data, globalMutate } = useInjectedSetup( | ||
() => configureGlobalSWR({ cacheProvider }), | ||
() => { | ||
const { mutate: localGlobalMutate } = useSWRConfig(); | ||
// eslint-disable-next-line no-plusplus | ||
const swrResult = useSWR(defaultKey, () => value++, { dedupingInterval: 0 }); | ||
|
||
return { | ||
globalMutate: localGlobalMutate, | ||
...swrResult, | ||
}; | ||
}, | ||
); | ||
|
||
await nextTick(); | ||
expect(data.value).toEqual(0); | ||
|
||
await mutate(); | ||
|
||
await nextTick(); | ||
expect(data.value).toEqual(1); | ||
|
||
await globalMutate(defaultKey); | ||
|
||
await nextTick(); | ||
expect(data.value).toEqual(2); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import { reactive } from 'vue'; | ||
|
||
import { CacheProvider, CacheState, ScopeState } from '@/types'; | ||
|
||
/** | ||
* Holds the scope's states, isolated using cacheProvider as key | ||
*/ | ||
export const globalState = reactive(new WeakMap<CacheProvider<CacheState>, ScopeState>()); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
export * from './swr-config'; | ||
export * from './injection-keys'; | ||
export * from './enviroment'; | ||
export * from './global-state'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.