/** * Test Pattern: Do List * * Exercises the do-list contract via runSynced: * - empty initial state * - add items via addItem * - reference-addressed update (updateItem matched with equals()) * - title-addressed update (updateItemByTitle, case-insensitive, updates ALL * matching titles — the legacy-but-consumed omnibox API) * - reference-addressed remove (removeItem) * - held-reference survival (CT-1715): a reference stashed in a cell BEFORE * an update (a selection cell, a caller that read the item earlier) must * still `equals()`-match and still drive a subsequent operation AFTER the * item is updated — both for the reference-addressed updateItem and the * title-addressed updateItemByTitle. This guards the update-in-place * contract: updates write through the element's cells; replacing the array * slot with a fresh object literal would re-mint the entity identity and * orphan every held reference. * * Run: deno task cf test packages/patterns/do-list/do-list.test.tsx --verbose */ import { action, computed, equals, pattern, Writable } from "commonfabric"; import DoList, { type DoItem } from "./do-list.tsx"; export default pattern(() => { const doList = DoList({}); // Simulates an external holder (selection cell / attachment source) that // read an item once and keeps the reference across later mutations. const held = new Writable(null); const heldByTitle = new Writable(null); // ========================================================================== // Actions // ========================================================================== const add_alpha = action(() => { doList.addItem.send({ title: "Alpha" }); }); const add_beta = action(() => { doList.addItem.send({ title: "Beta" }); }); const add_gamma = action(() => { doList.addItem.send({ title: "Gamma" }); }); // Reference-addressed: read the live item from the list, then send it. const done_first_by_ref = action(() => { const item = doList.items[0]; if (item) doList.updateItem.send({ item, done: true }); }); const rename_first_by_ref = action(() => { const item = doList.items[0]; if (item) doList.updateItem.send({ item, title: "Alpha!" }); }); // Title-addressed (LLM/omnibox API), case-insensitive. const rename_beta_by_title = action(() => { doList.updateItemByTitle.send({ title: "BETA", newTitle: "Beta2", done: true, }); }); // Duplicate titles: the title-addressed handler updates ALL matches (the // original `.map()` semantics, preserved by the in-place rewrite). const add_dup_a = action(() => { doList.addItem.send({ title: "Dup" }); }); const add_dup_b = action(() => { doList.addItem.send({ title: "Dup" }); }); const done_all_dups_by_title = action(() => { doList.updateItemByTitle.send({ title: "dup", done: true }); }); // Held-reference survival across the reference-addressed updateItem. const add_held_target = action(() => { doList.addItem.send({ title: "Held" }); }); const stash_held = action(() => { const item = doList.items[5]; if (item) held.set(item); }); const rename_held_target = action(() => { const item = doList.items[5]; if (item) doList.updateItem.send({ item, title: "HeldRenamed" }); }); const done_via_held = action(() => { const h = held.get(); if (h) doList.updateItem.send({ item: h, done: true }); }); const remove_via_held = action(() => { const h = held.get(); if (h) doList.removeItem.send({ item: h }); }); // Held-reference survival across the TITLE-addressed updateItemByTitle. const add_title_held_target = action(() => { doList.addItem.send({ title: "TitleHeld" }); }); const stash_title_held = action(() => { const item = doList.items[5]; if (item) heldByTitle.set(item); }); const rename_title_held_by_title = action(() => { doList.updateItemByTitle.send({ title: "titleheld", newTitle: "TitleHeldRenamed", done: true, }); }); const remove_via_title_held = action(() => { const h = heldByTitle.get(); if (h) doList.removeItem.send({ item: h }); }); // ========================================================================== // Assertions // ========================================================================== const assert_initial_empty = computed(() => doList.itemCount === 0); const assert_three = computed(() => doList.itemCount === 3); const assert_titles = computed(() => doList.items[0]?.title === "Alpha" && doList.items[1]?.title === "Beta" && doList.items[2]?.title === "Gamma" ); const assert_first_done = computed(() => doList.items[0]?.done === true); const assert_first_renamed = computed(() => doList.items[0]?.title === "Alpha!" && doList.items[0]?.done === true ); const assert_others_untouched = computed(() => doList.items[1]?.title === "Beta" && doList.items[1]?.done === false && doList.items[2]?.title === "Gamma" && doList.items[2]?.done === false ); const assert_beta_renamed_done = computed(() => doList.items[1]?.title === "Beta2" && doList.items[1]?.done === true ); const assert_five = computed(() => doList.itemCount === 5); const assert_all_dups_done = computed(() => { const dups = doList.items.filter((i: DoItem) => i.title === "Dup"); return dups.length === 2 && dups.every((i: DoItem) => i.done === true); }); // Held-reference survival (reference-addressed update). const assert_six = computed(() => doList.itemCount === 6); const assert_held_stashed = computed(() => { const h = held.get(); return h !== null && equals(doList.items[5], h); }); const assert_held_renamed = computed(() => doList.items[5]?.title === "HeldRenamed" ); // KEY: the stale-but-once-valid reference still equals()-matches the item // AFTER updateItem patched it. const assert_held_survives_update = computed(() => { const h = held.get(); return h !== null && equals(doList.items[5], h); }); // KEY: the held reference still DRIVES mutations after the update. const assert_done_via_held = computed(() => doList.items[5]?.done === true); const assert_removed_via_held = computed(() => doList.itemCount === 5 && doList.items.find((i: DoItem) => i.title === "HeldRenamed") === undefined ); // Held-reference survival (title-addressed update). const assert_six_again = computed(() => doList.itemCount === 6); const assert_title_held_stashed = computed(() => { const h = heldByTitle.get(); return h !== null && equals(doList.items[5], h); }); const assert_title_held_renamed = computed(() => doList.items[5]?.title === "TitleHeldRenamed" && doList.items[5]?.done === true ); const assert_title_held_survives_update = computed(() => { const h = heldByTitle.get(); return h !== null && equals(doList.items[5], h); }); const assert_removed_via_title_held = computed(() => doList.itemCount === 5 && doList.items.find((i: DoItem) => i.title === "TitleHeldRenamed") === undefined ); // ========================================================================== // Test Sequence // ========================================================================== return { tests: [ // Initial empty { assertion: assert_initial_empty }, // Add three items { action: add_alpha }, { action: add_beta }, { action: add_gamma }, { assertion: assert_three }, { assertion: assert_titles }, // Reference-addressed update (equals() identity) { action: done_first_by_ref }, { assertion: assert_first_done }, { action: rename_first_by_ref }, { assertion: assert_first_renamed }, { assertion: assert_others_untouched }, // Title-addressed update (case-insensitive) { action: rename_beta_by_title }, { assertion: assert_beta_renamed_done }, // Title-addressed update hits ALL matching titles { action: add_dup_a }, { action: add_dup_b }, { assertion: assert_five }, { action: done_all_dups_by_title }, { assertion: assert_all_dups_done }, // Held-reference survival: stash → reference-addressed update → the old // reference still matches and still drives done/remove. { action: add_held_target }, { assertion: assert_six }, { action: stash_held }, { assertion: assert_held_stashed }, { action: rename_held_target }, { assertion: assert_held_renamed }, { assertion: assert_held_survives_update }, { action: done_via_held }, { assertion: assert_done_via_held }, { action: remove_via_held }, { assertion: assert_removed_via_held }, // Held-reference survival: stash → TITLE-addressed update → the old // reference still matches and still drives removal. { action: add_title_held_target }, { assertion: assert_six_again }, { action: stash_title_held }, { assertion: assert_title_held_stashed }, { action: rename_title_held_by_title }, { assertion: assert_title_held_renamed }, { assertion: assert_title_held_survives_update }, { action: remove_via_title_held }, { assertion: assert_removed_via_title_held }, ], doList, }; });