diff --git a/.changeset/wet-months-explain.md b/.changeset/wet-months-explain.md new file mode 100644 index 000000000..cf96ac26d --- /dev/null +++ b/.changeset/wet-months-explain.md @@ -0,0 +1,5 @@ +--- +"mobx": patch +--- + +fix: #3919 new set methods not working with observable set diff --git a/packages/mobx/__tests__/v5/base/set.js b/packages/mobx/__tests__/v5/base/set.js index 70abfa60d..6ffcf79f4 100644 --- a/packages/mobx/__tests__/v5/base/set.js +++ b/packages/mobx/__tests__/v5/base/set.js @@ -293,7 +293,7 @@ test("set.forEach is reactive", () => { describe("The Set object methods do what they are supposed to do", () => { const reactiveSet = set([1, 2, 3, 4, 5]) - test("Observable Set methods returns correct result", () => { + test("with native Set", () => { const intersectionObservableResult = reactiveSet.intersection(new Set([1, 2, 6])) const unionObservableResult = reactiveSet.union(new Set([1, 2, 6])) const differenceObservableResult = reactiveSet.difference(new Set([1, 2, 3, 4, 5, 6, 7])) @@ -311,7 +311,25 @@ describe("The Set object methods do what they are supposed to do", () => { expect(isDisjointFromObservableResult).toBeTruthy() }) - test("Observable Set proper works with Set-like objects", () => { + test("with ObservableSet #3919", () => { + const intersectionObservableResult = reactiveSet.intersection(set([1, 2, 6])) + const unionObservableResult = reactiveSet.union(set([1, 2, 6])) + const differenceObservableResult = reactiveSet.difference(set([1, 2, 3, 4, 5, 6, 7])) + const symmetricDifferenceObservableResult = reactiveSet.symmetricDifference(set([3, 4])) + const isSubsetOfObservableResult = reactiveSet.isSubsetOf(set([1, 2, 3])) + const isSupersetOfObservableResult = reactiveSet.isSupersetOf(set([1, 2, 3, 4, 5, 6])) + const isDisjointFromObservableResult = reactiveSet.isDisjointFrom(set([6, 7])) + + expect(intersectionObservableResult).toEqual(new Set([1, 2])) + expect(unionObservableResult).toEqual(new Set([1, 2, 3, 4, 5, 6])) + expect(differenceObservableResult).toEqual(new Set()) + expect(symmetricDifferenceObservableResult).toEqual(new Set([1, 2, 5])) + expect(isSubsetOfObservableResult).toBeFalsy() + expect(isSupersetOfObservableResult).toBeFalsy() + expect(isDisjointFromObservableResult).toBeTruthy() + }) + + test("with Set-like", () => { const intersectionObservableResult = reactiveSet.intersection( new Map([1, 2, 6].map(i => [i, i])) ) diff --git a/packages/mobx/src/types/observableset.ts b/packages/mobx/src/types/observableset.ts index 02ec57e95..fb6e77d5a 100644 --- a/packages/mobx/src/types/observableset.ts +++ b/packages/mobx/src/types/observableset.ts @@ -244,7 +244,7 @@ export class ObservableSet implements Set, IInterceptable(otherSet: ReadonlySetLike | Set): Set { - if (isES6Set(otherSet)) { + if (isES6Set(otherSet) && !isObservableSet(otherSet)) { return otherSet.intersection(this) } else { const dehancedSet = new Set(this) @@ -253,7 +253,7 @@ export class ObservableSet implements Set, IInterceptable(otherSet: ReadonlySetLike | Set): Set { - if (isES6Set(otherSet)) { + if (isES6Set(otherSet) && !isObservableSet(otherSet)) { return otherSet.union(this) } else { const dehancedSet = new Set(this) @@ -266,7 +266,7 @@ export class ObservableSet implements Set, IInterceptable(otherSet: ReadonlySetLike | Set): Set { - if (isES6Set(otherSet)) { + if (isES6Set(otherSet) && !isObservableSet(otherSet)) { return otherSet.symmetricDifference(this) } else { const dehancedSet = new Set(this) @@ -283,7 +283,7 @@ export class ObservableSet implements Set, IInterceptable | Set): boolean { - if (isES6Set(otherSet)) { + if (isES6Set(otherSet) && !isObservableSet(otherSet)) { return otherSet.isDisjointFrom(this) } else { const dehancedSet = new Set(this) diff --git a/packages/mobx/src/utils/utils.ts b/packages/mobx/src/utils/utils.ts index f4ac2137c..a00b92748 100644 --- a/packages/mobx/src/utils/utils.ts +++ b/packages/mobx/src/utils/utils.ts @@ -140,18 +140,27 @@ export function createInstanceofPredicate( } as any } -export function isES6Map(thing: any): thing is Map { +/** + * Yields true for both native and observable Map, even across different windows. + */ +export function isES6Map(thing: unknown): thing is Map { return thing != null && Object.prototype.toString.call(thing) === "[object Map]" } -export function isPlainES6Map(thing: Map): boolean { +/** + * Makes sure a Map is an instance of non-inherited native or observable Map. + */ +export function isPlainES6Map(thing: Map): boolean { const mapProto = Object.getPrototypeOf(thing) const objectProto = Object.getPrototypeOf(mapProto) const nullProto = Object.getPrototypeOf(objectProto) return nullProto === null } -export function isES6Set(thing: any): thing is Set { +/** + * Yields true for both native and observable Set, even across different windows. + */ +export function isES6Set(thing: unknown): thing is Set { return thing != null && Object.prototype.toString.call(thing) === "[object Set]" }