// 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()); }