diff --git a/packages/reactivity/__tests__/collections/Set.spec.ts b/packages/reactivity/__tests__/collections/Set.spec.ts index 2aeef198256..e4333e3db38 100644 --- a/packages/reactivity/__tests__/collections/Set.spec.ts +++ b/packages/reactivity/__tests__/collections/Set.spec.ts @@ -459,5 +459,139 @@ describe('reactivity/collections', () => { const result = set.add('a') expect(result).toBe(set) }) + + it('should suppoort Set.difference call', () => { + const setA = reactive(new Set([1, 2, 3, 4])) + const setB = new Set([3, 4, 5, 6]) + + if ('difference' in Set.prototype) { + // @ts-expect-error + const result = setA.difference(setB) + + expect(result).toBeInstanceOf(Set) + expect(result.size).toBe(2) + expect(result.has(1)).toBe(true) + expect(result.has(2)).toBe(true) + } + }) + + it('should suppoort Set.intersection call', () => { + const setA = reactive(new Set([1, 2, 3, 4])) + const setB = new Set([3, 4, 5, 6]) + + if ('intersection' in Set.prototype) { + // @ts-expect-error + const result = setA.intersection(setB) + + expect(result).toBeInstanceOf(Set) + expect(result.size).toBe(2) + expect(result.has(3)).toBe(true) + expect(result.has(4)).toBe(true) + } + }) + + it('should suppoort Set.symmetricDifference call', () => { + const setA = reactive(new Set([1, 2, 3, 4])) + const setB = new Set([3, 4, 5, 6]) + + if ('symmetricDifference' in Set.prototype) { + // @ts-expect-error + const result = setA.symmetricDifference(setB) + + expect(result).toBeInstanceOf(Set) + expect(result.size).toBe(4) + expect(result.has(1)).toBe(true) + expect(result.has(2)).toBe(true) + expect(result.has(5)).toBe(true) + expect(result.has(6)).toBe(true) + } + }) + + it('should suppoort Set.union call', () => { + const setA = reactive(new Set([1, 2, 3, 4])) + const setB = new Set([3, 4, 5, 6]) + + if ('union' in Set.prototype) { + // @ts-expect-error + const result = setA.union(setB) + + expect(result).toBeInstanceOf(Set) + expect(result.size).toBe(6) + expect(result.has(1)).toBe(true) + expect(result.has(2)).toBe(true) + expect(result.has(3)).toBe(true) + expect(result.has(4)).toBe(true) + expect(result.has(5)).toBe(true) + expect(result.has(6)).toBe(true) + } + }) + + it('should suppoort Set.isDisjointFrom call', () => { + const setA = reactive(new Set([1, 2, 3, 4])) + const setB = new Set([3, 4, 5, 6]) + + if ('isDisjointFrom' in Set.prototype) { + // @ts-expect-error + const result = setA.isDisjointFrom(setB) + + expect(result).toBe(false) + } + }) + + it('should suppoort Set.isSubsetOf call', () => { + const setA = reactive(new Set([1, 2, 3, 4])) + const setB = new Set([3, 4, 5, 6]) + + if ('isSubsetOf' in Set.prototype) { + // @ts-expect-error + const result = setA.isSubsetOf(setB) + + expect(result).toBe(false) + } + }) + + it('should suppoort Set.isSupersetOf call', () => { + const setA = reactive(new Set([1, 2, 3, 4])) + const setB = new Set([3, 4, 5, 6]) + + if ('isSupersetOf' in Set.prototype) { + // @ts-expect-error + const result = setA.isSupersetOf(setB) + + expect(result).toBe(false) + } + }) + + it('should observe this set or other setlike when calling composition method', () => { + let dummy: any + const e1 = { val: 1 } + const e2 = { val: 2 } + const e3 = { val: 3 } + const e4 = { val: 4 } + + const setA = reactive(new Set([e1, e2])) + const setB = reactive(new Set([e3, e4])) + + if ('intersection' in Set.prototype) { + effect(() => { + // @ts-expect-error + dummy = setA.intersection(setB) + }) + + expect(dummy.size).toBe(0) + + setB.add(e1) + expect(dummy.size).toBe(1) + + setB.add(e2) + expect(dummy.size).toBe(2) + + setA.delete(e1) + expect(dummy.size).toBe(1) + + setA.clear() + expect(dummy.size).toBe(0) + } + }) }) }) diff --git a/packages/reactivity/src/collectionHandlers.ts b/packages/reactivity/src/collectionHandlers.ts index ffc3289f2ed..40fbeb9ea96 100644 --- a/packages/reactivity/src/collectionHandlers.ts +++ b/packages/reactivity/src/collectionHandlers.ts @@ -24,6 +24,8 @@ type IterableCollections = (Map | Set) & Target type WeakCollections = (WeakMap | WeakSet) & Target type MapTypes = (Map | WeakMap) & Target type SetTypes = (Set | WeakSet) & Target +type SetType = Set & Target +type SetLikeType = Pick, 'size' | 'has' | 'keys'> const toShallow = (value: T): T => value @@ -91,6 +93,18 @@ function createReadonlyMethod(type: TriggerOpTypes): Function { } } +function createSetCompositionMethod(method: string, readonly: boolean) { + return function (this: SetType, other: SetLikeType) { + const target = this[ReactiveFlags.RAW] + const rawTarget = toRaw(target) + + if (!readonly) { + track(rawTarget, TrackOpTypes.ITERATE, ITERATE_KEY) + } + return rawTarget[method](other) + } +} + type Instrumentations = Record function createInstrumentations( @@ -246,6 +260,20 @@ function createInstrumentations( }, ) + extend(instrumentations, { + difference: createSetCompositionMethod('difference', readonly), + intersection: createSetCompositionMethod('intersection', readonly), + symmetricDifference: createSetCompositionMethod( + 'symmetricDifference', + readonly, + ), + union: createSetCompositionMethod('union', readonly), + + isDisjointFrom: createSetCompositionMethod('isDisjointFrom', readonly), + isSubsetOf: createSetCompositionMethod('isSubsetOf', readonly), + isSupersetOf: createSetCompositionMethod('isSupersetOf', readonly), + }) + const iteratorMethods = [ 'keys', 'values',