Adding jasmine tests; code cleanup.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<section id="inputtype_${id}" class="jsinput"
|
||||
data="${gradefn}"
|
||||
<section id="inputtype_${id}" class="jsinput"
|
||||
data="${gradefn}"
|
||||
% if saved_state:
|
||||
data-stored="${saved_state|x}"
|
||||
% endif
|
||||
@@ -11,7 +11,7 @@
|
||||
% endif
|
||||
>
|
||||
|
||||
|
||||
|
||||
<div class="script_placeholder" data-src="${applet_loader}"/>
|
||||
% if status == 'unsubmitted':
|
||||
<div class="unanswered" id="status_${id}">
|
||||
@@ -23,8 +23,8 @@
|
||||
<div class="incorrect" id="status_${id}">
|
||||
% endif
|
||||
|
||||
<iframe name="iframe_${id}"
|
||||
id="iframe_${id}"
|
||||
<iframe name="iframe_${id}"
|
||||
id="iframe_${id}"
|
||||
sandbox="allow-scripts allow-popups allow-same-origin allow-forms allow-pointer-lock"
|
||||
seamless="seamless"
|
||||
frameborder="0"
|
||||
@@ -32,7 +32,7 @@
|
||||
height="${height}"
|
||||
width="${width}"
|
||||
/>
|
||||
<input type="hidden" name="input_${id}" id="input_${id}"
|
||||
<input type="hidden" name="input_${id}" id="input_${id}"
|
||||
waitfor=""
|
||||
value="${value|h}"/>
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
% if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']:
|
||||
</div>
|
||||
% endif
|
||||
|
||||
|
||||
% if msg:
|
||||
<span class="message">${msg|n}</span>
|
||||
% endif
|
||||
|
||||
@@ -16,8 +16,16 @@ h2 {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
iframe[seamless]{
|
||||
background-color: transparent;
|
||||
border: 0px none transparent;
|
||||
padding: 0px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.inline-error {
|
||||
color: darken($error-red, 10%);
|
||||
color: darken($error-red, 11%);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -142,7 +142,7 @@ class @Problem
|
||||
# off @answers
|
||||
check_waitfor: =>
|
||||
for inp in @inputs
|
||||
if ($(inp).attr("waitfor")?)
|
||||
if ($(inp).is("input[waitfor]"))
|
||||
try
|
||||
$(inp).data("waitfor")()
|
||||
catch e
|
||||
|
||||
@@ -4,45 +4,66 @@
|
||||
// most relevantly, jquery). Keep in mind what happens in which context
|
||||
// when modifying this file.
|
||||
|
||||
/* Check whether there is anything to be done */
|
||||
|
||||
// When all the problems are first loaded, we want to make sure the
|
||||
// constructor only runs once for each iframe; but we also want to make
|
||||
// sure that if part of the page is reloaded (e.g., a problem is
|
||||
// submitted), the constructor is called again.
|
||||
|
||||
if (!jsinput) {
|
||||
console.log("hi");
|
||||
jsinput = {
|
||||
runs : 1,
|
||||
arr : [],
|
||||
exists : function(id) {
|
||||
jsinput.arr.filter(function(e, i, a) {
|
||||
return e.id = id;
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
jsinput.runs++;
|
||||
|
||||
|
||||
if ($(document).find('section[class="jsinput"]').length > jsinput.runs) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/* Utils */
|
||||
|
||||
|
||||
jsinput._DEBUG = jsinput._DEBUG || true;
|
||||
|
||||
var debuglog = function(text) { if (jsinput._DEBUG) { console.log(text);}};
|
||||
|
||||
var eqTimeout = function(fn, pred, time, max) {
|
||||
var i = 0;
|
||||
while (pred(fn) && i < max) {
|
||||
setTimeout(fn, time);
|
||||
}
|
||||
return fn;
|
||||
};
|
||||
|
||||
var isUndef = function (e) { return (typeof(e) === 'undefined'); };
|
||||
|
||||
// _deepKey and _ctxCall are helper functions used to ensure that gradefn
|
||||
// etc. can be nested objects (e.g., "firepad.getText") and that when
|
||||
// called they receive the appropriate objects as "this" (e.g., "firepad").
|
||||
|
||||
// Take a string and find the nested object that corresponds to it. E.g.:
|
||||
// deepKey(obj, "an.example") -> obj["an"]["example"]
|
||||
var _deepKey = function(obj, path){
|
||||
for (var i = 0, path=path.split('.'), len = path.length; i < len; i++){
|
||||
obj = obj[path[i]];
|
||||
for (var i = 0, p=path.split('.'), len = p.length; i < len; i++){
|
||||
obj = obj[p[i]];
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
|
||||
var _ctxCall = function(obj, fn) {
|
||||
var func = _deepKey(obj, fn);
|
||||
var oldthis = fn.split('.');
|
||||
oldthis.pop();
|
||||
oldthis = oldthis.join();
|
||||
var newthis = _deepKey(obj, oldthis);
|
||||
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
args = args.slice(2, args.length);
|
||||
/* END Utils */
|
||||
|
||||
|
||||
return func.apply(newthis, args);
|
||||
};
|
||||
|
||||
// First time this function was called?
|
||||
var isFirst = typeof(jsinput.jsinputarr) != 'undefined';
|
||||
|
||||
// Use this array to keep track of the elements that have already been
|
||||
// initialized.
|
||||
jsinput.jsinputarr = jsinput.jsinputarr || [];
|
||||
jsinput.jsinputarr.exists = function (id) {
|
||||
this.filter(function(e, i, a) {
|
||||
return e.id = id;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
function jsinputConstructor(spec) {
|
||||
// Define an class that will be instantiated for each jsinput element
|
||||
@@ -55,70 +76,62 @@
|
||||
/* Private methods */
|
||||
|
||||
var sect = $(spec.elem).parent().find('section[class="jsinput"]');
|
||||
// Get the hidden input field to pass to customresponse
|
||||
function inputfield() {
|
||||
var parent = $(spec.elem).parent();
|
||||
return parent.find('input[id^="input_"]');
|
||||
}
|
||||
|
||||
// For the state and grade functions below, use functions instead of
|
||||
// storing their return values since we might need to call them
|
||||
// repeatedly, and they might change (e.g., they might not be defined
|
||||
// when we first try calling them).
|
||||
|
||||
// Get the grade function name
|
||||
function getgradefn() {
|
||||
return $(sect).attr("data");
|
||||
}
|
||||
|
||||
// Get state getter
|
||||
function getgetstate() {
|
||||
return $(sect).attr("data-getstate");
|
||||
}
|
||||
// Get state setter
|
||||
function getsetstate() {
|
||||
var gss = $(sect).attr("data-setstate");
|
||||
return gss;
|
||||
}
|
||||
// Get stored state
|
||||
function getstoredstate() {
|
||||
return $(sect).attr("data-stored");
|
||||
}
|
||||
|
||||
var sectattr = function (e) { return $(sect).attr(e); };
|
||||
var thisIFrame = $(spec.elem).
|
||||
find('iframe[name^="iframe_"]').
|
||||
get(0);
|
||||
|
||||
var cWindow = thisIFrame.contentWindow;
|
||||
|
||||
// Get the hidden input field to pass to customresponse
|
||||
function _inputfield() {
|
||||
var parent = $(spec.elem).parent();
|
||||
return parent.find('input[id^="input_"]');
|
||||
}
|
||||
var inputfield = _inputfield();
|
||||
|
||||
// Get the grade function name
|
||||
var getgradefn = sectattr("data");
|
||||
// Get state getter
|
||||
var getgetstate = sectattr("data-getstate");
|
||||
// Get state setter
|
||||
var getsetstate = sectattr("data-setstate");
|
||||
// Get stored state
|
||||
var getstoredstate = sectattr("data-stored");
|
||||
|
||||
|
||||
|
||||
// Put the return value of gradefn in the hidden inputfield.
|
||||
// If passed an argument, does not call gradefn, and instead directly
|
||||
// updates the inputfield with the passed value.
|
||||
var update = function (answer) {
|
||||
|
||||
var ans;
|
||||
ans = _ctxCall(cWindow, gradefn);
|
||||
ans = _deepKey(cWindow, gradefn);
|
||||
// setstate presumes getstate, so don't getstate unless setstate is
|
||||
// defined.
|
||||
if (getgetstate() && getsetstate()) {
|
||||
if (getgetstate && getsetstate) {
|
||||
var state, store;
|
||||
state = _ctxCall(cWindow, getgetstate());
|
||||
state = _deepKey(cWindow, getgetstate);
|
||||
store = {
|
||||
answer: ans,
|
||||
state: state
|
||||
};
|
||||
inputfield().val(JSON.stringify(store));
|
||||
|
||||
debuglog("Store: " + store);
|
||||
inputfield.val(JSON.stringify(store));
|
||||
} else {
|
||||
inputfield().val(ans);
|
||||
inputfield.val(ans);
|
||||
debuglog("Answer: " + ans);
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
// Find the update button, and bind the update function to its click
|
||||
// event.
|
||||
function updateHandler() {
|
||||
function bindUpdate() {
|
||||
var updatebutton = $(spec.elem).
|
||||
find('button[class="update"]').get(0);
|
||||
find('button[class="update"]').
|
||||
get(0);
|
||||
$(updatebutton).click(update);
|
||||
}
|
||||
|
||||
@@ -130,111 +143,93 @@
|
||||
|
||||
/* Initialization */
|
||||
|
||||
jsinput.jsinputarr.push(that);
|
||||
jsinput.arr.push(that);
|
||||
|
||||
// Put the update function as the value of the inputfield's "waitfor"
|
||||
// attribute so that it is called when the check button is clicked.
|
||||
function bindCheck() {
|
||||
inputfield().data('waitfor', that.update);
|
||||
debuglog("Update function: " + that.update);
|
||||
inputfield.data('waitfor', that.update);
|
||||
return;
|
||||
}
|
||||
|
||||
var gradefn = getgradefn();
|
||||
var gradefn = getgradefn;
|
||||
debuglog("Gradefn: " + gradefn);
|
||||
|
||||
if (spec.passive === false) {
|
||||
updateHandler();
|
||||
bindCheck();
|
||||
// Check whether application takes in state and there is a saved
|
||||
// state to give it. If getsetstate is specified but calling it
|
||||
// fails, wait and try again, since the iframe might still be
|
||||
// loading.
|
||||
if (getsetstate() && getstoredstate()) {
|
||||
var sval;
|
||||
if (typeof(getstoredstate()) === "object") {
|
||||
sval = getstoredstate()["state"];
|
||||
} else {
|
||||
sval = getstoredstate();
|
||||
}
|
||||
function whileloop(n) {
|
||||
if (n < 10){
|
||||
try {
|
||||
_ctxCall(cWindow, getsetstate(), sval);
|
||||
} catch (err) {
|
||||
setTimeout(whileloop(n+1), 200);
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.log("Error: could not set state");
|
||||
}
|
||||
}
|
||||
whileloop(0);
|
||||
|
||||
}
|
||||
// If there is a separate "Update" button, bind update to it.
|
||||
bindUpdate();
|
||||
} else {
|
||||
// NOT CURRENTLY SUPPORTED
|
||||
// If set up to passively receive updates (intercept a function's
|
||||
// return value whenever the function is called) add an event
|
||||
// listener that listens to messages that match "that"'s id.
|
||||
// Decorate the iframe gradefn with updateDecorator.
|
||||
iframe.contentWindow[gradefn] = updateDecorator(iframe.contentWindow[gradefn]);
|
||||
iframe.contentWindow.addEventListener('message', function (e) {
|
||||
var id = e.data[0],
|
||||
msg = e.data[1];
|
||||
if (id === spec.id) { update(msg); }
|
||||
});
|
||||
// Otherwise, bind update to the check button.
|
||||
bindCheck();
|
||||
}
|
||||
|
||||
bindCheck();
|
||||
|
||||
// Check whether application takes in state and there is a saved
|
||||
// state to give it. If getsetstate is specified but calling it
|
||||
// fails, wait and try again, since the iframe might still be
|
||||
// loading.
|
||||
if (getsetstate && getstoredstate) {
|
||||
var sval;
|
||||
if (typeof(getstoredstate) === "object") {
|
||||
sval = getstoredstate["state"];
|
||||
} else {
|
||||
sval = getstoredstate;
|
||||
}
|
||||
|
||||
debuglog("Stored state: "+ sval);
|
||||
debuglog("Set_statefn: " + getsetstate);
|
||||
|
||||
function whileloop(n) {
|
||||
if (n < 10){
|
||||
try {
|
||||
_deepKey(cWindow, getsetstate)(sval);
|
||||
} catch (err) {
|
||||
setTimeout(whileloop(n+1), 200);
|
||||
}
|
||||
}
|
||||
else {
|
||||
debuglog("Error: could not set state");
|
||||
_deepKey(cWindow, getsetstate)(sval);
|
||||
}
|
||||
}
|
||||
whileloop(0);
|
||||
|
||||
}
|
||||
|
||||
|
||||
return that;
|
||||
}
|
||||
|
||||
function updateDecorator(fn, id) {
|
||||
// NOT CURRENTLY SUPPORTED
|
||||
// Simple function decorator that posts the output of a function to the
|
||||
// parent iframe before returning the original function's value.
|
||||
// Can be used to decorate one or more gradefn (instead of using an
|
||||
// explicit "Update" button) when gradefn is automatically called as part
|
||||
// of an application's natural behavior.
|
||||
// The id argument is used to specify which of the instances of jsinput on
|
||||
// the parent page the message is being posted to.
|
||||
return function () {
|
||||
var result = fn.apply(null, arguments);
|
||||
window.parent.contentWindow.postMessage([id, result], document.referrer);
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
function walkDOM() {
|
||||
// Find all jsinput elements, and create a jsinput object for each one
|
||||
var all = $(document).find('section[class="jsinput"]');
|
||||
var newid;
|
||||
all.each(function() {
|
||||
|
||||
// Find all jsinput elements, and create a jsinput object for each one
|
||||
var all = $(document).find('section[class="jsinput"]');
|
||||
|
||||
all.each(function(index, value) {
|
||||
// Get just the mako variable 'id' from the id attribute
|
||||
newid = $(this).attr("id").replace(/^inputtype_/, "");
|
||||
if (! jsinput.jsinputarr.exists(newid)){
|
||||
newid = $(value).attr("id").replace(/^inputtype_/, "");
|
||||
|
||||
|
||||
if (!jsinput.exists(newid)){
|
||||
var newJsElem = jsinputConstructor({
|
||||
id: newid,
|
||||
elem: this,
|
||||
passive: false
|
||||
elem: value,
|
||||
passive: true
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Inject css into, and retrieve frame size from, the iframe (for non
|
||||
// "seamless"-supporting browsers).
|
||||
//var iframeInjection = {
|
||||
//injectStyles : function (style) {
|
||||
//$(document.body).css(style);
|
||||
//},
|
||||
//sendMySize : function () {
|
||||
//var height = html.height,
|
||||
//width = html.width;
|
||||
//window.parent.postMessage(['height', height], '*');
|
||||
//window.parent.postMessage(['width', width], '*');
|
||||
//}
|
||||
//};
|
||||
// This is ugly, but without a timeout pages with multiple/heavy jsinputs
|
||||
// don't load properly.
|
||||
if ($.isReady) {
|
||||
setTimeout(walkDOM, 1000);
|
||||
} else {
|
||||
$(document).ready(setTimeout(walkDOM, 1000));
|
||||
}
|
||||
|
||||
|
||||
setTimeout(walkDOM, 100);
|
||||
})(window.jsinput = window.jsinput || {})
|
||||
})(window.jsinput = window.jsinput || false);
|
||||
|
||||
71
common/static/js/capa/spec/jsinput/jsinput.js
Normal file
71
common/static/js/capa/spec/jsinput/jsinput.js
Normal file
@@ -0,0 +1,71 @@
|
||||
describe("A jsinput has:", function () {
|
||||
|
||||
beforeEach(function () {
|
||||
$('#fixture').remove();
|
||||
$.ajax({
|
||||
async: false,
|
||||
url: 'mainfixture.html',
|
||||
success: function(data) {
|
||||
$('body').append($(data));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("The ctxCall function", function() {
|
||||
it("Evaluatates nested-object functions", function() {
|
||||
var ctxTest = {
|
||||
ctxFn : function () {
|
||||
return this.name ;
|
||||
}
|
||||
};
|
||||
var fnString = "nest.ctxFn";
|
||||
var holder = {};
|
||||
holder.nest = ctxTest;
|
||||
var fn = _ctxCall(holder, fnString);
|
||||
expect(fnString).toBe(holder.nest.ctxFn());
|
||||
});
|
||||
|
||||
it("Throws an exception when the object does not exits", function () {
|
||||
var notObj = _ctxCall("twas", "brilling");
|
||||
expect(notObj).toThrow();
|
||||
});
|
||||
|
||||
it("Throws an exception when the function does not exist", function () {
|
||||
var anobj = {};
|
||||
var notFn = _ctxCall("anobj", "brillig");
|
||||
expect(notFn).toThrow();
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
describe("The jsinput constructor", function(){
|
||||
var testJsElem = jsinputConstructor({
|
||||
id: 3781,
|
||||
elem: "<div id='abc'> a div </div>",
|
||||
passive: false
|
||||
});
|
||||
|
||||
it("Returns an object", function(){
|
||||
expect(typeof(testJsElem)).toEqual('object');
|
||||
});
|
||||
|
||||
it("Adds the object to the jsinput array", function() {
|
||||
expect(jsinput.jsinputarr.exists(3781)).toBe(true);
|
||||
});
|
||||
|
||||
describe("The returned object", function() {
|
||||
|
||||
it("Has a public 'update' method", function(){
|
||||
expect(testJsElem.update).toBeDefined();
|
||||
});
|
||||
|
||||
it("Changes the parent's inputfield", function() {
|
||||
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
)
|
||||
@@ -1,16 +0,0 @@
|
||||
describe("jsinput test", function () {
|
||||
|
||||
beforeEach(function () {
|
||||
$('#fixture').remove();
|
||||
$.ajax({
|
||||
async: false,
|
||||
url: 'mainfixture.html',
|
||||
success: function(data) {
|
||||
$('body').append($(data));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("")
|
||||
}
|
||||
)
|
||||
Reference in New Issue
Block a user