Forráskód Böngészése

Added tests and refactored x-bind logic

Caleb Porzio 5 éve
szülő
commit
afc4d9c251
4 módosított fájl, 87 hozzáadás és 76 törlés
  1. 5 7
      dist/alpine-ie11.js
  2. 5 7
      dist/alpine.js
  3. 1 8
      src/directives/bind.js
  4. 76 54
      test/bind.spec.js

+ 5 - 7
dist/alpine-ie11.js

@@ -6029,15 +6029,13 @@
         var newClasses = value.split(' ').filter(Boolean);
         el.setAttribute('class', arrayUnique(_originalClasses.concat(newClasses)).join(' '));
       }
-    } else if (isBooleanAttr(attrName)) {
-      // Boolean attributes have to be explicitly added and removed, not just set.
-      if (!!value) {
-        el.setAttribute(attrName, '');
-      } else {
+    } else {
+      // If an attribute's bound value is null, undefined or false, remove the attribute
+      if ([null, undefined, false].includes(value)) {
         el.removeAttribute(attrName);
+      } else {
+        isBooleanAttr(attrName) ? el.setAttribute(attrName, attrName) : el.setAttribute(attrName, value);
       }
-    } else {
-      el.setAttribute(attrName, value);
     }
   }
 

+ 5 - 7
dist/alpine.js

@@ -575,15 +575,13 @@
         const newClasses = value.split(' ').filter(Boolean);
         el.setAttribute('class', arrayUnique(originalClasses.concat(newClasses)).join(' '));
       }
-    } else if (isBooleanAttr(attrName)) {
-      // Boolean attributes have to be explicitly added and removed, not just set.
-      if (!!value) {
-        el.setAttribute(attrName, '');
-      } else {
+    } else {
+      // If an attribute's bound value is null, undefined or false, remove the attribute
+      if ([null, undefined, false].includes(value)) {
         el.removeAttribute(attrName);
+      } else {
+        isBooleanAttr(attrName) ? el.setAttribute(attrName, attrName) : el.setAttribute(attrName, value);
       }
-    } else {
-      el.setAttribute(attrName, value);
     }
   }
 

+ 1 - 8
src/directives/bind.js

@@ -68,19 +68,12 @@ export function handleAttributeBindingDirective(component, el, attrName, express
             const newClasses = value.split(' ').filter(Boolean)
             el.setAttribute('class', arrayUnique(originalClasses.concat(newClasses)).join(' '))
         }
-    } else if (isBooleanAttr(attrName)) {
-        // Boolean attributes have to be explicitly added and removed, not just set.
-        if (!! value) {
-            el.setAttribute(attrName, '')
-        } else {
-            el.removeAttribute(attrName)
-        }
     } else {
         // If an attribute's bound value is null, undefined or false, remove the attribute
         if ([null, undefined, false].includes(value)) {
             el.removeAttribute(attrName)
         } else {
-            el.setAttribute(attrName, value)
+            isBooleanAttr(attrName) ? el.setAttribute(attrName, attrName) : el.setAttribute(attrName, value)
         }
     }
 }

+ 76 - 54
test/bind.spec.js

@@ -197,14 +197,24 @@ test('non-boolean attributes set to null/undefined/false are removed from the el
     expect(document.querySelectorAll('span')[2].getAttribute('visible')).toBeNull()
 })
 
