[Plot] Complete specs for plot

Complete specs for transitioned plot view, WTD-533.
This commit is contained in:
Victor Woeltjen 2014-12-02 11:08:21 -08:00
parent 2f475fc17a
commit 870172ec6f
5 changed files with 253 additions and 7 deletions

View File

@ -13,7 +13,7 @@
{ {
"key": "mctChart", "key": "mctChart",
"implementation": "MCTChart.js", "implementation": "MCTChart.js",
"depends": [ "$interval" ] "depends": [ "$interval", "$log" ]
} }
], ],
"controllers": [ "controllers": [

View File

@ -36,7 +36,7 @@ define(
buffer; buffer;
if (!gl) { if (!gl) {
return false; throw new Error("WebGL unavailable.");
} }
// Initialize shaders // Initialize shaders

View File

@ -14,15 +14,23 @@ define(
* *
* @constructor * @constructor
*/ */
function MCTChart($interval) { function MCTChart($interval, $log) {
function linkChart(scope, element) { function linkChart(scope, element) {
var canvas = element.find("canvas")[0], var canvas = element.find("canvas")[0],
chart;
// Try to initialize GLChart, which allows drawing using WebGL.
// This may fail, particularly where browsers do not support
// WebGL, so catch that here.
try {
chart = new GLChart(canvas); chart = new GLChart(canvas);
} catch (e) {
$log.warn("Cannot initialize mct-chart; " + e.message);
return;
}
function doDraw() { function doDraw(draw) {
var draw = scope.draw;
canvas.width = canvas.offsetWidth; canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight; canvas.height = canvas.offsetHeight;
chart.clear(); chart.clear();
@ -57,7 +65,7 @@ define(
function drawIfResized() { function drawIfResized() {
if (canvas.width !== canvas.offsetWidth || if (canvas.width !== canvas.offsetWidth ||
canvas.height !== canvas.offsetHeight) { canvas.height !== canvas.offsetHeight) {
doDraw(); doDraw(scope.draw);
} }
} }

View File

@ -9,6 +9,103 @@ define(
"use strict"; "use strict";
describe("A WebGL chart", function () { describe("A WebGL chart", function () {
var mockCanvas,
mockGL,
glChart;
beforeEach(function () {
mockCanvas = jasmine.createSpyObj("canvas", [ "getContext" ]);
mockGL = jasmine.createSpyObj(
"gl",
[
"createShader",
"compileShader",
"shaderSource",
"attachShader",
"createProgram",
"linkProgram",
"useProgram",
"enableVertexAttribArray",
"getAttribLocation",
"getUniformLocation",
"createBuffer",
"lineWidth",
"enable",
"blendFunc",
"viewport",
"clear",
"uniform2fv",
"uniform4fv",
"bufferData",
"bindBuffer",
"vertexAttribPointer",
"drawArrays"
]
);
mockGL.ARRAY_BUFFER = "ARRAY_BUFFER";
mockGL.DYNAMIC_DRAW = "DYNAMIC_DRAW";
mockGL.TRIANGLE_FAN = "TRIANGLE_FAN";
mockGL.LINE_STRIP = "LINE_STRIP";
// Echo back names for uniform locations, so we can
// test which of these are set for certain operations.
mockGL.getUniformLocation.andCallFake(function (a, name) {
return name;
});
mockCanvas.getContext.andReturn(mockGL);
glChart = new GLChart(mockCanvas);
});
it("allows the canvas to be cleared", function () {
glChart.clear();
expect(mockGL.clear).toHaveBeenCalled();
});
it("doees not construct if WebGL is unavailable", function () {
mockCanvas.getContext.andReturn(undefined);
expect(function () {
return new GLChart(mockCanvas);
}).toThrow();
});
it("allows dimensions to be set", function () {
glChart.setDimensions([120, 120], [0, 10]);
expect(mockGL.uniform2fv)
.toHaveBeenCalledWith("uDimensions", [120, 120]);
expect(mockGL.uniform2fv)
.toHaveBeenCalledWith("uOrigin", [0, 10]);
});
it("allows lines to be drawn", function () {
var testBuffer = [ 0, 1, 3, 8 ],
testColor = [ 0.25, 0.33, 0.66, 1.0 ],
testPoints = 2;
glChart.drawLine(testBuffer, testColor, testPoints);
expect(mockGL.bufferData).toHaveBeenCalledWith(
mockGL.ARRAY_BUFFER,
testBuffer,
mockGL.DYNAMIC_DRAW
);
expect(mockGL.uniform4fv)
.toHaveBeenCalledWith("uColor", testColor);
expect(mockGL.drawArrays)
.toHaveBeenCalledWith("LINE_STRIP", 0, testPoints);
});
it("allows squares to be drawn", function () {
var testMin = [0, 1],
testMax = [10, 10],
testColor = [ 0.25, 0.33, 0.66, 1.0 ];
glChart.drawSquare(testMin, testMax, testColor);
expect(mockGL.uniform4fv)
.toHaveBeenCalledWith("uColor", testColor);
expect(mockGL.drawArrays)
.toHaveBeenCalledWith("TRIANGLE_FAN", 0, 4);
});
}); });
} }
); );

View File

@ -9,6 +9,147 @@ define(
"use strict"; "use strict";
describe("The mct-chart directive", function () { describe("The mct-chart directive", function () {
var mockInterval,
mockLog,
mockScope,
mockElement,
mockCanvas,
mockGL,
mctChart;
beforeEach(function () {
mockInterval =
jasmine.createSpy("$interval");
mockLog =
jasmine.createSpyObj("$log", ["warn", "info", "debug"]);
mockScope =
jasmine.createSpyObj("$scope", ["$watchCollection"]);
mockElement =
jasmine.createSpyObj("element", ["find"]);
// mct-chart uses GLChart, so it needs WebGL API
mockCanvas = jasmine.createSpyObj("canvas", [ "getContext" ]);
mockGL = jasmine.createSpyObj(
"gl",
[
"createShader",
"compileShader",
"shaderSource",
"attachShader",
"createProgram",
"linkProgram",
"useProgram",
"enableVertexAttribArray",
"getAttribLocation",
"getUniformLocation",
"createBuffer",
"lineWidth",
"enable",
"blendFunc",
"viewport",
"clear",
"uniform2fv",
"uniform4fv",
"bufferData",
"bindBuffer",
"vertexAttribPointer",
"drawArrays"
]
);
mockGL.ARRAY_BUFFER = "ARRAY_BUFFER";
mockGL.DYNAMIC_DRAW = "DYNAMIC_DRAW";
mockGL.TRIANGLE_FAN = "TRIANGLE_FAN";
mockGL.LINE_STRIP = "LINE_STRIP";
// Echo back names for uniform locations, so we can
// test which of these are set for certain operations.
mockGL.getUniformLocation.andCallFake(function (a, name) {
return name;
});
mockElement.find.andReturn([mockCanvas]);
mockCanvas.getContext.andReturn(mockGL);
mctChart = new MCTChart(mockInterval, mockLog);
});
it("is applicable at the element level", function () {
expect(mctChart.restrict).toEqual("E");
});
it("places a 'draw' attribute in-scope", function () {
// Should ask Angular for the draw attribute
expect(mctChart.scope.draw).toEqual("=");
});
it("watches for changes in the drawn object", function () {
mctChart.link(mockScope, mockElement);
expect(mockScope.$watchCollection)
.toHaveBeenCalledWith("draw", jasmine.any(Function));
});
it("issues one draw call per line", function () {
mctChart.link(mockScope, mockElement);
mockScope.$watchCollection.mostRecentCall.args[1]({
lines: [ {}, {}, {} ]
});
expect(mockGL.drawArrays.calls.length).toEqual(3);
});
it("issues one draw call per box", function () {
mctChart.link(mockScope, mockElement);
mockScope.$watchCollection.mostRecentCall.args[1]({
boxes: [
{ start: [0, 0], end: [1, 1] },
{ start: [0, 0], end: [1, 1] },
{ start: [0, 0], end: [1, 1] },
{ start: [0, 0], end: [1, 1] }
]
});
expect(mockGL.drawArrays.calls.length).toEqual(4);
});
it("does not fail if no draw object is in scope", function () {
mctChart.link(mockScope, mockElement);
expect(mockScope.$watchCollection.mostRecentCall.args[1])
.not.toThrow();
});
it("draws on canvas resize", function () {
mctChart.link(mockScope, mockElement);
// Should track canvas size in an interval
expect(mockInterval).toHaveBeenCalledWith(
jasmine.any(Function),
jasmine.any(Number)
);
// Verify pre-condition
expect(mockGL.clear).not.toHaveBeenCalled();
mockCanvas.width = 100;
mockCanvas.offsetWidth = 150;
mockCanvas.height = 200;
mockCanvas.offsetHeight = 200;
mockInterval.mostRecentCall.args[0]();
// Use clear as an indication that drawing has occurred
expect(mockGL.clear).toHaveBeenCalled();
});
it("warns if no WebGL context is available", function () {
mockCanvas.getContext.andReturn(undefined);
mctChart.link(mockScope, mockElement);
expect(mockLog.warn).toHaveBeenCalled();
});
it("logs nothing in nominal situations (WebGL available)", function () {
// Complement the previous test
mctChart.link(mockScope, mockElement);
expect(mockLog.warn).not.toHaveBeenCalled();
});
}); });
} }
); );