Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test collection and object listeners for updates on links #6784

Merged
merged 10 commits into from
Jul 12, 2024
266 changes: 256 additions & 10 deletions integration-tests/tests/src/tests/observable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,15 @@ describe("Observable", () => {
});

describe("Object", () => {
type Person = { name: string; age: number | undefined; friends: Realm.List<Person> };
type EmbeddedAddress = { street: string; city: string };
type Person = {
name: string;
age: number | undefined;
friends: Realm.List<Person>;
bestFriend: Person | null;
embeddedAddress: EmbeddedAddress | null;
};

openRealmBeforeEach({
schema: [
{
Expand All @@ -429,6 +437,16 @@ describe("Observable", () => {
name: "string",
age: "int?",
friends: "Person[]",
bestFriend: "Person?",
embeddedAddress: "EmbeddedAddress?",
},
},
{
name: "EmbeddedAddress",
embedded: true,
properties: {
street: "string",
city: "string",
},
},
],
Expand Down Expand Up @@ -467,7 +485,7 @@ describe("Observable", () => {
await expectObjectNotifications(this.object, ["name"], [EMPTY_OBJECT_CHANGESET]);
});

it("calls listener", async function (this: RealmObjectContext<Person>) {
it("calls listener when primitive property is updated", async function (this: RealmObjectContext<Person>) {
await expectObjectNotifications(this.object, undefined, [
EMPTY_OBJECT_CHANGESET,
() => {
Expand All @@ -479,6 +497,60 @@ describe("Observable", () => {
]);
});

it("does not call listener when non-embedded object is updated", async function (this: RealmObjectContext<Person>) {
const bob = this.realm.objects<Person>("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;
});
expect(this.object.bestFriend?.name).equals("Bob");
},
{ deleted: false, changedProperties: ["bestFriend"] },
// Updating the link should 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");
},
]);
});

it("does not call listener when embedded object is updated", async function (this: RealmObjectContext<Person>) {
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" };
});
expect(this.object.embeddedAddress).deep.equals({ street: "1633 Broadway", city: "New York" });
},
{ deleted: false, changedProperties: ["embeddedAddress"] },
// Updating the link should 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");
},
() => {
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");
},
]);
});

it("removes listeners", async function (this: RealmObjectContext<Person>) {
await expectListenerRemoval({
addListener: (listener) => this.object.addListener(listener),
Expand All @@ -504,7 +576,7 @@ describe("Observable", () => {
});

describe("key-path filtered", () => {
it("calls listener only on relevant changes", async function (this: RealmObjectContext<Person>) {
it("calls listener when specified primitive is updated", async function (this: RealmObjectContext<Person>) {
await expectObjectNotifications(
this.object,
["name"],
Expand Down Expand Up @@ -537,7 +609,9 @@ describe("Observable", () => {
},
],
);
});

it("calls listener when specified list of links is updated", async function (this: RealmObjectContext<Person>) {
await expectObjectNotifications(
this.object,
["friends"],
Expand All @@ -563,7 +637,9 @@ describe("Observable", () => {
},
],
);
});

it("calls listener when specified primitive on link in list is updated", async function (this: RealmObjectContext<Person>) {
await expectObjectNotifications(
this.object,
["friends.name"],
Expand All @@ -578,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(() => {
Expand All @@ -589,7 +674,9 @@ describe("Observable", () => {
},
],
);
});

it("calls listener when one-level wildcard is specified and top-level property is updated", async function (this: RealmObjectContext<Person>) {
await expectObjectNotifications(
this.object,
["*"],
Expand Down Expand Up @@ -628,6 +715,67 @@ describe("Observable", () => {
);
});

it("calls listener when two-level wildcard is specified and non-embedded object is updated", async function (this: RealmObjectContext<Person>) {
const bob = this.realm.objects<Person>("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<Person>) {
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<Person>) {
const completion1 = expectObjectNotifications(
this.object,
Expand Down Expand Up @@ -675,8 +823,15 @@ describe("Observable", () => {
});

describe("Results", () => {
type Person = { name: string; age: number | undefined; friends: Realm.List<Person> };
// change: with / without key-paths
type EmbeddedAddress = { street: string; city: string };
type Person = {
name: string;
age: number | undefined;
friends: Realm.List<Person>;
bestFriend: Person | null;
embeddedAddress: EmbeddedAddress | null;
};

openRealmBeforeEach({
schema: [
{
Expand All @@ -685,6 +840,16 @@ describe("Observable", () => {
name: "string",
age: "int?",
friends: "Person[]",
bestFriend: "Person?",
embeddedAddress: "EmbeddedAddress?",
},
},
{
name: "EmbeddedAddress",
embedded: true,
properties: {
street: "string",
city: "string",
},
},
],
Expand Down Expand Up @@ -724,7 +889,7 @@ describe("Observable", () => {
await expectCollectionNotifications(this.realm.objects("Person"), ["name"], [EMPTY_COLLECTION_CHANGESET]);
});

it("calls listener", async function (this: RealmObjectContext<Person>) {
it("calls listener when primitive is updated", async function (this: RealmObjectContext<Person>) {
const collection = this.realm.objects("Person");
await expectCollectionNotifications(collection, undefined, [
EMPTY_COLLECTION_CHANGESET,
Expand All @@ -742,6 +907,87 @@ describe("Observable", () => {
]);
});

it("calls listener when non-embedded object is updated", async function (this: RealmObjectContext<Person>) {
const collection = this.realm.objects<Person>("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],
},
]);
});

it("calls listener when embedded object is updated", async function (this: RealmObjectContext<Person>) {
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<Person>) {
const collection = this.realm.objects("Person");
await expectListenerRemoval({
Expand Down Expand Up @@ -769,7 +1015,7 @@ describe("Observable", () => {
});

describe("key-path filtered", () => {
it("fires on relevant changes to a primitive", async function (this: RealmObjectContext<Person>) {
it("calls listener when specified primitive is updated", async function (this: RealmObjectContext<Person>) {
const collection = this.realm.objects<Person>("Person").filtered("name = $0 OR age = 42", "Alice");
await expectCollectionNotifications(
collection,
Expand Down Expand Up @@ -832,7 +1078,7 @@ describe("Observable", () => {
);
});

it("fires on relevant changes to a list", async function (this: RealmObjectContext<Person>) {
it("calls listener when specified list of links is updated", async function (this: RealmObjectContext<Person>) {
const collection = this.realm.objects<Person>("Person").filtered("name = $0 OR age = 42", "Alice");
await expectCollectionNotifications(
collection,
Expand Down Expand Up @@ -863,7 +1109,7 @@ describe("Observable", () => {
);
});

it("fires on relevant changes to a primitive of a list", async function (this: RealmObjectContext<Person>) {
it("calls listener when specified primitive on link in list is updated", async function (this: RealmObjectContext<Person>) {
const collection = this.realm.objects<Person>("Person").filtered("name = $0 OR age = 42", "Alice");
await expectCollectionNotifications(
collection,
Expand Down Expand Up @@ -894,7 +1140,7 @@ describe("Observable", () => {
);
});

it("fires on relevant changes to a wildcard", async function (this: RealmObjectContext<Person>) {
it("calls listener when one-level wildcard is specified and top-level property is updated", async function (this: RealmObjectContext<Person>) {
const collection = this.realm.objects<Person>("Person").filtered("name = $0 OR age = 42", "Alice");
await expectCollectionNotifications(
collection,
Expand Down
Loading
Loading