diff --git a/src/lib/codegenerator/generator.js b/src/lib/codegenerator/generator.js index 2390137f..ae922794 100644 --- a/src/lib/codegenerator/generator.js +++ b/src/lib/codegenerator/generator.js @@ -612,18 +612,27 @@ const generateForLoopCode = function (templateObject, parent) { effectKey = effectKey.match(/[^.]+$/)[0] } - // get the reference to range from and to - const effectKeysRegex = /\$([^,} ]+)/g - const effectKeys = [...range.matchAll(effectKeysRegex)].map((match) => `'${match[1]}'`) + const collectionExpr = result[2].trim() + const propName = collectionExpr.replace(/^\$/, '').split('.')[0] ctx.renderCode.push(` let eff${forStartCounter} = () => { - forloops[${forStartCounter}](${cast(result[2], ':for')}, elms, created[${forStartCounter}]) + // Access underlying prop/state if this is a direct property to ensure tracking + if (component[Symbol.for('propKeys')] && component[Symbol.for('propKeys')].indexOf('${propName}') !== -1) { + void component[Symbol.for('props')]['${propName}'] + } else if (component[Symbol.for('stateKeys')] && component[Symbol.for('stateKeys')].indexOf('${propName}') !== -1) { + void component[Symbol.for('state')]['${propName}'] + } + + const collection = ${cast(result[2], ':for')} + forloops[${forStartCounter}](collection, elms, created[${forStartCounter}]) } component[Symbol.for('effects')].push(eff${forStartCounter}) - effect(eff${forStartCounter}, ['${effectKey}', ${effectKeys.join(',')}]) + // Use null as currentKey to track all property accesses during effect execution. + // This ensures computed property dependencies are tracked when accessing underlying reactive properties. + effect(eff${forStartCounter}, null) `) ctx.cleanupCode.push(` diff --git a/src/lib/codegenerator/generator.test.js b/src/lib/codegenerator/generator.test.js index e6a04bb3..115aed9c 100644 --- a/src/lib/codegenerator/generator.test.js +++ b/src/lib/codegenerator/generator.test.js @@ -3284,7 +3284,8 @@ test('Generate code for a template with a simple for-loop on an Element', (asser } let eff1 = () => { - forloops[1](component.items, elms, created[1]) + const collection = component.items + forloops[1](collection, elms, created[1]) } component[Symbol.for('effects')].push(eff1) @@ -3439,7 +3440,8 @@ test('Generate code for a template with a simple for-loop on an Element, Using d } let eff1 = () => { - forloops[1](component.$appState.list, elms, created[1]) + const collection = component.$appState.list + forloops[1](collection, elms, created[1]) } component[Symbol.for('effects')].push(eff1) @@ -3594,7 +3596,8 @@ test('Generate code for a template with a simple for-loop on an Element, Using d } let eff1 = () => { - forloops[1](component.content.data, elms, created[1]) + const collection = component.content.data + forloops[1](collection, elms, created[1]) } component[Symbol.for('effects')].push(eff1) @@ -3747,7 +3750,8 @@ test('Generate code for a template with a simple for-loop on an Element with a c } let eff1 = () => { - forloops[1](component.items, elms, created[1]) + const collection = component.items + forloops[1](collection, elms, created[1]) } component[Symbol.for('effects')].push(eff1) @@ -3900,7 +3904,8 @@ test('Generate code for a template with a simple for-loop on an Element with a k } let eff1 = () => { - forloops[1](component.items, elms, created[1]) + const collection = component.items + forloops[1](collection, elms, created[1]) } component[Symbol.for('effects')].push(eff1) @@ -4090,7 +4095,8 @@ test('Generate code for a template with a simple for-loop on a Component with a } let eff1 = () => { - forloops[1](component.items, elms, created[1]) + const collection = component.items + forloops[1](collection, elms, created[1]) } component[Symbol.for('effects')].push(eff1) @@ -4254,7 +4260,8 @@ test('Generate code for a template with a simple for-loop on an Element with an } let eff1 = () => { - forloops[1](component.items, elms, created[1]) + const collection = component.items + forloops[1](collection, elms, created[1]) } component[Symbol.for('effects')].push(eff1) @@ -4307,6 +4314,42 @@ test('Generate code for a template with a simple for-loop on an Element with an assert.end() }) +test('Generate code for for-loop with empty array that gets populated - verifies reactivity fix', (assert) => { + const templateObject = { + children: [ + { + [Symbol.for('componentType')]: 'Element', + children: [ + { + [Symbol.for('componentType')]: 'Element', + ':for': 'item in $items', + }, + ], + }, + ], + } + + const generated = generator.call(scope, templateObject) + const renderCode = generated.render.toString() + + assert.ok( + renderCode.includes('const collection = component.items'), + 'Effect should store array in variable to ensure reactive tracking when array changes from empty to populated' + ) + + assert.ok( + renderCode.includes('effect(eff1') && renderCode.includes("'items'"), + 'Effect should be registered with items key for tracking' + ) + + assert.ok( + renderCode.includes('forloops[1](collection, elms, created[1])'), + 'Forloop should use the collection variable to ensure reactive property access is tracked' + ) + + assert.end() +}) + test('Generate code for a template with double $$ (i.e. referencing a Blits plugin)', (assert) => { const templateObject = { children: [