From 81353d1d29ec0a7fdde180b105d80fa57a218203 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Thu, 7 Dec 2023 21:07:38 +0100 Subject: [PATCH] Passing actions when expecting notifications in tests (#6308) * Allow passing actions when expecting notifications * Apply suggestions from code review Co-authored-by: LJ <81748770+elle-j@users.noreply.github.com> --------- Co-authored-by: LJ <81748770+elle-j@users.noreply.github.com> --- .../tests/src/tests/observable.ts | 200 ++++++++++++------ 1 file changed, 134 insertions(+), 66 deletions(-) diff --git a/integration-tests/tests/src/tests/observable.ts b/integration-tests/tests/src/tests/observable.ts index ed36fdf0c2..c834e36f15 100644 --- a/integration-tests/tests/src/tests/observable.ts +++ b/integration-tests/tests/src/tests/observable.ts @@ -39,61 +39,125 @@ function expectObservableMethods(observable: Observable) { expect(observable.removeAllListeners).to.be.a("function"); } +type Action = () => void; +type ChangeAndActions = { expectedChange: ChangeSet; actions: Action[] }; + +/** + * Transforms an array of changeset objects and actions into an array of objects for each changeset with all actions which immediately proceed it. + */ +function inlineActions(changesAndActions: (ChangeSet | Action)[]) { + const initialActions: Action[] = []; + const changes: ChangeAndActions[] = []; + + for (const changeOrAction of changesAndActions) { + if (typeof changeOrAction === "function") { + if (changes.length > 0) { + const current = changes[changes.length - 1]; + current.actions.push(changeOrAction); + } else { + initialActions.push(changeOrAction); + } + } else { + changes.push({ expectedChange: changeOrAction, actions: [] }); + } + } + + return { initialActions, changes }; +} + +/** + * Calls a list of functions in separate ticks + */ +function performActions(actions: Action[]) { + for (const action of actions) { + // Using separate ticks to let the calling function return early + setImmediate(action); + } +} + +type RealmChangeSet = { schema?: Realm.CanonicalObjectSchema[] }; + function expectRealmNotifications( realm: Realm, eventName: RealmEventName, - expectedChangeSets: { schema?: Realm.CanonicalObjectSchema[] }[], + changesAndActions: (Action | RealmChangeSet)[], ) { const handle = createPromiseHandle(); + + const { initialActions, changes } = inlineActions(changesAndActions); + performActions(initialActions); + realm.addListener( eventName, createListenerStub( handle, - ...expectedChangeSets.map( - (expectedChanges, c) => (realm: Realm, name: string, schema?: Realm.CanonicalObjectSchema[]) => { - expect(realm).instanceOf(Realm); - expect(name).equals(eventName, `Realm change event #${c} name didn't match`); - expect(schema).deep.equals(expectedChanges.schema, `Realm change event #${c} schema didn't match`); - }, + ...changes.map( + ({ expectedChange }, c) => + (realm: Realm, name: string, schema?: Realm.CanonicalObjectSchema[]) => { + expect(realm).instanceOf(Realm); + expect(name).equals(eventName, `Realm change event #${c} name didn't match`); + expect(schema).deep.equals(expectedChange.schema, `Realm change event #${c} schema didn't match`); + }, ), ), ); return handle; } -function expectObjectNotifications(object: Realm.Object, expectedChangeSets: ObjectChangeSet[]) { +function expectObjectNotifications(object: Realm.Object, changesAndActions: (Action | ObjectChangeSet)[]) { const handle = createPromiseHandle(); + + const { initialActions, changes } = inlineActions(changesAndActions); + performActions(initialActions); + object.addListener( createListenerStub( handle, - ...expectedChangeSets.map((expectedChanges, c) => (_: Realm.Object, changes: ObjectChangeSet) => { - expect(changes).deep.equals(expectedChanges, `Changeset #${c} didn't match`); + ...changes.map(({ expectedChange, actions }, c) => (_: Realm.Object, actualChange: ObjectChangeSet) => { + expect(actualChange).deep.equals(expectedChange, `Changeset #${c} didn't match`); + performActions(actions); }), ), ); return handle; } -function expectCollectionNotifications(collection: Realm.Collection, expectedChangeSets: CollectionChangeSet[]) { +function expectCollectionNotifications( + collection: Realm.Collection, + changesAndActions: (Action | CollectionChangeSet)[], +) { const handle = createPromiseHandle(); + + const { initialActions, changes } = inlineActions(changesAndActions); + performActions(initialActions); + collection.addListener( createListenerStub( handle, - ...expectedChangeSets.map((expectedChanges, c) => (_: Realm.Collection, changes: CollectionChangeSet) => { - expect(changes).deep.equals(expectedChanges, `Changeset #${c} didn't match`); + ...changes.map(({ expectedChange, actions }, c) => (_: Realm.Collection, actualChange: CollectionChangeSet) => { + expect(actualChange).deep.equals(expectedChange, `Changeset #${c} didn't match`); + performActions(actions); }), ), ); return handle; } -function expectDictionaryNotifications(dictionary: Realm.Dictionary, expectedChangeSets: DictionaryChangeSet[]) { +function expectDictionaryNotifications( + dictionary: Realm.Dictionary, + changesAndActions: (Action | DictionaryChangeSet)[], +) { const handle = createPromiseHandle(); + + const { initialActions, changes } = inlineActions(changesAndActions); + performActions(initialActions); + dictionary.addListener( createListenerStub( handle, - ...expectedChangeSets.map((expectedChanges, c) => (_: Realm.Dictionary, changes: DictionaryChangeSet) => { - expect(changes).deep.equals(expectedChanges, `Changeset #${c} didn't match`); + ...changes.map(({ expectedChange, actions }, c) => (_: Realm.Dictionary, actualChange: DictionaryChangeSet) => { + expect(actualChange).deep.equals(expectedChange, `Changeset #${c} didn't match`); + performActions(actions); }), ), ); @@ -142,11 +206,14 @@ describe("Observable", () => { describe("change", () => { it("calls listener", async function (this: RealmContext) { - const completion = expectRealmNotifications(this.realm, "change", [{}]); - - this.realm.write(() => { - this.realm.create("Person", { name: "Alice" }); - }); + const completion = expectRealmNotifications(this.realm, "change", [ + () => { + this.realm.write(() => { + this.realm.create("Person", { name: "Alice" }); + }); + }, + {}, + ]); await completion; }); @@ -179,13 +246,14 @@ describe("Observable", () => { describe("beforenotify", () => { // Skipping on React Native because the callback is called one too many times it.skipIf(environment.reactNative, "calls listener", async function (this: RealmContext) { - const completion = expectRealmNotifications(this.realm, "beforenotify", [{}]); - - this.realm.write(() => { - this.realm.create("Person", { name: "Alice" }); - }); - - await completion; + await expectRealmNotifications(this.realm, "beforenotify", [ + () => { + this.realm.write(() => { + this.realm.create("Person", { name: "Alice" }); + }); + }, + {}, + ]); }); it("removes listeners", async function (this: RealmContext) { @@ -219,7 +287,12 @@ describe("Observable", () => { }; it("calls listener", async function (this: InternalRealmContext) { - const completion = expectRealmNotifications(this.realm, "schema", [ + await expectRealmNotifications(this.realm, "schema", [ + () => { + this.realm.write(() => { + this.realm._updateSchema([{ name: "Person", properties: { name: "string" } }]); + }); + }, { schema: [ { @@ -241,12 +314,6 @@ describe("Observable", () => { ], }, ]); - - this.realm.write(() => { - this.realm._updateSchema([{ name: "Person", properties: { name: "string" } }]); - }); - - await completion; }); it("removes listeners", async function (this: InternalRealmContext) { @@ -303,16 +370,15 @@ describe("Observable", () => { }); it("calls listener", async function (this: RealmObjectContext) { - const completion = expectObjectNotifications(this.object, [ + await expectObjectNotifications(this.object, [ { deleted: false, changedProperties: [] }, + () => { + this.realm.write(() => { + this.object.name = "Bob"; + }); + }, { deleted: false, changedProperties: ["name"] }, ]); - - this.realm.write(() => { - this.object.name = "Bob"; - }); - - await completion; }); it("removes listeners", async function (this: RealmObjectContext) { @@ -378,13 +444,18 @@ describe("Observable", () => { it("calls listener", async function (this: RealmObjectContext) { const collection = this.realm.objects("Person"); - const completion = expectCollectionNotifications(collection, [ + await expectCollectionNotifications(collection, [ { deletions: [], insertions: [], newModifications: [], oldModifications: [], }, + () => { + this.realm.write(() => { + this.object.name = "Bob"; + }); + }, { deletions: [], insertions: [], @@ -392,12 +463,6 @@ describe("Observable", () => { oldModifications: [0], }, ]); - - this.realm.write(() => { - this.object.name = "Bob"; - }); - - await completion; }); it("removes listeners", async function (this: RealmObjectContext) { @@ -476,6 +541,12 @@ describe("Observable", () => { newModifications: [], oldModifications: [], }, + () => { + this.realm.write(() => { + collection[1].name = "Diana"; + delete collection[0]; + }); + }, { deletions: [], insertions: [], @@ -484,11 +555,6 @@ describe("Observable", () => { }, ]); - this.realm.write(() => { - collection[1].name = "Diana"; - delete collection[0]; - }); - await completion; }); @@ -567,6 +633,13 @@ describe("Observable", () => { oldModifications: [], newModifications: [], }, + () => { + this.realm.write(() => { + // collection["bob"].name = "Bobby"; + // TODO: It seems we cannot trigger a notification when a property value changes. + collection.add(this.object); + }); + }, { deletions: [], insertions: [0], @@ -575,12 +648,6 @@ describe("Observable", () => { }, ]); - this.realm.write(() => { - // collection["bob"].name = "Bobby"; - // TODO: It seems we cannot trigger a notification when a property value changes. - collection.add(this.object); - }); - await completion; }); @@ -671,6 +738,13 @@ describe("Observable", () => { insertions: [], modifications: [], }, + () => { + this.realm.write(() => { + // collection["bob"].name = "Bobby"; + // TODO: It seems we cannot trigger a notification when a property value changes. + collection["bob"] = this.object; + }); + }, { deletions: [], insertions: [], @@ -678,12 +752,6 @@ describe("Observable", () => { }, ]); - this.realm.write(() => { - // collection["bob"].name = "Bobby"; - // TODO: It seems we cannot trigger a notification when a property value changes. - collection["bob"] = this.object; - }); - await completion; });