Skip to content

Commit

Permalink
add teardown option; tests for $destroy()
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Nov 3, 2013
1 parent c56cbdc commit 5ffa301
Show file tree
Hide file tree
Showing 4 changed files with 182 additions and 24 deletions.
7 changes: 2 additions & 5 deletions Gruntfile.js
Expand Up @@ -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']
}
}

Expand Down
35 changes: 24 additions & 11 deletions src/compiler.js
Expand Up @@ -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)) {
Expand All @@ -591,6 +602,7 @@ CompilerProto.destroy = function () {
binding.unbind()
}
}

// remove self from parentCompiler
var parent = compiler.parentCompiler,
childId = compiler.childId
Expand All @@ -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) {
Expand Down
16 changes: 16 additions & 0 deletions test/unit/specs/api.js
Expand Up @@ -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)
Expand Down
148 changes: 140 additions & 8 deletions 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}}')
Expand Down Expand Up @@ -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)
})

})

})

0 comments on commit 5ffa301

Please sign in to comment.