Selaa lähdekoodia

compiler clean up, vm & partial API, jshint test files

Evan You 12 vuotta sitten
vanhempi
commit
1e827e87aa

+ 7 - 1
Gruntfile.js

@@ -31,7 +31,13 @@ module.exports = function( grunt ) {
             build: {
                 src: ['src/**/*.js'],
                 options: {
-                    jshintrc: "./.jshintrc"
+                    jshintrc: './.jshintrc'
+                }
+            },
+            test: {
+                src: ['test/e2e/**/*.js', 'test/unit/**/*.js'],
+                options: {
+                    jshintrc: 'test/.jshintrc'
                 }
             }
         },

+ 88 - 56
src/compiler.js

@@ -9,7 +9,9 @@ var Emitter     = require('./emitter'),
     ExpParser   = require('./exp-parser'),
     slice       = Array.prototype.slice,
     vmAttr,
-    eachAttr
+    eachAttr,
+    partialAttr,
+    transitionAttr
 
 /*
  *  The DOM compiler
@@ -17,9 +19,7 @@ var Emitter     = require('./emitter'),
  */
 function Compiler (vm, options) {
 
-    // need to refresh this everytime we compile
-    eachAttr = config.prefix + '-each'
-    vmAttr   = config.prefix + '-viewmodel'
+    refreshPrefix()
 
     options = this.options = options || {}
     utils.extend(this, options.compilerOptions || {})
@@ -67,18 +67,21 @@ function Compiler (vm, options) {
     vm.$parent   = options.parentCompiler && options.parentCompiler.vm
 
     // now for the compiler itself...
-    this.vm         = vm
-    this.el         = el
-    this.directives = []
-    // anonymous expression bindings that needs to be unbound during destroy()
-    this.expressions = []
+    this.vm   = vm
+    this.el   = el
+    this.dirs = []
+    // keep track of anonymous expression bindings
+    // that needs to be unbound during destroy()
+    this.exps = []
 
     // Store things during parsing to be processed afterwards,
     // because we want to have created all bindings before
     // observing values / parsing dependencies.
     var observables = this.observables = []
-    var computed = this.computed = [] // computed props to parse deps from
-    var ctxBindings = this.contextBindings = [] // computed props with dynamic context
+    // computed props to parse deps from
+    var computed    = this.computed    = []
+    // computed props with dynamic context
+    var ctxBindings = this.ctxBindings = []
 
     // prototypal inheritance of bindings
     var parent = this.parentCompiler
@@ -111,7 +114,7 @@ function Compiler (vm, options) {
 
     // now parse the DOM, during which we will create necessary bindings
     // and bind the parsed directives
-    this.compileNode(this.el, true)
+    this.compile(this.el, true)
 
     // observe root values so that they emit events when
     // their nested values change (for an Object)
@@ -126,7 +129,7 @@ function Compiler (vm, options) {
     // extract dependencies for computed properties with dynamic context
     if (ctxBindings.length) this.bindContexts(ctxBindings)
     // unset these no longer needed stuff
-    this.observables = this.computed = this.contextBindings = this.arrays = null
+    this.observables = this.computed = this.ctxBindings = this.arrays = null
 }
 
 var CompilerProto = Compiler.prototype
@@ -166,9 +169,9 @@ CompilerProto.setupObserver = function () {
 /*
  *  Compile a DOM node (recursive)
  */
-CompilerProto.compileNode = function (node, root) {
+CompilerProto.compile = function (node, root) {
 
-    var compiler = this, i, j
+    var compiler = this
 
     if (node.nodeType === 3) { // text node
 
@@ -176,13 +179,14 @@ CompilerProto.compileNode = function (node, root) {
 
     } else if (node.nodeType === 1) {
 
-        var eachExp = node.getAttribute(eachAttr),
-            vmExp   = node.getAttribute(vmAttr),
-            directive
+        var opts       = compiler.options,
+            eachExp    = node.getAttribute(eachAttr),
+            vmExp      = node.getAttribute(vmAttr),
+            partialExp = node.getAttribute(partialAttr)
 
         if (eachExp) { // each block
 
-            directive = Directive.parse(eachAttr, eachExp, compiler, node)
+            var directive = Directive.parse(eachAttr, eachExp, compiler, node)
             if (directive) {
                 compiler.bindDirective(directive)
             }
@@ -190,8 +194,7 @@ CompilerProto.compileNode = function (node, root) {
         } else if (vmExp && !root) { // nested ViewModels
 
             node.removeAttribute(vmAttr)
-            var opts = compiler.options,
-                ChildVM = (opts.vms && opts.vms[vmExp]) || utils.vms[vmExp]
+            var ChildVM = (opts.vms && opts.vms[vmExp]) || utils.vms[vmExp]
             if (ChildVM) {
                 new ChildVM({
                     el: node,
@@ -204,36 +207,54 @@ CompilerProto.compileNode = function (node, root) {
 
         } else { // normal node
 
-            // parse if has attributes
-            if (node.attributes && node.attributes.length) {
-                var attrs = slice.call(node.attributes),
-                    attr, valid, exps, exp
-                i = attrs.length
-                while (i--) {
-                    attr = attrs[i]
-                    if (attr.name === vmAttr) continue
-                    valid = false
-                    exps = attr.value.split(',')
-                    j = exps.length
-                    while (j--) {
-                        exp = exps[j]
-                        directive = Directive.parse(attr.name, exp, compiler, node)
-                        if (directive) {
-                            valid = true
-                            compiler.bindDirective(directive)
-                        }
-                    }
-                    if (valid) node.removeAttribute(attr.name)
+            if (partialExp) { // set partial
+                var partial =
+                    (opts.partials && opts.partials[partialExp]) ||
+                    utils.partials[partialExp]
+                if (partial) {
+                    node.innerHTML = ''
+                    node.appendChild(partial.cloneNode(true))
                 }
             }
 
-            // recursively compile childNodes
-            if (node.childNodes.length) {
-                var nodes = slice.call(node.childNodes)
-                for (i = 0, j = nodes.length; i < j; i++) {
-                    this.compileNode(nodes[i])
+            this.compileNode(node)
+
+        }
+    }
+}
+
+/*
+ *  Compile a normal node
+ */
+CompilerProto.compileNode = function (node) {
+    var i, j
+    // parse if has attributes
+    if (node.attributes && node.attributes.length) {
+        var attrs = slice.call(node.attributes),
+            attr, valid, exps, exp
+        i = attrs.length
+        while (i--) {
+            attr = attrs[i]
+            if (attr.name === vmAttr) continue
+            valid = false
+            exps = attr.value.split(',')
+            j = exps.length
+            while (j--) {
+                exp = exps[j]
+                var directive = Directive.parse(attr.name, exp, this, node)
+                if (directive) {
+                    valid = true
+                    this.bindDirective(directive)
                 }
             }
+            if (valid) node.removeAttribute(attr.name)
+        }
+    }
+    // recursively compile childNodes
+    if (node.childNodes.length) {
+        var nodes = slice.call(node.childNodes)
+        for (i = 0, j = nodes.length; i < j; i++) {
+            this.compile(nodes[i])
         }
     }
 }
@@ -244,16 +265,15 @@ CompilerProto.compileNode = function (node, root) {
 CompilerProto.compileTextNode = function (node) {
     var tokens = TextParser.parse(node.nodeValue)
     if (!tokens) return
-    var compiler = this,
-        dirname = config.prefix + '-text',
+    var dirname = config.prefix + '-text',
         el, token, directive
     for (var i = 0, l = tokens.length; i < l; i++) {
         token = tokens[i]
         el = document.createTextNode('')
         if (token.key) {
-            directive = Directive.parse(dirname, token.key, compiler, el)
+            directive = Directive.parse(dirname, token.key, this, el)
             if (directive) {
-                compiler.bindDirective(directive)
+                this.bindDirective(directive)
             }
         } else {
             el.nodeValue = token
@@ -268,7 +288,7 @@ CompilerProto.compileTextNode = function (node) {
  */
 CompilerProto.bindDirective = function (directive) {
 
-    this.directives.push(directive)
+    this.dirs.push(directive)
 
     var key = directive.key,
         baseKey = key.split('.')[0],
@@ -334,7 +354,7 @@ CompilerProto.createBinding = function (key, isExp) {
             utils.log('  created anonymous binding: ' + key)
             binding.value = { get: result.getter }
             this.markComputed(binding)
-            this.expressions.push(binding)
+            this.exps.push(binding)
             // need to create the bindings for keys
             // that do not exist yet
             var i = result.vars.length, v
@@ -497,10 +517,10 @@ CompilerProto.destroy = function () {
     // unwatch
     this.observer.off()
     var i, key, dir, inss, binding,
-        directives = this.directives,
-        exps = this.expressions,
-        bindings = this.bindings,
-        el = this.el
+        el         = this.el,
+        directives = this.dirs,
+        exps       = this.exps,
+        bindings   = this.bindings
     // remove all directives that are instances of external bindings
     i = directives.length
     while (i--) {
@@ -536,6 +556,18 @@ CompilerProto.destroy = function () {
 
 // Helpers --------------------------------------------------------------------
 
+/*
+ *  Refresh prefix in case it has been changed
+ *  during compilations
+ */
+function refreshPrefix () {
+    var prefix     = config.prefix
+    eachAttr       = prefix + '-each'
+    vmAttr         = prefix + '-viewmodel'
+    partialAttr    = prefix + '-partial'
+    transitionAttr = prefix + '-transition'
+}
+
 /*
  *  determine which viewmodel a key belongs to based on nesting symbols
  */

+ 2 - 2
src/directives/each.js

@@ -184,8 +184,8 @@ module.exports = {
      */
     detach: function () {
         var c = this.container,
-            p = this.parent = c.parentNode,
-            n = this.next = c.nextSibling
+            p = this.parent = c.parentNode
+        this.next = c.nextSibling
         if (p) p.removeChild(c)
     },
 

+ 9 - 1
src/main.js

@@ -45,7 +45,15 @@ api.vm = function (id, Ctor) {
  */
 api.partial = function (id, partial) {
     if (!partial) return utils.partials[id]
-    utils.partials[id] = partial
+    utils.partials[id] = templateToFragment(partial)
+}
+
+/*
+ *  Allows user to register/retrieve a transition definition object
+ */
+api.transition = function (id, transition) {
+    if (!transition) return utils.transitions[id]
+    utils.transitions[id] = transition
 }
 
 api.ViewModel = ViewModel

+ 3 - 2
src/utils.js

@@ -3,8 +3,9 @@ var config    = require('./config'),
 
 module.exports = {
 
-    vms: {},
-    partials: {},
+    vms         : {},
+    partials    : {},
+    transitions : {},
 
     /*
      *  Define an ienumerable property

+ 23 - 0
test/.jshintrc

@@ -0,0 +1,23 @@
+{
+    "eqeqeq": true,
+    "browser": true,
+    "asi": true,
+    "multistr": true,
+    "undef": true,
+    "unused": true,
+    "trailing": true,
+    "sub": true,
+    "node": true,
+    "laxbreak": true,
+    "globals": {
+        "console": true,
+        "it": true,
+        "describe": true,
+        "before": true,
+        "after": true,
+        "assert": true,
+        "mock": true,
+        "seed": true,
+        "$": true
+    }
+}

+ 82 - 8
test/unit/specs/api.js

@@ -92,10 +92,10 @@ describe('UNIT: API', function () {
                 msg = 'wowaaaa?'
             dirTest = {
                 bind: function (value) {
-                    this.el.setAttribute(testId + 'bind', msg + 'bind')
+                    this.el.setAttribute(testId + 'bind', value + 'bind')
                 },
                 update: function (value) {
-                    this.el.setAttribute(testId + 'update', msg + 'update')
+                    this.el.setAttribute(testId + 'update', value + 'update')
                 },
                 unbind: function () {
                     this.el.removeAttribute(testId + 'bind')
@@ -122,15 +122,83 @@ describe('UNIT: API', function () {
     })
 
     describe('vm()', function () {
-        it('should be tested', function () {
-            assert.ok(false)
+
+        var testId = 'api-vm-test',
+            Test = seed.ViewModel.extend({
+                className: 'hihi',
+                data: { hi: 'ok' }
+            }),
+            utils = require('seed/src/utils')
+
+        it('should register a VM constructor', function () {
+            seed.vm(testId, Test)
+            assert.strictEqual(utils.vms[testId], Test)
+        })
+
+        it('should retrieve the VM if has only one arg', function () {
+            assert.strictEqual(seed.vm(testId), Test)
+        })
+
+        it('should work with sd-viewmodel', function () {
+            mock(testId, '<div sd-viewmodel="' + testId + '">{{hi}}</div>')
+            var t = new seed.ViewModel({ el: '#' + testId }),
+                child = t.$el.querySelector('div')
+            assert.strictEqual(child.className, 'hihi')
+            assert.strictEqual(child.textContent, 'ok')
         })
+
     })
 
     describe('partial()', function () {
-        it('should be tested', function () {
+
+        var testId = 'api-partial-test',
+            partial = '<div class="partial-test"><a>{{hi}}</a></div><span>hahaha</span>',
+            utils = require('seed/src/utils')
+
+        it('should register the partial as a dom fragment', function () {
+            seed.partial(testId, partial)
+            var converted = utils.partials[testId]
+            assert.ok(converted instanceof window.DocumentFragment)
+            assert.strictEqual(converted.querySelector('.partial-test a').innerHTML, '{{hi}}')
+            assert.strictEqual(converted.querySelector('span').innerHTML, 'hahaha')
+        })
+
+        it('should retrieve the partial if has only one arg', function () {
+            assert.strictEqual(utils.partials[testId], seed.partial(testId))
+        })
+
+        it('should work with sd-partial', function () {
+            mock(testId, 'hello', {
+                'sd-partial': testId
+            })
+            var t = new seed.ViewModel({
+                el: '#' + testId,
+                data: { hi: 'hohoho' }
+            })
+            assert.strictEqual(t.$el.querySelector('.partial-test a').textContent, 'hohoho')
+            assert.strictEqual(t.$el.querySelector('span').innerHTML, 'hahaha')
+        })
+    })
+
+    describe('transition()', function () {
+        
+        var testId = 'api-trans-test',
+            transition = {},
+            utils = require('seed/src/utils')
+
+        it('should register a transition object', function () {
+            seed.transition(testId, transition)
+            assert.strictEqual(utils.transitions[testId], transition)
+        })
+
+        it('should retrieve the transition if has only one arg', function () {
+            assert.strictEqual(seed.transition(testId), transition)
+        })
+
+        it('should work with sd-transition', function () {
             assert.ok(false)
         })
+
     })
 
     describe('ViewModel.extend()', function () {
@@ -176,9 +244,9 @@ describe('UNIT: API', function () {
                 it('should be called on the instance when instantiating', function () {
                     var called = false,
                         Test = seed.ViewModel.extend({ init: function () {
-                            called = true                           
-                        }}),
-                        test = new Test({ el: document.createElement('div') })
+                            called = true
+                        }})
+                    new Test({ el: document.createElement('div') })
                     assert.ok(called)
                 })
 
@@ -384,6 +452,12 @@ describe('UNIT: API', function () {
                 })
             })
 
+            describe('transitions', function () {
+                it('should be tested', function () {
+                    assert.ok(false)
+                })
+            })
+
         })
 
     })

+ 0 - 1
test/unit/specs/binding.js

@@ -107,7 +107,6 @@ describe('UNIT: Binding', function () {
         
         var b = new Binding(null, 'test'),
             unbound = 0,
-            pubbed = false,
             numInstances = 3,
             instance = {
                 unbind: function () {

+ 1 - 1
test/unit/specs/directive.js

@@ -55,7 +55,7 @@ describe('UNIT: Directive', function () {
         
         it('should copy the definition as _update if the def is a function', function () {
             var d = Directive.parse('sd-test', 'abc', compiler)
-            assert.strictEqual(d._update, test)                
+            assert.strictEqual(d._update, test)
         })
 
         it('should copy methods if the def is an object', function () {

+ 1 - 1
test/unit/specs/observer.js

@@ -98,7 +98,7 @@ describe('UNIT: Observer', function () {
 
         it('should overwrite the native array mutator methods', function () {
             ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(function (method) {
-                assert.notStrictEqual(arr[method], Array.prototype[method])                
+                assert.notStrictEqual(arr[method], Array.prototype[method])
             })
         })
 

+ 4 - 4
test/unit/specs/viewmodel.js

@@ -43,7 +43,7 @@ describe('UNIT: ViewModel', function () {
         it('should trigger callback when a plain value changes', function () {
             var val
             vm.$watch('a.b.c', function (newVal) {
-                val = newVal            
+                val = newVal
             })
             data.b.c = 'new value!'
             assert.strictEqual(val, data.b.c)
@@ -87,13 +87,13 @@ describe('UNIT: ViewModel', function () {
         
         it('should unwatch the stuff', function () {
             var triggered = false
-            vm.$watch('a.b.c', function (newVal) {
+            vm.$watch('a.b.c', function () {
                 triggered = true
             })
-            vm.$watch('a', function (newVal) {
+            vm.$watch('a', function () {
                 triggered = true
             })
-            vm.$watch('b', function (newVal) {
+            vm.$watch('b', function () {
                 triggered = true
             })
             vm.$unwatch('a')