Quellcode durchsuchen

tests for async components

Evan You vor 11 Jahren
Ursprung
Commit
dffd4c7a0d

+ 1 - 0
gruntfile.js

@@ -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'

+ 4 - 2
src/directives/component.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 () {

+ 2 - 0
src/directives/repeat.js

@@ -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
   },

+ 2 - 3
src/instance/misc.js

@@ -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)

+ 2 - 1
test/.jshintrc

@@ -27,6 +27,7 @@
     "casper": true,
     "DocumentFragment": true,
     "jQuery": true,
-    "$": true
+    "$": true,
+    "hasWarned": true
   }
 }

+ 7 - 0
test/unit/lib/util.js

@@ -0,0 +1,7 @@
+var scope = typeof window === 'undefined'
+  ? global
+  : window
+
+scope.hasWarned = function (_, msg) {
+  return _.warn.calls.argsFor(0)[0].indexOf(msg) > -1
+}

+ 1 - 0
test/unit/runner.html

@@ -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>

+ 287 - 5
test/unit/specs/async_component_spec.js

@@ -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)
+    })
   })
 
 })