ソースを参照

Bug: Updates correct scope when x-for looping over element with x-data (#3504)

* :test_tube: Adds test for failing scope conflict

* :white_check_mark: Passes x-for scope test

* Refactor implementation

---------

Co-authored-by: Caleb Porzio <calebporzio@gmail.com>
Eric Kwoka 2 年 前
コミット
117668f8f1

+ 12 - 4
packages/alpinejs/src/directives/x-for.js

@@ -1,4 +1,4 @@
-import { addScopeToNode, refreshScope } from '../scope'
+import { addScopeToNode } from '../scope'
 import { evaluateLater } from '../evaluator'
 import { directive } from '../directives'
 import { reactive } from '../reactivity'
@@ -168,7 +168,7 @@ function loop(el, iteratorNames, evaluateItems, evaluateKey) {
                 marker.remove()
             })
 
-            refreshScope(elForSpot, scopes[keys.indexOf(keyForSpot)])
+            elForSpot._x_refreshXForScope(scopes[keys.indexOf(keyForSpot)])
         }
 
         // We can now create and add new elements.
@@ -185,7 +185,15 @@ function loop(el, iteratorNames, evaluateItems, evaluateKey) {
 
             let clone = document.importNode(templateEl.content, true).firstElementChild
 
-            addScopeToNode(clone, reactive(scope), templateEl)
+            let reactiveScope = reactive(scope)
+
+            addScopeToNode(clone, reactiveScope, templateEl)
+
+            clone._x_refreshXForScope = (newScope) => {
+                Object.entries(newScope).forEach(([key, value]) => {
+                    reactiveScope[key] = value
+                })
+            }
 
             mutateDom(() => {
                 lastEl.after(clone)
@@ -204,7 +212,7 @@ function loop(el, iteratorNames, evaluateItems, evaluateKey) {
         // data it depends on in case the data has changed in an
         // "unobservable" way.
         for (let i = 0; i < sames.length; i++) {
-            refreshScope(lookup[sames[i]], scopes[keys.indexOf(sames[i])])
+            lookup[sames[i]]._x_refreshXForScope(scopes[keys.indexOf(sames[i])])
         }
 
         // Now we'll log the keys (and the order they're in) for comparing

+ 2 - 10
packages/alpinejs/src/scope.js

@@ -15,14 +15,6 @@ export function hasScope(node) {
     return !! node._x_dataStack
 }
 
-export function refreshScope(element, scope) {
-    let existingScope = element._x_dataStack[0]
-
-    Object.entries(scope).forEach(([key, value]) => {
-        existingScope[key] = value
-    })
-}
-
 export function closestDataStack(node) {
     if (node._x_dataStack) return node._x_dataStack
 
@@ -60,7 +52,7 @@ export function mergeProxies(objects) {
                     if ((descriptor.get && descriptor.get._x_alreadyBound) || (descriptor.set && descriptor.set._x_alreadyBound)) {
                         return true
                     }
-                    
+
                     // Properly bind getters and setters to this wrapper Proxy.
                     if ((descriptor.get || descriptor.set) && descriptor.enumerable) {
                         // Only bind user-defined getters, not our magic properties.
@@ -81,7 +73,7 @@ export function mergeProxies(objects) {
                         })
                     }
 
-                    return true 
+                    return true
                 }
 
                 return false

+ 22 - 2
tests/cypress/integration/directives/x-for.spec.js

@@ -560,6 +560,27 @@ test('renders children using directives injected by x-html correctly',
     }
 )
 
+test(
+    'handles x-data directly inside x-for',
+    html`
+        <div x-data="{ items: [{x:0, k:1},{x:1, k:2}] }">
+            <button x-on:click="items = [{x:3, k:1},{x:4, k:2}]">update</button>
+            <template x-for="item in items" :key="item.k">
+                <div :id="'item-' + item.k" x-data="{ inner: true }">
+                    <span x-text="item.x.toString()"></span>:
+                    <span x-text="item.k"></span>
+                </div>
+            </template>
+        </div>
+    `,
+    ({ get }) => {
+        get('#item-1 span:nth-of-type(1)').should(haveText('0'))
+        get('#item-2 span:nth-of-type(1)').should(haveText('1'))
+        get('button').click()
+        get('#item-1 span:nth-of-type(1)').should(haveText('3'))
+        get('#item-2 span:nth-of-type(1)').should(haveText('4'))
+})
+
 test('x-for throws descriptive error when key is undefined',
     html`
         <div x-data="{ items: [
@@ -581,7 +602,6 @@ test('x-for throws descriptive error when key is undefined',
             </template>
         </div>
     `,
-    ({ get }) => {
-    },
+    ({ get }) => {},
     true
 )