add teardown option; tests for $destroy()

This commit is contained in:
Evan You 2013-11-02 22:19:52 -04:00
parent c56cbdcc39
commit 5ffa301467
4 changed files with 182 additions and 24 deletions

View File

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

View File

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

View File

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

View File

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