const patch = { user: 'Cleiton' }
store.$patch(patch)
+ expect(spy).toHaveBeenCalledTimes(1)
expect(spy).toHaveBeenCalledWith(
expect.objectContaining({
payload: patch,
store.$state
)
})
+ const flushOptions = ['post', 'pre', 'sync'] as const
+
+ flushOptions.forEach((flush) => {
+ it('calls once inside components with flush ' + flush, async () => {
+ const pinia = createPinia()
+ setActivePinia(pinia)
+ const spy1 = jest.fn()
+
+ mount(
+ {
+ setup() {
+ const s1 = useStore()
+ s1.$subscribe(spy1, { flush })
+ },
+ template: `<p/>`,
+ },
+ { global: { plugins: [pinia] } }
+ )
+
+ const s1 = useStore()
+
+ expect(spy1).toHaveBeenCalledTimes(0)
+
+ s1.user = 'Edu'
+ await nextTick()
+ await nextTick()
+ expect(spy1).toHaveBeenCalledTimes(1)
+
+ s1.$patch({ user: 'a' })
+ await nextTick()
+ await nextTick()
+ expect(spy1).toHaveBeenCalledTimes(2)
+
+ s1.$patch((state) => {
+ state.user = 'other'
+ })
+ await nextTick()
+ await nextTick()
+ expect(spy1).toHaveBeenCalledTimes(3)
+ })
+ })
+
+ it('works with multiple different flush', async () => {
+ const spyPre = jest.fn()
+ const spyPost = jest.fn()
+ const spySync = jest.fn()
+
+ const s1 = useStore()
+ s1.$subscribe(spyPre, { flush: 'pre' })
+ s1.$subscribe(spyPost, { flush: 'post' })
+ s1.$subscribe(spySync, { flush: 'sync' })
+
+ expect(spyPre).toHaveBeenCalledTimes(0)
+ expect(spyPost).toHaveBeenCalledTimes(0)
+ expect(spySync).toHaveBeenCalledTimes(0)
+
+ s1.user = 'Edu'
+ expect(spyPre).toHaveBeenCalledTimes(0)
+ expect(spyPost).toHaveBeenCalledTimes(0)
+ expect(spySync).toHaveBeenCalledTimes(1)
+ await nextTick()
+ expect(spyPre).toHaveBeenCalledTimes(1)
+ expect(spyPost).toHaveBeenCalledTimes(1)
+ expect(spySync).toHaveBeenCalledTimes(1)
+
+ s1.$patch({ user: 'a' })
+ // patch still triggers all subscriptions immediately
+ expect(spyPre).toHaveBeenCalledTimes(2)
+ expect(spyPost).toHaveBeenCalledTimes(2)
+ expect(spySync).toHaveBeenCalledTimes(2)
+ await nextTick()
+ expect(spyPre).toHaveBeenCalledTimes(2)
+ expect(spyPost).toHaveBeenCalledTimes(2)
+ expect(spySync).toHaveBeenCalledTimes(2)
+
+ s1.$patch((state) => {
+ state.user = 'other'
+ })
+ expect(spyPre).toHaveBeenCalledTimes(3)
+ expect(spyPost).toHaveBeenCalledTimes(3)
+ expect(spySync).toHaveBeenCalledTimes(3)
+ await nextTick()
+ expect(spyPre).toHaveBeenCalledTimes(3)
+ expect(spyPost).toHaveBeenCalledTimes(3)
+ expect(spySync).toHaveBeenCalledTimes(3)
+ })
+
+ it('works with multiple different flush and multiple state changes', async () => {
+ const spyPre = jest.fn()
+ const spyPost = jest.fn()
+ const spySync = jest.fn()
+
+ const s1 = useStore()
+ s1.$subscribe(spyPre, { flush: 'pre' })
+ s1.$subscribe(spyPost, { flush: 'post' })
+ s1.$subscribe(spySync, { flush: 'sync' })
+
+ s1.user = 'Edu'
+ expect(spyPre).toHaveBeenCalledTimes(0)
+ expect(spyPost).toHaveBeenCalledTimes(0)
+ expect(spySync).toHaveBeenCalledTimes(1)
+ s1.$patch({ user: 'a' })
+ expect(spyPre).toHaveBeenCalledTimes(1)
+ expect(spyPost).toHaveBeenCalledTimes(1)
+ expect(spySync).toHaveBeenCalledTimes(2)
+ await nextTick()
+ expect(spyPre).toHaveBeenCalledTimes(1)
+ expect(spyPost).toHaveBeenCalledTimes(1)
+ expect(spySync).toHaveBeenCalledTimes(2)
+ })
it('unsubscribes callback when unsubscribe is called', () => {
const spy = jest.fn()
ref,
set,
del,
+ nextTick,
isVue2,
} from 'vue-demi'
import {
// internal state
let isListening: boolean // set to true at the end
+ let isSyncListening: boolean // set to true at the end
let subscriptions: SubscriptionCallback<S>[] = markRaw([])
let actionSubscriptions: StoreOnActionListener<Id, S, G, A>[] = markRaw([])
let debuggerEvents: DebuggerEvent[] | DebuggerEvent
| ((state: UnwrapRef<S>) => void)
): void {
let subscriptionMutation: SubscriptionCallbackMutation<S>
- isListening = false
+ isListening = isSyncListening = false
// reset the debugger events since patches are sync
/* istanbul ignore else */
if (__DEV__) {
events: debuggerEvents as DebuggerEvent[],
}
}
- isListening = true
+ nextTick().then(() => {
+ isListening = true
+ })
+ isSyncListening = true
// because we paused the watcher, we need to manually call the subscriptions
triggerSubscriptions(
subscriptions,
watch(
() => pinia.state.value[$id] as UnwrapRef<S>,
(state) => {
- if (isListening) {
+ if (options.flush === 'sync' ? isSyncListening : isListening) {
callback(
{
storeId: $id,
// avoid devtools logging this as a mutation
isListening = false
+ isSyncListening = false
pinia.state.value[$id] = toRef(newStore._hmrPayload, 'hotState')
- isListening = true
+ isSyncListening = true
+ nextTick().then(() => {
+ isListening = true
+ })
for (const actionName in newStore._hmrPayload.actions) {
const action: _Method = newStore[actionName]
}
isListening = true
+ isSyncListening = true
return store
}
$reset(): void
/**
- * Setups a callback to be called whenever the state changes. It also returns
- * a function to remove the callback. Note than when calling
- * `store.$subscribe()` inside of a component, it will be automatically
- * cleanup up when the component gets unmounted unless `detached` is set to
- * true.
+ * Setups a callback to be called whenever the state changes. It also returns a function to remove the callback. Note
+ * than when calling `store.$subscribe()` inside of a component, it will be automatically cleanup up when the
+ * component gets unmounted unless `detached` is set to true.
*
* @param callback - callback passed to the watcher
- * @param options - `watch` options + `detached` to detach the subscription
- * from the context (usually a component) this is called from
+ * @param options - `watch` options + `detached` to detach the subscription from the context (usually a component)
+ * this is called from. Note that the `flush` option does not affect calls to `store.$patch()`.
* @returns function that removes the watcher
*/
$subscribe(