1
0
Эх сурвалжийг харах

Rework tests and finalize code for Alpine.morph

Caleb Porzio 3 жил өмнө
parent
commit
984aa4d147

+ 4 - 3
packages/alpinejs/src/alpine.js

@@ -3,17 +3,17 @@ import { mutateDom, deferMutations, flushAndStopDeferringMutations } from './mut
 import { mapAttributes, directive, setPrefix as prefix } from './directives'
 import { start, addRootSelector, closestRoot, initTree } from './lifecycle'
 import { setEvaluator, evaluate, evaluateLater } from './evaluator'
+import { mergeProxies, closestDataStack } from './scope'
 import { transition } from './directives/x-transition'
+import { clone, skipDuringClone } from './clone'
 import { interceptor } from './interceptor'
-import { setStyles } from './utils/styles'
 import { debounce } from './utils/debounce'
 import { throttle } from './utils/throttle'
-import { mergeProxies } from './scope'
+import { setStyles } from './utils/styles'
 import { nextTick } from './nextTick'
 import { plugin } from './plugin'
 import { magic } from './magics'
 import { store } from './store'
-import { clone, skipDuringClone } from './clone'
 import { data } from './datas'
 
 let Alpine = {
@@ -25,6 +25,7 @@ let Alpine = {
     flushAndStopDeferringMutations,
     disableEffectScheduling,
     setReactivityEngine,
+    closestDataStack,
     skipDuringClone,
     addRootSelector,
     deferMutations,

+ 1 - 5
packages/alpinejs/src/clone.js

@@ -12,16 +12,12 @@ export function onlyDuringClone(callback) {
     return (...args) => isCloning && callback(...args)
 }
 
-export function skipWalkingSubClone(callback) {
-    return (...args) => isCloning || callback(...args)
-}
-
 export function interuptCrawl(callback) {
     return (...args) => isCloning || callback(...args)
 }
 
 export function clone(oldEl, newEl) {
-    newEl._x_dataStack = oldEl._x_dataStack
+    if (! newEl._x_dataStack) newEl._x_dataStack = oldEl._x_dataStack
 
     isCloning = true
 

+ 2 - 0
packages/morph/src/index.js

@@ -1,6 +1,8 @@
 import { morph } from './morph'
 
 export default function (Alpine) {
+    Alpine.morph = morph
+    
     Alpine.directive('morph', (el, { expression }, { effect, evaluateLater }) => {
         let evaluate = evaluateLater(expression)
 

+ 13 - 3
packages/morph/src/morph.js

@@ -1,8 +1,18 @@
 
 export function morph(dom, toHtml, options) {
     assignOptions(options)
+    
+    let toEl = createElement(toHtml)
+
+    // If there is no x-data on the element we're morphing,
+    // let's seed it with the outer Alpine scope on the page.
+    if (window.Alpine && ! dom._x_dataStack) {
+        toEl._x_dataStack = window.Alpine.closestDataStack(dom)
+        
+        toEl._x_dataStack && window.Alpine.clone(dom, toEl)
+    }
 
-    patch(dom, createElement(toHtml))
+    patch(dom, toEl)
 
     return dom
 }
@@ -36,7 +46,7 @@ function createElement(html) {
 }
 
 function patch(dom, to) {
-    if (dom.isEqualNode(to)) return
+   if (dom.isEqualNode(to)) return
 
     if (differentElementNamesTypesOrKeys(dom, to)) {
         return patchElement(dom, to)
@@ -46,7 +56,7 @@ function patch(dom, to) {
 
     if (shouldSkip(updating, dom, to, () => updateChildrenOnly = true)) return
 
-    // window.Alpine && initializeAlpineOnTo(dom, to, () => updateChildrenOnly = true)
+    window.Alpine && initializeAlpineOnTo(dom, to, () => updateChildrenOnly = true)
 
     if (textOrComment(to)) {
         patchNodeValue(dom, to)

+ 74 - 45
tests/cypress/integration/plugins/morph.spec.js

@@ -1,65 +1,94 @@
 import { haveText, html, test } from '../../utils'
 
-test('can morph components',
+test('can morph components and preserve Alpine state',
     [html`
-        <div x-data="{ frame: 0 }">
-            <template x-ref="0">
-                <h1><div></div>foo</h1>
-            </template>
-
-            <template x-ref="1">
-                <h1><div x-data="{ text: 'yo' }" x-text="text"></div> foo</h1>
-            </template>
+        <div x-data="{ foo: 'bar' }">
+            <button @click="foo = 'baz'">Change Foo</button>
+            <span x-text="foo"></span>
+        </div>
+    `],
+    ({ get }, reload, window, document) => {
+        let toHtml = document.querySelector('div').outerHTML
 
-            <template x-ref="2">
-                <h1><div x-data="{ text: 'yo' }" x-text="text + 'yo'"></div> foo</h1>
-            </template>
+        get('span').should(haveText('bar'))
+        get('button').click()
+        get('span').should(haveText('baz'))
+        
+        get('div').then(([el]) => window.Alpine.morph(el, toHtml))
 
-            <button @click="frame++">morph</button>
+        get('span').should(haveText('baz'))
+    },
+)
 
-            <article x-morph="$refs[frame % 3].innerHTML"></article>
-        </div>
+test('morphing target uses outer Alpine scope',
+    [html`
+        <article x-data="{ foo: 'bar' }">
+            <div>
+                <button @click="foo = 'baz'">Change Foo</button>
+                <span x-text="foo"></span>
+            </div>
+        </article>
     `],
-    ({ get }) => {
-        get('article h1').should(haveText('foo'))
-        get('button').click()
-        get('article h1').should(haveText('yo foo'))
+    ({ get }, reload, window, document) => {
+        let toHtml = document.querySelector('div').outerHTML
+
+        get('span').should(haveText('bar'))
         get('button').click()
-        get('article h1').should(haveText('yoyo foo'))
+        get('span').should(haveText('baz'))
+        
+        get('div').then(([el]) => window.Alpine.morph(el, toHtml))
+
+        get('span').should(haveText('baz'))
     },
 )
 
-test('components within morph retain state between',
+test('can morph with HTML change and preserve Alpine state',
     [html`
-        <div x-data="{ frame: 0 }">
-            <template x-ref="0">
-                <div x-data="{ count: 1 }">
-                    <button @click="count++">inc</button>
-
-                    <span x-text="String(frame) + count"></span>
-                </div>
-            </template>
+        <div x-data="{ foo: 'bar' }">
+            <button @click="foo = 'baz'">Change Foo</button>
+            <span x-text="foo"></span>
+        </div>
+    `],
+    ({ get }, reload, window, document) => {
+        let toHtml = document.querySelector('div').outerHTML.replace('Change Foo', 'Changed Foo')
 
-            <template x-ref="1">
-                <div x-data="{ count: 1 }">
-                    <button @click="count++">inc</button>
+        get('span').should(haveText('bar'))
+        get('button').click()
+        get('span').should(haveText('baz'))
+        get('button').should(haveText('Change Foo'))
 
-                    <span x-text="String(frame) + 'foo' + count"></span>
-                </div>
-            </template>
+        get('div').then(([el]) => window.Alpine.morph(el, toHtml))
+        
+        get('span').should(haveText('baz'))
+        get('button').should(haveText('Changed Foo'))
+    },
+)
 
-            <button @click="frame++" id="morph">morph</button>
+test('morphing an element with multiple nested Alpine components preserves scope',
+    [html`
+        <div x-data="{ foo: 'bar' }">
+            <button @click="foo = 'baz'">Change Foo</button>
+            <span x-text="foo"></span>
 
-            <article x-morph="$refs[frame % 2].innerHTML"></article>
+            <div x-data="{ bob: 'lob' }">
+                <a href="#" @click.prevent="bob = 'law'">Change Bob</a>
+                <h1 x-text="bob"></h1>
+            </div>
         </div>
     `],
-    ({ get }) => {
-        get('article span').should(haveText('01'))
-        get('article button').click()
-        get('article span').should(haveText('02'))
-        get('#morph').click()
-        get('article span').should(haveText('1foo2'))
-        get('article button').click()
-        get('article span').should(haveText('1foo3'))
+    ({ get }, reload, window, document) => {
+        let toHtml = document.querySelector('div').outerHTML
+
+        get('span').should(haveText('bar'))
+        get('h1').should(haveText('lob'))
+        get('button').click()
+        get('a').click()
+        get('span').should(haveText('baz'))
+        get('h1').should(haveText('law'))
+
+        get('div').then(([el]) => window.Alpine.morph(el, toHtml))
+        
+        get('span').should(haveText('baz'))
+        get('h1').should(haveText('law'))
     },
 )

+ 3 - 1
tests/cypress/utils.js

@@ -75,7 +75,9 @@ function injectHtmlAndBootAlpine(cy, templateAndPotentiallyScripts, callback, pa
             })
         }
 
-        callback(cy, reload)
+        cy.window().then(window => {
+            callback(cy, reload, window, window.document)
+        }) 
     })
 }
 

+ 0 - 102
tests/jest/morph/alpine-scope.spec.js

@@ -1,102 +0,0 @@
-let { morph } = require('@alpinejs/morph')
-let Alpine = require('alpinejs').default
-let createElement = require('./createElement.js')
-
-test('morphing an element with changed Alpine scope', () => {
-    let template = `<div x-data="{ foo: 'bar' }">
-        <button @click="foo = 'baz'">Change Foo</button>
-        <span x-text="foo"></span>
-    </div>`
-
-    let dom = createElement(template)
-
-    document.body.appendChild(dom)
-
-    window.Alpine = Alpine
-    window.Alpine.start()
-
-    dom.querySelector('button').click()
-
-    expect(dom.querySelector('span').textContent).toEqual('baz')
-
-    morph(dom, template)
-
-    expect(dom.querySelector('span').textContent).toEqual('baz')
-})
-
-test('morphing element with changed HTML AND Alpine scope', () => {
-    let template = `<div x-data="{ foo: 'bar' }">
-        <button @click="foo = 'baz'">Change Foo</button>
-        <span x-text="foo"></span>
-    </div>`
-
-    let dom = createElement(template)
-
-    document.body.appendChild(dom)
-
-    window.Alpine = Alpine
-    window.Alpine.start()
-
-    dom.querySelector('button').click()
-
-    expect(dom.querySelector('span').textContent).toEqual('baz')
-    expect(dom.querySelector('button').textContent).toEqual('Change Foo')
-
-    morph(dom, template.replace('Change Foo', 'Changed Foo'))
-
-    expect(dom.querySelector('span').textContent).toEqual('baz')
-    expect(dom.querySelector('button').textContent).toEqual('Changed Foo')
-})
-
-test('morphing an element with multiple nested Alpine components preserves scope', () => {
-    let template = `<div x-data="{ foo: 'bar' }">
-        <button @click="foo = 'baz'">Change Foo</button>
-        <span x-text="foo"></span>
-
-        <div x-data="{ bob: 'lob' }">
-            <a href="#" @click.prevent="bob = 'law'">Change Bob</a>
-            <h1 x-text="bob"></h1>
-        </div>
-    </div>`
-
-    let dom = createElement(template)
-
-    document.body.appendChild(dom)
-
-    window.Alpine = Alpine
-    window.Alpine.start()
-
-    dom.querySelector('button').click()
-    dom.querySelector('a').click()
-
-    expect(dom.querySelector('span').textContent).toEqual('baz')
-    expect(dom.querySelector('h1').textContent).toEqual('law')
-
-    morph(dom, template)
-
-    expect(dom.querySelector('span').textContent).toEqual('baz')
-    expect(dom.querySelector('h1').textContent).toEqual('law')
-})
-
-test('morphing an alpine component with static javascript re-evaluates', () => {
-    window.count = 1
-
-    let template = `<div x-data>
-        <span x-text="window.count"></span>
-    </div>`
-
-    let dom = createElement(template)
-
-    document.body.appendChild(dom)
-
-    window.Alpine = Alpine
-    window.Alpine.start()
-
-    expect(dom.querySelector('span').textContent).toEqual('1')
-
-    window.count++
-
-    morph(dom, template)
-
-    expect(dom.querySelector('span').textContent).toEqual('2')
-})