mirror of
https://gitee.com/vuejs/vue.git
synced 2024-12-02 03:57:36 +08:00
tests for async components
This commit is contained in:
parent
d869ba0550
commit
dffd4c7a0d
@ -26,6 +26,7 @@ module.exports = function (grunt) {
|
||||
options: {
|
||||
frameworks: ['jasmine', 'commonjs'],
|
||||
files: [
|
||||
'test/unit/lib/util.js',
|
||||
'test/unit/lib/jquery.js',
|
||||
'src/**/*.js',
|
||||
'test/unit/specs/**/*.js'
|
||||
|
@ -36,8 +36,10 @@ module.exports = {
|
||||
// extract inline template as a DocumentFragment
|
||||
this.template = _.extractContent(this.el, true)
|
||||
}
|
||||
// pending callback for async component resolution
|
||||
this._pendingCb = null
|
||||
// component resolution related state
|
||||
this._pendingCb =
|
||||
this.ctorId =
|
||||
this.Ctor = null
|
||||
// if static, build right now.
|
||||
if (!this._isDynamicLiteral) {
|
||||
this.resolveCtor(this.expression, _.bind(function () {
|
||||
|
@ -106,6 +106,7 @@ module.exports = {
|
||||
copy._asComponent = false
|
||||
this._linkFn = compile(this.template, copy)
|
||||
} else {
|
||||
this.Ctor = null
|
||||
this.asComponent = true
|
||||
// check inline-template
|
||||
if (this._checkParam('inline-template') !== null) {
|
||||
@ -181,6 +182,7 @@ module.exports = {
|
||||
'Async resolution is not supported for v-repeat ' +
|
||||
'+ dynamic component. (component: ' + id + ')'
|
||||
)
|
||||
return _.Vue
|
||||
}
|
||||
return Ctor
|
||||
},
|
||||
|
@ -37,10 +37,10 @@ exports._resolveComponent = function (id, cb) {
|
||||
if (factory.resolved) {
|
||||
// cached
|
||||
cb(factory.resolved)
|
||||
} else if (factory.pending) {
|
||||
} else if (factory.requested) {
|
||||
factory.pendingCallbacks.push(cb)
|
||||
} else {
|
||||
factory.pending = true
|
||||
factory.requested = true
|
||||
var cbs = factory.pendingCallbacks = [cb]
|
||||
factory(function resolve (res) {
|
||||
if (_.isPlainObject(res)) {
|
||||
@ -48,7 +48,6 @@ exports._resolveComponent = function (id, cb) {
|
||||
}
|
||||
// cache resolved
|
||||
factory.resolved = res
|
||||
factory.pending = false
|
||||
// invoke callbacks
|
||||
for (var i = 0, l = cbs.length; i < l; i++) {
|
||||
cbs[i](res)
|
||||
|
@ -27,6 +27,7 @@
|
||||
"casper": true,
|
||||
"DocumentFragment": true,
|
||||
"jQuery": true,
|
||||
"$": true
|
||||
"$": true,
|
||||
"hasWarned": true
|
||||
}
|
||||
}
|
7
test/unit/lib/util.js
Normal file
7
test/unit/lib/util.js
Normal file
@ -0,0 +1,7 @@
|
||||
var scope = typeof window === 'undefined'
|
||||
? global
|
||||
: window
|
||||
|
||||
scope.hasWarned = function (_, msg) {
|
||||
return _.warn.calls.argsFor(0)[0].indexOf(msg) > -1
|
||||
}
|
@ -9,6 +9,7 @@
|
||||
<script src="lib/jasmine.js"></script>
|
||||
<script src="lib/jasmine-html.js"></script>
|
||||
<script src="lib/boot.js"></script>
|
||||
<script src="lib/util.js"></script>
|
||||
<script src="specs.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
@ -1,17 +1,299 @@
|
||||
var Vue = require('../../../src/vue')
|
||||
var _ = Vue.util
|
||||
|
||||
describe('Async components', function () {
|
||||
|
||||
var el
|
||||
beforeEach(function () {
|
||||
el = document.createElement('div')
|
||||
document.body.appendChild(el)
|
||||
spyOn(_, 'warn')
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
document.body.removeChild(el)
|
||||
})
|
||||
|
||||
describe('v-component', function () {
|
||||
// - normal
|
||||
// - dynamic
|
||||
// - nested component caching
|
||||
// - invalidate pending callback on teardown
|
||||
// - avoid duplicate requests
|
||||
|
||||
it('normal', function (done) {
|
||||
var vm = new Vue({
|
||||
el: el,
|
||||
template: '<div v-component="test"></div>',
|
||||
components: {
|
||||
test: function (resolve) {
|
||||
setTimeout(function () {
|
||||
resolve({
|
||||
template: 'ok'
|
||||
})
|
||||
next()
|
||||
}, 0)
|
||||
}
|
||||
}
|
||||
})
|
||||
function next () {
|
||||
expect(el.textContent).toBe('ok')
|
||||
done()
|
||||
}
|
||||
})
|
||||
|
||||
it('dynamic', function (done) {
|
||||
var vm = new Vue({
|
||||
el: el,
|
||||
template: '<div v-component="{{view}}"></div>',
|
||||
data: {
|
||||
view: 'a'
|
||||
},
|
||||
components: {
|
||||
a: function (resolve) {
|
||||
setTimeout(function () {
|
||||
resolve({
|
||||
template: 'A'
|
||||
})
|
||||
step1()
|
||||
}, 0)
|
||||
},
|
||||
b: function (resolve) {
|
||||
setTimeout(function () {
|
||||
resolve({
|
||||
template: 'B'
|
||||
})
|
||||
step2()
|
||||
}, 0)
|
||||
}
|
||||
}
|
||||
})
|
||||
var aCalled = false
|
||||
function step1 () {
|
||||
// ensure A is resolved only once
|
||||
expect(aCalled).toBe(false)
|
||||
aCalled = true
|
||||
expect(el.textContent).toBe('A')
|
||||
vm.view = 'b'
|
||||
}
|
||||
function step2 () {
|
||||
expect(el.textContent).toBe('B')
|
||||
vm.view = 'a'
|
||||
_.nextTick(function () {
|
||||
expect(el.textContent).toBe('A')
|
||||
done()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
it('invalidate pending on dynamic switch', function (done) {
|
||||
var vm = new Vue({
|
||||
el: el,
|
||||
template: '<div v-component="{{view}}"></div>',
|
||||
data: {
|
||||
view: 'a'
|
||||
},
|
||||
components: {
|
||||
a: function (resolve) {
|
||||
setTimeout(function () {
|
||||
resolve({
|
||||
template: 'A'
|
||||
})
|
||||
step1()
|
||||
}, 100)
|
||||
},
|
||||
b: function (resolve) {
|
||||
setTimeout(function () {
|
||||
resolve({
|
||||
template: 'B'
|
||||
})
|
||||
step2()
|
||||
}, 200)
|
||||
}
|
||||
}
|
||||
})
|
||||
expect(el.textContent).toBe('')
|
||||
vm.view = 'b'
|
||||
function step1 () {
|
||||
// called after A resolves, but A should have been
|
||||
// invalidated so not cotrId should be set
|
||||
expect(vm._directives[0].ctorId).toBe(null)
|
||||
}
|
||||
function step2 () {
|
||||
// B should resolve successfully
|
||||
expect(el.textContent).toBe('B')
|
||||
done()
|
||||
}
|
||||
})
|
||||
|
||||
it('invalidate pending on teardown', function (done) {
|
||||
var vm = new Vue({
|
||||
el: el,
|
||||
template: '<div v-component="a"></div>',
|
||||
data: {
|
||||
view: 'a'
|
||||
},
|
||||
components: {
|
||||
a: function (resolve) {
|
||||
setTimeout(function () {
|
||||
resolve({
|
||||
template: 'A'
|
||||
})
|
||||
next()
|
||||
}, 100)
|
||||
}
|
||||
}
|
||||
})
|
||||
expect(el.textContent).toBe('')
|
||||
// cache directive isntance before destroy
|
||||
var dir = vm._directives[0]
|
||||
vm.$destroy()
|
||||
function next () {
|
||||
// called after A resolves, but A should have been
|
||||
// invalidated so not cotrId should be set
|
||||
expect(dir.ctorId).toBe(null)
|
||||
done()
|
||||
}
|
||||
})
|
||||
|
||||
it('avoid duplicate requests', function (done) {
|
||||
var factoryCallCount = 0
|
||||
var instanceCount = 0
|
||||
var vm = new Vue({
|
||||
el: el,
|
||||
template:
|
||||
'<div v-component="a"></div>' +
|
||||
'<div v-component="a"></div>',
|
||||
components: {
|
||||
a: factory
|
||||
}
|
||||
})
|
||||
function factory (resolve) {
|
||||
factoryCallCount++
|
||||
setTimeout(function () {
|
||||
resolve({
|
||||
template: 'A',
|
||||
created: function () {
|
||||
instanceCount++
|
||||
}
|
||||
})
|
||||
next()
|
||||
}, 0)
|
||||
}
|
||||
function next () {
|
||||
expect(factoryCallCount).toBe(1)
|
||||
expect(el.textContent).toBe('AA')
|
||||
expect(instanceCount).toBe(2)
|
||||
done()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('v-repeat', function () {
|
||||
// - normal
|
||||
// - invalidate on teardown
|
||||
// - warn for dynamic
|
||||
|
||||
it('normal', function (done) {
|
||||
var vm = new Vue({
|
||||
el: el,
|
||||
template: '<div v-repeat="list" v-component="test"></div>',
|
||||
data: {
|
||||
list: [1, 2, 3]
|
||||
},
|
||||
components: {
|
||||
test: function (resolve) {
|
||||
setTimeout(function () {
|
||||
resolve({
|
||||
template: '{{$value}}'
|
||||
})
|
||||
next()
|
||||
}, 0)
|
||||
}
|
||||
}
|
||||
})
|
||||
function next () {
|
||||
expect(el.textContent).toBe('123')
|
||||
done()
|
||||
}
|
||||
})
|
||||
|
||||
it('only resolve once', function (done) {
|
||||
var vm = new Vue({
|
||||
el: el,
|
||||
template: '<div v-repeat="list" v-component="test"></div>',
|
||||
data: {
|
||||
list: [1, 2, 3]
|
||||
},
|
||||
components: {
|
||||
test: function (resolve) {
|
||||
setTimeout(function () {
|
||||
resolve({
|
||||
template: '{{$value}}'
|
||||
})
|
||||
next()
|
||||
}, 100)
|
||||
}
|
||||
}
|
||||
})
|
||||
// hijack realUpdate - this should only be called once
|
||||
// spyOn doesn't work here
|
||||
var update = vm._directives[0].realUpdate
|
||||
var callCount = 0
|
||||
vm._directives[0].realUpdate = function () {
|
||||
callCount++
|
||||
update.apply(this, arguments)
|
||||
}
|
||||
vm.list = [2, 3, 4]
|
||||
function next () {
|
||||
expect(el.textContent).toBe('234')
|
||||
expect(callCount).toBe(1)
|
||||
done()
|
||||
}
|
||||
})
|
||||
|
||||
it('invalidate on teardown', function (done) {
|
||||
var vm = new Vue({
|
||||
el: el,
|
||||
template: '<div v-repeat="list" v-component="test"></div>',
|
||||
data: {
|
||||
list: [1, 2, 3]
|
||||
},
|
||||
components: {
|
||||
test: function (resolve) {
|
||||
setTimeout(function () {
|
||||
resolve({
|
||||
template: '{{$value}}'
|
||||
})
|
||||
next()
|
||||
}, 0)
|
||||
}
|
||||
}
|
||||
})
|
||||
var dir = vm._directives[0]
|
||||
vm.$destroy()
|
||||
function next () {
|
||||
expect(el.textContent).toBe('')
|
||||
expect(dir.Ctor).toBe(null)
|
||||
done()
|
||||
}
|
||||
})
|
||||
|
||||
it('warn when used with dynamic v-repeat', function () {
|
||||
var vm = new Vue({
|
||||
el: el,
|
||||
template: '<div v-repeat="list" v-component="{{c}}"></div>',
|
||||
data: {
|
||||
list: [1, 2, 3],
|
||||
c: 'test'
|
||||
},
|
||||
components: {
|
||||
test: function (resolve) {
|
||||
setTimeout(function () {
|
||||
resolve({
|
||||
template: '{{$value}}'
|
||||
})
|
||||
}, 0)
|
||||
}
|
||||
}
|
||||
})
|
||||
expect(hasWarned(_, 'Async resolution is not supported')).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
})
|
Loading…
Reference in New Issue
Block a user