import { mockWarn } from 'jest-mock-warn'
describe('Map Helpers', () => {
- const useCartStore = defineStore({ id: 'cart' })
const useStore = defineStore({
id: 'main',
state: () => ({
describe('mapStores', () => {
mockWarn()
- it('mapStores computes only once when mapping one store', async () => {
- const pinia = createPinia()
- const fromStore = jest.fn(function () {
- // @ts-ignore
- return this.mainStore
- })
- const Component = defineComponent({
- template: `<p @click="fromStore.n++">{{ fromStore.n }}</p>`,
- computed: {
- ...mapStores(useStore),
- fromStore,
- },
- })
-
- const wrapper = mount(Component, { global: { plugins: [pinia] } })
- // const store = useStore()
- // const other = useCartStore()
- expect(wrapper.vm.mainStore).toBeDefined()
- expect(wrapper.text()).toBe('0')
- await nextTick()
- expect(fromStore).toHaveBeenCalledTimes(1)
-
- await wrapper.trigger('click')
- expect(wrapper.text()).toBe('1')
- expect(fromStore).toHaveBeenCalledTimes(1)
- await wrapper.trigger('click')
- expect(wrapper.text()).toBe('2')
- expect(fromStore).toHaveBeenCalledTimes(1)
- })
-
- it('mapStores computes only once when mapping multiple stores', async () => {
- const pinia = createPinia()
- const fromStore = jest.fn(function () {
- // @ts-ignore
- return this.mainStore
- })
- const Component = defineComponent({
- template: `<p @click="fromStore.n++">{{ mainStore.n }} {{ fromStore.n }} {{ cartStore.$id }}</p>`,
- computed: {
- ...mapStores(useStore, useCartStore),
- fromStore,
- },
- })
-
- const wrapper = mount(Component, { global: { plugins: [pinia] } })
- expect(wrapper.text()).toBe('0 0 cart')
- await nextTick()
- expect(fromStore).toHaveBeenCalledTimes(1)
-
- await wrapper.trigger('click')
- expect(wrapper.text()).toBe('1 1 cart')
- expect(fromStore).toHaveBeenCalledTimes(1)
- await wrapper.trigger('click')
- expect(wrapper.text()).toBe('2 2 cart')
- expect(fromStore).toHaveBeenCalledTimes(1)
- })
it('can set custom suffix', async () => {
const pinia = createPinia()
const wrapper = mount(Component, { global: { plugins: [pinia] } })
// const store = useStore()
- // const other = useCartStore()
// @ts-expect-error: by default this shouldn't exist
expect(wrapper.vm.main).toBeDefined()
expect(wrapper.vm.mainStore).not.toBeDefined()
-import { createPinia, defineStore, setActivePinia } from '../src'
+import { createPinia, defineStore, Pinia, setActivePinia } from '../src'
import { computed, nextTick, ref, watch } from 'vue'
describe('State', () => {
- const useStore = () => {
+ const useStore = (pinia?: Pinia) => {
// create a new store
- setActivePinia(createPinia())
+ setActivePinia(pinia || createPinia())
return defineStore({
id: 'main',
state: () => ({
name: 'Eduardo',
counter: 0,
+ nested: { n: 0 },
}),
})()
}
expect(upperCased.value).toBe('ED')
})
+ it('can be set with patch', () => {
+ const pinia = createPinia()
+ const store = useStore(pinia)
+
+ store.$patch({ name: 'a' })
+
+ expect(store.name).toBe('a')
+ expect(store.$state.name).toBe('a')
+ expect(pinia.state.value.main.name).toBe('a')
+ })
+
+ it('can be set on store', () => {
+ const pinia = createPinia()
+ const store = useStore(pinia)
+
+ store.name = 'a'
+
+ expect(store.name).toBe('a')
+ expect(store.$state.name).toBe('a')
+ expect(pinia.state.value.main.name).toBe('a')
+ })
+
+ it('can be set on store.$state', () => {
+ const pinia = createPinia()
+ const store = useStore(pinia)
+
+ store.$state.name = 'a'
+
+ expect(store.name).toBe('a')
+ expect(store.$state.name).toBe('a')
+ expect(pinia.state.value.main.name).toBe('a')
+ })
+
+ it('can be nested set with patch', () => {
+ const pinia = createPinia()
+ const store = useStore(pinia)
+
+ store.$patch({ nested: { n: 3 } })
+
+ expect(store.nested.n).toBe(3)
+ expect(store.$state.nested.n).toBe(3)
+ expect(pinia.state.value.main.nested.n).toBe(3)
+ })
+
+ it('can be nested set on store', () => {
+ const pinia = createPinia()
+ const store = useStore(pinia)
+
+ store.nested.n = 3
+
+ expect(store.nested.n).toBe(3)
+ expect(store.$state.nested.n).toBe(3)
+ expect(pinia.state.value.main.nested.n).toBe(3)
+ })
+
+ it('can be nested set on store.$state', () => {
+ const pinia = createPinia()
+ const store = useStore(pinia)
+
+ store.$state.nested.n = 3
+
+ expect(store.nested.n).toBe(3)
+ expect(store.$state.nested.n).toBe(3)
+ expect(pinia.state.value.main.nested.n).toBe(3)
+ })
+
// it('watch', () => {
// setActivePinia(createPinia())
// defineStore({
expect(spy).toHaveBeenCalledTimes(1)
})
+ it('can be given a ref', () => {
+ const pinia = createPinia()
+ const store = useStore(pinia)
+
+ // If the ref is directly set to the store, it won't work,
+ // it must be set into the `store.$state` so it connects to pinia
+ // store.name = ref('Ed')
+
+ // @ts-expect-error
+ store.$state.name = ref('Ed')
+
+ expect(store.name).toBe('Ed')
+ expect(store.$state.name).toBe('Ed')
+ expect(pinia.state.value.main.name).toBe('Ed')
+
+ store.name = 'Other'
+ expect(store.name).toBe('Other')
+ expect(store.$state.name).toBe('Other')
+ expect(pinia.state.value.main.name).toBe('Other')
+ })
+
it('unwraps refs', () => {
const name = ref('Eduardo')
const counter = ref(0)
import { reactive, ref } from 'vue'
-import {
- createPinia,
- defineSetupStore,
- defineStore,
- setActivePinia,
-} from '../src'
+import { createPinia, defineStore, Pinia, setActivePinia } from '../src'
describe('store.$patch', () => {
const useStore = () => {
})
describe('skipping nested objects', () => {
- const useStore = () => {
+ const useStore = (pinia?: Pinia) => {
// create a new store
- setActivePinia(createPinia())
+ setActivePinia(pinia || createPinia())
return defineStore({
id: 'main',
state: () => ({
arr: [] as any[],
+ name: 'Eduardo',
item: { a: 0, b: 0 } as null | { a: number; b?: number },
}),
})()
}
+ // const useStore = (pinia?: Pinia) => {
+ // // create a new store
+ // setActivePinia(pinia || createPinia())
+ // return defineSetupStore('main', () => {
+ // const arr = ref([] as any[])
+ // const item = ref({ a: 0, b: 0 } as null | { a: number; b?: number })
- it('ref', () => {
- const store = useStore()
+ // return { arr, item }
+ // })()
+ // }
+
+ it('ref of primitive', () => {
+ const pinia = createPinia()
+ const store = useStore(pinia)
+ const name = ref('Edu')
+ // @ts-expect-error: because it's a ref
+ store.$patch({ name })
+ expect(pinia.state.value.main.name).toEqual('Edu')
+ expect(store.$state.name).toEqual('Edu')
+ expect(store.name).toEqual('Edu')
+ })
+
+ it('ref of object', () => {
+ const pinia = createPinia()
+ const store = useStore(pinia)
const item = ref({ a: 1, b: 1 })
const oldItem = store.item
// @ts-expect-error: because it's a ref
- store.$patch({ item })
+ store.$state.item = item
expect(oldItem).toEqual({ a: 0, b: 0 })
+ expect(pinia.state.value.main.item).toEqual({ a: 1, b: 1 })
+ expect(store.$state.item).toEqual({ a: 1, b: 1 })
expect(store.item).toEqual({ a: 1, b: 1 })
+
+ // @ts-expect-error: because it's a ref
+ store.$patch({ item: ref({ a: 2, b: 2 }) })
+ expect(pinia.state.value.main.item).toEqual({ a: 2, b: 2 })
+ expect(store.$state.item).toEqual({ a: 2, b: 2 })
+ expect(store.item).toEqual({ a: 2, b: 2 })
})
it('nested ref', () => {
idFromPlugin: Id
globalA: string
globalB: string
- notShared: number
shared: number
}
store.$state.shared = ref(20)
}
// @ts-expect-error: TODO: allow setting refs
- store.notShared = ref(10)
- // @ts-expect-error: TODO: allow setting refs
store.shared = toRef(store.$state, 'shared')
})
expect(store.shared).toBe(1)
expect(store2.$state.shared).toBe(1)
expect(store2.shared).toBe(1)
-
- store.notShared = 5
- expect(store.$state).not.toHaveProperty('notShared')
- expect(store.notShared).toBe(5)
- expect(store2.$state).not.toHaveProperty('notShared')
- expect(store2.notShared).toBe(10)
})
})
StateTree,
Store,
StoreDefinition,
- ActionsTree,
} from './types'
/**
? _StoreObject<L> & _Spread<R>
: unknown
-function getCachedStore<
- Id extends string = string,
- S extends StateTree = StateTree,
- G extends GettersTree<S> = GettersTree<S>,
- A /* extends ActionsTree */ = ActionsTree
->(
- vm: ComponentPublicInstance,
- useStore: StoreDefinition<Id, S, G, A>
-): Store<Id, S, G, A> {
- const cache = '_pStores' in vm ? vm._pStores! : (vm._pStores = {})
- const id = useStore.$id
- return (cache[id] ||
- (cache[id] = useStore(vm.$pinia) as unknown as Store)) as unknown as Store<
- Id,
- S,
- G,
- A
- >
-}
-
export let mapStoreSuffix = 'Store'
/**
return stores.reduce((reduced, useStore) => {
// @ts-ignore: $id is added by defineStore
- reduced[useStore.$id + mapStoreSuffix] = function (this: Vue) {
- return getCachedStore(this, useStore)
+ reduced[useStore.$id + mapStoreSuffix] = function (
+ this: ComponentPublicInstance
+ ) {
+ return useStore(this.$pinia)
}
return reduced
}, {} as _Spread<Stores>)
? keysOrMapper.reduce((reduced, key) => {
reduced[key] = function (this: ComponentPublicInstance) {
// @ts-expect-error
- return getCachedStore(this, useStore)[key]
+ return useStore(this.$pinia)[key]
} as () => any
return reduced
}, {} as _MapStateReturn<S, G>)
: Object.keys(keysOrMapper).reduce((reduced, key: keyof KeyMapper) => {
reduced[key] = function (this: ComponentPublicInstance) {
- const store = getCachedStore(this, useStore)
+ const store = useStore(this.$pinia)
const storeKey = keysOrMapper[key]
// for some reason TS is unable to infer the type of storeKey to be a
// function
...args: any[]
) {
// @ts-expect-error
- return (getCachedStore(this, useStore)[key] as _Method)(...args)
+ return (useStore(this.$pinia)[key] as _Method)(...args)
}
return reduced
}, {} as _MapActionsReturn<A>)
...args: any[]
) {
// @ts-expect-error
- return getCachedStore(this, useStore)[keysOrMapper[key]](...args)
+ return useStore(this.$pinia)[keysOrMapper[key]](...args)
}
return reduced
}, {} as _MapActionsObjectReturn<A, KeyMapper>)
reduced[key] = {
get(this: ComponentPublicInstance) {
// @ts-expect-error
- return getCachedStore(this, useStore)[key]
+ return useStore(this.$pinia)[key]
},
set(this: ComponentPublicInstance, value) {
// it's easier to type it here as any
// @ts-expect-error
- return (getCachedStore(this, useStore)[key] = value as any)
+ return (useStore(this.$pinia)[key] = value as any)
},
}
return reduced
reduced[key] = {
get(this: ComponentPublicInstance) {
// @ts-expect-error
- return getCachedStore(this, useStore)[keysOrMapper[key]]
+ return useStore(this.$pinia)[keysOrMapper[key]]
},
set(this: ComponentPublicInstance, value) {
// it's easier to type it here as any
// @ts-expect-error
- return (getCachedStore(this, useStore)[keysOrMapper[key]] =
- value as any)
+ return (useStore(this.$pinia)[keysOrMapper[key]] = value as any)
},
}
return reduced
$pinia: Pinia
/**
- * Cache of stores instantiated by the current instance. Used by map
- * helpers.
+ * Cache of stores instantiated by the current instance. Used by devtools to
+ * list currently used stores.
*
* @internal
*/
} from './rootStore'
import { IS_CLIENT } from './env'
-function innerPatch<T extends StateTree>(
- target: T,
- patchToApply: DeepPartial<T>
-): T {
- // TODO: get all keys like symbols as well
- for (const key in patchToApply) {
- const subPatch = patchToApply[key]
- const targetValue = target[key]
- if (
- isPlainObject(targetValue) &&
- isPlainObject(subPatch) &&
- !isRef(subPatch) &&
- !isReactive(subPatch)
- ) {
- target[key] = innerPatch(targetValue, subPatch)
- } else {
- // @ts-ignore
- target[key] = subPatch
- }
- }
-
- return target
-}
-
-const { assign } = Object
-
/**
- * Create an object of computed properties referring to
+ * Create an object of computed properties referring to the root state. This
+ * allows direct modification of `store.state` while still changing the root
+ * state.
*
* @param rootStateRef - pinia.state
* @param id - unique name
return reactiveObject
}
+function innerPatch<T extends StateTree>(
+ target: T,
+ patchToApply: DeepPartial<T>
+): T {
+ // no need to go through symbols because they cannot be serialized anyway
+ for (const key in patchToApply) {
+ const subPatch = patchToApply[key]
+ const targetValue = target[key]
+ if (
+ isPlainObject(targetValue) &&
+ isPlainObject(subPatch) &&
+ !isRef(subPatch) &&
+ !isReactive(subPatch)
+ ) {
+ target[key] = innerPatch(targetValue, subPatch)
+ } else {
+ // @ts-ignore
+ target[key] = subPatch
+ }
+ }
+
+ return target
+}
+
+const { assign } = Object
+
export interface DefineSetupStoreOptions<
Id extends string,
S extends StateTree,
*/
$subscribe(callback: SubscriptionCallback<S>): () => void
- /**
- * Array of registered action subscriptions.Set without the generics to avoid
- * errors between the generic version of Store and specific stores.
- *
- * @internal
- */
- _as: StoreOnActionListener[]
-
/**
* @alpha Please send feedback at https://github.com/posva/pinia/issues/240
* Setups a callback to be called every time an action is about to get