expect(inComponentWatch).toHaveBeenCalledTimes(2)
})
- it('ref in state reactivity outlives component life', async () => {
+ // FIXME: same limitation as above
+ it.skip('ref in state reactivity outlives component life', async () => {
let n: Ref<number>
const globalWatch = jest.fn()
const destroy = watch(() => pinia.state.value.a?.n, globalWatch)
-import { defineComponent } from '@vue/composition-api'
+import {
+ defineComponent,
+ getCurrentInstance,
+ watch,
+} from '@vue/composition-api'
import { createLocalVue, mount } from '@vue/test-utils'
-import { MutationType } from '../src'
import Vue from 'vue'
import {
createPinia,
expect(store2.$state.nested.a.b).toBe('string')
})
- it('subscribe to changes', () => {
- const store = useStore()
- const spy = jest.fn()
- store.$subscribe(spy)
-
- store.$state.a = false
+ it('should outlive components', async () => {
+ const pinia = createPinia()
+ pinia.Vue = Vue
+ const localVue = createLocalVue()
+ localVue.use(PiniaPlugin)
+ const useStore = defineStore({
+ id: 'main',
+ state: () => ({ n: 0 }),
+ })
- expect(spy).toHaveBeenCalledWith(
+ const wrapper = mount(
{
- payload: {},
- storeName: 'main',
- type: MutationType.direct,
+ setup() {
+ const store = useStore()
+
+ return { store }
+ },
+
+ template: `<p>n: {{ store.n }}</p>`,
},
- store.$state
+ {
+ pinia,
+ localVue,
+ }
)
- })
- it('subscribe to changes done via patch', () => {
- const store = useStore()
+ expect(wrapper.text()).toBe('n: 0')
+
+ const store = useStore(pinia)
+
const spy = jest.fn()
- store.$subscribe(spy)
+ watch(() => store.n, spy)
+
+ expect(spy).toHaveBeenCalledTimes(0)
+ store.n++
+ await wrapper.vm.$nextTick()
+ expect(spy).toHaveBeenCalledTimes(1)
+ expect(wrapper.text()).toBe('n: 1')
- const patch = { a: false }
- store.$patch(patch)
+ await wrapper.destroy()
+ store.n++
+ await wrapper.vm.$nextTick()
+ expect(spy).toHaveBeenCalledTimes(2)
+ })
- expect(spy).toHaveBeenCalledWith(
+ it('should not break getCurrentInstance', () => {
+ const pinia = createPinia()
+ pinia.Vue = Vue
+ const localVue = createLocalVue()
+ localVue.use(PiniaPlugin)
+ const useStore = defineStore({
+ id: 'other',
+ state: () => ({ a: true }),
+ })
+ let store: ReturnType<typeof useStore> | undefined
+
+ let i1: any = {}
+ let i2: any = {}
+ const wrapper = mount(
{
- payload: patch,
- storeName: 'main',
- type: MutationType.patchObject,
+ setup() {
+ i1 = getCurrentInstance()
+ store = useStore()
+ i2 = getCurrentInstance()
+
+ return { store }
+ },
+
+ template: `<p>a: {{ store.a }}</p>`,
},
- store.$state
+ { pinia, localVue }
)
+
+ expect(i1).toBe(i2)
})
it('reuses stores from parent components', () => {
createPinia,
Pinia,
PiniaPlugin,
+ MutationType,
} from '../src'
describe('Subscriptions', () => {
store.$subscribe(spy)
store.$state.name = 'Cleiton'
expect(spy).toHaveBeenCalledTimes(1)
+ expect(spy).toHaveBeenCalledWith(
+ expect.objectContaining({
+ storeName: 'main',
+ storeId: 'main',
+ type: MutationType.direct,
+ }),
+ store.$state
+ )
+ })
+
+ it('subscribe to changes done via patch', () => {
+ const store = useStore()
+ const spy = jest.fn()
+ store.$subscribe(spy)
+
+ const patch = { name: 'Cleiton' }
+ store.$patch(patch)
+
+ expect(spy).toHaveBeenCalledWith(
+ expect.objectContaining({
+ payload: patch,
+ storeName: 'main',
+ storeId: 'main',
+ type: MutationType.patchObject,
+ }),
+ store.$state
+ )
})
it('unsubscribes callback when unsubscribe is called', () => {
StoreWithState,
StoreOnActionListener,
StoreOnActionListenerContext,
+ SubscriptionCallback,
PiniaCustomProperties,
DefineStoreOptions,
} from './types'
StoreDefinition,
GettersTree,
DefineStoreOptions,
- GenericStore,
StoreOnActionListener,
MutationType,
+ ActionsTree,
+ SubscriptionCallbackMutation,
} from './types'
import { useStoreDevtools } from './devtools'
import {
function $patch(
partialStateOrMutator: DeepPartial<S> | ((state: S) => void)
): void {
- let partialState: DeepPartial<S> = {}
- let type: MutationType
+ let subscriptionMutation: SubscriptionCallbackMutation<S>
isListening = false
if (typeof partialStateOrMutator === 'function') {
partialStateOrMutator(pinia.state.value[$id])
- type = MutationType.patchFunction
+ subscriptionMutation = {
+ type: MutationType.patchFunction,
+ storeName: $id,
+ storeId: $id,
+ }
} else {
innerPatch(pinia.state.value[$id], partialStateOrMutator)
- partialState = partialStateOrMutator
- type = MutationType.patchObject
+ subscriptionMutation = {
+ type: MutationType.patchObject,
+ payload: partialStateOrMutator,
+ storeName: $id,
+ storeId: $id,
+ }
}
isListening = true
// because we paused the watcher, we need to manually call the subscriptions
subscriptions.forEach((callback) => {
- callback(
- { storeName: $id, type, payload: partialState },
- pinia.state.value[$id] as UnwrapRef<S>
- )
+ callback(subscriptionMutation, pinia.state.value[$id] as UnwrapRef<S>)
})
}
(state) => {
if (isListening) {
callback(
- { storeName: $id, type: MutationType.direct, payload: {} },
+ {
+ storeName: $id,
+ storeId: $id,
+ type: MutationType.direct,
+ },
state
)
}
// maybe reset? for $state = {} and $reset
}
+/**
+ * Base type for the context passed to a subscription callback.
+ *
+ * @internal
+ */
+export interface _SubscriptionCallbackMutationBase {
+ /**
+ * Type of the mutation.
+ */
+ type: MutationType
+
+ /**
+ * @deprecated use `storeId` instead.
+ */
+ storeName: string
+
+ /**
+ * `id` of the store doing the mutation.
+ */
+ storeId: string
+}
+
+/**
+ * Context passed to a subscription callback when directly mutating the state of
+ * a store with `store.someState = newValue` or `store.$state.someState =
+ * newValue`.
+ */
+export interface SubscriptionCallbackMutationDirect
+ extends _SubscriptionCallbackMutationBase {
+ type: MutationType.direct
+}
+
+/**
+ * Context passed to a subscription callback when `store.$patch()` is called
+ * with an object.
+ */
+export interface SubscriptionCallbackMutationPatchObject<S>
+ extends _SubscriptionCallbackMutationBase {
+ type: MutationType.patchObject
+
+ /**
+ * Object passed to `store.$patch()`.
+ */
+ payload: DeepPartial<S>
+}
+
+/**
+ * Context passed to a subscription callback when `store.$patch()` is called
+ * with a function.
+ */
+export interface SubscriptionCallbackMutationPatchFunction
+ extends _SubscriptionCallbackMutationBase {
+ type: MutationType.patchFunction
+
+ /**
+ * Object passed to `store.$patch()`.
+ */
+ // payload: DeepPartial<UnwrapRef<S>>
+}
+
+/**
+ * Context object passed to a subscription callback.
+ */
+export type SubscriptionCallbackMutation<S> =
+ | SubscriptionCallbackMutationDirect
+ | SubscriptionCallbackMutationPatchObject<S>
+ | SubscriptionCallbackMutationPatchFunction
+
+export type UnwrapPromise<T> = T extends Promise<infer V> ? V : T
+
+/**
+ * Callback of a subscription
+ */
+export type SubscriptionCallback<S> = (
+ /**
+ * Object with information relative to the store mutation that triggered the
+ * subscription.
+ */
+ mutation: SubscriptionCallbackMutation<S>,
+
+ /**
+ * State of the store when the subscription is triggered. Same as
+ * `store.$state`.
+ */
+ state: UnwrapRef<S>
+) => void
+
/**
* Context object passed to callbacks of `store.$onAction(context => {})`
*/
context: StoreOnActionListenerContext
) => void
-/**
- * Callback of a subscription
- */
-export type SubscriptionCallback<S> = (
- // TODO: make type an enumeration
- // TODO: payload should be optional
- mutation: {
- storeName: string
- type: MutationType
-
- payload: DeepPartial<UnwrapRef<S>>
- },
- state: UnwrapRef<S>
-) => void
-
/**
* Base store with state and functions
* @internal
* cleanup up when the component gets unmounted.
*
* @param callback - callback passed to the watcher
- * @param onTrigger - DEV ONLY watcher debugging
- * (https://v3.vuejs.org/guide/reactivity-computed-watchers.html#watcher-debugging)
* @returns function that removes the watcher
*/
- $subscribe(
- callback: SubscriptionCallback<S>,
- onTrigger?: (event: any) => void
- ): () => void
+ $subscribe(callback: SubscriptionCallback<S>): () => void
/**
* Array of registered action subscriptions.
* Store type to build a store
*/
export type Store<
- Id extends string,
- S extends StateTree,
- G extends GettersTree<S>,
+ Id extends string = string,
+ S extends StateTree = StateTree,
+ G extends GettersTree<S> = GettersTree<S>,
// has the actions without the context (this) for typings
- A
+ A = ActionsTree
> = StoreWithState<Id, S> &
UnwrapRef<S> &
StoreWithGetters<G> &
((state: UnwrapRef<S>) => any) | (() => any)
>
+/**
+ * @internal
+ */
+export type ActionsTree = Record<string, _Method>
+
/**
* Options parameter of `defineStore()`. Can be extended to augment stores with
* the plugin API.