Add unit tests.
This commit is contained in:
@@ -34,6 +34,7 @@ requirejs.config({
|
||||
"sinon": "xmodule_js/common_static/js/vendor/sinon-1.7.1",
|
||||
"squire": "xmodule_js/common_static/js/vendor/Squire",
|
||||
"jasmine-jquery": "xmodule_js/common_static/js/vendor/jasmine-jquery",
|
||||
"jasmine-imagediff": "xmodule_js/common_static/js/vendor/jasmine-imagediff",
|
||||
"jasmine-stealth": "xmodule_js/common_static/js/vendor/jasmine-stealth",
|
||||
"jasmine.async": "xmodule_js/common_static/js/vendor/jasmine.async",
|
||||
"draggabilly": "xmodule_js/common_static/js/vendor/draggabilly.pkgd",
|
||||
@@ -151,6 +152,9 @@ requirejs.config({
|
||||
"jasmine-jquery": {
|
||||
deps: ["jasmine"]
|
||||
},
|
||||
"jasmine-imagediff": {
|
||||
deps: ["jasmine"]
|
||||
},
|
||||
"jasmine-stealth": {
|
||||
deps: ["jasmine"]
|
||||
},
|
||||
|
||||
@@ -47,6 +47,7 @@ lib_paths:
|
||||
- xmodule_js/common_static/js/vendor/Squire.js
|
||||
- xmodule_js/common_static/js/vendor/jasmine-jquery.js
|
||||
- xmodule_js/common_static/js/vendor/jasmine-stealth.js
|
||||
- xmodule_js/common_static/js/vendor/jasmine-imagediff.js
|
||||
- xmodule_js/common_static/js/vendor/jasmine.async.js
|
||||
- xmodule_js/common_static/js/vendor/jquery.maskedinput.min.js
|
||||
- xmodule_js/common_static/js/vendor/CodeMirror/codemirror.js
|
||||
|
||||
@@ -46,6 +46,7 @@ lib_paths:
|
||||
- xmodule_js/common_static/js/vendor/Squire.js
|
||||
- xmodule_js/common_static/js/vendor/jasmine-jquery.js
|
||||
- xmodule_js/common_static/js/vendor/jasmine-stealth.js
|
||||
- xmodule_js/common_static/js/vendor/jasmine-imagediff.js
|
||||
- xmodule_js/common_static/js/vendor/jasmine.async.js
|
||||
- xmodule_js/common_static/js/vendor/CodeMirror/codemirror.js
|
||||
- xmodule_js/src/xmodule.js
|
||||
|
||||
@@ -36,6 +36,7 @@ lib_paths:
|
||||
- common_static/coffee/src/ajax_prefix.js
|
||||
- common_static/coffee/src/logger.js
|
||||
- common_static/js/vendor/jasmine-jquery.js
|
||||
- common_static/js/vendor/jasmine-imagediff.js
|
||||
- common_static/js/vendor/require.js
|
||||
- RequireJS-namespace-undefine.js
|
||||
- common_static/js/vendor/jquery.min.js
|
||||
|
||||
@@ -303,6 +303,114 @@ describe 'Problem', ->
|
||||
expect($('input#1_2_1_choiceinput_2bc').attr('disabled')).not.toEqual('disabled')
|
||||
expect($('input#1_2_1').attr('disabled')).not.toEqual('disabled')
|
||||
|
||||
describe 'imageinput', ->
|
||||
imageinput_html='''
|
||||
<section class="problem">
|
||||
<div class="imageinput capa_inputtype" id="inputtype_1_2_1">
|
||||
<input class="imageinput" type="hidden" name="input_1_2_1" id="input_1_2_1">
|
||||
<div style="position:relative;">
|
||||
<div id="imageinput_1_2_1">test</div>
|
||||
<div id="answer_1_2_1" data-width="100" data-height="100"></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
'''
|
||||
states = [
|
||||
{
|
||||
desc: 'rectangle is drawn correctly',
|
||||
data: {'rectangle': '(10,10)-(30,30)'}
|
||||
},
|
||||
{
|
||||
desc: 'region is drawn correctly',
|
||||
data: {'regions': '[[10,10],[30,30],[70,30],[20,30]]'}
|
||||
},
|
||||
{
|
||||
desc: 'mixed shapes are drawn correctly',
|
||||
data: {
|
||||
'rectangle': '(10,10)-(30,30);(5,5)-(20,20)',
|
||||
'regions': '''[
|
||||
[[50,50],[40,40],[70,30],[50,70]],
|
||||
[[90,95],[95,95],[90,70],[70,70]]
|
||||
]'''
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
beforeEach ->
|
||||
@problem = new Problem($('.xblock-student_view'))
|
||||
@problem.el.prepend imageinput_html
|
||||
|
||||
stubRequest = (data) =>
|
||||
spyOn($, 'postWithPrefix').andCallFake (url, callback) ->
|
||||
callback answers: "1_2_1": data
|
||||
|
||||
getImage = (coords, c_width, c_height) =>
|
||||
types =
|
||||
rectangle: (coords) =>
|
||||
reg = /^\(([0-9]+),([0-9]+)\)-\(([0-9]+),([0-9]+)\)$/
|
||||
rects = coords.replace(/\s*/g, '').split(/;/)
|
||||
|
||||
$.each rects, (index, rect) =>
|
||||
abs = Math.abs
|
||||
points = reg.exec(rect)
|
||||
if points
|
||||
# width
|
||||
width = abs(points[3] - points[1])
|
||||
# height
|
||||
height = abs(points[4] - points[2])
|
||||
|
||||
ctx.rect(points[1], points[2], width, height)
|
||||
|
||||
ctx.stroke()
|
||||
ctx.fill()
|
||||
|
||||
regions: (coords) =>
|
||||
parseCoords = (coords) =>
|
||||
reg = JSON.parse(coords)
|
||||
|
||||
if typeof reg[0][0][0] == "undefined"
|
||||
reg = [reg]
|
||||
|
||||
return reg
|
||||
|
||||
$.each parseCoords(coords), (index, region) =>
|
||||
ctx.beginPath()
|
||||
$.each region, (index, point) =>
|
||||
if index is 0
|
||||
ctx.moveTo(point[0], point[1])
|
||||
else
|
||||
ctx.lineTo(point[0], point[1]);
|
||||
|
||||
ctx.closePath()
|
||||
ctx.stroke()
|
||||
ctx.fill()
|
||||
|
||||
canvas = document.createElement('canvas')
|
||||
canvas.width = c_width or 100
|
||||
canvas.height = c_height or 100
|
||||
|
||||
if canvas.getContext
|
||||
ctx = canvas.getContext('2d')
|
||||
else
|
||||
return console.log 'Canvas is not supported.'
|
||||
|
||||
ctx.fillStyle = 'rgba(255,255,255,.3)';
|
||||
ctx.strokeStyle = "#FF0000";
|
||||
ctx.lineWidth = "2";
|
||||
|
||||
$.each coords, (key, value) =>
|
||||
types[key](value) if types[key]?
|
||||
|
||||
return canvas
|
||||
|
||||
$.each states, (index, state) =>
|
||||
it state.desc, ->
|
||||
stubRequest(state.data)
|
||||
@problem.show()
|
||||
img = getImage(state.data)
|
||||
|
||||
expect(img).toImageDiffEqual($('canvas')[0])
|
||||
|
||||
describe 'when the answers are already shown', ->
|
||||
beforeEach ->
|
||||
@problem.el.addClass 'showed'
|
||||
@@ -409,4 +517,3 @@ describe 'Problem', ->
|
||||
expect(@problem.answers).toEqual "input_1_1=one&input_1_2=two"
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -162,6 +162,8 @@ beforeEach ->
|
||||
toBeInArray: (array) ->
|
||||
return $.inArray(@.actual, array) > -1
|
||||
|
||||
@addMatchers imagediff.jasmine
|
||||
|
||||
# Stub jQuery.cookie
|
||||
$.cookie = jasmine.createSpy('jQuery.cookie').andReturn '1.0'
|
||||
|
||||
|
||||
@@ -506,18 +506,18 @@ class @Problem
|
||||
parseCoords = (coords) =>
|
||||
reg = JSON.parse(coords)
|
||||
|
||||
if typeof reg[0][0] is undefined
|
||||
if typeof reg[0][0][0] == "undefined"
|
||||
reg = [reg]
|
||||
|
||||
return reg
|
||||
|
||||
$.each parseCoords(coords), (index, regions) =>
|
||||
$.each parseCoords(coords), (index, region) =>
|
||||
ctx.beginPath()
|
||||
$.each regions, (index, points) =>
|
||||
$.each region, (index, point) =>
|
||||
if index is 0
|
||||
ctx.moveTo(points[0], points[1])
|
||||
ctx.moveTo(point[0], point[1])
|
||||
else
|
||||
ctx.lineTo(points[0], points[1]);
|
||||
ctx.lineTo(point[0], point[1]);
|
||||
|
||||
ctx.closePath()
|
||||
ctx.stroke()
|
||||
|
||||
382
common/static/js/vendor/jasmine-imagediff.js
vendored
Normal file
382
common/static/js/vendor/jasmine-imagediff.js
vendored
Normal file
@@ -0,0 +1,382 @@
|
||||
// js-imagediff 1.0.3
|
||||
// (c) 2011-2012 Carl Sutherland, Humble Software
|
||||
// Distributed under the MIT License
|
||||
// For original source and documentation visit:
|
||||
// http://www.github.com/HumbleSoftware/js-imagediff
|
||||
|
||||
(function (name, definition) {
|
||||
var root = this;
|
||||
if (typeof module !== 'undefined') {
|
||||
var Canvas = require('canvas');
|
||||
module.exports = definition(root, name, Canvas);
|
||||
} else if (typeof define === 'function' && typeof define.amd === 'object') {
|
||||
define(definition);
|
||||
} else {
|
||||
root[name] = definition(root, name);
|
||||
}
|
||||
})('imagediff', function (root, name, Canvas) {
|
||||
|
||||
var
|
||||
TYPE_ARRAY = /\[object Array\]/i,
|
||||
TYPE_CANVAS = /\[object (Canvas|HTMLCanvasElement)\]/i,
|
||||
TYPE_CONTEXT = /\[object CanvasRenderingContext2D\]/i,
|
||||
TYPE_IMAGE = /\[object (Image|HTMLImageElement)\]/i,
|
||||
TYPE_IMAGE_DATA = /\[object ImageData\]/i,
|
||||
|
||||
UNDEFINED = 'undefined',
|
||||
|
||||
canvas = getCanvas(),
|
||||
context = canvas.getContext('2d'),
|
||||
previous = root[name],
|
||||
imagediff, jasmine;
|
||||
|
||||
// Creation
|
||||
function getCanvas (width, height) {
|
||||
var
|
||||
canvas = Canvas ?
|
||||
new Canvas() :
|
||||
document.createElement('canvas');
|
||||
if (width) canvas.width = width;
|
||||
if (height) canvas.height = height;
|
||||
return canvas;
|
||||
}
|
||||
function getImageData (width, height) {
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
context.clearRect(0, 0, width, height);
|
||||
return context.createImageData(width, height);
|
||||
}
|
||||
|
||||
|
||||
// Type Checking
|
||||
function isImage (object) {
|
||||
return isType(object, TYPE_IMAGE);
|
||||
}
|
||||
function isCanvas (object) {
|
||||
return isType(object, TYPE_CANVAS);
|
||||
}
|
||||
function isContext (object) {
|
||||
return isType(object, TYPE_CONTEXT);
|
||||
}
|
||||
function isImageData (object) {
|
||||
return !!(object &&
|
||||
isType(object, TYPE_IMAGE_DATA) &&
|
||||
typeof(object.width) !== UNDEFINED &&
|
||||
typeof(object.height) !== UNDEFINED &&
|
||||
typeof(object.data) !== UNDEFINED);
|
||||
}
|
||||
function isImageType (object) {
|
||||
return (
|
||||
isImage(object) ||
|
||||
isCanvas(object) ||
|
||||
isContext(object) ||
|
||||
isImageData(object)
|
||||
);
|
||||
}
|
||||
function isType (object, type) {
|
||||
return typeof (object) === 'object' && !!Object.prototype.toString.apply(object).match(type);
|
||||
}
|
||||
|
||||
|
||||
// Type Conversion
|
||||
function copyImageData (imageData) {
|
||||
var
|
||||
height = imageData.height,
|
||||
width = imageData.width,
|
||||
data = imageData.data,
|
||||
newImageData, newData, i;
|
||||
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
newImageData = context.getImageData(0, 0, width, height);
|
||||
newData = newImageData.data;
|
||||
|
||||
for (i = imageData.data.length; i--;) {
|
||||
newData[i] = data[i];
|
||||
}
|
||||
|
||||
return newImageData;
|
||||
}
|
||||
function toImageData (object) {
|
||||
if (isImage(object)) { return toImageDataFromImage(object); }
|
||||
if (isCanvas(object)) { return toImageDataFromCanvas(object); }
|
||||
if (isContext(object)) { return toImageDataFromContext(object); }
|
||||
if (isImageData(object)) { return object; }
|
||||
}
|
||||
function toImageDataFromImage (image) {
|
||||
var
|
||||
height = image.height,
|
||||
width = image.width;
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
context.clearRect(0, 0, width, height);
|
||||
context.drawImage(image, 0, 0);
|
||||
return context.getImageData(0, 0, width, height);
|
||||
}
|
||||
function toImageDataFromCanvas (canvas) {
|
||||
var
|
||||
height = canvas.height,
|
||||
width = canvas.width,
|
||||
context = canvas.getContext('2d');
|
||||
return context.getImageData(0, 0, width, height);
|
||||
}
|
||||
function toImageDataFromContext (context) {
|
||||
var
|
||||
canvas = context.canvas,
|
||||
height = canvas.height,
|
||||
width = canvas.width;
|
||||
return context.getImageData(0, 0, width, height);
|
||||
}
|
||||
function toCanvas (object) {
|
||||
var
|
||||
data = toImageData(object),
|
||||
canvas = getCanvas(data.width, data.height),
|
||||
context = canvas.getContext('2d');
|
||||
|
||||
context.putImageData(data, 0, 0);
|
||||
return canvas;
|
||||
}
|
||||
|
||||
|
||||
// ImageData Equality Operators
|
||||
function equalWidth (a, b) {
|
||||
return a.width === b.width;
|
||||
}
|
||||
function equalHeight (a, b) {
|
||||
return a.height === b.height;
|
||||
}
|
||||
function equalDimensions (a, b) {
|
||||
return equalHeight(a, b) && equalWidth(a, b);
|
||||
}
|
||||
function equal (a, b, tolerance) {
|
||||
|
||||
var
|
||||
aData = a.data,
|
||||
bData = b.data,
|
||||
length = aData.length,
|
||||
i;
|
||||
|
||||
tolerance = tolerance || 0;
|
||||
|
||||
if (!equalDimensions(a, b)) return false;
|
||||
for (i = length; i--;) if (aData[i] !== bData[i] && Math.abs(aData[i] - bData[i]) > tolerance) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Diff
|
||||
function diff (a, b) {
|
||||
return (equalDimensions(a, b) ? diffEqual : diffUnequal)(a, b);
|
||||
}
|
||||
function diffEqual (a, b) {
|
||||
|
||||
var
|
||||
height = a.height,
|
||||
width = a.width,
|
||||
c = getImageData(width, height), // c = a - b
|
||||
aData = a.data,
|
||||
bData = b.data,
|
||||
cData = c.data,
|
||||
length = cData.length,
|
||||
row, column,
|
||||
i, j, k, v;
|
||||
|
||||
for (i = 0; i < length; i += 4) {
|
||||
cData[i] = Math.abs(aData[i] - bData[i]);
|
||||
cData[i+1] = Math.abs(aData[i+1] - bData[i+1]);
|
||||
cData[i+2] = Math.abs(aData[i+2] - bData[i+2]);
|
||||
cData[i+3] = Math.abs(255 - Math.abs(aData[i+3] - bData[i+3]));
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
function diffUnequal (a, b) {
|
||||
|
||||
var
|
||||
height = Math.max(a.height, b.height),
|
||||
width = Math.max(a.width, b.width),
|
||||
c = getImageData(width, height), // c = a - b
|
||||
aData = a.data,
|
||||
bData = b.data,
|
||||
cData = c.data,
|
||||
rowOffset,
|
||||
columnOffset,
|
||||
row, column,
|
||||
i, j, k, v;
|
||||
|
||||
|
||||
for (i = cData.length - 1; i > 0; i = i - 4) {
|
||||
cData[i] = 255;
|
||||
}
|
||||
|
||||
// Add First Image
|
||||
offsets(a);
|
||||
for (row = a.height; row--;){
|
||||
for (column = a.width; column--;) {
|
||||
i = 4 * ((row + rowOffset) * width + (column + columnOffset));
|
||||
j = 4 * (row * a.width + column);
|
||||
cData[i+0] = aData[j+0]; // r
|
||||
cData[i+1] = aData[j+1]; // g
|
||||
cData[i+2] = aData[j+2]; // b
|
||||
// cData[i+3] = aData[j+3]; // a
|
||||
}
|
||||
}
|
||||
|
||||
// Subtract Second Image
|
||||
offsets(b);
|
||||
for (row = b.height; row--;){
|
||||
for (column = b.width; column--;) {
|
||||
i = 4 * ((row + rowOffset) * width + (column + columnOffset));
|
||||
j = 4 * (row * b.width + column);
|
||||
cData[i+0] = Math.abs(cData[i+0] - bData[j+0]); // r
|
||||
cData[i+1] = Math.abs(cData[i+1] - bData[j+1]); // g
|
||||
cData[i+2] = Math.abs(cData[i+2] - bData[j+2]); // b
|
||||
}
|
||||
}
|
||||
|
||||
// Helpers
|
||||
function offsets (imageData) {
|
||||
rowOffset = Math.floor((height - imageData.height) / 2);
|
||||
columnOffset = Math.floor((width - imageData.width) / 2);
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
// Validation
|
||||
function checkType () {
|
||||
var i;
|
||||
for (i = 0; i < arguments.length; i++) {
|
||||
if (!isImageType(arguments[i])) {
|
||||
throw {
|
||||
name : 'ImageTypeError',
|
||||
message : 'Submitted object was not an image.'
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Jasmine Matchers
|
||||
function get (element, content) {
|
||||
element = document.createElement(element);
|
||||
if (element && content) {
|
||||
element.innerHTML = content;
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
jasmine = {
|
||||
|
||||
toBeImageData : function () {
|
||||
return imagediff.isImageData(this.actual);
|
||||
},
|
||||
|
||||
toImageDiffEqual : function (expected, tolerance) {
|
||||
|
||||
if (typeof (document) !== UNDEFINED) {
|
||||
this.message = function () {
|
||||
var
|
||||
div = get('div'),
|
||||
a = get('div', '<div>Actual:</div>'),
|
||||
b = get('div', '<div>Expected:</div>'),
|
||||
c = get('div', '<div>Diff:</div>'),
|
||||
diff = imagediff.diff(this.actual, expected),
|
||||
canvas = getCanvas(),
|
||||
context;
|
||||
|
||||
canvas.height = diff.height;
|
||||
canvas.width = diff.width;
|
||||
|
||||
div.style.overflow = 'hidden';
|
||||
a.style.float = 'left';
|
||||
b.style.float = 'left';
|
||||
c.style.float = 'left';
|
||||
|
||||
context = canvas.getContext('2d');
|
||||
context.putImageData(diff, 0, 0);
|
||||
|
||||
a.appendChild(toCanvas(this.actual));
|
||||
b.appendChild(toCanvas(expected));
|
||||
c.appendChild(canvas);
|
||||
|
||||
div.appendChild(a);
|
||||
div.appendChild(b);
|
||||
div.appendChild(c);
|
||||
|
||||
return [
|
||||
div,
|
||||
"Expected not to be equal."
|
||||
];
|
||||
};
|
||||
}
|
||||
|
||||
return imagediff.equal(this.actual, expected, tolerance);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Image Output
|
||||
function imageDataToPNG (imageData, outputFile, callback) {
|
||||
|
||||
var
|
||||
canvas = toCanvas(imageData),
|
||||
base64Data,
|
||||
decodedImage;
|
||||
|
||||
callback = callback || Function;
|
||||
|
||||
base64Data = canvas.toDataURL().replace(/^data:image\/\w+;base64,/,"");
|
||||
decodedImage = new Buffer(base64Data, 'base64');
|
||||
require('fs').writeFile(outputFile, decodedImage, callback);
|
||||
}
|
||||
|
||||
|
||||
// Definition
|
||||
imagediff = {
|
||||
|
||||
createCanvas : getCanvas,
|
||||
createImageData : getImageData,
|
||||
|
||||
isImage : isImage,
|
||||
isCanvas : isCanvas,
|
||||
isContext : isContext,
|
||||
isImageData : isImageData,
|
||||
isImageType : isImageType,
|
||||
|
||||
toImageData : function (object) {
|
||||
checkType(object);
|
||||
if (isImageData(object)) { return copyImageData(object); }
|
||||
return toImageData(object);
|
||||
},
|
||||
|
||||
equal : function (a, b, tolerance) {
|
||||
checkType(a, b);
|
||||
a = toImageData(a);
|
||||
b = toImageData(b);
|
||||
return equal(a, b, tolerance);
|
||||
},
|
||||
diff : function (a, b) {
|
||||
checkType(a, b);
|
||||
a = toImageData(a);
|
||||
b = toImageData(b);
|
||||
return diff(a, b);
|
||||
},
|
||||
|
||||
jasmine : jasmine,
|
||||
|
||||
// Compatibility
|
||||
noConflict : function () {
|
||||
root[name] = previous;
|
||||
return imagediff;
|
||||
}
|
||||
};
|
||||
|
||||
if (typeof module !== 'undefined') {
|
||||
imagediff.imageDataToPNG = imageDataToPNG;
|
||||
}
|
||||
|
||||
return imagediff;
|
||||
});
|
||||
@@ -30,6 +30,7 @@ prepend_path: common/static
|
||||
lib_paths:
|
||||
- js/vendor/jquery.min.js
|
||||
- js/vendor/jasmine-jquery.js
|
||||
- js/vendor/jasmine-imagediff.js
|
||||
- js/vendor/underscore-min.js
|
||||
- js/vendor/backbone-min.js
|
||||
- js/vendor/jquery.timeago.js
|
||||
|
||||
@@ -31,6 +31,7 @@ lib_paths:
|
||||
- xmodule_js/common_static/coffee/src/ajax_prefix.js
|
||||
- xmodule_js/common_static/coffee/src/logger.js
|
||||
- xmodule_js/common_static/js/vendor/jasmine-jquery.js
|
||||
- xmodule_js/common_static/js/vendor/jasmine-imagediff.js
|
||||
- xmodule_js/common_static/js/vendor/require.js
|
||||
- js/RequireJS-namespace-undefine.js
|
||||
- xmodule_js/common_static/js/vendor/jquery.min.js
|
||||
|
||||
Reference in New Issue
Block a user