import { beVisible, haveText, html, test } from '../utils' test('element side effects are cleaned up after the elements are removed', html` <div x-data="{ foo: 1, bar: 1 }"> <button @click="bar++">bar</button> <a href="#" @click.prevent="$refs.span.remove()">remove</a> <span x-text="(() => { foo = foo + 1; return bar })" x-ref="span"></span> <h1 x-text="foo"></h1> <h2 x-text="bar"></h2> </div> `, ({ get }) => { get('h1').should(haveText('2')) get('h2').should(haveText('1')) get('button').click() get('h1').should(haveText('3')) get('h2').should(haveText('2')) get('a').click() get('button').click() get('h1').should(haveText('3')) get('h2').should(haveText('3')) } ) test('nested element side effects are cleaned up after the parent is removed', html` <div x-data="{ foo: 1, bar: 1 }"> <button @click="bar++">bar</button> <a href="#" @click.prevent="$refs.article.remove()">remove</a> <article x-ref="article"> <span x-text="(() => { foo = foo + 1; return bar })"></span> </article> <h1 x-text="foo"></h1> <h2 x-text="bar"></h2> </div> `, ({ get }) => { get('h1').should(haveText('2')) get('h2').should(haveText('1')) get('button').click() get('h1').should(haveText('3')) get('h2').should(haveText('2')) get('a').click() get('button').click() get('h1').should(haveText('3')) get('h2').should(haveText('3')) } ) test('element magic-based side effects are cleaned up after the element is removed', html` <div x-data="{ foo: 1, bar: 1 }"> <button @click="foo++">foo</button> <a href="#" @click.prevent="$refs.span.remove()">remove</a> <span x-init="$watch('foo', () => bar++)" x-ref="span"></span> <h1 x-text="foo"></h1> <h2 x-text="bar"></h2> </div> `, ({ get }) => { get('h1').should(haveText('1')) get('h2').should(haveText('1')) get('button').click() get('h1').should(haveText('2')) get('h2').should(haveText('2')) get('a').click() get('button').click() get('h1').should(haveText('3')) get('h2').should(haveText('2')) } ) test('can mutate directive value', html` <div x-data="{ foo: 'bar', bar: 'baz' }"> <button @click="$refs.target.setAttribute('x-text', 'bar')">change text</button> <span x-text="foo" x-ref="target"></span> </div> `, ({ get }) => { get('span').should(haveText('bar')) get('button').click() get('span').should(haveText('baz')) } ) test('can add new directive', html` <div x-data="{ foo: 'bar' }"> <button @click="$refs.target.setAttribute('x-text', 'foo')">change text</button> <span x-ref="target"></span> </div> `, ({ get }) => { get('span').should(haveText('')) get('button').click() get('span').should(haveText('bar')) } ) test('can pause and queue mutations for later resuming/flushing', html` <div x-data="{ foo: 1 }"> <button x-on:click="setTimeout(() => foo++)" x-ref="btn">foo</button> <h1 x-text="foo"></h1> <a href="#" @click="$refs.btn.removeAttribute('x-on:click')" id="remove">remove</a> <a href="#" @click="$refs.btn.setAttribute('x-on:click', 'foo++')" id="add">add</a> <a href="#" @click="Alpine.deferMutations()" id="defer">add</a> <a href="#" @click="Alpine.flushAndStopDeferringMutations()" id="flush">add</a> </div> `, ({ get }) => { get('h1').should(haveText('1')) get('button').click() get('h1').should(haveText('2')) get('#remove').click() get('button').click() get('h1').should(haveText('2')) get('#defer').click() get('#add').click() get('button').click() get('h1').should(haveText('2')) get('#flush').click() get('button').click() get('h1').should(haveText('3')) } ) test('does not initialise components twice when contained in multiple mutations', html` <div x-data="{ foo: 0, bar: 0, test() { container = document.createElement('div') this.$root.appendChild(container) alpineElement = document.createElement('span') alpineElement.setAttribute('x-data', '{init() {this.bar++}}') alpineElement.setAttribute('x-init', 'foo++') container.appendChild(alpineElement) } }"> <span id="one" x-text="foo"></span> <span id="two" x-text="bar"></span> <button @click="test">Test</button> </div> `, ({ get }) => { get('span#one').should(haveText('0')) get('span#two').should(haveText('0')) get('button').click() get('span#one').should(haveText('1')) get('span#two').should(haveText('1')) } ) test('directives keep working when node is moved into a different one', html` <div x-data="{ foo: 0, mutate() { let button = document.getElementById('one') button.remove() let container = document.createElement('p') container.appendChild(button) this.$root.appendChild(container) } }"> <button id="one" @click="foo++">increment</button> <button id="two" @click="mutate()">Mutate</button> <span x-text="foo"></span> </div> `, ({ get }) => { get('span').should(haveText('0')) get('button#one').click() get('span').should(haveText('1')) get('button#two').click() get('p').should(beVisible()) get('button#one').click() get('span').should(haveText('2')) } ) test('no side effects when directives are added to an element that is removed afterwards', html` <div x-data="{ foo: 0, mutate() { let span = document.createElement('span') span.setAttribute('x-on:keydown.a.window', 'foo = foo+1') let container = document.getElementById('container') container.appendChild(span) container.remove() } }"> <button @click="mutate()">Mutate</button> <p id="container"></p> <input type="text"> <span x-text="foo"></span> </div> `, ({ get }) => { get('span').should(haveText('0')) get('button').click() get('input').type('a') get('span').should(haveText('0')) } ) test( "previously initialized elements are not reinitialized on being moved", html` <script> let count = 0; document.addEventListener('alpine:init', () => { Alpine.directive('test', el => { if (count++ > 3) return; el.textContent = count; let wrapper = document.createElement('div'); wrapper.setAttribute('class', 'bg-blue-300 p-8'); el.parentNode.replaceChild(wrapper, el); wrapper.appendChild(el); }); }); </script> <div x-data> <div class="bg-red-300 w-32 h-32" x-test></div> </div> `, ({ get }) => { get("[x-test]").should(haveText("1")); } );