Bläddra i källkod

test for directive class

Evan You 11 år sedan
förälder
incheckning
73c9d99b2d

+ 3 - 4
changes.md

@@ -34,11 +34,11 @@ It's probably easy to understand why `el` and `parent` are instance only. But wh
 
 When events are used extensively for cross-vm communication, the ready hook can get kinda messy. The new `events` option is similar to its Backbone equivalent, where you can declaratiely register a bunch of event listeners.
 
-### new option: `inheritScope`.
+### new option: `isolated`.
 
-Default: `true`.
+Default: `false`.
 
-Whether to inherit parent scope data. Set it to `false` if you want to create a component that have an isolated scope of its own.
+Whether to inherit parent scope data. Set it to `true` if you want to create a component that have an isolated scope of its own. An isolated scope means you won't be able to bind to data on parent scopes in the component's template.
 
 ### removed options: `id`, `tagName`, `className`, `attributes`, `lazy`.
 
@@ -82,7 +82,6 @@ When authoring literal directives, you can now provide an `update()` function if
 ### New options
 
 - `twoWay`: indicates the directive is two-way and may write back to the model. Allows the use of `this.set(value)` inside directive functions.
-- `paramAttributes`: an Array of attribute names to extract as parameters for the directive. For example, given the option value `['my-param']` and markup `<input v-my-dir="msg" my-param="123">`, you can access `this.params['my-param']` with value `'123'` inside directive functions.
 
 ### Removed option: `isEmpty`
 

+ 3 - 3
gruntfile.js

@@ -31,11 +31,11 @@ module.exports = function (grunt) {
         frameworks: ['jasmine', 'commonjs'],
         files: [
           'src/**/*.js',
-          'test/unit/specs/*.js'
+          'test/unit/specs/**/*.js'
         ],
         preprocessors: {
           'src/**/*.js': ['commonjs'],
-          'test/unit/specs/*.js': ['commonjs']
+          'test/unit/specs/**/*.js': ['commonjs']
         },
         singleRun: true
       },
