Merge remote-tracking branch 'origin/open717' into open-master

This commit is contained in:
bwyu 2015-02-02 16:58:27 -08:00
commit 5c649935d2
6 changed files with 164 additions and 5 deletions

View File

@ -0,0 +1,10 @@
{
"extensions": {
"indicators": [
{
"implementation": "WatchIndicator.js",
"depends": ["$interval", "$rootScope"]
}
]
}
}

View File

@ -0,0 +1,77 @@
/*global define*/
define(
[],
function () {
"use strict";
/**
* Updates a count of currently-active Angular watches.
* @constructor
* @param $interval Angular's $interval
*/
function WatchIndicator($interval, $rootScope) {
var watches = 0;
function count(scope) {
if (scope) {
watches += (scope.$$watchers || []).length;
count(scope.$$childHead);
count(scope.$$nextSibling);
}
}
function update() {
watches = 0;
count($rootScope);
}
// Update state every second
$interval(update, 1000);
// Provide initial state, too
update();
return {
/**
* Get the glyph (single character used as an icon)
* to display in this indicator. This will return ".",
* which should appear as a dataflow icon.
* @returns {string} the character of the database icon
*/
getGlyph: function () {
return "E";
},
/**
* Get the name of the CSS class to apply to the glyph.
* This is used to color the glyph to match its
* state (one of ok, caution or err)
* @returns {string} the CSS class to apply to this glyph
*/
getGlyphClass: function () {
return (watches > 2000) ? "caution" :
(watches < 1000) ? "ok" :
undefined;
},
/**
* Get the text that should appear in the indicator.
* @returns {string} brief summary of connection status
*/
getText: function () {
return watches + " watches";
},
/**
* Get a longer-form description of the current connection
* space, suitable for display in a tooltip
* @returns {string} longer summary of connection status
*/
getDescription: function () {
return "";
}
};
}
return WatchIndicator;
}
);

View File

@ -35,7 +35,8 @@ define(
// Link; start listening for changes to an element's size
function link(scope, element, attrs) {
var lastBounds;
var lastBounds,
active = true;
// Determine how long to wait before the next update
function currentInterval() {
@ -62,9 +63,19 @@ define(
width: element[0].offsetWidth,
height: element[0].offsetHeight
});
$timeout(onInterval, currentInterval());
if (active) {
$timeout(onInterval, currentInterval());
}
}
// Stop running in the background
function deactivate() {
active = false;
}
// Unregister once out-of-scope
scope.$on("$destroy", deactivate);
// Handle the initial callback
onInterval();
}

View File

@ -14,7 +14,7 @@ define(
beforeEach(function () {
mockTimeout = jasmine.createSpy("$timeout");
mockScope = jasmine.createSpyObj("$scope", ["$eval"]);
mockScope = jasmine.createSpyObj("$scope", ["$eval", "$on"]);
testElement = { offsetWidth: 100, offsetHeight: 200 };
testAttrs = { mctResize: "some-expr" };
@ -63,6 +63,32 @@ define(
);
});
it("stops size checking for size changes after destroy", function () {
mctResize.link(mockScope, [testElement], testAttrs);
// First, make sure there's a $destroy observer
expect(mockScope.$on)
.toHaveBeenCalledWith("$destroy", jasmine.any(Function));
// Should have scheduled the first timeout
expect(mockTimeout.calls.length).toEqual(1);
// Fire the timeout
mockTimeout.mostRecentCall.args[0]();
// Should have scheduled another timeout
expect(mockTimeout.calls.length).toEqual(2);
// Broadcast a destroy event
mockScope.$on.mostRecentCall.args[1]();
// Fire the timeout
mockTimeout.mostRecentCall.args[0]();
// Should NOT have scheduled another timeout
expect(mockTimeout.calls.length).toEqual(2);
});
});
}
);

View File

@ -46,6 +46,7 @@ define(
function linkChart(scope, element) {
var canvas = element.find("canvas")[0],
activeInterval,
chart;
// Try to initialize GLChart, which allows drawing using WebGL.
@ -110,12 +111,22 @@ define(
}
}
// Stop watching for changes to size (scope destroyed)
function releaseInterval() {
if (activeInterval) {
$interval.cancel(activeInterval);
}
}
// Check for resize, on a timer
$interval(drawIfResized, 1000);
activeInterval = $interval(drawIfResized, 1000);
// Watch "draw" for external changes to the set of
// things to be drawn.
scope.$watchCollection("draw", doDraw);
// Stop checking for resize when scope is destroyed
scope.$on("$destroy", releaseInterval);
}
return {

View File

@ -15,6 +15,7 @@ define(
mockElement,
mockCanvas,
mockGL,
mockPromise,
mctChart;
beforeEach(function () {
@ -23,9 +24,11 @@ define(
mockLog =
jasmine.createSpyObj("$log", ["warn", "info", "debug"]);
mockScope =
jasmine.createSpyObj("$scope", ["$watchCollection"]);
jasmine.createSpyObj("$scope", ["$watchCollection", "$on"]);
mockElement =
jasmine.createSpyObj("element", ["find"]);
mockInterval.cancel = jasmine.createSpy("cancelInterval");
mockPromise = jasmine.createSpyObj("promise", ["then"]);
// mct-chart uses GLChart, so it needs WebGL API
@ -70,6 +73,7 @@ define(
mockElement.find.andReturn([mockCanvas]);
mockCanvas.getContext.andReturn(mockGL);
mockInterval.andReturn(mockPromise);
mctChart = new MCTChart(mockInterval, mockLog);
});
@ -150,6 +154,26 @@ define(
expect(mockLog.warn).not.toHaveBeenCalled();
});
// Avoid resource leaks
it("stops polling for size changes on destroy", function () {
mctChart.link(mockScope, mockElement);
// Should be listening for a destroy event
expect(mockScope.$on).toHaveBeenCalledWith(
"$destroy",
jasmine.any(Function)
);
// Precondition - interval still active
expect(mockInterval.cancel).not.toHaveBeenCalled();
// Broadcast a $destroy
mockScope.$on.mostRecentCall.args[1]();
// Should have stopped the interval
expect(mockInterval.cancel).toHaveBeenCalledWith(mockPromise);
});
});
}
);