From: Eduardo San Martin Morote Date: Mon, 3 Mar 2025 10:59:46 +0000 (+0100) Subject: fix: consistent computation of computed in tests with storeToRefs X-Git-Tag: @pinia/nuxt@0.11.0~9 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=417db7aacb35b98ebe4274fd43bba593eaa583df;p=thirdparty%2Fvuejs%2Fpinia.git fix: consistent computation of computed in tests with storeToRefs Close #2913 --- diff --git a/packages/testing/src/testing.spec.ts b/packages/testing/src/testing.spec.ts index 1ddd1153..75b40687 100644 --- a/packages/testing/src/testing.spec.ts +++ b/packages/testing/src/testing.spec.ts @@ -1,14 +1,18 @@ import { describe, expect, it, vi } from 'vitest' import { createTestingPinia, TestingOptions } from './testing' -import { createPinia, defineStore, setActivePinia } from 'pinia' +import { createPinia, defineStore, setActivePinia, storeToRefs } from 'pinia' import { mount } from '@vue/test-utils' import { defineComponent, ref, computed } from 'vue' describe('Testing', () => { const useCounter = defineStore('counter', { - state: () => ({ n: 0 }), + state: () => ({ n: 0, doubleComputedCallCount: 0 }), getters: { - double: (state) => state.n * 2, + double: (state) => { + // NOTE: not supposed to be done in a getter... + state.doubleComputedCallCount++ + return state.n * 2 + }, doublePlusOne(): number { return this.double + 1 }, @@ -22,7 +26,11 @@ describe('Testing', () => { const useCounterSetup = defineStore('counter-setup', () => { const n = ref(0) - const double = computed(() => n.value * 2) + const doubleComputedCallCount = ref(0) + const double = computed(() => { + doubleComputedCallCount.value++ + return n.value * 2 + }) const doublePlusOne = computed(() => double.value + 1) function increment(amount = 1) { n.value += amount @@ -31,7 +39,14 @@ describe('Testing', () => { n.value = 0 } - return { n, double, doublePlusOne, increment, $reset } + return { + n, + doubleComputedCallCount, + double, + doublePlusOne, + increment, + $reset, + } }) type CounterStore = @@ -301,6 +316,16 @@ describe('Testing', () => { expect(store.doublePlusOne).toBe(7) }) }) + + // https://github.com/vuejs/pinia/issues/2913 + it('does not compute getters immediately with storeToRefs', () => { + const pinia = createTestingPinia() + const store = useStore(pinia) + + expect(store.doubleComputedCallCount).toBe(0) + storeToRefs(store) + expect(store.doubleComputedCallCount).toBe(0) + }) } it('works with no actions', () => { diff --git a/packages/testing/src/testing.ts b/packages/testing/src/testing.ts index 964a07c8..8be334b7 100644 --- a/packages/testing/src/testing.ts +++ b/packages/testing/src/testing.ts @@ -1,4 +1,4 @@ -import { createApp, customRef, isReactive, isRef, toRaw, triggerRef } from 'vue' +import { computed, createApp, isReactive, isRef, toRaw, triggerRef } from 'vue' import type { App, ComputedRef, WritableComputedRef } from 'vue' import { Pinia, @@ -9,6 +9,9 @@ import { _DeepPartial, PiniaPluginContext, } from 'pinia' +// NOTE: the implementation type is correct and contains up to date types +// while the other types hide internal properties +import type { ComputedRefImpl } from '@vue/reactivity' export interface TestingOptions { /** @@ -206,7 +209,7 @@ function isPlainObject( function isComputed( v: ComputedRef | WritableComputedRef | unknown -): v is ComputedRef | WritableComputedRef { +): v is (ComputedRef | WritableComputedRef) & ComputedRefImpl { return !!v && isRef(v) && 'effect' in v } @@ -215,36 +218,33 @@ function WritableComputed({ store }: PiniaPluginContext) { for (const key in rawStore) { const originalComputed = rawStore[key] if (isComputed(originalComputed)) { - const originalFn = originalComputed.effect.fn - rawStore[key] = customRef((track, trigger) => { - // override the computed with a new one - const overriddenFn = () => - // @ts-expect-error: internal value - originalComputed._value - // originalComputed.effect.fn = overriddenFn - return { - get: () => { - track() - return originalComputed.value - }, - set: (newValue) => { - // reset the computed to its original value by setting it to its initial state - if (newValue === undefined) { - originalComputed.effect.fn = originalFn - // @ts-expect-error: private api to remove the current cached value - delete originalComputed._value - // @ts-expect-error: private api to force the recomputation - originalComputed._dirty = true - } else { - originalComputed.effect.fn = overriddenFn - // @ts-expect-error: private api - originalComputed._value = newValue - } - // this allows to trigger the original computed in setup stores - triggerRef(originalComputed) - trigger() - }, - } + const originalFn = originalComputed.fn + // override the computed with a new one + const overriddenFn = () => + // @ts-expect-error: internal cached value + originalComputed._value + // originalComputed.fn = overriddenFn + + rawStore[key] = computed({ + get() { + return originalComputed.value + }, + set(newValue) { + // reset the computed to its original value by setting it to its initial state + if (newValue === undefined) { + originalComputed.fn = originalFn + // @ts-expect-error: private api to remove the current cached value + delete originalComputed._value + // @ts-expect-error: private api to force the recomputation + originalComputed._dirty = true + } else { + originalComputed.fn = overriddenFn + // @ts-expect-error: private api + originalComputed._value = newValue + } + // this allows to trigger the original computed in setup stores + triggerRef(originalComputed) + }, }) } }