From c29b1e082c8b76800089f9ded47e7c84a8471799 Mon Sep 17 00:00:00 2001 From: Eduardo San Martin Morote Date: Wed, 12 May 2021 09:52:22 +0200 Subject: [PATCH] fix(types): unwrap refs passed to state Fix #491 --- __tests__/state.spec.ts | 52 +++++++++++++++++++++++++++++++++++++++- src/store.ts | 6 +++-- src/types.ts | 23 ++++++++++++------ test-dts/state.test-d.ts | 48 +++++++++++++++++++++++++++++++++++++ 4 files changed, 119 insertions(+), 10 deletions(-) create mode 100644 test-dts/state.test-d.ts diff --git a/__tests__/state.spec.ts b/__tests__/state.spec.ts index 44da9bf4..1137c052 100644 --- a/__tests__/state.spec.ts +++ b/__tests__/state.spec.ts @@ -1,4 +1,4 @@ -import { computed } from '@vue/composition-api' +import { computed, nextTick, ref, watch } from '@vue/composition-api' import Vue from 'vue' import { defineStore, setActivePinia, createPinia, Pinia } from '../src' @@ -32,4 +32,54 @@ describe('State', () => { store.name = 'Ed' expect(upperCased.value).toBe('ED') }) + + // it('watch', () => { + // setActivePinia(createPinia()) + // defineStore({ + // id: 'main', + // state: () => ({ + // name: 'Eduardo', + // counter: 0, + // }), + // })() + // }) + + it('state can be watched', async () => { + const store = useStore() + const spy = jest.fn() + watch(() => store.name, spy) + expect(spy).not.toHaveBeenCalled() + store.name = 'Ed' + await nextTick() + expect(spy).toHaveBeenCalledTimes(1) + }) + + it('unwraps refs', () => { + const name = ref('Eduardo') + const counter = ref(0) + const double = computed({ + get: () => counter.value * 2, + set(val) { + counter.value = val / 2 + }, + }) + + setActivePinia(createPinia()) + const useStore = defineStore({ + id: 'main', + state: () => ({ + name, + counter, + double, + }), + }) + + const store = useStore() + + expect(store.name).toBe('Eduardo') + name.value = 'Ed' + expect(store.name).toBe('Ed') + store.name = 'Edu' + expect(store.name).toBe('Edu') + }) }) diff --git a/src/store.ts b/src/store.ts index e9a7f0ac..aacd6889 100644 --- a/src/store.ts +++ b/src/store.ts @@ -9,6 +9,8 @@ import { onUnmounted, InjectionKey, provide, + WatchOptions, + UnwrapRef, } from '@vue/composition-api' import { StateTree, @@ -129,7 +131,7 @@ function initStore( subscriptions.forEach((callback) => { callback( { storeName: $id, type, payload: partialState }, - pinia.state.value[$id] + pinia.state.value[$id] as UnwrapRef ) }) } @@ -140,7 +142,7 @@ function initStore( // watch here to link the subscription to the current active instance // e.g. inside the setup of a component const stopWatcher = watch( - () => pinia.state.value[$id], + () => pinia.state.value[$id] as UnwrapRef, (state) => { if (isListening) { callback({ storeName: $id, type: '🧩 in place', payload: {} }, state) diff --git a/src/types.ts b/src/types.ts index 5099cd64..21965263 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,3 +1,4 @@ +import { UnwrapRef } from '@vue/composition-api' import { Pinia } from './rootStore' /** @@ -100,8 +101,15 @@ export type StoreOnActionListener = ( * Callback of a subscription */ export type SubscriptionCallback = ( - mutation: { storeName: string; type: string; payload: DeepPartial }, - state: S + // TODO: make type an enumeration + // TODO: payload should be optional + mutation: { + storeName: string + type: MutationType + + payload: DeepPartial> + }, + state: UnwrapRef ) => void /** @@ -117,7 +125,7 @@ export interface StoreWithState { /** * State of the Store. Setting it will replace the whole state. */ - $state: S + $state: UnwrapRef /** * Private property defining the pinia the store is attached to. @@ -260,7 +268,7 @@ export type Store< // has the actions without the context (this) for typings A > = StoreWithState & - S & + UnwrapRef & StoreWithGetters & StoreWithActions & PiniaCustomProperties @@ -326,7 +334,7 @@ export interface PiniaCustomProperties< */ export type GettersTree = Record< string, - ((state: S) => any) | (() => any) + ((state: UnwrapRef) => any) | (() => any) > /** @@ -350,14 +358,15 @@ export interface DefineStoreOptions< /** * Optional object of getters. */ - getters?: G & ThisType & PiniaCustomProperties> + getters?: G & + ThisType & StoreWithGetters & PiniaCustomProperties> /** * Optional object of actions. */ actions?: A & ThisType< A & - S & + UnwrapRef & StoreWithState & StoreWithGetters & PiniaCustomProperties diff --git a/test-dts/state.test-d.ts b/test-dts/state.test-d.ts new file mode 100644 index 00000000..671e2bcd --- /dev/null +++ b/test-dts/state.test-d.ts @@ -0,0 +1,48 @@ +import { computed, ref } from 'vue' +import { defineStore, expectType } from './' + +const name = ref('Eduardo') +const counter = ref(0) +const double = computed({ + get: () => counter.value * 2, + set(val) { + counter.value = val / 2 + }, +}) + +const useStore = defineStore({ + id: 'name', + state: () => ({ + n: 0, + name, + double, + counter, + }), + + getters: { + myDouble: (state) => { + expectType(state.double) + expectType(state.counter) + return state.n * 2 + }, + other(): undefined { + expectType(this.double) + expectType(this.counter) + return undefined + }, + }, + + actions: { + some() { + expectType(this.$state.double) + expectType(this.$state.counter) + expectType(this.double) + expectType(this.counter) + }, + }, +}) + +const store = useStore() + +expectType(store.$state.counter) +expectType(store.$state.double) -- 2.47.3