/// /** * CT-1158 TEST: Nested map with ifElse null handling * * This pattern demonstrates various map + ifElse combinations working correctly. * The fix ensures cell references are preserved even when ifElse returns null. * * TEST CASES: * 1. Basic List - simple map over items * 2. Category List - nested map with ifElse filtering by category * 3. Single Map + ifElse null - conditional rendering with null fallback * 4. Single Map + ifElse empty span - conditional rendering with empty element * * Use "Run Test Sequence" to verify all cases handle state changes correctly. */ import { Cell, computed, Default, derive, handler, ifElse, NAME, pattern, UI, } from "commontools"; interface Item { title: string; done: Default; category: Default; } interface Input { items: Default; log: Default; } // Handler to run the exact repro sequence (moved to module scope) const runRepro = handler; log: Cell }>( (_event, { items: itemsList, log: logList }) => { const logMsg = (msg: string) => { logList.push(`${new Date().toISOString().slice(11, 19)} - ${msg}`); }; logMsg("Starting repro sequence..."); // Step 1: Log initial state const initial = itemsList.get(); logMsg( `Initial items: ${ initial.map((i) => `${i.title}(done=${i.done})`).join(", ") }`, ); // Step 2: Check Milk (index 0) logMsg("Setting items[0].done = true (checking Milk)"); itemsList.key(0).key("done").set(true); // Log state after check const afterCheck = itemsList.get(); logMsg( `After check: ${ afterCheck.map((i) => `${i.title}(done=${i.done})`).join(", ") }`, ); // Step 3: Remove Milk (index 0) logMsg("Removing items[0] (Milk)"); const current = itemsList.get(); itemsList.set(current.toSpliced(0, 1)); // Log final state const final = itemsList.get(); logMsg( `Final items: ${ final.map((i) => `${i.title}(done=${i.done})`).join(", ") }`, ); logMsg("Test complete - all lists should show Bread and Cheese"); }, ); // Handler to reset items (moved to module scope) const resetItems = handler< unknown, { items: Cell; log: Cell } >( (_event, { items: itemsList, log: logList }) => { itemsList.set([ { title: "Milk", done: false, category: "Dairy" }, { title: "Bread", done: false, category: "Bakery" }, { title: "Cheese", done: true, category: "Dairy" }, ]); logList.set(["Reset to initial state"]); }, ); export default pattern(({ items, log }) => { // Derive categories from items const categories = derive({ items }, ({ items: arr }: { items: Item[] }) => { const cats = new Set(); for (const item of arr) { cats.add(item.category || "Uncategorized"); } return Array.from(cats).sort(); }); return { [NAME]: "Nested Map + ifElse Test", [UI]: (

Nested Map + ifElse Test

Run Test Sequence Reset
{/* Row 1: Basic List vs Category List */}
{/* Test 1: Basic List - simple items.map */}

1. Basic List

Simple map over items
{items.map((item, idx) => (
[{idx}] {item.title} ({item.category})
))}
{/* Test 2: Category List - nested map with ifElse null */}

2. Category List

Nested map with ifElse filtering by category
{categories.map((category) => (
{category}: {items.map((item, idx) => ifElse( computed(() => (item.category || "Uncategorized") === category ),
[{idx}] {item.title}
, null, ) )}
))}
{/* Row 2: Single map tests with ifElse */}
{/* Test 3: Single map + ifElse null */}

3. Filtered (ifElse null)

Shows only done items using null fallback
{items.map((item, idx) => ifElse( computed(() => item.done),
[{idx}] {item.title} (done)
, null, ) )}
(Shows only checked items)
{/* Test 4: Single map + ifElse empty span */}

4. Filtered (ifElse span)

Shows only done items using empty span fallback
{items.map((item, idx) => ifElse( computed(() => item.done),
[{idx}] {item.title} (done)
, , ) )}
(Shows only checked items)
{/* Log output */}
Log: {log.map((entry) =>
{entry}
)}
Expected after "Run Test Sequence":
  • Test 1: Shows Bread, Cheese
  • Test 2: Shows Bread (Bakery) and Cheese (Dairy)
  • Test 3: Shows Cheese (the only done item)
  • Test 4: Shows Cheese (the only done item)
), items, log, }; });