From e33cc6f294ea6adda251520250910368dadab8e2 Mon Sep 17 00:00:00 2001 From: Willy Hong Date: Sat, 23 Jul 2022 00:46:06 +0800 Subject: [PATCH 1/4] chore: add test --- test/use-condition-watcher.test.ts | 64 ++++++++++++++++++++++++++---- 1 file changed, 56 insertions(+), 8 deletions(-) diff --git a/test/use-condition-watcher.test.ts b/test/use-condition-watcher.test.ts index 0b1218a..d02f14a 100644 --- a/test/use-condition-watcher.test.ts +++ b/test/use-condition-watcher.test.ts @@ -1,38 +1,45 @@ import useConditionWatcher from 'vue-condition-watcher' -import { isRef, isReactive, isReadonly, createApp, nextTick, defineComponent } from 'vue-demi' +import { isRef, isReactive, isReadonly, defineComponent, isVue3 } from 'vue-demi' import type { VueWrapper } from '@vue/test-utils' -import { mount } from '@vue/test-utils' -import { describe, expect, test, vi, beforeEach } from 'vitest' +import { flushPromises, mount } from '@vue/test-utils' +import { describe, expect, beforeEach } from 'vitest' describe('Basic test of vue-condition-watcher', () => { - let basicTestConfig = {} + let basicTestConfig: any = {} beforeEach(() => { basicTestConfig = { - fetcher: (params) => new Promise((resolve) => resolve(params)), + fetcher: (params) => Promise.resolve(params), conditions: { gender: ['male'], results: 9, }, + defaultParams: { + name: 'runkids', + }, } }) it(`Check return value type`, () => { - const { conditions, data, error, isLoading, execute } = useConditionWatcher(basicTestConfig) + const { conditions, data, error, isLoading, execute, isFetching } = useConditionWatcher(basicTestConfig) expect(isReactive(conditions)).toBeTruthy() expect(isRef(data)).toBeTruthy() expect(isRef(error)).toBeTruthy() expect(isRef(isLoading)).toBeTruthy() + expect(isRef(isFetching)).toBeTruthy() expect(isLoading.value).toBeTypeOf('boolean') + expect(isLoading.value).toBe(true) + expect(isFetching.value).toBe(false) expect(execute).toBeTypeOf('function') }) - it(`Check data, error, isLoading is readonly`, () => { - const { data, error, isLoading } = useConditionWatcher(basicTestConfig) + it(`Check data, error, isLoading, isFetching is readonly`, () => { + const { data, error, isLoading, isFetching } = useConditionWatcher(basicTestConfig) expect(isReadonly(data)).toBeTruthy() expect(isReadonly(error)).toBeTruthy() expect(isReadonly(isLoading)).toBeTruthy() + expect(isReadonly(isFetching)).toBeTruthy() }) it(`Condition should be change`, () => { @@ -85,6 +92,47 @@ describe('Basic test of vue-condition-watcher', () => { }) }) +if (isVue3) { + describe('useConditionWatcher', () => { + const App = defineComponent({ + template: `
{{ data }}
`, + setup() { + const { data, conditions } = useConditionWatcher({ + fetcher: (params) => Promise.resolve(`Hello, world! ${params.name}!`), + conditions: { + name: '', + }, + }) + return { + data, + conditions, + } + }, + }) + + let wrapper: VueWrapper + + beforeEach(() => { + wrapper = mount(App) + }) + + afterEach(() => { + wrapper.unmount() + }) + + it('Should be defined', async () => { + expect(wrapper).toBeDefined() + }) + + it('Refetch after conditions value changed.', async () => { + expect(wrapper.text()).toContain('') + wrapper.vm.conditions.name = 'RUNKIDS' + await flushPromises() + expect(wrapper.text()).toContain(`Hello, world! RUNKIDS!`) + }) + }) +} + // const tick = async (times: number) => { // for (let _ in [...Array(times).keys()]) { // await nextTick() From c75a6561b83e7f214e9b68a75892058f4f0380b4 Mon Sep 17 00:00:00 2001 From: Vladlen Date: Sun, 28 Aug 2022 04:25:08 +1100 Subject: [PATCH 2/4] fix(docs): add missing execute function in example --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5f2c080..caaabce 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,7 @@ createApp({ const fetcher = params => axios.get('/user/', {params}) const router = useRouter() - const { conditions, data, loading, error } = useConditionWatcher( + const { conditions, data, loading, execute, error } = useConditionWatcher( { fetcher, conditions: { @@ -124,7 +124,7 @@ createApp({ } } ) - return { conditions, data, loading, error } + return { conditions, data, loading, execute, error } }, }) .use(router) From 8a3b723fa81a4fee1179aa6880f9802742a74aee Mon Sep 17 00:00:00 2001 From: Willy Hung Date: Wed, 30 Nov 2022 10:32:44 +0800 Subject: [PATCH 3/4] feat: improve mutate function type --- core/types.ts | 19 ++++++------- core/use-condition-watcher.ts | 46 +++++++++++++++++--------------- examples/vue3/package.json | 12 ++++----- examples/vue3/src/api.ts | 36 ++++++++++++++++++++++++- examples/vue3/src/views/Home.vue | 21 +++++---------- examples/vue3/vite.config.js | 2 +- 6 files changed, 82 insertions(+), 54 deletions(-) diff --git a/core/types.ts b/core/types.ts index 0b7c08e..f8ba983 100644 --- a/core/types.ts +++ b/core/types.ts @@ -1,5 +1,5 @@ -import { Ref, UnwrapNestedRefs } from 'vue-demi' import { Cache, HistoryOptions } from 'vue-condition-watcher/_internal' +import { Ref, UnwrapNestedRefs } from 'vue-demi' export type { HistoryOptions } @@ -7,6 +7,11 @@ export type VoidFn = () => void export type Conditions = { [K in keyof T]: T[K] } +export type FinalResult = + | Promise + | AfterFetchResult extends Result + ? Result + : AfterFetchResult export type OnConditionsChangeReturnValue = Partial> @@ -20,9 +25,9 @@ export interface OnFetchErrorContext { data: T | null } +type MutateFunction = (arg: (oldData: T) => any) => void type MutateData = (newData: any) => void -type MutateFunction = (arg: (oldData: any) => any) => void -export interface Mutate extends MutateData, MutateFunction {} +export interface Mutate extends MutateFunction, MutateData {} export interface Config, Result = unknown, AfterFetchResult = Result> { fetcher: (...args: any) => Promise @@ -41,11 +46,7 @@ export interface Config, Result = unknown, AfterFetch conditions: Partial & Record, cancel: VoidFn ) => Promise> | Record - afterFetch?: ( - data: Result - ) => Promise | AfterFetchResult extends Result - ? Result - : AfterFetchResult + afterFetch?: (data: Result) => FinalResult onFetchError?: (ctx: OnFetchErrorContext) => Promise> | Partial } @@ -56,7 +57,7 @@ export interface UseConditionWatcherReturn { readonly data: Readonly> readonly error: Ref execute: (throwOnFailed?: boolean) => void - mutate: Mutate + mutate: Mutate resetConditions: (conditions?: object) => void onConditionsChange: (fn: OnConditionsChangeContext) => void onFetchSuccess: (fn: (response: any) => void) => void diff --git a/core/use-condition-watcher.ts b/core/use-condition-watcher.ts index a44fefc..d04b13c 100644 --- a/core/use-condition-watcher.ts +++ b/core/use-condition-watcher.ts @@ -1,28 +1,28 @@ +import { Conditions, Config, Mutate, UseConditionWatcherReturn } from './types' import { + UnwrapNestedRefs, + computed, + getCurrentInstance, + isRef, + onUnmounted, reactive, + readonly, ref, + shallowRef, + unref, watch, - readonly, - UnwrapNestedRefs, - onUnmounted, watchEffect, - unref, - isRef, - shallowRef, - computed, - getCurrentInstance, } from 'vue-demi' -import { Config, UseConditionWatcherReturn, Conditions, Mutate } from './types' -import { usePromiseQueue, useHistory, useCache, createEvents } from 'vue-condition-watcher/_internal' +import { containsProp, isNoData as isDataEmpty, isObject, isServer, rAF } from 'vue-condition-watcher/_internal' +import { createEvents, useCache, useHistory, usePromiseQueue } from 'vue-condition-watcher/_internal' import { - filterNoneValueObject, createParams, - syncQuery2Conditions, - isEquivalent, deepClone, + filterNoneValueObject, + isEquivalent, pick, + syncQuery2Conditions, } from 'vue-condition-watcher/_internal' -import { containsProp, isNoData as isDataEmpty, isObject, isServer, rAF } from 'vue-condition-watcher/_internal' export default function useConditionWatcher, Result, AfterFetchResult = Result>( config: Config @@ -209,11 +209,13 @@ export default function useConditionWatcher, Re if (pollingTimer.value) return watchEffect((onCleanup) => { - if (unref(watcherConfig.pollingInterval)) { + const pollingInterval = unref(watcherConfig.pollingInterval) + + if (pollingInterval) { pollingTimer.value = (() => { let timerId = null function next() { - const interval = unref(watcherConfig.pollingInterval) + const interval = pollingInterval if (interval && timerId !== -1) { timerId = setTimeout(nun, interval) } @@ -249,15 +251,15 @@ export default function useConditionWatcher, Re * - 1. * mutate(newData) * - 2. - * mutate((currentData) => { - * currentData[0].name = 'runkids' - * return currentData + * mutate((draft) => { + * draft[0].name = 'runkids' + * return draft * }) */ - const mutate = (...args): Mutate => { + const mutate = (...args): Mutate => { const arg = args[0] if (arg === undefined) { - return data.value as any + return data.value } if (typeof arg === 'function') { data.value = arg(deepClone(data.value)) @@ -265,7 +267,7 @@ export default function useConditionWatcher, Re data.value = arg } cache.set({ ..._conditions }, data.value) - return data.value as any + return data.value } // - History mode base on vue-router diff --git a/examples/vue3/package.json b/examples/vue3/package.json index e7d92cb..5763521 100644 --- a/examples/vue3/package.json +++ b/examples/vue3/package.json @@ -7,14 +7,14 @@ }, "dependencies": { "element-plus": "^1.3.0-beta.1", - "vue": "^3.2.26", + "vue": "^3.2.45", "vue-condition-watcher": "1.4.7", - "vue-router": "^4.0.10" + "vue-router": "^4.1.6" }, "devDependencies": { - "@vitejs/plugin-vue": "^2.0.1", - "@vue/compiler-sfc": "^3.2.26", - "typescript": "^4.3.5", - "vite": "^2.7.10" + "@vitejs/plugin-vue": "^3.2.0", + "@vue/compiler-sfc": "^3.2.45", + "typescript": "^4.9.3", + "vite": "^3.2.4" } } diff --git a/examples/vue3/src/api.ts b/examples/vue3/src/api.ts index 3d2d698..e9748fa 100644 --- a/examples/vue3/src/api.ts +++ b/examples/vue3/src/api.ts @@ -1,5 +1,39 @@ +export type Users = { + cell: string + bob: { + date: string + age: number + } + email: string + gender: string + id: { + name: string + value: string + } + name: { + title: string + first: string + last: string + } + phone: string + picture: { + large: string + medium: string + thumbnail: string + } +}[] +export interface Result { + info: { + page: number + results: number + seed: string + version: string + } + results: Users +} + const users = (params: Record) => - fetch('https://randomuser.me/api/?' + query(params), { method: 'GET' }).then((res) => res.json()) + fetch('https://randomuser.me/api/?' + query(params), { method: 'GET' }).then((res) => res.json() as Promise) const photos = () => fetch('https://jsonplaceholder.typicode.com/photos', { method: 'GET' }).then((res) => res.json()) diff --git a/examples/vue3/src/views/Home.vue b/examples/vue3/src/views/Home.vue index 50d1e08..495861a 100644 --- a/examples/vue3/src/views/Home.vue +++ b/examples/vue3/src/views/Home.vue @@ -2,9 +2,9 @@ import { ref, nextTick, inject } from 'vue' import { useRouter } from 'vue-router' import type { ElScrollbar } from 'element-plus' -// import { useConditionWatcher } from '../../../../src/index' -import { useConditionWatcher } from 'vue-condition-watcher' -import api from '../api' +import useConditionWatcher from '../../../../core/index' +// import { useConditionWatcher } from 'vue-condition-watcher' +import api, { Result, Users } from '../api' const router = useRouter() const payload = ref('') @@ -25,7 +25,7 @@ const { onConditionsChange, onFetchSuccess, onFetchError -} = useConditionWatcher( +} = useConditionWatcher<{gender: string[]; page: number}, Result, Users>( { fetcher: (params) => { payload.value = JSON.stringify(params) @@ -45,15 +45,6 @@ const { pollingWhenOffline: true, revalidateOnFocus: true, cacheProvider: inject('cacheProvider'), - // cacheProvider: function localStorageProvider() { - // // example by https://swr.vercel.app/docs/advanced/cache#localstorage-based-persistent-cache - // const map = new Map(JSON.parse(localStorage.getItem('app-cache') || '[]')) - // window.addEventListener('beforeunload', () => { - // const appCache = JSON.stringify(Array.from(map.entries())) - // localStorage.setItem('app-cache', appCache) - // }) - // return map - // }, history: { sync: router, }, @@ -132,7 +123,7 @@ onFetchFinally(async () => { Refresh - Reset Conditions + Reset Conditions Mutate First Row Data @@ -181,4 +172,4 @@ onFetchFinally(async () => { - \ No newline at end of file + diff --git a/examples/vue3/vite.config.js b/examples/vue3/vite.config.js index 4dcc477..2192517 100644 --- a/examples/vue3/vite.config.js +++ b/examples/vue3/vite.config.js @@ -5,4 +5,4 @@ export default { optimizeDeps: { exclude: ['vue-demi'] } -} \ No newline at end of file +} From 20e48ddfdc495b704e2aef830b6e4c49a64c80e2 Mon Sep 17 00:00:00 2001 From: Willy Hung Date: Wed, 30 Nov 2022 10:36:02 +0800 Subject: [PATCH 4/4] release: v2.0.0-beta.2 --- README-zh_TW.md | 12 ++++++------ README.md | 12 ++++++------ core/package.json | 2 +- package.json | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/README-zh_TW.md b/README-zh_TW.md index bb89634..5ae75b1 100644 --- a/README-zh_TW.md +++ b/README-zh_TW.md @@ -381,9 +381,9 @@ mutate(newData) - 第二種:使用 callback function,會接受一個深拷貝的 `data` 資料,修改完後再返回結果 ```js -const finalData = mutate((currentData) => { - currentData[0].name = 'runkids' - return currentData +const finalData = mutate((draft) => { + draft[0].name = 'runkids' + return draft }) console.log(finalData[0]name === data.value[0].name) //true @@ -409,9 +409,9 @@ async function updateUserName (userId, newName, rowIndex = 0) { // 沒作用! 因為 `data` 是唯讀不可修改的. // Easy to use function will receive deep clone data, and return updated data. - mutate(currentData => { - currentData[rowIndex] = response.data - return currentData + mutate(draft => { + draft[rowIndex] = response.data + return draft }) console.log(data.value) //after: [{ id: 1, name: 'mutate name' }, { id: 2, name: 'vuejs' }] diff --git a/README.md b/README.md index caaabce..6f1a889 100644 --- a/README.md +++ b/README.md @@ -370,9 +370,9 @@ mutate(newData) - Second way, use function will receive deep clone data, and return updated data. ```js -const finalData = mutate((currentData) => { - currentData[0].name = 'runkids' - return currentData +const finalData = mutate((draft) => { + draft[0].name = 'runkids' + return draft }) console.log(finalData[0]name === data.value[0].name) //true @@ -398,9 +398,9 @@ async function updateUserName (userId, newName, rowIndex = 0) { // Not work! Because `data` is read only. // Easy to use function will receive deep clone data, and return updated data. - mutate(currentData => { - currentData[rowIndex] = response.data - return currentData + mutate(draft => { + draft[rowIndex] = response.data + return draft }) console.log(data.value) //after: [{ id: 1, name: 'mutate name' }, { id: 2, name: 'vuejs' }] diff --git a/core/package.json b/core/package.json index 9041173..29d4bac 100644 --- a/core/package.json +++ b/core/package.json @@ -1,6 +1,6 @@ { "name": "vue-condition-watcher-core", - "version": "0.0.1", + "version": "0.0.2", "main": "./dist/index.js", "module": "./dist/index.esm.js", "types": "./dist/index.d.ts", diff --git a/package.json b/package.json index 6358d1c..de8942e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vue-condition-watcher", - "version": "2.0.0-beta.1", + "version": "2.0.0-beta.2", "description": "Vue composition API for automatic data fetching. With conditions as the core. Easily control and sync to URL query string by conditions", "main": "./core/dist/index.js", "module": "./core/dist/index.esm.js",