mirror of
https://gitee.com/vuejs/vue.git
synced 2024-12-03 12:38:24 +08:00
tests for async components
This commit is contained in:
parent
d869ba0550
commit
dffd4c7a0d
@ -26,6 +26,7 @@ module.exports = function (grunt) {
|
|||||||
options: {
|
options: {
|
||||||
frameworks: ['jasmine', 'commonjs'],
|
frameworks: ['jasmine', 'commonjs'],
|
||||||
files: [
|
files: [
|
||||||
|
'test/unit/lib/util.js',
|
||||||
'test/unit/lib/jquery.js',
|
'test/unit/lib/jquery.js',
|
||||||
'src/**/*.js',
|
'src/**/*.js',
|
||||||
'test/unit/specs/**/*.js'
|
'test/unit/specs/**/*.js'
|
||||||
|
@ -36,8 +36,10 @@ module.exports = {
|
|||||||
// extract inline template as a DocumentFragment
|
// extract inline template as a DocumentFragment
|
||||||
this.template = _.extractContent(this.el, true)
|
this.template = _.extractContent(this.el, true)
|
||||||
}
|
}
|
||||||
// pending callback for async component resolution
|
// component resolution related state
|
||||||
this._pendingCb = null
|
this._pendingCb =
|
||||||
|
this.ctorId =
|
||||||
|
this.Ctor = null
|
||||||
// if static, build right now.
|
// if static, build right now.
|
||||||
if (!this._isDynamicLiteral) {
|
if (!this._isDynamicLiteral) {
|
||||||
this.resolveCtor(this.expression, _.bind(function () {
|
this.resolveCtor(this.expression, _.bind(function () {
|
||||||
|
@ -106,6 +106,7 @@ module.exports = {
|
|||||||
copy._asComponent = false
|
copy._asComponent = false
|
||||||
this._linkFn = compile(this.template, copy)
|
this._linkFn = compile(this.template, copy)
|
||||||
} else {
|
} else {
|
||||||
|
this.Ctor = null
|
||||||
this.asComponent = true
|
this.asComponent = true
|
||||||
// check inline-template
|
// check inline-template
|
||||||
if (this._checkParam('inline-template') !== null) {
|
if (this._checkParam('inline-template') !== null) {
|
||||||
@ -181,6 +182,7 @@ module.exports = {
|
|||||||
'Async resolution is not supported for v-repeat ' +
|
'Async resolution is not supported for v-repeat ' +
|
||||||
'+ dynamic component. (component: ' + id + ')'
|
'+ dynamic component. (component: ' + id + ')'
|
||||||
)
|
)
|
||||||
|
return _.Vue
|
||||||
}
|
}
|
||||||
return Ctor
|
return Ctor
|
||||||
},
|
},
|
||||||
|
@ -37,10 +37,10 @@ exports._resolveComponent = function (id, cb) {
|
|||||||
if (factory.resolved) {
|
if (factory.resolved) {
|
||||||
// cached
|
// cached
|
||||||
cb(factory.resolved)
|
cb(factory.resolved)
|
||||||
} else if (factory.pending) {
|
} else if (factory.requested) {
|
||||||
factory.pendingCallbacks.push(cb)
|
factory.pendingCallbacks.push(cb)
|
||||||
} else {
|
} else {
|
||||||
factory.pending = true
|
factory.requested = true
|
||||||
var cbs = factory.pendingCallbacks = [cb]
|
var cbs = factory.pendingCallbacks = [cb]
|
||||||
factory(function resolve (res) {
|
factory(function resolve (res) {
|
||||||
if (_.isPlainObject(res)) {
|
if (_.isPlainObject(res)) {
|
||||||
@ -48,7 +48,6 @@ exports._resolveComponent = function (id, cb) {
|
|||||||
}
|
}
|
||||||
// cache resolved
|
// cache resolved
|
||||||
factory.resolved = res
|
factory.resolved = res
|
||||||
factory.pending = false
|
|
||||||
// invoke callbacks
|
// invoke callbacks
|
||||||
for (var i = 0, l = cbs.length; i < l; i++) {
|
for (var i = 0, l = cbs.length; i < l; i++) {
|
||||||
cbs[i](res)
|
cbs[i](res)
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
"casper": true,
|
"casper": true,
|
||||||
"DocumentFragment": true,
|
"DocumentFragment": true,
|
||||||
"jQuery": 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.js"></script>
|
||||||
<script src="lib/jasmine-html.js"></script>
|
<script src="lib/jasmine-html.js"></script>
|
||||||
<script src="lib/boot.js"></script>
|
<script src="lib/boot.js"></script>
|
||||||
|
<script src="lib/util.js"></script>
|
||||||
<script src="specs.js"></script>
|
<script src="specs.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
@ -1,17 +1,299 @@
|
|||||||
|
var Vue = require('../../../src/vue')
|
||||||
|
var _ = Vue.util
|
||||||
|
|
||||||
describe('Async components', function () {
|
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 () {
|
describe('v-component', function () {
|
||||||
// - normal
|
|
||||||
// - dynamic
|
it('normal', function (done) {
|
||||||
// - nested component caching
|
var vm = new Vue({
|
||||||
// - invalidate pending callback on teardown
|
el: el,
|
||||||
// - avoid duplicate requests
|
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 () {
|
describe('v-repeat', function () {
|
||||||
// - normal
|
// - normal
|
||||||
// - invalidate on teardown
|
// - invalidate on teardown
|
||||||
// - warn for dynamic
|
// - 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