From de7667ea3ca2d78ec9448f830bc30c15560aa8df Mon Sep 17 00:00:00 2001 From: skirtle <65301168+skirtles-code@users.noreply.github.com> Date: Fri, 17 May 2024 09:20:13 +0100 Subject: [PATCH] fix(watch): deep watching symbol properties --- .../runtime-core/__tests__/apiWatch.spec.ts | 46 +++++++++++++++++++ packages/runtime-core/src/apiWatch.ts | 5 ++ 2 files changed, 51 insertions(+) diff --git a/packages/runtime-core/__tests__/apiWatch.spec.ts b/packages/runtime-core/__tests__/apiWatch.spec.ts index 359e06394e4..78386c82afe 100644 --- a/packages/runtime-core/__tests__/apiWatch.spec.ts +++ b/packages/runtime-core/__tests__/apiWatch.spec.ts @@ -932,6 +932,52 @@ describe('api: watch', () => { expect(dummy).toEqual([1, 2]) }) + it('deep with symbols', async () => { + const symbol1 = Symbol() + const symbol2 = Symbol() + const symbol3 = Symbol() + const symbol4 = Symbol() + + const raw: any = { + [symbol1]: { + [symbol2]: 1, + }, + } + + Object.defineProperty(raw, symbol3, { + writable: true, + enumerable: false, + value: 1, + }) + + const state = reactive(raw) + const spy = vi.fn() + + watch(() => state, spy, { deep: true }) + + await nextTick() + expect(spy).toHaveBeenCalledTimes(0) + + state[symbol1][symbol2] = 2 + await nextTick() + expect(spy).toHaveBeenCalledTimes(1) + + // Non-enumerable properties don't trigger deep watchers + state[symbol3] = 3 + await nextTick() + expect(spy).toHaveBeenCalledTimes(1) + + // Adding a new symbol property + state[symbol4] = 1 + await nextTick() + expect(spy).toHaveBeenCalledTimes(2) + + // Removing a symbol property + delete state[symbol4] + await nextTick() + expect(spy).toHaveBeenCalledTimes(3) + }) + it('immediate', async () => { const count = ref(0) const cb = vi.fn() diff --git a/packages/runtime-core/src/apiWatch.ts b/packages/runtime-core/src/apiWatch.ts index 0fd6b050cfa..fa83a856979 100644 --- a/packages/runtime-core/src/apiWatch.ts +++ b/packages/runtime-core/src/apiWatch.ts @@ -493,6 +493,11 @@ export function traverse( for (const key in value) { traverse(value[key], depth, seen) } + for (const key of Object.getOwnPropertySymbols(value)) { + if (Object.prototype.propertyIsEnumerable.call(value, key)) { + traverse(value[key as any], depth, seen) + } + } } return value }