fix(vue-app): use child transition name when navigating to parent (#6946)

This commit is contained in:
Matteo Rigon 2020-02-11 15:06:37 +01:00 committed by GitHub
parent b05d4a74f7
commit 539c865533
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 245 additions and 19 deletions

View File

@ -111,31 +111,29 @@ function componentOption (component, key, ...args) {
return option
}
function mapTransitions (Components, to, from) {
function mapTransitions (toComponents, to, from) {
const componentTransitions = (component) => {
const transition = componentOption(component, 'transition', to, from) || {}
return (typeof transition === 'string' ? { name: transition } : transition)
}
const tComponents = [].concat(Components)
const fromComponents = from ? getMatchedComponents(from) : []
const maxDepth = Math.max(toComponents.length, fromComponents.length)
// If leaving a child route to a parent, keep the child leave transition
while (from && from.matched.length > tComponents.length) { // eslint-disable-line no-unmodified-loop-condition
tComponents.push({})
const mergedTransitions = []
for (let i=0; i<maxDepth; i++) {
// Clone original objects to prevent overrides
const toTransitions = Object.assign({}, componentTransitions(toComponents[i]))
const transitions = Object.assign({}, componentTransitions(fromComponents[i]))
// Combine transitions & prefer `leave` properties of "from" route
Object.keys(toTransitions)
.filter(key => typeof toTransitions[key] !== 'undefined' && !key.toLowerCase().includes('leave'))
.forEach((key) => { transitions[key] = toTransitions[key] })
mergedTransitions.push(transitions)
}
return tComponents.map((Component, i) => {
// Clone original object to prevent overrides
const transitions = Object.assign({}, componentTransitions(Component))
// Combine transitions & prefer `leave` transitions of 'from' route
if (from && from.matched[i] && from.matched[i].components.default) {
const fromTransitions = componentTransitions(from.matched[i].components.default)
Object.keys(fromTransitions)
.filter(key => fromTransitions[key] && key.toLowerCase().includes('leave'))
.forEach((key) => { transitions[key] = fromTransitions[key] })
}
return transitions
})
return mergedTransitions
}
<% } %>
<% if (loading) { %>async <% } %>function loadAsyncComponents (to, from, next) {

View File

@ -0,0 +1,112 @@
import Browser from '../utils/browser'
import { loadFixture, getPort, Nuxt } from '../utils'
let port
const browser = new Browser()
const url = route => 'http://localhost:' + port + route
let nuxt = null
let page = null
const parseEvents = async (page) => {
const events = await page.evaluate(() => [...document.querySelectorAll('#transition-events li')].map(li => li.textContent))
return events.map(event => event.split('|'))
}
describe('page transitions (browser)', () => {
beforeAll(async () => {
const config = await loadFixture('page-transitions')
nuxt = new Nuxt(config)
await nuxt.ready()
port = await getPort()
await nuxt.server.listen(port, 'localhost')
await browser.start({
// slowMo: 50,
// headless: false
})
})
test('Open /', async () => {
page = await browser.page(url('/'))
expect(await page.$text('h1')).toBe('Index page')
})
test('Root page callbacks', async () => {
await page.nuxt.navigate('/callbacks')
const events = await parseEvents(page)
expect(events).toEqual(
[
['index', 'beforeLeave'],
['index', 'leave'],
['index', 'afterLeave'],
['callbacks', 'beforeEnter'],
['callbacks', 'enter'],
['callbacks', 'afterEnter']
]
)
})
test('Parent -> Child page callbacks', async () => {
await page.nuxt.navigate('/callbacks/child')
const events = await parseEvents(page)
expect(events).toEqual(
[
['callbacks-child', 'beforeEnter'],
['callbacks-child', 'enter'],
['callbacks-child', 'afterEnter']
]
)
})
test('Child -> Parent page callbacks', async () => {
await page.nuxt.navigate('/callbacks')
const events = await parseEvents(page)
expect(events).toEqual(
[
['callbacks-child', 'beforeLeave'],
['callbacks-child', 'leave'],
['callbacks-child', 'afterLeave']
]
)
})
test('Root page transition properties', async () => {
await page.nuxt.navigate('/transition-properties')
const transitionsData = await page.nuxt.transitionsData()
expect(transitionsData.length).toBe(1)
expect(transitionsData[0].name).toBe('custom')
expect(transitionsData[0].appear).toBe(true)
expect(transitionsData[0].css).toBe(false)
expect(transitionsData[0].mode).toBe('in-out')
expect(transitionsData[0].duration).toBe(3000)
})
test('Parent -> child transition properties', async () => {
await page.nuxt.navigate('/transition-properties/child')
const transitionsData = await page.nuxt.transitionsData()
expect(transitionsData.length).toBe(2)
expect(transitionsData[0].name).toBe('custom')
expect(transitionsData[1].name).toBe('custom-child')
})
test('Child -> parent transition properties', async () => {
await page.nuxt.navigate('/transition-properties')
const transitionsData = await page.nuxt.transitionsData()
expect(transitionsData.length).toBe(2)
expect(transitionsData[0].name).toBe('custom')
expect(transitionsData[1].name).toBe('custom-child')
})
afterAll(async () => {
await nuxt.close()
})
// Stop browser
afterAll(async () => {
await page.close()
await browser.close()
})
})

View File

@ -0,0 +1,6 @@
<template>
<div>
<ul id="transition-events"></ul>
<Nuxt />
</div>
</template>

View File

@ -0,0 +1,3 @@
import { buildFixture } from '../../utils/build'
buildFixture('page-transitions')

View File

@ -0,0 +1,12 @@
<template>
<div>
<h1>Callbacks page</h1>
<NuxtChild />
</div>
</template>
<script>
import { createTransitionObject } from '../utils/transition-properties'
export default {
transition: createTransitionObject('callbacks')
}
</script>

View File

@ -0,0 +1,11 @@
<template>
<div>
<h1>Callbacks child page</h1>
</div>
</template>
<script>
import { createTransitionObject } from '../../utils/transition-properties'
export default {
transition: createTransitionObject('callbacks-child', 'page', 'true')
}
</script>

View File

@ -0,0 +1,11 @@
<template>
<div>
<h1>Index page</h1>
</div>
</template>
<script>
import { createTransitionObject } from '../utils/transition-properties'
export default {
transition: createTransitionObject('index')
}
</script>

View File

@ -0,0 +1,18 @@
<template>
<div>
<h1>Transition properties page</h1>
<NuxtChild />
</div>
</template>
<script>
export default {
transition: {
name: 'custom',
appear: true,
css: false,
mode: 'in-out',
duration: 3000
}
}
</script>

View File

@ -0,0 +1,18 @@
<template>
<div>
<h1>Transition name child page</h1>
</div>
</template>
<script>
export default {
transition: {
name: 'custom-child'
}
}
</script>
<style>
.custom-child-enter-active, .custom-child-leave-active {
transition: opacity 1s ease;
}
</style>

View File

@ -0,0 +1,34 @@
const addEvent = (componentName, callbackName, clear = false) => {
const ul = document.querySelector('#transition-events')
if (clear) {
ul.innerHTML = ''
}
const li = document.createElement('li')
li.textContent = `${componentName}|${callbackName}`
ul.appendChild(li)
}
export const createTransitionObject = (componentName, transitionName = 'page', child = false) => ({
name: transitionName,
beforeEnter () {
addEvent(componentName, 'beforeEnter', child)
},
enter (el, done) {
addEvent(componentName, 'enter')
done()
},
afterEnter () {
addEvent(componentName, 'afterEnter')
},
beforeLeave () {
addEvent(componentName, 'beforeLeave', true)
},
leave (el, done) {
addEvent(componentName, 'leave')
done()
},
afterLeave () {
addEvent(componentName, 'afterLeave')
}
})

View File

@ -105,6 +105,9 @@ export default class Browser {
},
storeState () {
return page.evaluate($nuxt => $nuxt.$store.state, page.$nuxt)
},
transitionsData () {
return page.evaluate($nuxt => $nuxt.nuxt.transitions, page.$nuxt)
}
}
return page