Skip to content

Commit 148967f

Browse files
authored
feat: add TransitionStub component (#91)
* feat(setComputed): add setComputed method * fix: support setComputed on Vue 2.1 * feat(transitions): add TransitionStub component * fix: remove version from flow definition
1 parent de7e351 commit 148967f

File tree

5 files changed

+167
-1
lines changed

5 files changed

+167
-1
lines changed

flow/wrapper.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,5 @@ declare interface BaseWrapper { // eslint-disable-line no-undef
3333

3434
declare type WrapperOptions = { // eslint-disable-line no-undef
3535
attachedToDocument: boolean,
36-
version: number,
3736
error?: string
3837
}

src/components/TransitionStub.js

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/* eslint-disable */
2+
import { warn } from '../lib/util'
3+
4+
function getRealChild (vnode: ?VNode): ?VNode {
5+
const compOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions
6+
if (compOptions && compOptions.Ctor.options.abstract) {
7+
return getRealChild(getFirstComponentChild(compOptions.children))
8+
} else {
9+
return vnode
10+
}
11+
}
12+
13+
function getFirstComponentChild (children: ?Array<VNode>): ?VNode {
14+
if (Array.isArray(children)) {
15+
for (let i = 0; i < children.length; i++) {
16+
const c = children[i]
17+
if (c && (c.componentOptions || isAsyncPlaceholder(c))) {
18+
return c
19+
}
20+
}
21+
}
22+
}
23+
24+
function isAsyncPlaceholder (node: VNode): boolean {
25+
return node.isComment && node.asyncFactory
26+
}
27+
const camelizeRE = /-(\w)/g
28+
export const camelize = (str: string): string => {
29+
return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '')
30+
}
31+
32+
function extractTransitionData (comp: Component): Object {
33+
const data = {}
34+
const options: ComponentOptions = comp.$options
35+
// props
36+
for (const key in options.propsData) {
37+
data[key] = comp[key]
38+
}
39+
// events.
40+
// extract listeners and pass them directly to the transition methods
41+
const listeners: ?Object = options._parentListeners
42+
for (const key in listeners) {
43+
data[camelize(key)] = listeners[key]
44+
}
45+
return data
46+
}
47+
48+
function hasParentTransition (vnode: VNode): ?boolean {
49+
while ((vnode = vnode.parent)) {
50+
if (vnode.data.transition) {
51+
return true
52+
}
53+
}
54+
}
55+
56+
export default {
57+
render (h: Function) {
58+
let children: ?Array<VNode> = this.$options._renderChildren
59+
if (!children) {
60+
return
61+
}
62+
63+
// filter out text nodes (possible whitespaces)
64+
children = children.filter((c: VNode) => c.tag || isAsyncPlaceholder(c))
65+
/* istanbul ignore if */
66+
if (!children.length) {
67+
return
68+
}
69+
70+
// warn multiple elements
71+
if (children.length > 1) {
72+
warn(
73+
'<transition> can only be used on a single element. Use ' +
74+
'<transition-group> for lists.',
75+
this.$parent
76+
)
77+
}
78+
79+
const mode: string = this.mode
80+
81+
// warn invalid mode
82+
if (mode && mode !== 'in-out' && mode !== 'out-in'
83+
) {
84+
warn(
85+
'invalid <transition> mode: ' + mode,
86+
this.$parent
87+
)
88+
}
89+
90+
const rawChild: VNode = children[0]
91+
92+
// if this is a component root node and the component's
93+
// parent container node also has transition, skip.
94+
if (hasParentTransition(this.$vnode)) {
95+
return rawChild
96+
}
97+
98+
// apply transition data to child
99+
// use getRealChild() to ignore abstract components e.g. keep-alive
100+
const child: ?VNode = getRealChild(rawChild)
101+
102+
if (!child) {
103+
return rawChild
104+
}
105+
106+
(child.data || (child.data = {})).transition = extractTransitionData(this)
107+
108+
// mark v-show
109+
// so that the transition module can hand over the control to the directive
110+
if (child.data.directives && child.data.directives.some(d => d.name === 'show')) {
111+
child.data.show = true
112+
}
113+
114+
return rawChild
115+
}
116+
}

src/lib/util.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,7 @@
33
export function throwError (msg: string): void {
44
throw new Error(`[vue-test-utils]: ${msg}`)
55
}
6+
7+
export function warn (msg: string): void {
8+
console.error(`[vue-test-utils]: ${msg}`)
9+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<template>
2+
<transition>
3+
<div>{{a}}</div>
4+
</transition>
5+
</template>
6+
7+
<script>
8+
export default{
9+
name: 'component-with-transition',
10+
data: () => ({
11+
a: 'a'
12+
})
13+
}
14+
</script>
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import ComponentWithTransition from '~resources/components/component-with-transition.vue'
2+
import TransitionStub from '~src/components/TransitionStub'
3+
import mount from '~src/mount'
4+
5+
describe.only('TransitionStub', () => {
6+
it('update synchronously when used as stubs for Transition', () => {
7+
const wrapper = mount(ComponentWithTransition, {
8+
stubs: {
9+
'transition': TransitionStub
10+
}
11+
})
12+
expect(wrapper.text()).contains('a')
13+
wrapper.setData({ a: 'b' })
14+
expect(wrapper.text()).contains('b')
15+
})
16+
17+
it('logs error when has multiple children', () => {
18+
const TestComponent = {
19+
template: `
20+
<transition><div /><div /></transition>
21+
`
22+
}
23+
const msg = '[vue-test-utils]: <transition> can only be used on a single element. Use <transition-group> for lists.'
24+
const error = sinon.stub(console, 'error')
25+
mount(TestComponent, {
26+
stubs: {
27+
'transition': TransitionStub
28+
}
29+
})
30+
expect(error.args[0][0]).to.equal(msg)
31+
error.restore()
32+
})
33+
})

0 commit comments

Comments
 (0)