-test('boolean attributes set to false are removed from element', async () => {
+test('non-boolean empty string attributes are not removed', async () => {
     document.body.innerHTML = `
-        <div x-data="{ isSet: false }">
+        <div x-data="{}">
+            <a href="#hello" x-bind:href="''"></a>
+        </div>
+    `
+    Alpine.start()
+
+    expect(document.querySelectorAll('a')[0].getAttribute('href')).toEqual('')
+})
+
+test('truthy boolean attribute values are set to their attribute name', async () => {
+    document.body.innerHTML = `
+        <div x-data="{ isSet: true }">
             <input x-bind:disabled="isSet"></input>
             <input x-bind:checked="isSet"></input>
             <input x-bind:required="isSet"></input>
             <input x-bind:readonly="isSet"></input>
-            <input x-bind:hidden="isSet"></input>
             <details x-bind:open="isSet"></details>
             <select x-bind:multiple="isSet"></select>
             <option x-bind:selected="isSet"></option>
@@ -233,42 +243,43 @@ test('boolean attributes set to false are removed from element', async () => {
             ></script>
         </div>
     `
+
     Alpine.start()
 
-    expect(document.querySelectorAll('input')[0].getAttribute('disabled')).toBeNull()
-    expect(document.querySelectorAll('input')[1].getAttribute('checked')).toBeNull()
-    expect(document.querySelectorAll('input')[2].getAttribute('required')).toBeNull()
-    expect(document.querySelectorAll('input')[3].getAttribute('readOnly')).toBeNull()
-    expect(document.querySelectorAll('input')[4].getAttribute('hidden')).toBeNull()
-    expect(document.querySelectorAll('details')[0].getAttribute('open')).toBeNull()
-    expect(document.querySelectorAll('option')[0].getAttribute('selected')).toBeNull()
-    expect(document.querySelectorAll('select')[0].getAttribute('multiple')).toBeNull()
-    expect(document.querySelectorAll('textarea')[0].getAttribute('autofocus')).toBeNull()
-    expect(document.querySelectorAll('dl')[0].getAttribute('itemscope')).toBeNull()
-    expect(document.querySelectorAll('form')[0].getAttribute('novalidate')).toBeNull()
-    expect(document.querySelectorAll('iframe')[0].getAttribute('allowfullscreen')).toBeNull()
-    expect(document.querySelectorAll('iframe')[0].getAttribute('allowpaymentrequest')).toBeNull()
-    expect(document.querySelectorAll('button')[0].getAttribute('formnovalidate')).toBeNull()
-    expect(document.querySelectorAll('audio')[0].getAttribute('autoplay')).toBeNull()
-    expect(document.querySelectorAll('audio')[0].getAttribute('controls')).toBeNull()
-    expect(document.querySelectorAll('audio')[0].getAttribute('loop')).toBeNull()
-    expect(document.querySelectorAll('audio')[0].getAttribute('muted')).toBeNull()
-    expect(document.querySelectorAll('video')[0].getAttribute('playsinline')).toBeNull()
-    expect(document.querySelectorAll('track')[0].getAttribute('default')).toBeNull()
-    expect(document.querySelectorAll('img')[0].getAttribute('ismap')).toBeNull()
-    expect(document.querySelectorAll('ol')[0].getAttribute('reversed')).toBeNull()
-    expect(document.querySelectorAll('script')[0].getAttribute('async')).toBeNull()
-    expect(document.querySelectorAll('script')[0].getAttribute('defer')).toBeNull()
-    expect(document.querySelectorAll('script')[0].getAttribute('nomodule')).toBeNull()
+    expect(document.querySelectorAll('input')[0].disabled).toBeTruthy()
+    expect(document.querySelectorAll('input')[1].checked).toBeTruthy()
+    expect(document.querySelectorAll('input')[2].required).toBeTruthy()
+    expect(document.querySelectorAll('input')[3].readOnly).toBeTruthy()
+    expect(document.querySelectorAll('details')[0].open).toBeTruthy()
+    expect(document.querySelectorAll('option')[0].selected).toBeTruthy()
+    expect(document.querySelectorAll('select')[0].multiple).toBeTruthy()
+    expect(document.querySelectorAll('textarea')[0].autofocus).toBeTruthy()
+    expect(document.querySelectorAll('dl')[0].attributes.itemscope).toBeTruthy()
+    expect(document.querySelectorAll('form')[0].attributes.novalidate).toBeTruthy()
+    expect(document.querySelectorAll('iframe')[0].attributes.allowfullscreen).toBeTruthy()
+    expect(document.querySelectorAll('iframe')[0].attributes.allowpaymentrequest).toBeTruthy()
+    expect(document.querySelectorAll('button')[0].attributes.formnovalidate).toBeTruthy()
+    expect(document.querySelectorAll('audio')[0].attributes.autoplay).toBeTruthy()
+    expect(document.querySelectorAll('audio')[0].attributes.controls).toBeTruthy()
+    expect(document.querySelectorAll('audio')[0].attributes.loop).toBeTruthy()
+    expect(document.querySelectorAll('audio')[0].attributes.muted).toBeTruthy()
+    expect(document.querySelectorAll('video')[0].attributes.playsinline).toBeTruthy()
+    expect(document.querySelectorAll('track')[0].attributes.default).toBeTruthy()
+    expect(document.querySelectorAll('img')[0].attributes.ismap).toBeTruthy()
+    expect(document.querySelectorAll('ol')[0].attributes.reversed).toBeTruthy()
+    expect(document.querySelectorAll('script')[0].attributes.async).toBeTruthy()
+    expect(document.querySelectorAll('script')[0].attributes.defer).toBeTruthy()
+    expect(document.querySelectorAll('script')[0].attributes.nomodule).toBeTruthy()
 })
 
-test('boolean attributes set to true are added to element', async () => {
+test('null, undefined, or false boolean attribute values are removed', async () => {
     document.body.innerHTML = `
-        <div x-data="{ isSet: true }">
+        <div x-data="{ isSet: false }">
             <input x-bind:disabled="isSet"></input>
             <input x-bind:checked="isSet"></input>
             <input x-bind:required="isSet"></input>
             <input x-bind:readonly="isSet"></input>
+            <input x-bind:hidden="isSet"></input>
             <details x-bind:open="isSet"></details>
             <select x-bind:multiple="isSet"></select>
             <option x-bind:selected="isSet"></option>
@@ -297,33 +308,44 @@ test('boolean attributes set to true are added to element', async () => {
             ></script>
         </div>
     `
+    Alpine.start()
+
+    expect(document.querySelectorAll('input')[0].getAttribute('disabled')).toBeNull()
+    expect(document.querySelectorAll('input')[1].getAttribute('checked')).toBeNull()
+    expect(document.querySelectorAll('input')[2].getAttribute('required')).toBeNull()
+    expect(document.querySelectorAll('input')[3].getAttribute('readOnly')).toBeNull()
+    expect(document.querySelectorAll('input')[4].getAttribute('hidden')).toBeNull()
+    expect(document.querySelectorAll('details')[0].getAttribute('open')).toBeNull()
+    expect(document.querySelectorAll('option')[0].getAttribute('selected')).toBeNull()
+    expect(document.querySelectorAll('select')[0].getAttribute('multiple')).toBeNull()
+    expect(document.querySelectorAll('textarea')[0].getAttribute('autofocus')).toBeNull()
+    expect(document.querySelectorAll('dl')[0].getAttribute('itemscope')).toBeNull()
+    expect(document.querySelectorAll('form')[0].getAttribute('novalidate')).toBeNull()
+    expect(document.querySelectorAll('iframe')[0].getAttribute('allowfullscreen')).toBeNull()
+    expect(document.querySelectorAll('iframe')[0].getAttribute('allowpaymentrequest')).toBeNull()
+    expect(document.querySelectorAll('button')[0].getAttribute('formnovalidate')).toBeNull()
+    expect(document.querySelectorAll('audio')[0].getAttribute('autoplay')).toBeNull()
+    expect(document.querySelectorAll('audio')[0].getAttribute('controls')).toBeNull()
+    expect(document.querySelectorAll('audio')[0].getAttribute('loop')).toBeNull()
+    expect(document.querySelectorAll('audio')[0].getAttribute('muted')).toBeNull()
+    expect(document.querySelectorAll('video')[0].getAttribute('playsinline')).toBeNull()
+    expect(document.querySelectorAll('track')[0].getAttribute('default')).toBeNull()
+    expect(document.querySelectorAll('img')[0].getAttribute('ismap')).toBeNull()
+    expect(document.querySelectorAll('ol')[0].getAttribute('reversed')).toBeNull()
+    expect(document.querySelectorAll('script')[0].getAttribute('async')).toBeNull()
+    expect(document.querySelectorAll('script')[0].getAttribute('defer')).toBeNull()
+    expect(document.querySelectorAll('script')[0].getAttribute('nomodule')).toBeNull()
+})
 
+test('boolean empty string attributes are not removed', async () => {
+    document.body.innerHTML = `
+        <div x-data="{}">
+            <input x-bind:disabled="''">
+        </div>
+    `
     Alpine.start()
 
-    expect(document.querySelectorAll('input')[0].disabled).toBeTruthy()
-    expect(document.querySelectorAll('input')[1].checked).toBeTruthy()
-    expect(document.querySelectorAll('input')[2].required).toBeTruthy()
-    expect(document.querySelectorAll('input')[3].readOnly).toBeTruthy()
-    expect(document.querySelectorAll('details')[0].open).toBeTruthy()
-    expect(document.querySelectorAll('option')[0].selected).toBeTruthy()
-    expect(document.querySelectorAll('select')[0].multiple).toBeTruthy()
-    expect(document.querySelectorAll('textarea')[0].autofocus).toBeTruthy()
-    expect(document.querySelectorAll('dl')[0].attributes.itemscope).toBeTruthy()
-    expect(document.querySelectorAll('form')[0].attributes.novalidate).toBeTruthy()
-    expect(document.querySelectorAll('iframe')[0].attributes.allowfullscreen).toBeTruthy()
-    expect(document.querySelectorAll('iframe')[0].attributes.allowpaymentrequest).toBeTruthy()
-    expect(document.querySelectorAll('button')[0].attributes.formnovalidate).toBeTruthy()
-    expect(document.querySelectorAll('audio')[0].attributes.autoplay).toBeTruthy()
-    expect(document.querySelectorAll('audio')[0].attributes.controls).toBeTruthy()
-    expect(document.querySelectorAll('audio')[0].attributes.loop).toBeTruthy()
-    expect(document.querySelectorAll('audio')[0].attributes.muted).toBeTruthy()
-    expect(document.querySelectorAll('video')[0].attributes.playsinline).toBeTruthy()
-    expect(document.querySelectorAll('track')[0].attributes.default).toBeTruthy()
-    expect(document.querySelectorAll('img')[0].attributes.ismap).toBeTruthy()
-    expect(document.querySelectorAll('ol')[0].attributes.reversed).toBeTruthy()
-    expect(document.querySelectorAll('script')[0].attributes.async).toBeTruthy()
-    expect(document.querySelectorAll('script')[0].attributes.defer).toBeTruthy()
-    expect(document.querySelectorAll('script')[0].attributes.nomodule).toBeTruthy()
+    expect(document.querySelectorAll('input')[0].disabled).toEqual(true)
 })
 
 test('binding supports short syntax', async () => {