Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 134 additions & 0 deletions packages/reactivity/__tests__/collections/Set.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
})
})
})
28 changes: 28 additions & 0 deletions packages/reactivity/src/collectionHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ type IterableCollections = (Map<any, any> | Set<any>) & Target
type WeakCollections = (WeakMap<any, any> | WeakSet<any>) & Target
type MapTypes = (Map<any, any> | WeakMap<any, any>) & Target
type SetTypes = (Set<any> | WeakSet<any>) & Target
type SetType = Set<any> & Target
type SetLikeType = Pick<Set<any>, 'size' | 'has' | 'keys'>

const toShallow = <T extends unknown>(value: T): T => value

Expand Down Expand Up @@ -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<string | symbol, Function | number>

function createInstrumentations(
Expand Down Expand Up @@ -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',
Expand Down