From a3163be5e2739a5e39c1b1d97733565c06a0b632 Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Tue, 9 Jul 2024 16:18:20 +0200 Subject: [PATCH 01/10] Add observable test for updates on embedded object. --- .../tests/src/tests/observable.ts | 104 +++++++++++++++++- 1 file changed, 102 insertions(+), 2 deletions(-) diff --git a/integration-tests/tests/src/tests/observable.ts b/integration-tests/tests/src/tests/observable.ts index 1fbe8c2d02..78fcc1bf10 100644 --- a/integration-tests/tests/src/tests/observable.ts +++ b/integration-tests/tests/src/tests/observable.ts @@ -675,7 +675,15 @@ describe("Observable", () => { }); describe("Results", () => { - type Person = { name: string; age: number | undefined; friends: Realm.List }; + type EmbeddedAddress = { street: string; city: string }; + type Person = { + name: string; + age: number | undefined; + friends: Realm.List; + bestFriend: Person | null; + embeddedAddress: EmbeddedAddress | null; + }; + // change: with / without key-paths openRealmBeforeEach({ schema: [ @@ -685,6 +693,16 @@ describe("Observable", () => { name: "string", age: "int?", friends: "Person[]", + bestFriend: "Person?", + address: "EmbeddedAddress?", + }, + }, + { + name: "EmbeddedAddress", + embedded: true, + properties: { + street: "string", + city: "string", }, }, ], @@ -724,7 +742,7 @@ describe("Observable", () => { await expectCollectionNotifications(this.realm.objects("Person"), ["name"], [EMPTY_COLLECTION_CHANGESET]); }); - it("calls listener", async function (this: RealmObjectContext) { + it("calls listener when primitive property is updated", async function (this: RealmObjectContext) { const collection = this.realm.objects("Person"); await expectCollectionNotifications(collection, undefined, [ EMPTY_COLLECTION_CHANGESET, @@ -742,6 +760,88 @@ describe("Observable", () => { ]); }); + it("calls listener when non-embedded object is updated", async function (this: RealmObjectContext) { + const collection = this.realm.objects("Person"); + const bob = collection[1]; + expect(bob.name).equals("Bob"); + + await expectCollectionNotifications(collection, undefined, [ + EMPTY_COLLECTION_CHANGESET, + () => { + this.realm.write(() => { + this.object.bestFriend = bob; + }); + expect(this.object.bestFriend?.name).equals("Bob"); + }, + { + deletions: [], + insertions: [], + newModifications: [0], + oldModifications: [0], + }, + () => { + this.realm.write(() => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.object.bestFriend!.name = "Bobby"; + }); + expect(this.object.bestFriend?.name).equals("Bobby"); + }, + { + deletions: [], + insertions: [], + newModifications: [0, 1], + oldModifications: [0, 1], + }, + ]); + }); + + // TODO: Add link to issue with collection listener not being fired on updates to embedded object. + it.skip("calls listener when embedded object is updated", async function (this: RealmObjectContext) { + const collection = this.realm.objects("Person"); + + await expectCollectionNotifications(collection, undefined, [ + EMPTY_COLLECTION_CHANGESET, + () => { + this.realm.write(() => { + this.object.embeddedAddress = { street: "1633 Broadway", city: "New York" }; + }); + expect(this.object.embeddedAddress).deep.equals({ street: "1633 Broadway", city: "New York" }); + }, + { + deletions: [], + insertions: [], + newModifications: [0], + oldModifications: [0], + }, + () => { + this.realm.write(() => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.object.embeddedAddress!.street = "88 Kearny Street"; + }); + expect(this.object.embeddedAddress?.street).equals("88 Kearny Street"); + }, + { + deletions: [], + insertions: [], + newModifications: [0], + oldModifications: [0], + }, + () => { + this.realm.write(() => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.object.embeddedAddress!.city = "San Francisco"; + }); + expect(this.object.embeddedAddress?.city).equals("San Francisco"); + }, + { + deletions: [], + insertions: [], + newModifications: [0], + oldModifications: [0], + }, + ]); + }); + it("removes listeners", async function (this: RealmObjectContext) { const collection = this.realm.objects("Person"); await expectListenerRemoval({ From d7a95733ee3978f992ce3bcea1974e1096565fbb Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Wed, 10 Jul 2024 08:21:21 +0200 Subject: [PATCH 02/10] Update schema prop name. --- integration-tests/tests/src/tests/observable.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-tests/tests/src/tests/observable.ts b/integration-tests/tests/src/tests/observable.ts index 78fcc1bf10..3f35325a71 100644 --- a/integration-tests/tests/src/tests/observable.ts +++ b/integration-tests/tests/src/tests/observable.ts @@ -694,7 +694,7 @@ describe("Observable", () => { age: "int?", friends: "Person[]", bestFriend: "Person?", - address: "EmbeddedAddress?", + embeddedAddress: "EmbeddedAddress?", }, }, { From 3cd14bf1b1bfc2fd422ddc8d38aac710788c5c05 Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Wed, 10 Jul 2024 08:44:04 +0200 Subject: [PATCH 03/10] Add test with key path on embedded object. --- .../tests/src/tests/observable.ts | 60 ++++++++++++++++++- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/integration-tests/tests/src/tests/observable.ts b/integration-tests/tests/src/tests/observable.ts index 3f35325a71..fdaf55f6c7 100644 --- a/integration-tests/tests/src/tests/observable.ts +++ b/integration-tests/tests/src/tests/observable.ts @@ -795,8 +795,7 @@ describe("Observable", () => { ]); }); - // TODO: Add link to issue with collection listener not being fired on updates to embedded object. - it.skip("calls listener when embedded object is updated", async function (this: RealmObjectContext) { + it("calls listener when embedded object is updated", async function (this: RealmObjectContext) { const collection = this.realm.objects("Person"); await expectCollectionNotifications(collection, undefined, [ @@ -994,6 +993,63 @@ describe("Observable", () => { ); }); + it("fires on relevant changes to an embedded object", async function (this: RealmObjectContext) { + const collection = this.realm.objects("Person"); + + await expectCollectionNotifications( + collection, + ["embeddedAddress"], + [ + EMPTY_COLLECTION_CHANGESET, + () => { + this.realm.write(() => { + this.object.embeddedAddress = { street: "1633 Broadway", city: "New York" }; + }); + expect(this.object.embeddedAddress).deep.equals({ street: "1633 Broadway", city: "New York" }); + }, + { + deletions: [], + insertions: [], + newModifications: [0], + oldModifications: [0], + }, + () => { + this.realm.write(() => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.object.embeddedAddress!.street = "88 Kearny Street"; + }); + expect(this.object.embeddedAddress?.street).equals("88 Kearny Street"); + }, + { + deletions: [], + insertions: [], + newModifications: [0], + oldModifications: [0], + }, + () => { + this.realm.write(() => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.object.embeddedAddress!.city = "San Francisco"; + }); + expect(this.object.embeddedAddress?.city).equals("San Francisco"); + }, + { + deletions: [], + insertions: [], + newModifications: [0], + oldModifications: [0], + }, + // Perform a couple of changes that shouldn't trigger the listener. + () => { + this.realm.write(() => { + this.object.name = "New Name"; + }); + expect(this.object.name).equals("New Name"); + }, + ], + ); + }); + it("fires on relevant changes to a wildcard", async function (this: RealmObjectContext) { const collection = this.realm.objects("Person").filtered("name = $0 OR age = 42", "Alice"); await expectCollectionNotifications( From 830caa1f964166564814cef375142fa7a958983b Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Wed, 10 Jul 2024 09:34:28 +0200 Subject: [PATCH 04/10] Remove key path listener test for embedded object. --- .../tests/src/tests/observable.ts | 57 ------------------- 1 file changed, 57 deletions(-) diff --git a/integration-tests/tests/src/tests/observable.ts b/integration-tests/tests/src/tests/observable.ts index fdaf55f6c7..7a5beb5cb4 100644 --- a/integration-tests/tests/src/tests/observable.ts +++ b/integration-tests/tests/src/tests/observable.ts @@ -993,63 +993,6 @@ describe("Observable", () => { ); }); - it("fires on relevant changes to an embedded object", async function (this: RealmObjectContext) { - const collection = this.realm.objects("Person"); - - await expectCollectionNotifications( - collection, - ["embeddedAddress"], - [ - EMPTY_COLLECTION_CHANGESET, - () => { - this.realm.write(() => { - this.object.embeddedAddress = { street: "1633 Broadway", city: "New York" }; - }); - expect(this.object.embeddedAddress).deep.equals({ street: "1633 Broadway", city: "New York" }); - }, - { - deletions: [], - insertions: [], - newModifications: [0], - oldModifications: [0], - }, - () => { - this.realm.write(() => { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - this.object.embeddedAddress!.street = "88 Kearny Street"; - }); - expect(this.object.embeddedAddress?.street).equals("88 Kearny Street"); - }, - { - deletions: [], - insertions: [], - newModifications: [0], - oldModifications: [0], - }, - () => { - this.realm.write(() => { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - this.object.embeddedAddress!.city = "San Francisco"; - }); - expect(this.object.embeddedAddress?.city).equals("San Francisco"); - }, - { - deletions: [], - insertions: [], - newModifications: [0], - oldModifications: [0], - }, - // Perform a couple of changes that shouldn't trigger the listener. - () => { - this.realm.write(() => { - this.object.name = "New Name"; - }); - expect(this.object.name).equals("New Name"); - }, - ], - ); - }); - it("fires on relevant changes to a wildcard", async function (this: RealmObjectContext) { const collection = this.realm.objects("Person").filtered("name = $0 OR age = 42", "Alice"); await expectCollectionNotifications( From c9474c57e8b6133cf280609cccd5366c4e94fe84 Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Wed, 10 Jul 2024 11:16:48 +0200 Subject: [PATCH 05/10] Add tests for object listener. --- .../tests/src/tests/observable.ts | 82 ++++++++++++++++++- 1 file changed, 80 insertions(+), 2 deletions(-) diff --git a/integration-tests/tests/src/tests/observable.ts b/integration-tests/tests/src/tests/observable.ts index 7a5beb5cb4..34b2aa5891 100644 --- a/integration-tests/tests/src/tests/observable.ts +++ b/integration-tests/tests/src/tests/observable.ts @@ -420,7 +420,16 @@ describe("Observable", () => { }); describe("Object", () => { - type Person = { name: string; age: number | undefined; friends: Realm.List }; + type EmbeddedAddress = { street: string; city: string }; + type Person = { + name: string; + age: number | undefined; + friends: Realm.List; + bestFriend: Person | null; + embeddedAddress: EmbeddedAddress | null; + }; + + // change: with / without key-paths openRealmBeforeEach({ schema: [ { @@ -429,6 +438,16 @@ describe("Observable", () => { name: "string", age: "int?", friends: "Person[]", + bestFriend: "Person?", + embeddedAddress: "EmbeddedAddress?", + }, + }, + { + name: "EmbeddedAddress", + embedded: true, + properties: { + street: "string", + city: "string", }, }, ], @@ -467,7 +486,7 @@ describe("Observable", () => { await expectObjectNotifications(this.object, ["name"], [EMPTY_OBJECT_CHANGESET]); }); - it("calls listener", async function (this: RealmObjectContext) { + it("calls listener when primitive property is updated", async function (this: RealmObjectContext) { await expectObjectNotifications(this.object, undefined, [ EMPTY_OBJECT_CHANGESET, () => { @@ -479,6 +498,65 @@ describe("Observable", () => { ]); }); + // TODO: Is it intentional Core behavior to not trigger object listener for updates to links? + // If intentional, we should update this test to expect listeners not to be called. + it.skip("calls listener when non-embedded object is updated", async function (this: RealmObjectContext) { + const bob = this.realm.objects("Person")[1]; + expect(bob.name).equals("Bob"); + + await expectObjectNotifications(this.object, undefined, [ + EMPTY_OBJECT_CHANGESET, + () => { + this.realm.write(() => { + this.object.bestFriend = bob; + }); + expect(this.object.bestFriend?.name).equals("Bob"); + }, + { deleted: false, changedProperties: ["bestFriend"] }, + // Note: The below update will not trigger the listener. + () => { + this.realm.write(() => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.object.bestFriend!.name = "Bobby"; + }); + expect(this.object.bestFriend?.name).equals("Bobby"); + }, + { deleted: false, changedProperties: ["bestFriend"] }, + ]); + }); + + // TODO: Is it intentional Core behavior to not trigger object listener for updates to links? + // If intentional, we should update this test to expect listeners not to be called. + it.skip("calls listener when embedded object is updated", async function (this: RealmObjectContext) { + await expectObjectNotifications(this.object, undefined, [ + EMPTY_OBJECT_CHANGESET, + () => { + this.realm.write(() => { + this.object.embeddedAddress = { street: "1633 Broadway", city: "New York" }; + }); + expect(this.object.embeddedAddress).deep.equals({ street: "1633 Broadway", city: "New York" }); + }, + { deleted: false, changedProperties: ["embeddedAddress"] }, + // Note: The below update will not trigger the listener. + () => { + this.realm.write(() => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.object.embeddedAddress!.street = "88 Kearny Street"; + }); + expect(this.object.embeddedAddress?.street).equals("88 Kearny Street"); + }, + { deleted: false, changedProperties: ["embeddedAddress"] }, + () => { + this.realm.write(() => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.object.embeddedAddress!.city = "San Francisco"; + }); + expect(this.object.embeddedAddress?.city).equals("San Francisco"); + }, + { deleted: false, changedProperties: ["embeddedAddress"] }, + ]); + }); + it("removes listeners", async function (this: RealmObjectContext) { await expectListenerRemoval({ addListener: (listener) => this.object.addListener(listener), From 0d0584c6dbceea2489bb2ff554621a8cf3a9a8ce Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Wed, 10 Jul 2024 11:39:44 +0200 Subject: [PATCH 06/10] Remove comment. --- integration-tests/tests/src/tests/observable.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/integration-tests/tests/src/tests/observable.ts b/integration-tests/tests/src/tests/observable.ts index 34b2aa5891..24f0adec70 100644 --- a/integration-tests/tests/src/tests/observable.ts +++ b/integration-tests/tests/src/tests/observable.ts @@ -429,7 +429,6 @@ describe("Observable", () => { embeddedAddress: EmbeddedAddress | null; }; - // change: with / without key-paths openRealmBeforeEach({ schema: [ { @@ -762,7 +761,6 @@ describe("Observable", () => { embeddedAddress: EmbeddedAddress | null; }; - // change: with / without key-paths openRealmBeforeEach({ schema: [ { From fcd7833c558acfa9ca21bc61ca1ab400a69b7ff0 Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Thu, 11 Jul 2024 16:39:09 +0200 Subject: [PATCH 07/10] Update the object listener tests to not expect listener firing. --- integration-tests/tests/src/tests/observable.ts | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/integration-tests/tests/src/tests/observable.ts b/integration-tests/tests/src/tests/observable.ts index 24f0adec70..db01251312 100644 --- a/integration-tests/tests/src/tests/observable.ts +++ b/integration-tests/tests/src/tests/observable.ts @@ -497,14 +497,13 @@ describe("Observable", () => { ]); }); - // TODO: Is it intentional Core behavior to not trigger object listener for updates to links? - // If intentional, we should update this test to expect listeners not to be called. - it.skip("calls listener when non-embedded object is updated", async function (this: RealmObjectContext) { + it("does not call listener when non-embedded object is updated", async function (this: RealmObjectContext) { const bob = this.realm.objects("Person")[1]; expect(bob.name).equals("Bob"); await expectObjectNotifications(this.object, undefined, [ EMPTY_OBJECT_CHANGESET, + // Setting the link should trigger the listener. () => { this.realm.write(() => { this.object.bestFriend = bob; @@ -512,7 +511,7 @@ describe("Observable", () => { expect(this.object.bestFriend?.name).equals("Bob"); }, { deleted: false, changedProperties: ["bestFriend"] }, - // Note: The below update will not trigger the listener. + // Updating the link should NOT trigger the listener. () => { this.realm.write(() => { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion @@ -520,15 +519,13 @@ describe("Observable", () => { }); expect(this.object.bestFriend?.name).equals("Bobby"); }, - { deleted: false, changedProperties: ["bestFriend"] }, ]); }); - // TODO: Is it intentional Core behavior to not trigger object listener for updates to links? - // If intentional, we should update this test to expect listeners not to be called. - it.skip("calls listener when embedded object is updated", async function (this: RealmObjectContext) { + it("does not call listener when embedded object is updated", async function (this: RealmObjectContext) { await expectObjectNotifications(this.object, undefined, [ EMPTY_OBJECT_CHANGESET, + // Setting the link should trigger the listener. () => { this.realm.write(() => { this.object.embeddedAddress = { street: "1633 Broadway", city: "New York" }; @@ -536,7 +533,7 @@ describe("Observable", () => { expect(this.object.embeddedAddress).deep.equals({ street: "1633 Broadway", city: "New York" }); }, { deleted: false, changedProperties: ["embeddedAddress"] }, - // Note: The below update will not trigger the listener. + // Updating the link should NOT trigger the listener. () => { this.realm.write(() => { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion @@ -544,7 +541,6 @@ describe("Observable", () => { }); expect(this.object.embeddedAddress?.street).equals("88 Kearny Street"); }, - { deleted: false, changedProperties: ["embeddedAddress"] }, () => { this.realm.write(() => { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion @@ -552,7 +548,6 @@ describe("Observable", () => { }); expect(this.object.embeddedAddress?.city).equals("San Francisco"); }, - { deleted: false, changedProperties: ["embeddedAddress"] }, ]); }); From 8d1c19bb269a229574a37649bc9aadb1e2ac8e2c Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Fri, 12 Jul 2024 10:08:29 +0200 Subject: [PATCH 08/10] Update API docs to differentiate the object listener behavior. --- packages/realm/src/Collection.ts | 5 +++-- packages/realm/src/Object.ts | 9 +++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/realm/src/Collection.ts b/packages/realm/src/Collection.ts index 01c548dcd4..12d6b6c553 100644 --- a/packages/realm/src/Collection.ts +++ b/packages/realm/src/Collection.ts @@ -158,6 +158,9 @@ export abstract class Collection< * @note `deletions` and `oldModifications` report the indices in the collection before the change happened, * while `insertions` and `newModifications` report the indices into the new version of the collection. * @throws A {@link TypeAssertionError} if `callback` is not a function. + * @note + * Adding the listener is an asynchronous operation, so the callback is invoked the first time to notify the caller when the listener has been added. + * Thus, when the callback is invoked the first time it will contain empty arrays for each property in the `changes` object. * @example * wines.addListener((collection, changes) => { * // collection === wines @@ -171,8 +174,6 @@ export abstract class Collection< * wines.addListener((collection, changes) => { * console.log("A wine's brand might have changed"); * }, ["brand"]); - * @note Adding the listener is an asynchronous operation, so the callback is invoked the first time to notify the caller when the listener has been added. - * Thus, when the callback is invoked the first time it will contain empty arrays for each property in the `changes` object. */ addListener(callback: ChangeCallbackType, keyPaths?: string | string[]): void { assert.function(callback, "callback"); diff --git a/packages/realm/src/Object.ts b/packages/realm/src/Object.ts index 6851b1b01a..4e96620f0e 100644 --- a/packages/realm/src/Object.ts +++ b/packages/realm/src/Object.ts @@ -21,12 +21,14 @@ import { BSON, CanonicalObjectSchema, ClassHelpers, + type Collection, Constructor, DefaultObject, Dictionary, JSONCacheMap, ObjectChangeCallback, ObjectListeners, + ObjectSchema, OmittedRealmTypes, OrderedCollection, Realm, @@ -494,6 +496,11 @@ export class RealmObject { * // obj === wine @@ -507,8 +514,6 @@ export class RealmObject { * console.log("The wine got deleted or its brand might have changed"); * }, ["brand"]) - * @note Adding the listener is an asynchronous operation, so the callback is invoked the first time to notify the caller when the listener has been added. - * Thus, when the callback is invoked the first time it will contain empty array for `changes.changedProperties`. */ addListener(callback: ObjectChangeCallback, keyPaths?: string | string[]): void { assert.function(callback); From 66fc9893ae04c29600f6736995d7fd697c6452b9 Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Fri, 12 Jul 2024 10:32:13 +0200 Subject: [PATCH 09/10] Split up test into separate tests. --- .../tests/src/tests/observable.ts | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/integration-tests/tests/src/tests/observable.ts b/integration-tests/tests/src/tests/observable.ts index db01251312..b476ed127c 100644 --- a/integration-tests/tests/src/tests/observable.ts +++ b/integration-tests/tests/src/tests/observable.ts @@ -576,7 +576,7 @@ describe("Observable", () => { }); describe("key-path filtered", () => { - it("calls listener only on relevant changes", async function (this: RealmObjectContext) { + it("calls listener when specified primitive is updated", async function (this: RealmObjectContext) { await expectObjectNotifications( this.object, ["name"], @@ -609,7 +609,9 @@ describe("Observable", () => { }, ], ); + }); + it("calls listener when specified list of links is updated", async function (this: RealmObjectContext) { await expectObjectNotifications( this.object, ["friends"], @@ -635,7 +637,9 @@ describe("Observable", () => { }, ], ); + }); + it("calls listener when specified primitive on link in list is updated", async function (this: RealmObjectContext) { await expectObjectNotifications( this.object, ["friends.name"], @@ -650,6 +654,15 @@ describe("Observable", () => { deleted: false, changedProperties: ["friends"], }, + () => { + this.realm.write(() => { + this.object.friends[1].name = "Charles"; + }); + }, + { + deleted: false, + changedProperties: ["friends"], + }, // Perform a couple of changes that shouldn't trigger () => { this.realm.write(() => { @@ -661,7 +674,9 @@ describe("Observable", () => { }, ], ); + }); + it("calls listener when any non-link top-level property is updated (wildcard)", async function (this: RealmObjectContext) { await expectObjectNotifications( this.object, ["*"], @@ -813,7 +828,7 @@ describe("Observable", () => { await expectCollectionNotifications(this.realm.objects("Person"), ["name"], [EMPTY_COLLECTION_CHANGESET]); }); - it("calls listener when primitive property is updated", async function (this: RealmObjectContext) { + it("calls listener when primitive is updated", async function (this: RealmObjectContext) { const collection = this.realm.objects("Person"); await expectCollectionNotifications(collection, undefined, [ EMPTY_COLLECTION_CHANGESET, @@ -939,7 +954,7 @@ describe("Observable", () => { }); describe("key-path filtered", () => { - it("fires on relevant changes to a primitive", async function (this: RealmObjectContext) { + it("calls listener when specified primitive is updated", async function (this: RealmObjectContext) { const collection = this.realm.objects("Person").filtered("name = $0 OR age = 42", "Alice"); await expectCollectionNotifications( collection, @@ -1002,7 +1017,7 @@ describe("Observable", () => { ); }); - it("fires on relevant changes to a list", async function (this: RealmObjectContext) { + it("calls listener when specified list of links is updated", async function (this: RealmObjectContext) { const collection = this.realm.objects("Person").filtered("name = $0 OR age = 42", "Alice"); await expectCollectionNotifications( collection, @@ -1033,7 +1048,7 @@ describe("Observable", () => { ); }); - it("fires on relevant changes to a primitive of a list", async function (this: RealmObjectContext) { + it("calls listener when specified primitive on link in list is updated", async function (this: RealmObjectContext) { const collection = this.realm.objects("Person").filtered("name = $0 OR age = 42", "Alice"); await expectCollectionNotifications( collection, @@ -1064,7 +1079,7 @@ describe("Observable", () => { ); }); - it("fires on relevant changes to a wildcard", async function (this: RealmObjectContext) { + it("calls listener when any non-link top-level property is updated (wildcard)", async function (this: RealmObjectContext) { const collection = this.realm.objects("Person").filtered("name = $0 OR age = 42", "Alice"); await expectCollectionNotifications( collection, From a24d55ac6700d0e6d9e1ca53c4400581dbc43637 Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Fri, 12 Jul 2024 11:06:18 +0200 Subject: [PATCH 10/10] Add object listener tests for two-level wildcard on links. --- .../tests/src/tests/observable.ts | 65 ++++++++++++++++++- 1 file changed, 63 insertions(+), 2 deletions(-) diff --git a/integration-tests/tests/src/tests/observable.ts b/integration-tests/tests/src/tests/observable.ts index b476ed127c..2b02dd407d 100644 --- a/integration-tests/tests/src/tests/observable.ts +++ b/integration-tests/tests/src/tests/observable.ts @@ -676,7 +676,7 @@ describe("Observable", () => { ); }); - it("calls listener when any non-link top-level property is updated (wildcard)", async function (this: RealmObjectContext) { + it("calls listener when one-level wildcard is specified and top-level property is updated", async function (this: RealmObjectContext) { await expectObjectNotifications( this.object, ["*"], @@ -715,6 +715,67 @@ describe("Observable", () => { ); }); + it("calls listener when two-level wildcard is specified and non-embedded object is updated", async function (this: RealmObjectContext) { + const bob = this.realm.objects("Person")[1]; + expect(bob.name).equals("Bob"); + + await expectObjectNotifications( + this.object, + ["*.*"], + [ + EMPTY_OBJECT_CHANGESET, + () => { + this.realm.write(() => { + this.object.bestFriend = bob; + }); + expect(this.object.bestFriend?.name).equals("Bob"); + }, + { deleted: false, changedProperties: ["bestFriend"] }, + () => { + this.realm.write(() => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.object.bestFriend!.name = "Bobby"; + }); + expect(this.object.bestFriend?.name).equals("Bobby"); + }, + { deleted: false, changedProperties: ["bestFriend", "friends"] }, + ], + ); + }); + + it("calls listener when two-level wildcard is specified and embedded object is updated", async function (this: RealmObjectContext) { + await expectObjectNotifications( + this.object, + ["*.*"], + [ + EMPTY_OBJECT_CHANGESET, + () => { + this.realm.write(() => { + this.object.embeddedAddress = { street: "1633 Broadway", city: "New York" }; + }); + expect(this.object.embeddedAddress).deep.equals({ street: "1633 Broadway", city: "New York" }); + }, + { deleted: false, changedProperties: ["embeddedAddress"] }, + () => { + this.realm.write(() => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.object.embeddedAddress!.street = "88 Kearny Street"; + }); + expect(this.object.embeddedAddress?.street).equals("88 Kearny Street"); + }, + { deleted: false, changedProperties: ["embeddedAddress"] }, + () => { + this.realm.write(() => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.object.embeddedAddress!.city = "San Francisco"; + }); + expect(this.object.embeddedAddress?.city).equals("San Francisco"); + }, + { deleted: false, changedProperties: ["embeddedAddress"] }, + ], + ); + }); + it("combines key-paths when delivering notifications", async function (this: RealmObjectContext) { const completion1 = expectObjectNotifications( this.object, @@ -1079,7 +1140,7 @@ describe("Observable", () => { ); }); - it("calls listener when any non-link top-level property is updated (wildcard)", async function (this: RealmObjectContext) { + it("calls listener when one-level wildcard is specified and top-level property is updated", async function (this: RealmObjectContext) { const collection = this.realm.objects("Person").filtered("name = $0 OR age = 42", "Alice"); await expectCollectionNotifications( collection,