Переглянути джерело

add teardown option; tests for $destroy()

Evan You 12 роки тому
батько
коміт
5ffa301467
4 змінених файлів з 182 додано та 24 видалено
  1. 2 5
      Gruntfile.js
  2. 24 11
      src/compiler.js
  3. 16 0
      test/unit/specs/api.js
  4. 140 8
      test/unit/specs/viewmodel.js

+ 2 - 5
Gruntfile.js

@@ -65,12 +65,9 @@ module.exports = function( grunt ) {
         },
 
         watch: {
-            options: {
-                livereload: true
-            },
-            component: {
+            dev: {
                 files: ['src/**/*.js', 'component.json'],
-                tasks: ['component_build']
+                tasks: ['component_build', 'jsc']
             }
         }
 

+ 24 - 11
src/compiler.js

@@ -556,31 +556,42 @@ CompilerProto.getOption = function (type, id) {
  *  Unbind and remove element
  */
 CompilerProto.destroy = function () {
-    var compiler = this
-    log('compiler destroyed: ', compiler.vm.$el)
-    // unwatch
-    compiler.observer.off()
-    compiler.emitter.off()
-    var i, key, dir, inss, binding,
+
+    var compiler = this,
+        i, key, dir, instances, binding,
         el         = compiler.el,
         directives = compiler.dirs,
         exps       = compiler.exps,
-        bindings   = compiler.bindings
-    // remove all directives that are instances of external bindings
+        bindings   = compiler.bindings,
+        teardown   = compiler.options.teardown
+
+    // call user teardown first
+    if (teardown) teardown()
+
+    // unwatch
+    compiler.observer.off()
+    compiler.emitter.off()
+
+    // unbind all direcitves
     i = directives.length
     while (i--) {
         dir = directives[i]
+        // if this directive is an instance of an external binding
+        // e.g. a directive that refers to a variable on the parent VM
+        // we need to remove it from that binding's instances
         if (!dir.isSimple && dir.binding.compiler !== compiler) {
-            inss = dir.binding.instances
-            if (inss) inss.splice(inss.indexOf(dir), 1)
+            instances = dir.binding.instances
+            if (instances) instances.splice(instances.indexOf(dir), 1)
         }
         dir.unbind()
     }
+
     // unbind all expressions (anonymous bindings)
     i = exps.length
     while (i--) {
         exps[i].unbind()
     }
+
     // unbind/unobserve all own bindings
     for (key in bindings) {
         if (hasOwn.call(bindings, key)) {
@@ -591,6 +602,7 @@ CompilerProto.destroy = function () {
             binding.unbind()
         }
     }
+
     // remove self from parentCompiler
     var parent = compiler.parentCompiler,
         childId = compiler.childId
@@ -600,7 +612,8 @@ CompilerProto.destroy = function () {
             delete parent.vm.$[childId]
         }
     }
-    // remove el
+
+    // finally remove dom element
     if (el === document.body) {
         el.innerHTML = ''
     } else if (el.parentNode) {

+ 16 - 0
test/unit/specs/api.js

@@ -500,6 +500,22 @@ describe('UNIT: API', function () {
 
             })
 
+            describe('teardown', function () {
+                
+                it('should be called when a vm is destroyed', function () {
+                    var called = false
+                    var Test = Seed.extend({
+                        teardown: function () {
+                            called = true
+                        }
+                    })
+                    var test = new Test()
+                    test.$destroy()
+                    assert.ok(called)
+                })
+
+            })
+
             describe('transitions', function () {
                 // it('should be tested', function () {
                 //     assert.ok(false)

+ 140 - 8
test/unit/specs/viewmodel.js

@@ -1,11 +1,3 @@
-/*
- *  Only tests the following:
- *  - .$get()
- *  - .$set()
- *  - .$watch()
- *  - .$unwatch()
- */
-
 describe('UNIT: ViewModel', function () {
 
     mock('vm-test', '{{a.b.c}}')
@@ -227,4 +219,144 @@ describe('UNIT: ViewModel', function () {
 
     })
 
+    describe('.$destroy', function () {
+        
+        // since this simply delegates to Compiler.prototype.destroy(),
+        // that's what we are actually testing here.
+        var destroy = require('seed/src/compiler').prototype.destroy
+
+        var tearDownCalled = false,
+            observerOffCalled = false,
+            emitterOffCalled = false,
+            dirUnbindCalled = false,
+            expUnbindCalled = false,
+            bindingUnbindCalled = false,
+            unobserveCalled = 0,
+            elRemoved = false,
+            externalBindingUnbindCalled = false
+
+        var dirMock = {
+            binding: {
+                compiler: null,
+                instances: []
+            },
+            unbind: function () {
+                dirUnbindCalled = true
+            }
+        }
+        dirMock.binding.instances.push(dirMock)
+
+        var bindingsMock = Object.create({
+            'test2': {
+                unbind: function () {
+                    externalBindingUnbindCalled = true
+                }
+            }
+        })
+        bindingsMock.test = {
+            root: true,
+            key: 'test',
+            value: {
+                __observer__: {
+                    off: function () {
+                        unobserveCalled++
+                        return this
+                    }
+                }
+            },
+            unbind: function () {
+                bindingUnbindCalled = true
+            }
+        }
+
+        var compilerMock = {
+            options: {
+                teardown: function () {
+                    tearDownCalled = true
+                }
+            },
+            observer: {
+                off: function () {
+                    observerOffCalled = true
+                },
+                proxies: {
+                    'test.': {}
+                }
+            },
+            emitter: {
+                off: function () {
+                    emitterOffCalled = true
+                }
+            },
+            dirs: [dirMock],
+            exps: [{
+                unbind: function () {
+                    expUnbindCalled = true
+                }
+            }],
+            bindings: bindingsMock,
+            childId: 'test',
+            parentCompiler: {
+                childCompilers: [],
+                vm: {
+                    $: {
+                        'test': true
+                    }
+                }
+            },
+            el: {
+                parentNode: {
+                    removeChild: function () {
+                        elRemoved = true
+                    }
+                }
+            }
+        }
+
+        compilerMock.parentCompiler.childCompilers.push(compilerMock)
+
+        destroy.call(compilerMock)
+
+        it('should call the teardown option', function () {
+            assert.ok(tearDownCalled)
+        })
+
+        it('should turn observer and emitter off', function () {
+            assert.ok(observerOffCalled)
+            assert.ok(emitterOffCalled)
+        })
+
+        it('should unbind all directives', function () {
+            assert.ok(dirUnbindCalled)
+        })
+
+        it('should remove directives from external bindings', function () {
+            assert.strictEqual(dirMock.binding.instances.indexOf(dirMock), -1)
+        })
+
+        it('should unbind all expressions', function () {
+            assert.ok(expUnbindCalled)
+        })
+
+        it('should unbind and unobserve own bindings', function () {
+            assert.ok(bindingUnbindCalled)
+            assert.strictEqual(unobserveCalled, 3)
+        })
+
+        it('should not unbind external bindings', function () {
+            assert.notOk(externalBindingUnbindCalled)
+        })
+
+        it('should remove self from parentCompiler', function () {
+            var parent = compilerMock.parentCompiler
+            assert.ok(parent.childCompilers.indexOf(compilerMock), -1)
+            assert.strictEqual(parent.vm.$[compilerMock.childId], undefined)
+        })
+
+        it('should remove the dom element', function () {
+            assert.ok(elRemoved)
+        })
+
+    })
+
 })