diff --git a/functions/src/__tests__/organizer-name-sync.test.ts b/functions/src/__tests__/organizer-name-sync.test.ts new file mode 100644 index 0000000..d37657d --- /dev/null +++ b/functions/src/__tests__/organizer-name-sync.test.ts @@ -0,0 +1,49 @@ +import "./setup"; +import { afterAll, beforeEach, describe, expect, it } from "vitest"; +import { db } from "../platform"; +import { onUserUpdated } from "../users"; +import { captureSnapshot, deliverWritten, newEventId, purge } from "./helpers"; + +// A meetup keeps its organizer's name and photo, shown as "Organized by …". +// onUserUpdated copied a new name onto posts, comments, rosters and the rest, +// but not onto the meetups the person organizes, which kept the old name until +// the meetup itself was edited. + +const ORGANIZER = "rename-organizer"; +const SOMEONE_ELSE = "rename-someone-else"; +const USER = `users/${ORGANIZER}`; +const THEIRS = "meetups/rename-theirs"; +const NOT_THEIRS = "meetups/rename-not-theirs"; +const POST = "posts/rename-post"; +const OLD_PHOTO = "https://res.cloudinary.com/demo/image/upload/old.jpg"; +const NEW_PHOTO = "https://res.cloudinary.com/demo/image/upload/new.jpg"; + +async function wipe() { + await purge(USER, THEIRS, NOT_THEIRS, POST); +} + +beforeEach(async () => { + await wipe(); + await db.doc(USER).set({ displayName: "Old Name", avatarUrl: OLD_PHOTO }); + await db.doc(THEIRS).set({ organizerId: ORGANIZER, organizerName: "Old Name", organizerAvatar: OLD_PHOTO, title: "Theirs", status: "upcoming" }); + await db.doc(NOT_THEIRS).set({ organizerId: SOMEONE_ELSE, organizerName: "Someone Else", organizerAvatar: OLD_PHOTO, title: "Not theirs", status: "upcoming" }); + await db.doc(POST).set({ authorId: ORGANIZER, authorName: "Old Name", authorAvatar: OLD_PHOTO, text: "a post" }); +}); +afterAll(wipe); + +describe("a renamed organizer", () => { + it("is named, with the new photo, on the meetups they organize", async () => { + const before = await captureSnapshot(USER); + await db.doc(USER).update({ displayName: "New Name", avatarUrl: NEW_PHOTO }); + const after = await captureSnapshot(USER); + + await deliverWritten(onUserUpdated, before, after, { userId: ORGANIZER }, newEventId("user")); + + const theirs = (await db.doc(THEIRS).get()).data(); + expect([theirs?.organizerName, theirs?.organizerAvatar]).toEqual(["New Name", NEW_PHOTO]); + const post = (await db.doc(POST).get()).data(); + expect([post?.authorName, post?.authorAvatar]).toEqual(["New Name", NEW_PHOTO]); + const notTheirs = (await db.doc(NOT_THEIRS).get()).data(); + expect(notTheirs?.organizerName, "someone else's meetup changed").toBe("Someone Else"); + }); +}); diff --git a/functions/src/users.ts b/functions/src/users.ts index bd7c3da..62fd2de 100644 --- a/functions/src/users.ts +++ b/functions/src/users.ts @@ -194,6 +194,14 @@ export const onUserUpdated = onDocumentWritten( if (nameChanged) checkinFields.userName = after.displayName; if (avatarChanged) checkinFields.userAvatar = after.avatarUrl; + // The meetups they organize carry their name and photo as well, shown as + // "Organized by …". updateMeetupCallable refreshed those only when the + // meetup itself was edited, so a renamed organizer kept the old name on + // every meetup they had not touched since. + const organizerFields: Record = {}; + if (nameChanged) organizerFields.organizerName = after.displayName; + if (avatarChanged) organizerFields.organizerAvatar = after.avatarUrl; + const syncTasks: Array<[string, () => Promise]> = [ ["posts", () => syncCollection(db.collection("posts").where("authorId", "==", userId), postFields)], ["comments", () => syncCollection(db.collectionGroup("comments").where("authorId", "==", userId), commentFields)], @@ -203,6 +211,7 @@ export const onUserUpdated = onDocumentWritten( ["family", () => syncCollection(db.collectionGroup("family").where("userId", "==", userId), familyFields)], ["followers", () => syncCollection(db.collectionGroup("followers").where("userId", "==", userId), followerMirrorFields)], ["checkins", () => syncCollection(db.collectionGroup("checkins").where("userId", "==", userId), checkinFields)], + ["meetups", () => syncCollection(db.collection("meetups").where("organizerId", "==", userId), organizerFields)], ]; const syncFailures = await Promise.all(