| // Copyright 2008 The Closure Library Authors. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS-IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| goog.provide('goog.DisposableTest'); |
| goog.setTestOnly('goog.DisposableTest'); |
| |
| goog.require('goog.Disposable'); |
| goog.require('goog.testing.jsunit'); |
| goog.require('goog.testing.recordFunction'); |
| var d1, d2; |
| |
| // Sample subclass of goog.Disposable. |
| |
| function DisposableTest() { |
| goog.Disposable.call(this); |
| this.element = document.getElementById('someElement'); |
| } |
| goog.inherits(DisposableTest, goog.Disposable); |
| |
| DisposableTest.prototype.disposeInternal = function() { |
| DisposableTest.superClass_.disposeInternal.call(this); |
| delete this.element; |
| }; |
| |
| // Class that doesn't inherit from goog.Disposable, but implements the |
| // disposable interface via duck typing. |
| |
| function DisposableDuck() { |
| this.element = document.getElementById('someElement'); |
| } |
| |
| DisposableDuck.prototype.dispose = function() { |
| delete this.element; |
| }; |
| |
| // Class which calls dispose recursively. |
| |
| function RecursiveDisposable() { |
| this.disposedCount = 0; |
| } |
| goog.inherits(RecursiveDisposable, goog.Disposable); |
| |
| RecursiveDisposable.prototype.disposeInternal = function() { |
| ++this.disposedCount; |
| assertEquals('Disposed too many times', 1, this.disposedCount); |
| this.dispose(); |
| }; |
| |
| // Test methods. |
| |
| function setUp() { |
| d1 = new goog.Disposable(); |
| d2 = new DisposableTest(); |
| } |
| |
| function tearDown() { |
| goog.Disposable.MONITORING_MODE = goog.Disposable.MonitoringMode.OFF; |
| goog.Disposable.INCLUDE_STACK_ON_CREATION = true; |
| goog.Disposable.instances_ = {}; |
| d1.dispose(); |
| d2.dispose(); |
| } |
| |
| function testConstructor() { |
| assertFalse(d1.isDisposed()); |
| assertFalse(d2.isDisposed()); |
| assertEquals(document.getElementById('someElement'), d2.element); |
| } |
| |
| function testDispose() { |
| assertFalse(d1.isDisposed()); |
| d1.dispose(); |
| assertTrue('goog.Disposable instance should have been disposed of', |
| d1.isDisposed()); |
| |
| assertFalse(d2.isDisposed()); |
| d2.dispose(); |
| assertTrue('goog.DisposableTest instance should have been disposed of', |
| d2.isDisposed()); |
| } |
| |
| function testDisposeInternal() { |
| assertNotUndefined(d2.element); |
| d2.dispose(); |
| assertUndefined('goog.DisposableTest.prototype.disposeInternal should ' + |
| 'have deleted the element reference', d2.element); |
| } |
| |
| function testDisposeAgain() { |
| d2.dispose(); |
| assertUndefined('goog.DisposableTest.prototype.disposeInternal should ' + |
| 'have deleted the element reference', d2.element); |
| // Manually reset the element to a non-null value, and call dispose(). |
| // Because the object is already marked disposed, disposeInternal won't |
| // be called again. |
| d2.element = document.getElementById('someElement'); |
| d2.dispose(); |
| assertNotUndefined('disposeInternal should not be called again if the ' + |
| 'object has already been marked disposed', d2.element); |
| } |
| |
| function testDisposeWorksRecursively() { |
| new RecursiveDisposable().dispose(); |
| } |
| |
| function testStaticDispose() { |
| assertFalse(d1.isDisposed()); |
| goog.dispose(d1); |
| assertTrue('goog.Disposable instance should have been disposed of', |
| d1.isDisposed()); |
| |
| assertFalse(d2.isDisposed()); |
| goog.dispose(d2); |
| assertTrue('goog.DisposableTest instance should have been disposed of', |
| d2.isDisposed()); |
| |
| var duck = new DisposableDuck(); |
| assertNotUndefined(duck.element); |
| goog.dispose(duck); |
| assertUndefined('goog.dispose should have disposed of object that ' + |
| 'implements the disposable interface', duck.element); |
| } |
| |
| function testStaticDisposeOnNonDisposableType() { |
| // Call goog.dispose() with various types and make sure no errors are |
| // thrown. |
| goog.dispose(true); |
| goog.dispose(false); |
| goog.dispose(null); |
| goog.dispose(undefined); |
| goog.dispose(''); |
| goog.dispose([]); |
| goog.dispose({}); |
| |
| function A() {} |
| goog.dispose(new A()); |
| } |
| |
| function testMonitoringFailure() { |
| function BadDisposable() {}; |
| goog.inherits(BadDisposable, goog.Disposable); |
| |
| goog.Disposable.MONITORING_MODE = |
| goog.Disposable.MonitoringMode.PERMANENT; |
| |
| var badDisposable = new BadDisposable; |
| assertArrayEquals('no disposable objects registered', [], |
| goog.Disposable.getUndisposedObjects()); |
| assertThrows('the base ctor should have been called', |
| goog.bind(badDisposable.dispose, badDisposable)); |
| } |
| |
| function testGetUndisposedObjects() { |
| goog.Disposable.MONITORING_MODE = |
| goog.Disposable.MonitoringMode.PERMANENT; |
| |
| var d1 = new DisposableTest(); |
| var d2 = new DisposableTest(); |
| assertSameElements('the undisposed instances', [d1, d2], |
| goog.Disposable.getUndisposedObjects()); |
| |
| d1.dispose(); |
| assertSameElements('1 undisposed instance left', [d2], |
| goog.Disposable.getUndisposedObjects()); |
| |
| d1.dispose(); |
| assertSameElements('second disposal of the same object is no-op', [d2], |
| goog.Disposable.getUndisposedObjects()); |
| |
| d2.dispose(); |
| assertSameElements('all objects have been disposed of', [], |
| goog.Disposable.getUndisposedObjects()); |
| } |
| |
| function testClearUndisposedObjects() { |
| goog.Disposable.MONITORING_MODE = |
| goog.Disposable.MonitoringMode.PERMANENT; |
| |
| var d1 = new DisposableTest(); |
| var d2 = new DisposableTest(); |
| d2.dispose(); |
| goog.Disposable.clearUndisposedObjects(); |
| assertSameElements('no undisposed object in the registry', [], |
| goog.Disposable.getUndisposedObjects()); |
| |
| assertThrows('disposal after clearUndisposedObjects()', function() { |
| d1.dispose(); |
| }); |
| |
| // d2 is already disposed of, the redisposal shouldn't throw error. |
| d2.dispose(); |
| } |
| |
| function testRegisterDisposable() { |
| var d1 = new DisposableTest(); |
| var d2 = new DisposableTest(); |
| |
| d1.registerDisposable(d2); |
| d1.dispose(); |
| |
| assertTrue('d2 should be disposed when d1 is disposed', d2.isDisposed()); |
| } |
| |
| function testDisposeAll() { |
| var d1 = new DisposableTest(); |
| var d2 = new DisposableTest(); |
| |
| goog.disposeAll(d1, d2); |
| |
| assertTrue('d1 should be disposed', d1.isDisposed()); |
| assertTrue('d2 should be disposed', d2.isDisposed()); |
| } |
| |
| function testDisposeAllRecursive() { |
| var d1 = new DisposableTest(); |
| var d2 = new DisposableTest(); |
| var d3 = new DisposableTest(); |
| var d4 = new DisposableTest(); |
| |
| goog.disposeAll(d1, [[d2], d3, d4]); |
| |
| assertTrue('d1 should be disposed', d1.isDisposed()); |
| assertTrue('d2 should be disposed', d2.isDisposed()); |
| assertTrue('d3 should be disposed', d3.isDisposed()); |
| assertTrue('d4 should be disposed', d4.isDisposed()); |
| } |
| |
| function testCreationStack() { |
| if (!new Error().stack) |
| return; |
| goog.Disposable.MONITORING_MODE = |
| goog.Disposable.MonitoringMode.PERMANENT; |
| var disposableStack = new DisposableTest().creationStack; |
| // Check that the name of this test function occurs in the stack trace. |
| assertNotEquals(-1, disposableStack.indexOf('testCreationStack')); |
| } |
| |
| function testMonitoredWithoutCreationStack() { |
| if (!new Error().stack) |
| return; |
| goog.Disposable.MONITORING_MODE = |
| goog.Disposable.MonitoringMode.PERMANENT; |
| goog.Disposable.INCLUDE_STACK_ON_CREATION = false; |
| var d1 = new DisposableTest(); |
| |
| // Check that it is tracked, but not with a creation stack. |
| assertUndefined(d1.creationStack); |
| assertSameElements('the undisposed instance', [d1], |
| goog.Disposable.getUndisposedObjects()); |
| } |
| |
| function testOnDisposeCallback() { |
| var callback = goog.testing.recordFunction(); |
| d1.addOnDisposeCallback(callback); |
| assertEquals('callback called too early', 0, callback.getCallCount()); |
| d1.dispose(); |
| assertEquals('callback should be called once on dispose', |
| 1, callback.getCallCount()); |
| } |
| |
| function testOnDisposeCallbackOrder() { |
| var invocations = []; |
| var callback = function(str) { |
| invocations.push(str); |
| }; |
| d1.addOnDisposeCallback(goog.partial(callback, 'a')); |
| d1.addOnDisposeCallback(goog.partial(callback, 'b')); |
| goog.dispose(d1); |
| assertArrayEquals('callbacks should be called in chronological order', |
| ['a', 'b'], invocations); |
| } |
| |
| function testAddOnDisposeCallbackAfterDispose() { |
| var callback = goog.testing.recordFunction(); |
| var scope = {}; |
| goog.dispose(d1); |
| d1.addOnDisposeCallback(callback, scope); |
| assertEquals('Callback should be immediately called if already disposed', 1, |
| callback.getCallCount()); |
| assertEquals('Callback scope should be respected', scope, |
| callback.getLastCall().getThis()); |
| } |
| |
| function testInteractiveMonitoring() { |
| var d1 = new DisposableTest(); |
| goog.Disposable.MONITORING_MODE = |
| goog.Disposable.MonitoringMode.INTERACTIVE; |
| var d2 = new DisposableTest(); |
| |
| assertSameElements('only 1 undisposed instance tracked', [d2], |
| goog.Disposable.getUndisposedObjects()); |
| |
| // No errors should be thrown. |
| d1.dispose(); |
| |
| assertSameElements('1 undisposed instance left', [d2], |
| goog.Disposable.getUndisposedObjects()); |
| |
| d2.dispose(); |
| assertSameElements('all disposed', [], |
| goog.Disposable.getUndisposedObjects()); |
| } |