]> git.ipfire.org Git - thirdparty/vuejs/pinia.git/commitdiff
fix(types): unwrap refs passed to state
authorEduardo San Martin Morote <posva13@gmail.com>
Wed, 12 May 2021 07:52:22 +0000 (09:52 +0200)
committerEduardo San Martin Morote <posva13@gmail.com>
Thu, 13 May 2021 11:05:27 +0000 (13:05 +0200)
Fix #491

__tests__/state.spec.ts
src/store.ts
src/types.ts
test-dts/state.test-d.ts [new file with mode: 0644]

index 44da9bf4023bb6e5421911e398156d9eecdf7e05..1137c0528a2868cbae0895f09797746501fdd3be 100644 (file)
@@ -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')
+  })
 })
index e9a7f0ac14f26a73c0cd11b88ae92542772afe40..aacd6889d67101c71861dd8a450ecc60b5e6fc9e 100644 (file)
@@ -9,6 +9,8 @@ import {
   onUnmounted,
   InjectionKey,
   provide,
+  WatchOptions,
+  UnwrapRef,
 } from '@vue/composition-api'
 import {
   StateTree,
@@ -129,7 +131,7 @@ function initStore<Id extends string, S extends StateTree>(
     subscriptions.forEach((callback) => {
       callback(
         { storeName: $id, type, payload: partialState },
-        pinia.state.value[$id]
+        pinia.state.value[$id] as UnwrapRef<S>
       )
     })
   }
@@ -140,7 +142,7 @@ function initStore<Id extends string, S extends StateTree>(
     // 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<S>,
       (state) => {
         if (isListening) {
           callback({ storeName: $id, type: '🧩 in place', payload: {} }, state)
index 5099cd644698b430ec98e4787dfcbba4e0a39c41..21965263aa836e50690df51f3d7be9421700cdaa 100644 (file)
@@ -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<S> = (
-  mutation: { storeName: string; type: string; payload: DeepPartial<S> },
-  state: S
+  // TODO: make type an enumeration
+  // TODO: payload should be optional
+  mutation: {
+    storeName: string
+    type: MutationType
+
+    payload: DeepPartial<UnwrapRef<S>>
+  },
+  state: UnwrapRef<S>
 ) => void
 
 /**
@@ -117,7 +125,7 @@ export interface StoreWithState<Id extends string, S extends StateTree> {
   /**
    * State of the Store. Setting it will replace the whole state.
    */
-  $state: S
+  $state: UnwrapRef<S>
 
   /**
    * 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<Id, S> &
-  S &
+  UnwrapRef<S> &
   StoreWithGetters<G> &
   StoreWithActions<A> &
   PiniaCustomProperties<Id, S, G, A>
@@ -326,7 +334,7 @@ export interface PiniaCustomProperties<
  */
 export type GettersTree<S extends StateTree> = Record<
   string,
-  ((state: S) => any) | (() => any)
+  ((state: UnwrapRef<S>) => any) | (() => any)
 >
 
 /**
@@ -350,14 +358,15 @@ export interface DefineStoreOptions<
   /**
    * Optional object of getters.
    */
-  getters?: G & ThisType<S & StoreWithGetters<G> & PiniaCustomProperties>
+  getters?: G &
+    ThisType<UnwrapRef<S> & StoreWithGetters<G> & PiniaCustomProperties>
   /**
    * Optional object of actions.
    */
   actions?: A &
     ThisType<
       A &
-        S &
+        UnwrapRef<S> &
         StoreWithState<Id, S> &
         StoreWithGetters<G> &
         PiniaCustomProperties
diff --git a/test-dts/state.test-d.ts b/test-dts/state.test-d.ts
new file mode 100644 (file)
index 0000000..671e2bc
--- /dev/null
@@ -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<number>(state.double)
+      expectType<number>(state.counter)
+      return state.n * 2
+    },
+    other(): undefined {
+      expectType<number>(this.double)
+      expectType<number>(this.counter)
+      return undefined
+    },
+  },
+
+  actions: {
+    some() {
+      expectType<number>(this.$state.double)
+      expectType<number>(this.$state.counter)
+      expectType<number>(this.double)
+      expectType<number>(this.counter)
+    },
+  },
+})
+
+const store = useStore()
+
+expectType<number>(store.$state.counter)
+expectType<number>(store.$state.double)