HTML Canvas Unit testing - javascript

How can I unit-test Javascript that draws on an HTML canvas? Drawing on the canvas should be checked.

I wrote an example for unit-testing canvas and other image-y types with Jasmine and js-imagediff.
Jasmine Canvas Unit Testing
I find this to be better than making sure specific methods on a mock Canvas have been invoked because different series of methods may produce the same method. Typically, I will create a canvas with the expected value or use a known-stable version of the code to test a development version against.

As discussed in the question comments it's important to check that certain functions have been invoked with suitable parameters. pcjuzer proposed the usage of proxy pattern. The following example (RightJS code) shows one way to do this:
var Context = new Class({
initialize: function($canvasElem) {
this._ctx = $canvasElem._.getContext('2d');
this._calls = []; // names/args of recorded calls
this._initMethods();
},
_initMethods: function() {
// define methods to test here
// no way to introspect so we have to do some extra work :(
var methods = {
fill: function() {
this._ctx.fill();
},
lineTo: function(x, y) {
this._ctx.lineTo(x, y);
},
moveTo: function(x, y) {
this._ctx.moveTo(x, y);
},
stroke: function() {
this._ctx.stroke();
}
// and so on
};
// attach methods to the class itself
var scope = this;
var addMethod = function(name, method) {
scope[methodName] = function() {
scope.record(name, arguments);
method.apply(scope, arguments);
};
}
for(var methodName in methods) {
var method = methods[methodName];
addMethod(methodName, method);
}
},
assign: function(k, v) {
this._ctx[k] = v;
},
record: function(methodName, args) {
this._calls.push({name: methodName, args: args});
},
getCalls: function() {
return this._calls;
}
// TODO: expand API as needed
});
// Usage
var ctx = new Context($('myCanvas'));
ctx.moveTo(34, 54);
ctx.lineTo(63, 12);
ctx.assign('strokeStyle', "#FF00FF");
ctx.stroke();
var calls = ctx.getCalls();
console.log(calls);
You can find a functional demo here.
I have used a similar pattern to implement some features missing from the API. You might need to hack it a bit to fit your purposes. Good luck!

