From a3e8aafbcc82003a66caded61143eb64c4ef02cd Mon Sep 17 00:00:00 2001 From: skirtle <65301168+skirtles-code@users.noreply.github.com> Date: Thu, 30 May 2024 04:03:44 +0100 Subject: [PATCH] fix(watch): support traversing symbol properties in deep watcher (#10969) close #402 --- .../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 8229dce36..265bc0a0d 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 ffad8ad54..bab9e0764 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 }