Browse Source

Fix issue https://github.com/alpinejs/alpine/issues/2803 (and add tests): Ensure an already-flushed job is never dequeued from the queue, which would cause other jobs to be accidentally skipped. (#3278)

Co-authored-by: Ferran Conde Codorniu <ferran.conde.codorniu@cosanta.nl>
Ferran Conde Codorniu 2 years ago
parent
commit
a07b6c0b9e

+ 4 - 1
packages/alpinejs/src/scheduler.js

@@ -2,6 +2,7 @@
 let flushPending = false
 let flushing = false
 let queue = []
+let lastFlushedIndex = -1
 
 export function scheduler (callback) { queueJob(callback) }
 
@@ -13,7 +14,7 @@ function queueJob(job) {
 export function dequeueJob(job) {
     let index = queue.indexOf(job)
 
-    if (index !== -1) queue.splice(index, 1)
+    if (index !== -1 && index > lastFlushedIndex) queue.splice(index, 1)
 }
 
 function queueFlush() {
@@ -30,9 +31,11 @@ export function flushJobs() {
 
     for (let i = 0; i < queue.length; i++) {
         queue[i]()
+        lastFlushedIndex = i
     }
 
     queue.length = 0
+    lastFlushedIndex = -1
 
     flushing = false
 }

+ 37 - 0
tests/cypress/integration/directives/x-if.spec.js

@@ -73,3 +73,40 @@ test('x-if removed dom does not evaluate reactive expressions in dom tree',
         get('span').should(notExist())
     }
 )
+
+// Attempting to skip an already-flushed reactive effect would cause inconsistencies when updating other effects.
+// See https://github.com/alpinejs/alpine/issues/2803 for more details.
+test('x-if removed dom does not attempt skipping already-processed reactive effects in dom tree',
+    html`
+    <div x-data="{
+        isEditing: true,
+        foo: 'random text',
+        stopEditing() {
+          this.foo = '';
+          this.isEditing = false;
+        },
+    }">
+        <button @click="stopEditing">Stop editing</button>
+        <template x-if="isEditing">
+            <div id="div-editing">
+              <h2>Editing</h2>
+              <input id="foo" name="foo" type="text" x-model="foo" />
+            </div>
+        </template>
+
+        <template x-if="!isEditing">
+            <div id="div-not-editing"><h2>Not editing</h2></div>
+        </template>
+
+        <template x-if="!isEditing">
+            <div id="div-also-not-editing"><h2>Also not editing</h2></div>
+        </template>
+    </div>
+    `,
+    ({ get }) => {
+        get('button').click()
+        get('div#div-editing').should(notExist())
+        get('div#div-not-editing').should(exist())
+        get('div#div-also-not-editing').should(exist())
+    }
+)