I make really simple canvases and test them with mocha. I do it similarly to Juho Vepsäläinen but mine looks a little simpler. I wrote it in ec2015.
CanvasMock class:
import ContextMock from './ContextMock.js'
export default class {
constructor (width, height)
{
this.mock = [];
this.width = width;
this.height = height;
this.context = new ContextMock(this.mock);
}
getContext (string)
{
this.mock.push('[getContext ' + string + ']')
return this.context
}
}
ContextMock class:
export default class {
constructor(mock)
{
this.mock = mock
}
beginPath()
{
this.mock.push('[beginPath]')
}
moveTo(x, y)
{
this.mock.push('[moveTo ' + x + ', ' + y + ']')
}
lineTo(x, y)
{
this.mock.push('[lineTo ' + x + ', ' + y + ']')
}
stroke()
{
this.mock.push('[stroke]')
}
}
some mocha tests that evaluates the functionality of the mock itself:
describe('CanvasMock and ContextMock', ()=> {
it('should be able to return width and height', ()=> {
let canvas = new CanvasMock(500,600)
assert.equal(canvas.width, 500)
assert.equal(canvas.height, 600)
})
it('should be able to update mock for getContext', ()=> {
let canvas = new CanvasMock(500,600)
let ctx = canvas.getContext('2d')
assert.equal(canvas.mock, '[getContext 2d]')
})
})
A mocha tests that evaluates the functionality of a function that returns a canvas:
import Myfunction from 'MyFunction.js'
describe('MyFuntion', ()=> {
it('should be able to return correct canvas', ()=> {
let testCanvas = new CanvasMock(500,600)
let ctx = testCanvas.getContext('2d')
ctx.beginPath()
ctx.moveTo(0,0)
ctx.lineTo(8,8)
ctx.stroke()
assert.deepEqual(MyFunction(new CanvasMock(500,600), 8, 8), canvas.mock, [ '[getContext 2d]', '[beginPath]', '[moveTo 0, 0]', [lineTo 8, 8]', '[stroke]' ])
})
so in this example myfunction takes the canvas you passed in as an argument ( Myfunction(new CanvasMock(500,600), 8, 8) ) and writes a line on it from 0,0 to whatever you pass in as the arguments ( Myfunction(new CanvasMock(500,600),** 8, 8**) ) and then returns the edited canvas.
so when you use the function in real life you can pass in an actual canvas, not a canvas mock and then it will run those same methods but do actual canvas things.
read about mocks here

Since the "shapes" and "lines" drawn on a canvas are not actual objects (it's like ink on paper), it would be very hard (impossible?) to do a normal unit test on that.
The best you can do with standard canvas it analyze the pixel data (from the putImageData/getImageData. Like what bedraw was saying).
Now, I haven't tried this yet, but it might be more what you need. Cake is a library for the canvas. It's using alot of the putImageData/getImageData. This example might help with what you are trying to do with a test.
Hope that helps answer your question.

I've been looking at canvas testing recently and I've now thought about a page that allows comparing the canvas to a "known good" image version of what the canvas should look like. This would make a visual comparison quick and easy.
And maybe have a button that, assuming the output is OK, updates the image version on the server (by sending the toDataUrl() output to it). This new version can then be used for future comparisons.
Not exactly (at all) automated - but it does make comparing the output of your code easy.
Edit:
Now I've made this:
The left chart is the real canvas whilst the right is an image stored in a database of what it should look like (taken from when I know the code is working). There'll be lots of these to test all (eventually) aspects of my code.

From a developer's point of view the canvas is almost write-only because once drawn it's difficult to programmatically get something useful back. Sure one can do a point by point recognition but that's too tedious and such tests are hard to be written and maintained.
It's better to intercept the calls made to a canvas object and investigate those. Here are a few options:
Create a wrapper object that records all the calls. Juho Vepsäläinen posted a such example.
If possible use a library like frabric.js that offers a higher level of abstraction for drawing. The "drawings" are JS objects that can be inspected directly or converted to SVG which is easier to inspect and test.
Use Canteen to intercept all the function calls and attribute changes of a canvas object. This is similar with option 1.
Use Canteen with rabbit which offers you a few Jasmine custom matchers for size and alignment and a function getBBox() that can be used to determine the size and the position of the stuff being drawn on the canvas.

Related

Access an instance from a function outside of the object constructor

I have a problem I can't understand after a lot of attempts to solve it.
To help you understand, there are 2 classes (Game and Board), and a third file with the jQuery keypress controls. Game is about the logic of the game, and Board about the display.
Here is a part of the code I hope sufficient to understand.
// GAME CLASS
function Game(width, height) {
this.width = width;
this.height = height;
this.forbiddenPosition = [];
this.chartBoard = this.resetBoard();
this.generateGame();
}
Game.prototype.generateGame = function () {
this.player1 = new Player("Joueur 1", 100, dagger);
this.player2 = new Player("Joueur 2", 100, dagger);
const playerArray = [this.player1, this.player2];
}
Game.prototype.getPlayer1 = function () {
return this.player1;
};
Game.prototype.getPlayer2 = function () {
return this.player2;
};
Game.prototype.switchTurn = function (player1, player2) {
console.log(player1);
console.log(player2);
};
// BOARD CLASS
const ctx = $('#board').get(0).getContext('2d');
function Board (width, height) {
this.width = width;
this.height = height;
this.game = new Game(this.width, this.height);
this.displayInfoPlayers(this.game.getPlayer1(), this.game.getPlayer2());
}
Board.prototype.displayInfoPlayers = function (player1, player2) {
$('.canvas-side__left').css('visibility', 'visible');
$('.canvas-side__right').css('visibility', 'visible');
$('.canvas-side__left').addClass('animated slideInLeft');
$('.canvas-side__right').addClass('animated slideInRight');
$(".canvas-side__left").html("<h2 class='canvas-side--title'>" + player1.name + "</h2><p class='canvas-side--health'>" + player1.health + "</p><p class='canvas-side--health'>" + player1.weapon.name + "</p>");
$(".canvas-side__right").html("<h2 class='canvas-side--title'>" + player2.name + "</h2><p class='canvas-side--health'>" + player2.health + "</p><p class='canvas-side--health'>" + player2.weapon.name + "</p>");
};
// CONTROL
$(document).on('keypress', function (e) {
if (e.which == 13) {
Game.prototype.switchTurn(Game.prototype.getPlayer1(), Game.prototype.getPlayer2());
e.stopPropagation();
}
});
Board class is linked to Game class and so uses this. The control using jQuery code are in a third file and not into a class.
When I press Enter, I get undefined for player1 and 2. I tried different ways to call the getter functions and nothing works. I also tried to put the controls inside the Game file and still nothing.
I get either undefined or getPlayer1() is not a function.
I am looking for a way to call these getter functions from everywhere so I can use player1 and 2 which I need to move on the board.
There are several issues there.
The keypress event handler is using Game.prototype, not an instance of Game. You want to be using an instance of Game you've created and stored somewhere. Game.prototype doesn't have the player1 and player2 properties. They're added to instances of Game by the Game constructor. Nothing ever adds them to Game.prototype (which is correct, they shouldn't be on the prototype).
There's no need for getPlayer1, etc. You can directly access player1 and player2. (It's possible to make player1 and player2 private and only provide accessors for them, but it's a bit complicated at the moment and probably not something you want to take on yet.)
Within Game methods, you need to consistently use this.player1 and this.player2, don't pass the players around.
It seems odd for Board to create an instance of Game. It seems like it should be the other way around.
I suggest stepping back from this task and trying something simpler first (like creating a class, an instance of the class, and using that instance in an event handler), then incrementally adding complexity and making sure at each stage you're clear on what's happening. As you go, you may have more specific questions, which you can post on SO (after thorough research, etc.).
You can do something like this and it should work. Essentially, you prototype the function you're trying to access which is not declared until after the constructor.
class Test {
constructor() {
this.five = Test.prototype.getFive();
}
getFive() {
return 5;
}
}
let test = new Test();
console.log(test.five); // Returns 5

How to mock a nested method?

I'm learning more about Jasmine Unit Testing and I've ran into something that I can't figure out. I'm new to both JavaScript and Unit Testing. I've tried to look for examples about nested methods and mocking them, but I'm still unable to have a successful test. I'm making a game with PhaserJS (HTML5 Game Library) and I've written successful tests so far. This is an example of my successful test.
function createGameScreenBorder(gameState) {
var border = gameState.game.add.graphics();
}
This is my test block.
it("create gamescreen background border", function() {
gameState.game = {
add: jasmine.createSpyObj('add', ['graphics'])
};
createGameScreenBorder(gameState);
expect(gameState.game.add.graphics).toHaveBeenCalled();
});
Now the above code works, it doesn't do much. What I want is to draw a rectangle which is a method part of the graphics method.
function createGameScreenBorder(gameState) {
var border = gameState.game.add.graphics();
// drawRect: x, y width, length
border.drawRect(0, 0, 0, 0);
}
This is my test block.
it("create gamescreen background border", function() {
gameState.game = {
add: {
graphics: jasmine.createSpyObj('graphics', ['drawRect'])
}
}
createGameScreenBorder(gameState);
expect(gameState.game.add.graphics).toHaveBeenCalled();
expect(gameState.game.add.graphics().lineStyle).toHaveBeenCalledWith(0,0,0,0);
});
I want to be able to make sure that drawRect() is called with my parameters, but I am confused as to how to do it.
Thank you!
The gameState.game.add.graphics() returns an object that has a drawRect() method on it.
First you want to check if the gameState.game.add.graphics() was called - this is already done. Then check if the drawRect() was called on the object returned from this method. To do that, set up your spy to return an object that also has a spy on it.
it("create gamescreen background border", function() {
let resultObject = {
drawRect: jasmine.createSpy()
};
gameState.game.add = {
graphics: jasmine.createSpy().and.callFake(() => {
return resultObject;
})
};
createGameScreenBorder(gameState);
expect(gameState.game.add.graphics).toHaveBeenCalled();
expect(resultObject.drawRect).toHaveBeenCalledWith(0, 0, 0, 0);
});

Node Function Scope

I jumped into the deep end recently and have been slowly learning to swim. I'm working on a CLI for building out a simple text game world. That code is becoming a convoluted mess and so I have tried to recreate the error I am getting in a simpler form below.
Try as I might I can't seem to understand the best way to structure all of my functions. In my project I have a parser function that breaks input up and searches for a 'verb' to invoke via a try/catch block. When a verb i.e. 'look' runs it accesses my database module and sends a query based on several parameters to return the description of a room or thing. Because this is all asynchronous virtually everything is wrapped in a promise but I am leaving that out of this example. The following is not the actual project, just a simple recreation of the way I have my objects set up.
APP:
// ***********************
const player = require('./scope_test_player');
player.look();
player.water();
Module1:
// ***********************
const apple_tree = require('./scope_test_apple_tree');
module.exports = {
look: function(){
console.log(
'The apple tree is '+apple_tree.height+'ft tall and has '
+apple_tree.apples+' apples growing on it'
);
},
water: function() {
apple_tree.grow();
}
};
Module2:
// ***********************
const player = require('./scope_test_player');
module.exports = {
height: 10,
nutrition: 0.3,
apples: [],
fertilize: function(number) {
this.nutrition+=number;
},
grow: function() {
this.height+=this.nutrition;
}
};
In the above code I get 'TypeError: apple_tree.grow is not a function' from water or undefined from look. This is the bane of my existence and I have been getting this seemingly at random in my main project which leads me to believe I dont understand scope. I know I can require the module within the function and it will work, but that is hideous and would add hundreds of lines of code by the end. How do I cleanly access the functions of objects from within other objects?
You problem is that have a cyclic dependencies in your project and that you overwrite the exports property of the module. Because of that and the way node cachges required modules, you will get the original module.exports object in scope_test_player file and not the one you have overwritten. To solve that you need to write it that way:
// ***********************
const apple_tree = require('./scope_test_apple_tree');
module.exports.look = function() {
console.log(
'The apple tree is ' + apple_tree.height + 'ft tall and has ' + apple_tree.apples + ' apples growing on it'
);
};
module.exports.water = function() {
apple_tree.grow();
};
And
// ***********************
const player = require('./scope_test_player');
module.exports.height = 10;
module.exports.nutrition = 10;
module.exports.apples = [];
module.exports.fertilize = function(number) {
this.nutrition = +number;
};
module.exports.growth = function() {
this.height = +this.nutrition;
}
But this is a really bad design in gerenal and you should find another way how to solve that. You should always avoid loops/circles in your dependency tree.
UPDATE
In node each file is wrappted into load function in this way:
function moduleLoaderFunction( module, exports /* some other paramteres that are not relavant here*/)
{
// the original code of your file
}
node.js internally does something like this for a require:
var loadedModules = {}
function require(moduleOrFile) {
var resolvedPath = getResolvedPath(moduleOrFile)
if( !loadedModules[resolvedPath] ) {
// if the file was not loaded already create and antry in the loaded modules object
loadedModules[resolvedPath] = {
exports : {}
}
// call the laoded function with the initial values
moduleLoaderFunction(loadedModules[resolvedPath], loadedModules[resolvedPath].exports)
}
return loadedModules[resolvedPath].exports
}
Because of the cyclic require, the require function will return the original cache[resolvedPath].exports, the one that was initially set before you assinged your own object to it.
Is Module1 = scope_test_player and Module2 = scope_test_apple_tree?
Maybe you have a cyclic reference here?
APP requires scope_test_player
// loop
scope_test_player requires scope_test_apple_tree
scope_test_apple_tree requires scope_test_player
// loop
As I can see scope_test_apple_tree doesn't use player.
Can you try to remove:
const player = require('./scope_test_player');
from Module2 ?
There are a few issues to address.
Remove the player require in Module 2(scope_test_apple_tree.js):
const player = require('./scope_test_player')
It doesn't do any damage keeping it there but it's just unnecessary.
Also, replace =+ with += in fertilize and grow which is what I think you are going for.
I was able to run the code natually with those fixes.
If you want to refactor, I'd probably flatten out the require files and do it in the main file controlling the player actions and explicitly name the functions with what is needed to run it (in this case...the tree).
Keeping mostly your coding conventions, my slight refactor would look something like:
index.js
const player = require('./scope_test_player');
const apple_tree = require('./scope_test_apple_tree');
player.lookAtTree(apple_tree);
player.waterTree(apple_tree);
scope_test_player.js
module.exports = {
lookAtTree: function(tree){
console.log(
'The apple tree is '+tree.height+'ft tall and has '
+tree.apples.length+' apples growing on it'
);
},
waterTree: function(tree) {
tree.grow();
console.log('The apple tree grew to', tree.height, 'in height')
}
};
scope_test_apple_tree.js
module.exports = {
height: 10,
nutrition: 0.3,
apples: [],
fertilize: function(number) {
this.nutrition += number;
},
grow: function() {
this.height += this.nutrition;
}
};
Yes, I had circular dependencies in my code because I was unaware of the danger they imposed. When I removed them from the main project sure enough it started working again. It now seems that I'm going to be forced into redesigning the project as having two modules randomly referencing each other is going to cause more problems.

Overriding CanvasRenderingContext2D.getImageData()

I am trying to override the built in method CanvasRenderingContext2D.getImageData(). I would like to override the implementation so that the modified function uses the canvas context to modify the canvas and then calls the original function which should return different data that if the function was not overridden. The reason I am doing this is to prevent browser fingerprinting.
canvas.js
(function(){
'use strict';
var originalGetImageData = CanvasRenderingContext2D.prototype.getImageData;
// This function just adds 1 to each RGBA component in the array for testing.
// Will add random values for the real thing.
function randomiseImageData(image) {
var imageData = image.data;
var imageLength = imageData.length;
for (var i = 0; i < imageLength; i++) {
imageData[i] += 1;
}
var modifiedImage = new ImageData(image.width, image.height);
return modifiedImage;
}
CanvasRenderingContext2D.prototype.getImageData = function(sx, sy, sw, sh) {
console.log("[ALERT] " + window.location.hostname + " called CanvasRenderingContext2D.getImageData()");
const origin = window.location.hostname;
Math.seedrandom(origin);
var image = originalGetImageData.call(this, sx, sy, sw, sh);
return randomiseImageData(image);
};
})();
You are returning a new empty ImageData object.
I guess what you want is to return the filled one.
Since you already modified the data array, you can simply return the original ImageData, your modifications will have been made.
// shortened version
(function(){
const ori = CanvasRenderingContext2D.prototype.getImageData;
CanvasRenderingContext2D.prototype.getImageData = function(){
let imageData = ori.apply(this, arguments);
// modify the Uint8Array
imageData.data.forEach((v, i, a) => a[i]+=1);
// return the now modified ImageData
return imageData;
};
})()
var ctx = document.createElement('canvas').getContext('2d');
console.log(ctx.getImageData(0,0,1,1));
If you really want to create a new ImageData, then it's
new ImageData(imageData, image.width, image.height);
// ^^
// pass the data to fill the new ImageData object
But note that browser support is not great, and that you won't win anything by doing so.
You can not remove the fingerprint.
For more The Web never forgets
You can not circumvent fingerprinting. The best you can do is return the most common fingerprint (which is not easily ascertained) increasing the set of devices you may belong to.
Returning a random set of pixels (or incrementing each pixel channel by one) is about the worst you can do if you are the only one doing it. It would absolutly mark your browser as unique and would let traking software know that the browser that returns changed data is just one, or one of a very small set.
The best way to stop fingerprinting is via a common and widely adopted data return strategy. If every browser returned all zero (transparent black) then there would be no uniqueness and thus no way to track the device based on the canvas.
Canvas fingerprinting is only part of a fingerprint, there are many more sources of data the help identify a device. The browser, browser version, OS, OS version, screen resolution, and a long list of others. Even if you eliminate the canvas as a source of uniqueness it is pointless unless you do the same with the rest of the information.
Mitigation
So with that said the code to return zeroed data is as follows.
(function () {
if (window.CanvasRenderingContext2D) {
const gid = CanvasRenderingContext2D.prototype.getImageData;
CanvasRenderingContext2D.prototype.getImageData = function (x, y, w, h) {
var data = gid.bind(this)(x, y, w, h);
data.data.fill(0); // fill with zero
return data;
}
// Token way to avoid JS from finding out that you have overwritten the prototype overwrite
// the toString method as well (note ctx.getImageData.toString.toString() will
// still show you have changed the prototype but over writing Object.toSting is not worth the problems)
CanvasRenderingContext2D.prototype.getImageData.toString = function () {
return "function getImageData() { [native code] }";
}
}
}());

simple requestAnimationFrame not working JavaScript

I am currently trying to understand animation using JavaScript and HTML5.
From what I have gathered after researching on the internet, requestAnimationFrame repeatedly calls a method.
I have created a plane, and created a method which will move the plane in a diagonal line. However, it there seems to be no animation.
I'm fairly new to this so it could be just me not quite getting the concept. I don't think it is to do with my browser as I have tried it on both chrome and internet explorer, and they should be up to date as I only installed them a few months ago as this is a new laptop.
Here is my main class, it should include all relevent code:
/*global window, document, alert, Vector, Moth, Matrix, Plane, SceneGraphNode*/
function onLoad() {
var mainCanvas, mainContext, planePosition, plane;
// this function will initialise our variables
function initialiseCanvasContext() {
// Find the canvas element using its id attribute.
mainCanvas = document.getElementById('mainCanvas');
// if it couldn't be found
if (!mainCanvas) {
// make a message box pop up with the error.
alert('Error: I cannot find the canvas element!');
return;
}
// Get the 2D canvas context.
mainContext = mainCanvas.getContext('2d');
if (!mainContext) {
alert('Error: failed to get context!');
return;
}
planePosition = new Vector(0, 0, 0);
plane = new Plane(planePosition);
}
function translate(pPosition) {
var matrix = Matrix.createTranslation(pPosition);
matrix.transform(mainContext);
}
function scale(pPosition) {
var matrix = Matrix.createScale(pPosition);
matrix.transform(mainContext);
}
function rotate(pPosition) {
var matrix = Matrix.createRotation(pPosition);
matrix.transform(mainContext);
}
function drawPlane() {
scaleVector = new Vector(0.25, 0.25, 0);
scale(scaleVector);
translate(new Vector(0, 0));
rotate(0);
plane.draw(mainContext);
}
function drawMoth() {
var moth, mothPosition;
mothPosition = new Vector(mainCanvas.width / 2, mainCanvas.height / 2, 0);
moth = new Moth(mothPosition);
moth.draw(mainContext);
}
function drawPlane() {
plane = new Plane(planePosition);
scale(new Vector(0.25, 0.25, 0));
plane.draw(mainContext);
}
function animatePlane() {
translate(planePosition.add(new Vector(100, 100, 0)));
drawPlane();
window.requestAnimationFrame(animatePlane);
}
initialiseCanvasContext();
drawMoth();
animatePlane();
}
window.addEventListener('load', onLoad, false);
Please let me know if you think it would help to see any associated methods. I have also attached the result.
A lot of things are undefined here (the Matrix and Vector objects and their methods). Please include the code if you have it, otherwise keep reading.
It seems you've got a bit of a gap in your Object-Oriented JS knowledge. I'm guessing you know a good chunk of this, but it will be worth your time to solidify your knowledge of the following:
Know how to make a new object. Understand what an object is.
Understand how to make a constructor function.
Understand that using the new keyword simply calls an object's constructor function.
Understand inheritance. The this keyword vs. prototypical inheritance.
Understand encapsulation.
Understand the Canvas API
Add comments to your code. If other people are going to read you're code, please add comments. It helps. All you really need is a single comment per function explaining what it does. Explaining what the input parameters are is useful to.
Here are some resources to get you started (and perhaps finished):
Read this (and it's two prerequisites): http://javascriptissexy.com/oop-in-javascript-what-you-need-to-know/
Check out the built-in Canvas methods: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API
Here are the code structures that were missing
function Vector(x, y, z) {
this.x = x;
this.y = y;
this.z = z;
}
var Matrix = {
createTranslation: function() {
},
createRotation: function() {
},
createScale: function() {
},
transform: function() {
}
};
function Plane(position) {
this.add = function() {
this.draw = function() {
};
};
}
function Moth(position) {
this.draw = function() {
};
}
I know this doesn't answer your question and I'm likely about to be downvoted to hell, but I can't really help you until you understand what you're doing a bit better. Good luck!

Categories

Resources