@@ -51,7 +51,7 @@ module.exports = function (grunt) {
           reporters: ['progress', 'coverage'],
           preprocessors: {
             'src/**/*.js': ['commonjs', 'coverage'],
-            'test/unit/specs/*.js': ['commonjs']
+            'test/unit/specs/**/*.js': ['commonjs']
           },
           coverageReporter: {
             reporters: [

+ 9 - 25
src/directive.js

@@ -27,42 +27,23 @@ function Directive (name, el, vm, descriptor) {
   this.arg = descriptor.arg
   this.expression = descriptor.expression
   this.filters = descriptor.filters
-
   // private
   this._locked = false
   this._bound = false
-  // init definition
-  this._initDef()
+  // init
   this._bind()
 }
 
 var p = Directive.prototype
 
 /**
- * Initialize the directive instance's definition.
- */
-
-p._initDef = function () {
-  var def = this.vm.$options.directives[this.name]
-  _.extend(this, def)
-  // init params
-  var el = this.el
-  var attrs = this.paramAttributes
-  if (attrs) {
-    var params = this.params = {}
-    attrs.forEach(function (p) {
-      params[p] = el.getAttribute(p)
-      el.removeAttribute(p)
-    })
-  }
-}
-
-/**
- * Initialize the directive, setup the watcher,
- * call definition bind() and update() if present.
+ * Initialize the directive, mixin definition properties,
+ * setup the watcher, call definition bind() and update()
+ * if present.
  */
 
 p._bind = function () {
+  _.extend(this, this.vm.$options.directives[this.name])
   this._watcherExp = this.expression
   this._checkDynamicLiteral()
   if (this.bind) {
@@ -186,7 +167,10 @@ p.set = function (value, lock) {
     }
     this._watcher.set(value)
     if (lock) {
-      _.nextTick(this._unlock, this)
+      var self = this
+      _.nextTick(function () {
+        self._locked = false        
+      })
     }
   }
 }

+ 1 - 1
src/instance/scope.js

@@ -14,7 +14,7 @@ var scopeEvents = ['set', 'mutate', 'add', 'delete']
 
 exports._initScope = function () {
   var parent = this.$parent
-  var inherit = parent && this.$options.inheritScope
+  var inherit = parent && !this.$options.isolated
   var data = this._data
   var scope = this.$scope = inherit
     ? Object.create(parent.$scope)

+ 2 - 2
src/observe/object-augmentations.js

@@ -10,7 +10,7 @@ var objectAgumentations = Object.create(Object.prototype)
  * @public
  */
 
-_.define(objectAgumentations, '$add', function (key, val) {
+_.define(objectAgumentations, '$add', function $add (key, val) {
   if (this.hasOwnProperty(key)) return
   // make sure it's defined on itself.
   _.define(this, key, val, true)
@@ -29,7 +29,7 @@ _.define(objectAgumentations, '$add', function (key, val) {
  * @public
  */
 
-_.define(objectAgumentations, '$delete', function (key) {
+_.define(objectAgumentations, '$delete', function $delete (key) {
   if (!this.hasOwnProperty(key)) return
   delete this[key]
   var ob = this.$observer

+ 5 - 6
src/vue.js

@@ -35,12 +35,11 @@ extend(Vue, require('./api/global'))
  */
 
 Vue.options = {
-  directives    : require('./directives'),
-  filters       : require('./filters'),
-  partials      : {},
-  transitions   : {},
-  components    : {},
-  inheritScope  : true
+  directives  : require('./directives'),
+  filters     : require('./filters'),
+  partials    : {},
+  transitions : {},
+  components  : {}
 }
 
 /**

+ 1 - 0
src/watcher.js

@@ -72,6 +72,7 @@ p.initDeps = function (res) {
   this.value = this.get()
   var computed = this.vm.$options.computed
   this.isComputed =
+    this.filters || // filters may access instance data
     res.computed || // inline expression
     (computed && computed[expression]) // computed property
 }

+ 149 - 0
test/unit/specs/directive_spec.js

@@ -0,0 +1,149 @@
+var Vue = require('../../../src/vue')
+var Directive = require('../../../src/directive')
+var nextTick = Vue.nextTick
+
+describe('Directive', function () {
+
+  var el = {} // simply a mock to be able to run in Node
+  var vm, def
+
+  beforeEach(function () {
+    def = {
+      bind: jasmine.createSpy('bind'),
+      update: jasmine.createSpy('update'),
+      unbind: jasmine.createSpy('unbind')
+    }
+    vm = new Vue({
+      data:{
+        a:1
+      },
+      filters: {
+        test: function (v) {
+          return v * 2
+        }
+      },
+      directives: {
+        test: def
+      }
+    })
+  })
+
+  it('normal', function (done) {
+    var d = new Directive('test', el, vm, {
+      expression: 'a',
+      arg: 'someArg',
+      filters: [{name:'test'}]
+    })
+    // properties
+    expect(d.el).toBe(el)
+    expect(d.name).toBe('test')
+    expect(d.vm).toBe(vm)
+    expect(d.arg).toBe('someArg')
+    expect(d.expression).toBe('a')
+    // init calls
+    expect(def.bind).toHaveBeenCalled()
+    expect(def.update).toHaveBeenCalledWith(2)
+    expect(d._bound).toBe(true)
+    // update
+    vm.a = 2
+    nextTick(function () {
+      expect(def.update).toHaveBeenCalledWith(4, 2)
+      // teardown
+      d._teardown()
+      expect(def.unbind).toHaveBeenCalled()
+      expect(d._bound).toBe(false)
+      expect(d._watcher.active).toBe(false)
+      done()
+    })
+  })
+
+  it('static literal', function () {
+    def.isLiteral = true
+    var d = new Directive('test', el, vm, {
+      expression: 'a'
+    })
+    expect(d._watcher).toBeUndefined()
+    expect(d.expression).toBe('a')
+    expect(d.bind).toHaveBeenCalled()
+    expect(d.update).not.toHaveBeenCalled()
+  })
+
+  it('static literal, interpolate with no update', function () {
+    def.isLiteral = true
+    delete def.update
+    var d = new Directive('test', el, vm, {
+      expression: '{{a}}'
+    })
+    expect(d._watcher).toBeUndefined()
+    expect(d.expression).toBe(1)
+    expect(d.bind).toHaveBeenCalled()
+  })
+
+  it('dynamic literal', function (done) {
+    def.isLiteral = true
+    var d = new Directive('test', el, vm, {
+      expression: '{{a}}'
+    })
+    expect(d._watcher).toBeDefined()
+    expect(d.expression).toBe(1)
+    expect(def.bind).toHaveBeenCalled()
+    expect(def.update).toHaveBeenCalledWith(1)
+    vm.a = 2
+    nextTick(function () {
+      expect(def.update).toHaveBeenCalledWith(2, 1)
+      done()
+    })
+  })
+
+  it('expression function', function () {
+    def.isFn = true
+    var d = new Directive('test', el, vm, {
+      expression: 'a++'
+    })
+    expect(d._watcher).toBeUndefined()
+    expect(d.bind).toHaveBeenCalled()
+    var wrappedFn = d.update.calls.argsFor(0)[0]
+    expect(typeof wrappedFn).toBe('function')
+    // test invoke the wrapped fn
+    wrappedFn()
+    expect(vm.a).toBe(2)
+  })
+
+  it('two-way', function (done) {
+    def.twoWay = true
+    vm.$options.filters.test = {
+      write: function (v) {
+        return v * 3
+      }
+    }
+    var d = new Directive('test', el, vm, {
+      expression: 'a',
+      filters: [{name:'test'}]
+    })
+    d.set(2)
+    expect(vm.a).toBe(6)
+    nextTick(function () {
+      expect(def.update.calls.count()).toBe(2)
+      expect(def.update).toHaveBeenCalledWith(6, 1)
+      // locked set
+      d.set(3, true)
+      expect(vm.a).toBe(9)
+      nextTick(function () {
+        // should have no update calls
+        expect(def.update.calls.count()).toBe(2)
+        done()
+      })
+    })
+  })
+
+  it('invalid dynamic literal', function () {
+    var _ = Vue.util
+    spyOn(_, 'warn')    
+    def.isLiteral = true
+    new Directive('test', el, vm, {
+      expression: 'abc {{a}}'
+    })
+    expect(_.warn).toHaveBeenCalled()
+  })
+
+})

+ 0 - 0
test/unit/specs/filters_spec.js


+ 12 - 0
test/unit/specs/parse/text_spec.js

@@ -64,4 +64,16 @@ describe('Text Parser', function () {
     expect(res1).toBe(res2)
   })
 
+  it('custom delimiters', function () {
+    config.delimiters = ['[%', '%]']
+    assertParse({
+      text: '[%* text %] and [[% html %]]',
+      expected: [
+        { tag: true, value: 'text', html: false, oneTime: true },
+        { value: ' and ' },
+        { tag: true, value: 'html', html: true, oneTime: false },
+      ]
+    })
+  })
+
 })

+ 11 - 0
test/unit/specs/util/option_spec.js

@@ -1,4 +1,5 @@
 var _ = require('../../../../src/util')
+var Vue = require('../../../../src/vue')
 var merge = require('../../../../src/util/merge-option')
 
 describe('Util - Option merging', function () {
@@ -111,6 +112,16 @@ describe('Util - Option merging', function () {
     expect(res.d).toBe(asset4)
   })
 
+  it('guard components', function () {
+    var res = merge({}, {
+      components: {
+        a: { template: 'hi' }
+      }
+    })
+    expect(typeof res.components.a).toBe('function')
+    expect(res.components.a.super).toBe(Vue)
+  })
+
   it('ignore el, data & parent when inheriting', function () {
     var res = merge({}, {el:1, data:2, parent:3})
     expect(res.el).toBeUndefined()