Skip to content

Commit

Permalink
fix(types): unwrap refs passed to state
Browse files Browse the repository at this point in the history
Fix #491
  • Loading branch information
posva committed May 13, 2021
1 parent 57d8503 commit c29b1e0
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 10 deletions.
52 changes: 51 additions & 1 deletion __tests__/state.spec.ts
Original file line number Diff line number Diff line change
@@ -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'

Expand Down Expand Up @@ -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')
})
})
6 changes: 4 additions & 2 deletions src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {
onUnmounted,
InjectionKey,
provide,
WatchOptions,
UnwrapRef,
} from '@vue/composition-api'
import {
StateTree,
Expand Down Expand Up @@ -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>
)
})
}
Expand All @@ -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)
Expand Down
23 changes: 16 additions & 7 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { UnwrapRef } from '@vue/composition-api'
import { Pinia } from './rootStore'

/**
Expand Down Expand Up @@ -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

/**
Expand All @@ -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.
Expand Down Expand Up @@ -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>
Expand Down Expand Up @@ -326,7 +334,7 @@ export interface PiniaCustomProperties<
*/
export type GettersTree<S extends StateTree> = Record<
string,
((state: S) => any) | (() => any)
((state: UnwrapRef<S>) => any) | (() => any)
>

/**
Expand All @@ -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
Expand Down
48 changes: 48 additions & 0 deletions test-dts/state.test-d.ts
Original file line number Diff line number Diff line change
@@ -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)

0 comments on commit c29b1e0

Please sign in to comment.