/** * Test Pattern: Fair Share — joinWithProfile identity preservation * * Exercises the exported joinWithProfile handler (the participant/roster * "join with your profile" idiom) against test-owned cells: * - joining as a new person pushes a {name, avatar?} snapshot and selects * them as "you" (myName) * - joining as an existing avatar-less person BACKFILLS the avatar * - the avatar backfill must preserve the person's entity identity * (CT-1715): it writes through the element's cell; replacing the array * slot with a fresh object literal would re-mint the entity and orphan * every previously-held reference (selection cells, rows read earlier) * - an existing avatar is NOT overwritten by a later join * - held-reference survival: a reference stashed BEFORE the backfill still * `equals()`-matches the person and still drives a subsequent * equals()-located removal AFTER the backfill * * The full Fair Share pattern is not instantiated here: its UI resolves * `wish()` queries, and the ledger handler under test is module-scope state * bound, so the contract is exercised directly on the cells. * * Run: deno task cf test packages/patterns/fair-share/main.test.tsx --verbose */ import { action, computed, equals, handler, pattern, Writable, } from "commonfabric"; import { joinWithProfile } from "./main.tsx"; interface Person { name: string; avatar?: string; } // Test plumbing: remove the person the held reference points at, locating it // with equals() — proves a reference held across the avatar backfill still // drives operations (it would silently no-op if the backfill had re-minted // the person's entity identity). const removeHeldPerson = handler< void, { people: Writable; held: Writable } >((_event, { people, held }) => { const cur = people.get(); const idx = cur.findIndex((p) => equals(held, p)); if (idx >= 0) { people.set(cur.toSpliced(idx, 1)); } }); export default pattern(() => { const peopleCell = new Writable([]); const myNameCell = new Writable(""); // Simulates an external holder (selection cell / balance row) that read a // person once and keeps the reference across later mutations. Typed // non-null (placeholder initial value) so the cell can be bound directly // as handler state. const held = new Writable({ name: "" }); // ========================================================================== // Actions // ========================================================================== // Ann is added by hand first (no avatar) — the backfill target. const action_join_ann_no_avatar = joinWithProfile({ people: peopleCell, myName: myNameCell, name: "Ann", avatar: "", }); const action_stash_held = action(() => { const p = peopleCell.get()[0]; if (p) held.set(p); }); // Ann joins again with a profile avatar — exercises the backfill branch. const action_join_ann_with_avatar = joinWithProfile({ people: peopleCell, myName: myNameCell, name: "Ann", avatar: "🙂", }); // A later join must NOT clobber an existing avatar snapshot. const action_join_ann_other_avatar = joinWithProfile({ people: peopleCell, myName: myNameCell, name: "Ann", avatar: "🦊", }); // New person with avatar — the push branch. const action_join_bob = joinWithProfile({ people: peopleCell, myName: myNameCell, name: "Bob", avatar: "🐱", }); const action_remove_via_held = removeHeldPerson({ people: peopleCell, held, }); // ========================================================================== // Assertions // ========================================================================== const assert_ann_added = computed(() => { const cur = peopleCell.get(); return cur.length === 1 && cur[0]?.name === "Ann" && !cur[0]?.avatar; }); const assert_my_name_ann = computed(() => myNameCell.get() === "Ann"); const assert_held_stashed = computed(() => { const h = held.get(); return h.name === "Ann" && equals(peopleCell.get()[0], h); }); const assert_avatar_backfilled = computed(() => peopleCell.get()[0]?.avatar === "🙂" ); // KEY: the stale-but-once-valid reference still equals()-matches the // person AFTER the backfill wrote through the element's cell. const assert_held_survives_backfill = computed(() => { const h = held.get(); return equals(peopleCell.get()[0], h); }); // The held reference also READS the update (it would show the stale, // orphaned entity if the backfill had re-minted identity). const assert_held_reads_backfill = computed(() => held.get().avatar === "🙂"); const assert_avatar_not_clobbered = computed(() => peopleCell.get()[0]?.avatar === "🙂" ); const assert_bob_added_with_avatar = computed(() => { const cur = peopleCell.get(); return cur.length === 2 && cur[1]?.name === "Bob" && cur[1]?.avatar === "🐱"; }); const assert_my_name_bob = computed(() => myNameCell.get() === "Bob"); // KEY: the held reference still DRIVES an equals()-located removal. const assert_removed_via_held = computed(() => { const cur = peopleCell.get(); return cur.length === 1 && cur[0]?.name === "Bob"; }); // ========================================================================== // Test Sequence // ========================================================================== return { tests: [ // Join as a new (avatar-less) person { action: action_join_ann_no_avatar }, { assertion: assert_ann_added }, { assertion: assert_my_name_ann }, // Stash an external reference BEFORE the backfill { action: action_stash_held }, { assertion: assert_held_stashed }, // Avatar backfill preserves identity { action: action_join_ann_with_avatar }, { assertion: assert_avatar_backfilled }, { assertion: assert_held_survives_backfill }, { assertion: assert_held_reads_backfill }, // Existing avatar is not overwritten { action: action_join_ann_other_avatar }, { assertion: assert_avatar_not_clobbered }, // New person push branch { action: action_join_bob }, { assertion: assert_bob_added_with_avatar }, { assertion: assert_my_name_bob }, // The held reference still drives removal after the backfill { action: action_remove_via_held }, { assertion: assert_removed_via_held }, ], peopleCell, }; });