From 2655270b2a63be03002d4a66faf1bd655ff14bd0 Mon Sep 17 00:00:00 2001 From: Tao Date: Sun, 26 Jan 2025 22:17:04 +0800 Subject: [PATCH] fix(proxySet): fix `.symmetricDifference`,`.isDisjointFrom`; add `.difference` (#1040) * fix(proxySet): fix `.symmetricDifference`,`.isDisjointFrom`; add `.difference` * chore(proxySet): re-order methods according to the proposal --------- Co-authored-by: Daishi Kato --- docs/api/utils/proxySet.mdx | 7 +-- src/vanilla/utils/proxySet.ts | 70 +++++++++++++++---------- tests/proxySet.test.tsx | 96 +++++++++++++++++++++++++++++++++++ 3 files changed, 142 insertions(+), 31 deletions(-) diff --git a/docs/api/utils/proxySet.mdx b/docs/api/utils/proxySet.mdx index 0a98f643..34357a95 100644 --- a/docs/api/utils/proxySet.mdx +++ b/docs/api/utils/proxySet.mdx @@ -16,11 +16,12 @@ Native `Sets` store their data in internal slots which are not observable. This `proxySet` is useful when you need the functionality of a `Set` but still want to track changes to the data. `proxySet` can be useful if you're wanting to store unique values or if you want to perform mathematical `Set` operations on the data, such as union, intersection, or difference. `proxySet` supports all of the new methods introduced to `Set`: - `intersection` -- `isDisjointFrom` +- `union` +- `difference` +- `symmetricDifference` - `isSubsetOf` - `isSupersetOf` -- `symmetricDifference` -- `union` +- `isDisjointFrom` You can see a full list of the methods supported by `proxySet` in the [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set). diff --git a/src/vanilla/utils/proxySet.ts b/src/vanilla/utils/proxySet.ts index 74a40d83..7e5a8fb4 100644 --- a/src/vanilla/utils/proxySet.ts +++ b/src/vanilla/utils/proxySet.ts @@ -10,11 +10,12 @@ type InternalProxySet = Set & { index: number epoch: number intersection: (other: Set) => Set - isDisjointFrom: (other: Set) => boolean + union: (other: Set) => Set + difference: (other: Set) => Set + symmetricDifference: (other: Set) => Set isSubsetOf: (other: Set) => boolean isSupersetOf: (other: Set) => boolean - symmetricDifference: (other: Set) => Set - union: (other: Set) => Set + isDisjointFrom: (other: Set) => boolean } /** @@ -158,13 +159,44 @@ export function proxySet(initialValues?: Iterable | null) { } return proxySet(resultSet) }, - isDisjointFrom(other: Set): boolean { + union(other: Set) { this.epoch // touch property for tracking + const resultSet = proxySet() const otherSet = proxySet(other) - return ( - this.size === other.size && - [...this.values()].every((value) => !otherSet.has(value)) - ) + for (const value of this.values()) { + resultSet.add(value) + } + for (const value of otherSet) { + resultSet.add(value) + } + return proxySet(resultSet) + }, + difference(other: Set) { + this.epoch // touch property for tracking + const resultSet = proxySet() + const otherSet = proxySet(other) + for (const value of this.values()) { + if (!otherSet.has(value)) { + resultSet.add(value) + } + } + return proxySet(resultSet) + }, + symmetricDifference(other: Set) { + this.epoch // touch property for tracking + const resultSet = proxySet() + const otherSet = proxySet(other) + for (const value of this.values()) { + if (!otherSet.has(value)) { + resultSet.add(value) + } + } + for (const value of otherSet.values()) { + if (!this.has(value)) { + resultSet.add(value) + } + } + return proxySet(resultSet) }, isSubsetOf(other: Set) { this.epoch // touch property for tracking @@ -182,28 +214,10 @@ export function proxySet(initialValues?: Iterable | null) { [...otherSet].every((value) => this.has(value)) ) }, - symmetricDifference(other: Set) { - this.epoch // touch property for tracking - const resultSet = proxySet() - const otherSet = proxySet(other) - for (const value of this.values()) { - if (!otherSet.has(value)) { - resultSet.add(value) - } - } - return proxySet(resultSet) - }, - union(other: Set) { + isDisjointFrom(other: Set): boolean { this.epoch // touch property for tracking - const resultSet = proxySet() const otherSet = proxySet(other) - for (const value of this.values()) { - resultSet.add(value) - } - for (const value of otherSet) { - resultSet.add(value) - } - return proxySet(resultSet) + return [...this.values()].every((value) => !otherSet.has(value)) }, } diff --git a/tests/proxySet.test.tsx b/tests/proxySet.test.tsx index 05db8cbc..1ec7f834 100644 --- a/tests/proxySet.test.tsx +++ b/tests/proxySet.test.tsx @@ -847,3 +847,99 @@ describe('ui updates - useSnapshot - iterator methods', () => { }) }) }) + +// https://github.com/tc39/proposal-set-methods +describe('proposal set methods', () => { + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/intersection#examples + it('.intersection', () => { + const odds = proxySet([1, 3, 5, 7, 9]) + const squares = proxySet([1, 4, 9]) + const result = odds.intersection(squares) // Set(2) { 1, 9 } + expect(result).toEqual(proxySet([1, 9])) + }) + + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/union#examples + it('.union', () => { + const evens = proxySet([2, 4, 6, 8]) + const squares = proxySet([1, 4, 9]) + const result = evens.union(squares) // Set(6) { 2, 4, 6, 8, 1, 9 } + expect(result).toEqual(proxySet([2, 4, 6, 8, 1, 9])) + }) + + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/difference#examples + it('.difference', () => { + const odds = proxySet([1, 3, 5, 7, 9]) + const squares = proxySet([1, 4, 9]) + const result = odds.difference(squares) // Set(3) { 3, 5, 7 } + expect(result).toEqual(proxySet([3, 5, 7])) + }) + + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/symmetricDifference#examples + it('.symmetricDifference', () => { + const evens = proxySet([2, 4, 6, 8]) + const squares = proxySet([1, 4, 9]) + const result = evens.symmetricDifference(squares) // Set(5) { 2, 6, 8, 1, 9 } + expect(result).toEqual(proxySet([2, 6, 8, 1, 9])) + }) + + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/isSubsetOf#examples + describe('.isSubsetOf', () => { + it('The set of multiples of 4 (<20) is a subset of even numbers (<20)', () => { + const fours = proxySet([4, 8, 12, 16]) + const evens = proxySet([2, 4, 6, 8, 10, 12, 14, 16, 18]) + expect(fours.isSubsetOf(evens)).toBe(true) // true + }) + + it('The set of prime numbers (<20) is not a subset of all odd numbers (<20), because 2 is prime but not odd', () => { + const primes = proxySet([2, 3, 5, 7, 11, 13, 17, 19]) + const odds = proxySet([3, 5, 7, 9, 11, 13, 15, 17, 19]) + expect(primes.isSubsetOf(odds)).toBe(false) // false + }) + + it('Equivalent sets are subsets of each other', () => { + const set1 = proxySet([1, 2, 3]) + const set2 = proxySet([1, 2, 3]) + expect(set1.isSubsetOf(set2)).toBe(true) // true + expect(set2.isSubsetOf(set1)).toBe(true) // true + }) + }) + + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/isSupersetOf#examples + describe('.isSupersetOf', () => { + it('The set of even numbers (<20) is a superset of multiples of 4 (<20)', () => { + const evens = proxySet([2, 4, 6, 8, 10, 12, 14, 16, 18]) + const fours = proxySet([4, 8, 12, 16]) + expect(evens.isSupersetOf(fours)).toBe(true) // true + }) + + it('The set of all odd numbers (<20) is not a superset of prime numbers (<20), because 2 is prime but not odd', () => { + const primes = proxySet([2, 3, 5, 7, 11, 13, 17, 19]) + const odds = proxySet([3, 5, 7, 9, 11, 13, 15, 17, 19]) + expect(odds.isSupersetOf(primes)).toBe(false) // false + }) + + it('Equivalent sets are supersets of each other', () => { + const set1 = proxySet([1, 2, 3]) + const set2 = proxySet([1, 2, 3]) + expect(set1.isSupersetOf(set2)).toBe(true) // true + expect(set2.isSupersetOf(set1)).toBe(true) // true + }) + }) + + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/isDisjointFrom#examples + describe('.isDisjointFrom', () => { + it('The set of perfect squares (<20) is disjoint from the set of prime numbers (<20)', () => { + // , because a perfect square is by definition decomposable into the product of two integers, while 1 is also not considered a prime number + const primes = proxySet([2, 3, 5, 7, 11, 13, 17, 19]) + const squares = proxySet([1, 4, 9, 16]) + expect(primes.isDisjointFrom(squares)).toBe(true) // true + }) + + it('The set of perfect squares (<20) is not disjoint from the set of composite numbers (<20)', () => { + // , because all non-1 perfect squares are by definition composite numbers + const composites = proxySet([4, 6, 8, 9, 10, 12, 14, 15, 16, 18]) + const squares = proxySet([1, 4, 9, 16]) + expect(composites.isDisjointFrom(squares)).toBe(false) // false + }) + }) +})