Commit 9a2ca855 authored by Romain Courteaud's avatar Romain Courteaud

Use promise a+ library to correctly propagate error.

Replace jQuery by RSVP.
Allow to declareGadget without attaching it to the DOM.
Correctly handle error.

IFrame are currently broken.
parent 9b2a662b
......@@ -16,12 +16,14 @@ all: external lint test build doc
external: lib/sinon/sinon.js \
lib/sinon/sinon-qunit.js \
lib/jquery/jquery.js \
lib/rsvp/rsvp.js \
lib/jschannel/jschannel.js \
lib/require/require.js \
lib/qunit/qunit.js \
lib/qunit/qunit.css \
lib/jio/jio.js \
lib/jio/md5.js \
lib/jio/sha256.js \
lib/jio/complex_queries.js \
lib/jio/localstorage.js
......@@ -31,13 +33,17 @@ lib/sinon/sinon.js:
lib/sinon/sinon-qunit.js:
@mkdir -p $(@D)
# curl -s -o $@ http://sinonjs.org/releases/sinon-qunit-1.0.0.js
curl -s -o $@ https://raw.github.com/jfromaniello/jmail/master/scripts/Tests/sinon-qunit-1.0.0.js
curl -s -o $@ http://sinonjs.org/releases/sinon-qunit-1.0.0.js
# curl -s -o $@ https://raw.github.com/jfromaniello/jmail/master/scripts/Tests/sinon-qunit-1.0.0.js
lib/jquery/jquery.js:
@mkdir -p $(@D)
curl -s -o $@ http://code.jquery.com/jquery-2.0.3.js
lib/rsvp/rsvp.js:
@mkdir -p $(@D)
curl -s -o $@ http://git.erp5.org/gitweb/rsvp.js.git/blob_plain/HEAD:/dist/rsvp-2.0.4.js
lib/jschannel/jschannel.js:
@mkdir -p $(@D)
curl -s -o $@ http://mozilla.github.io/jschannel/src/jschannel.js
......@@ -56,15 +62,19 @@ lib/jio/jio.js:
lib/jio/md5.js:
@mkdir -p $(@D)
curl -s -o $@ http://git.erp5.org/gitweb/jio.git/blob_plain/refs/heads/master:/lib/md5/md5.js
curl -s -o $@ http://git.erp5.org/gitweb/jio.git/blob_plain/HEAD:/src/md5.amd.js
lib/jio/sha256.js:
@mkdir -p $(@D)
curl -s -o $@ http://git.erp5.org/gitweb/jio.git/blob_plain/HEAD:/src/sha256.amd.js
lib/jio/localstorage.js:
@mkdir -p $(@D)
curl -s -o $@ http://git.erp5.org/gitweb/jio.git/blob_plain/refs/heads/master:/src/jio.storage/localstorage.js
curl -s -o $@ http://git.erp5.org/gitweb/jio.git/blob_plain/HEAD:/src/jio.storage/localstorage.js
lib/jio/complex_queries.js:
@mkdir -p $(@D)
curl -s -o $@ http://git.erp5.org/gitweb/jio.git/blob_plain/refs/heads/master:/complex_queries.js
curl -s -o $@ http://git.erp5.org/gitweb/jio.git/blob_plain/HEAD:/complex_queries.js
$(RENDERJS_MIN): $(RENDERJS)
$(UGLIFY_CMD) "$<" > "$@"
......@@ -88,4 +98,4 @@ lint: ${BUILDDIR}/$(RENDERJS).lint
doc:
$(YUIDOC_CMD) .
clean:
rm -rf $(RENDERJS_MIN) ${BUILDDIR} lib/sinon lib/jquery lib/jschannel lib/qunit lib/jio lib/require
rm -rf $(RENDERJS_MIN) ${BUILDDIR} lib/sinon lib/jquery lib/jschannel lib/qunit lib/jio lib/require lib/rsvp
# npm install uglify-js
UGLIFY_CMD = $(shell which uglifyjs || echo node ~/node_modules/uglify-js/bin/uglifyjs)
UGLIFY_CMD = ~/node_modules/.bin/uglifyjs
# npm install jslint
LINT_CMD = $(shell which jslint || echo node ~/node_modules/jslint/bin/jslint.js) --terse
LINT_CMD = /home/romain/devel/nowm/gidzit/node_modules/.bin/jslint
YUIDOC_CMD = $(shell which yuidoc)
PHANTOMJS_CMD = xvfb-run phantomjs
......@@ -3,7 +3,7 @@
<head>
<title>Ace Editor</title>
<script src="./ace/ace.js" type="text/javascript" charset="utf-8"></script>
<script src="../../lib/jquery/jquery.js" type="text/javascript"></script>
<script src="../../lib/rsvp/rsvp.js" type="text/javascript"></script>
<script src="../../lib/jschannel/jschannel.js" type="text/javascript"></script>
<script src="../../renderjs.js" type="text/javascript"></script>
<script src="aceeditor.js" type="text/javascript"></script>
......
/*global window, jQuery, rJS, ace */
/*global window, rJS, ace */
"use strict";
(function (window, $, rJS) {
(function (window, rJS) {
var gk = rJS(window);
gk.declareMethod('setContent', function (value) {
rJS(this).editor.getSession().setValue(value);
// return rJS(this).context.find('textarea').val(escape_text(value));
})
.declareMethod('getContent', function () {
return rJS(this).editor.getSession().getValue();
// return rJS(this).context.find('textarea').val();
});
gk.ready(function () {
var g = rJS(this);
g.editor = ace.edit("editor");
gk.ready(function (g) {
g.editor = ace.edit(g.element.getElementsByTagName('div')[0]);
g.editor.setTheme("ace/theme/monokai");
// g.context.find("textarea").jqte();
// editor.setTheme("ace/theme/twilight");
// editor.getSession().setMode("ace/mode/javascript");
});
}(window, jQuery, rJS, ace));
}(window, rJS, ace));
......@@ -2,7 +2,7 @@
<head>
<title>Catalog Gadget</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<script src="../../lib/jquery/jquery.js" type="text/javascript"></script>
<script src="../../lib/rsvp/rsvp.js" type="text/javascript"></script>
<script src="../../lib/jschannel/jschannel.js" type="text/javascript"></script>
<script src="../../renderjs.js" type="text/javascript"></script>
<script src="catalog.js" type="text/javascript"></script>
......
/*global window, jQuery, rJS */
"use strict";
(function (window, $, rJS, undefined) {
(function (window, $, rJS) {
var gk = rJS(window),
io_dict = {
......
......@@ -2,7 +2,7 @@
<head>
<title>Simple Text Editor Gadget</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<script src="../../lib/jquery/jquery.js" type="text/javascript"></script>
<script src="../../lib/rsvp/rsvp.js" type="text/javascript"></script>
<script src="../../lib/jschannel/jschannel.js" type="text/javascript"></script>
<script src="../../renderjs.js" type="text/javascript"></script>
<script src="editor.js" type="text/javascript"></script>
......
/*global window, jQuery, rJS */
/*global window, rJS */
"use strict";
(function (window, $, rJS) {
(function (window, rJS) {
function escape_text(text) {
// &, ", ', <, >, /
......@@ -12,10 +12,11 @@
var gk = rJS(window);
gk.declareMethod('setContent', function (value) {
return rJS(this).context.find('textarea').val(escape_text(value));
rJS(this).element.getElementsByTagName('textarea')[0].value =
escape_text(value);
})
.declareMethod('getContent', function () {
return rJS(this).context.find('textarea').val();
return rJS(this).element.getElementsByTagName('textarea')[0].value;
});
}(window, jQuery, rJS));
}(window, rJS));
......@@ -2,10 +2,10 @@
<head>
<title>IO</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<script src="../../lib/jquery/jquery.js" type="text/javascript"></script>
<script src="../../lib/rsvp/rsvp.js" type="text/javascript"></script>
<script src="../../lib/jschannel/jschannel.js" type="text/javascript"></script>
<script src="../../renderjs.js" type="text/javascript"></script>
<script src="../../lib/jio/md5.js" type="text/javascript"></script>
<script src="../../lib/jio/sha256.js" type="text/javascript"></script>
<script src="../../lib/jio/jio.js" type="text/javascript"></script>
<script src="../../lib/jio/complex_queries.js" type="text/javascript"></script>
<script src="../../lib/jio/localstorage.js" type="text/javascript"></script>
......
......@@ -5,7 +5,7 @@
var gk = rJS(window);
gk.declareMethod('configureIO', function (key) {
rJS(this).jio = jIO.newJio({
rJS(this).jio = jIO.createJIO({
"type": "local",
"username": "couscous",
"application_name": "renderjs"
......@@ -14,60 +14,36 @@
})
.declareMethod('getIO', function () {
var deferred = $.Deferred(),
default_value = "",
gadget = rJS(this);
var gadget = rJS(this);
gadget.jio.getAttachment({
return gadget.jio.getAttachment({
"_id": gadget.jio_key,
"_attachment": "body.txt"
}, function (err, response) {
if (err) {
if (err.status === 404) {
deferred.resolve(default_value);
} else {
deferred.reject(err);
}
} else {
deferred.resolve(response || default_value);
}
}).then(function (response) {
return jIO.util.readBlobAsText(response.data);
}).then(function (response) {
return response.target.result;
});
return deferred.promise();
})
.declareMethod('setIO', function (value) {
var deferred = $.Deferred(),
default_value = "",
gadget = rJS(this);
gadget.jio.put({"_id": gadget.jio_key},
function (err, response) {
if (err) {
deferred.reject(err);
} else {
gadget.jio.putAttachment({
"_id": gadget.jio_key,
"_attachment": "body.txt",
"_data": value,
"_mimetype": "text/plain"
}, function (err, response) {
if (err) {
deferred.reject(err);
} else {
deferred.resolve();
}
});
}
var gadget = rJS(this);
return gadget.jio.put({"_id": gadget.jio_key})
.then(function () {
return gadget.jio.putAttachment({
"_id": gadget.jio_key,
"_attachment": "body.txt",
"_data": value,
"_mimetype": "text/plain"
});
});
return deferred.promise();
})
.declareMethod('configureDataSourceCallback', function (that, callback) {
var g = rJS(this);
g.context.find('a').unbind('click').click(function () {
callback.apply(that).done(function (value) {
$(g.element).find('a').unbind('click').click(function () {
callback.apply(that).then(function (value) {
g.setIO(value);
});
});
......
......@@ -3,7 +3,7 @@
<title>JQuery Text Editor Gadget</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="stylesheet" href="jqte/jquery-te-1.4.0.css" />
<script src="../../lib/jquery/jquery.js" type="text/javascript"></script>
<script src="../../lib/rsvp/rsvp.js" type="text/javascript"></script>
<script src="../../lib/jschannel/jschannel.js" type="text/javascript"></script>
<script src="../../renderjs.js" type="text/javascript"></script>
<script src="jqte/jquery-te-1.4.0.js" type="text/javascript"></script>
......
/*global window, jQuery, rJS */
/*global window, rJS, jQuery */
"use strict";
(function (window, $, rJS) {
(function (window, rJS, $) {
var gk = rJS(window);
gk.declareMethod('setContent', function (value) {
// return rJS(this).context.find('textarea').val(escape_text(value));
return rJS(this).context.find('#textarea-b').jqteVal(value);
return $(rJS(this).element).find('#textarea-b').jqteVal(value);
})
.declareMethod('getContent', function () {
return rJS(this).context.find('#textarea-b').val();
return $(rJS(this).element).find('#textarea-b').val();
});
gk.ready(function () {
var g = rJS(this);
g.context.find("#textarea-b").jqte();
gk.ready(function (g) {
$(g.element).find("#textarea-b").jqte();
});
}(window, jQuery, rJS));
}(window, rJS, jQuery));
......@@ -3,5 +3,5 @@ iframe {
margin:0;
padding:0;
width:80%;
height:100px;
height:300px;
}
......@@ -6,9 +6,10 @@
<title>Office JS</title>
<link rel="stylesheet" href="../../lib/jqm/jquery.mobile.css" />
<link rel="stylesheet" href="officejs.css" />
<script src="../../lib/jquery/jquery.js" type="text/javascript"></script>
<script src="../../lib/rsvp/rsvp.js" type="text/javascript"></script>
<script src="../../lib/jschannel/jschannel.js" type="text/javascript"></script>
<script src="../../renderjs.js" type="text/javascript"></script>
<script src="../../lib/jquery/jquery.js" type="text/javascript"></script>
<script src="officejs.js" type="text/javascript"></script>
<script src="../../lib/jqm/jquery.mobile.js" type="text/javascript"></script>
<link rel="http://www.renderjs.org/rel/interface"
......@@ -32,7 +33,7 @@
<!-- panel -->
<div data-role="panel" id="menu" data-theme="a" class="bare_panel"></div>
<div class="catalog_location"></div>
<!--div class="catalog_location"></div-->
<!-- content -->
<div data-role="content">
......
/*global window, jQuery, rJS */
/*global document, window, jQuery, rJS, RSVP */
"use strict";
(function (window, $, rJS) {
(function (window, $, rJS, RSVP) {
function attachIOToEditor(editor, io, id) {
editor.context.trigger('create');
io.context.trigger('create');
function attachIOToEditor(all_param) {
var editor = all_param[0],
io = all_param[1],
id = all_param[2];
$(io.element).trigger('create');
$(editor.element).trigger('create');
// .then(function (element) {
// element.trigger('create');
// });
// io.getElement()
// .then(function (element) {
// element.trigger('create');
// });
io.configureIO(id).done(function () {
io.configureDataSourceCallback(editor, editor.getContent);
io.getIO().done(function (data) {
editor.setContent(data);
return io.configureIO(id)
.then(function () {
return io.configureDataSourceCallback(editor, editor.getContent);
})
.then(function () {
return io.getIO().fail(function (error) {
if (error.status === 404) {
return "";
}
throw error;
});
})
.then(function (value) {
return editor.setContent(value);
});
});
}
rJS(window).ready(function () {
var g = rJS(this),
catalog_context = g.context.find(".catalog_location").last(),
editor_a_context = g.context.find(".editor_a").last(),
io_a_context = g.context.find(".editor_a_safe").last();
function handleError(rejectedReason) {
var word_list;
console.warn(rejectedReason);
if (rejectedReason instanceof Error) {
word_list = rejectedReason.toString();
} else {
word_list = JSON.stringify(rejectedReason);
}
// XXX Escape text
document.getElementsByTagName('body')[0].innerHTML = word_list;
throw rejectedReason;
}
function createLoadNewEditorCallback(g, editor_path, e_c, io_path, i_c) {
// throw new Error("nutnut");
console.log("createLoadNewEditorCallback");
return function () {
// var new_element = document.createElement("div");
// // console.log(e_c);
// // e_c[0].innerHTML = '';
e_c.empty();
// e_c[0].appendChild(new_element);
console.log("inside");
return RSVP.all([
g.declareGadget(editor_path, {element: e_c[0]}),
// g.declareGadget(editor_path),
g.declareGadget(io_path),
"officejs"
])
.then(function (all_param) {
// e_c.empty();
// e_c[0].appendChild(all_param[0].element);
i_c.empty();
i_c[0].appendChild(all_param[1].element);
return attachIOToEditor(all_param);
})
.fail(handleError);
};
}
rJS(window).ready(function (g) {
var editor_a_context = $(g.element).find(".editor_a").last(),
io_a_context = $(g.element).find(".editor_a_safe").last();
// editor_b_context = g.context.find(".editor_b").last(),
// io_b_context = g.context.find(".editor_b_safe").last();
// First, load the catalog gadget
g.declareGadget('./catalog.html', catalog_context).done(
function (catalog) {
g.declareGadget('./catalog.html')
.then(function (catalog) {
// Fetch the list of editor and io gadgets
// This is done in 2 different queries to the catalog
$.when(
return RSVP.all([
catalog.allDocs(
{query: 'interface: "http://www.renderjs.org/interface/editor"'}
),
catalog.allDocs(
{query: 'interface: "http://www.renderjs.org/interface/io"'}
)
).done(function (editor_list, io_list) {
var panel_context = g.context.find(".bare_panel");
]);
})
.then(function (all_list) {
var panel_context = $(g.element).find(".bare_panel"),
editor_list = all_list[0],
io_list = all_list[1],
editor_definition,
i;
// Load 1 editor and 1 IO and plug them
$.when(
g.declareIframedGadget(editor_list[0].path, editor_a_context),
g.declareGadget(io_list[0].path, io_a_context),
"officejs"
).done(attachIOToEditor);
// Load 1 editor and 1 IO and plug them
console.log(io_list[0].path);
return RSVP.all([
g.declareGadget(editor_list[0].path),// editor_a_context),
g.declareGadget(io_list[0].path),// io_a_context),
"officejs"
])
.then(function (all_param) {
editor_a_context.empty();
console.log("first G");
console.log(all_param[0].element);
editor_a_context[0].appendChild(all_param[0].element);
console.log(editor_a_context[0]);
io_a_context.empty();
io_a_context[0].appendChild(all_param[1].element);
return attachIOToEditor(all_param);
})
.then(function () {
// Fill the panel
for (i = 0; i < editor_list.length; i += 1) {
editor_definition = editor_list[i];
panel_context.append(
'<a href="#" data-role="button" data-icon="edit" ' +
'data-iconpos="left">' + editor_definition.title + '</a>'
);
// $(editor_definition.element).click(
panel_context.find('a').last().click(
createLoadNewEditorCallback(g, editor_definition.path,
editor_a_context, io_list[0].path, io_a_context)
);
// XXX Handle links
// panel_context.find('a').last().click(function () {
// $.when(
// g.declareGadget(editor_definition.path,
// editor_a_context),
// g.declareGadget(io_list[0].path, io_a_context),
// "officejs"
// ).done(attachIOToEditor);
// });
}
panel_context.trigger('create');
});
// Fill the panel
$.each(editor_list, function (i, editor_definition) {
panel_context.append(
'<a href="#" data-role="button" data-icon="edit" ' +
'data-iconpos="left">' + editor_definition.title + '</a>'
);
panel_context.find('a').last().click(function () {
$.when(
g.declareIframedGadget(editor_definition.path,
editor_a_context),
g.declareGadget(io_list[0].path, io_a_context),
"officejs"
).done(attachIOToEditor);
});
});
panel_context.trigger('create');
});
}
);
})
.fail(handleError);
// $.when(
// g.declareGadget('./jqteditor.html', editor_a_context),
// g.declareGadget('./io.html', io_a_context),
......@@ -76,4 +158,4 @@
// "officejs_b").done(attachIOToEditor);
});
}(window, jQuery, rJS));
}(window, jQuery, rJS, RSVP));
......@@ -9,10 +9,20 @@
*
* @module complex_queries
*/
var complex_queries;
(function () {
// define([module_name], [dependencies], module);
(function (dependencies, module) {
"use strict";
var to_export = {}, module_name = "complex_queries";
if (typeof define === 'function' && define.amd) {
return define(dependencies, module);
}
if (typeof exports === 'object') {
return module(exports);
}
window.complex_queries = {};
module(window.complex_queries);
}(['exports'], function (to_export) {
"use strict";
/**
* Add a secured (write permission denied) property to an object.
*
......@@ -28,6 +38,7 @@ var complex_queries;
"value": value
});
}
/**
* Parse a text request to a json query object tree
*
......@@ -36,6 +47,7 @@ var complex_queries;
*/
function parseStringToObject(string) {
/*
Default template driver for JS/CC generated parsers running as
browser-based JavaScript/ECMAScript applications.
......@@ -718,343 +730,157 @@ if ((error_count = __NODEJS_parse(string, error_offsets, error_lookaheads)) > 0)
}
}
return result;
} // parseStringToObject
_export('parseStringToObject', parseStringToObject);
/*jslint indent: 2, maxlen: 80, sloppy: true */
var query_class_dict = {};
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global _export: true */
/*global Query: true, query_class_dict: true, inherits: true,
_export: true, QueryFactory: true */
/**
* Escapes regexp special chars from a string.
* The ComplexQuery inherits from Query, and compares one or several metadata
* values.
*
* @param {String} string The string to escape
* @return {String} The escaped string
* @class ComplexQuery
* @extends Query
* @param {Object} [spec={}] The specifications
* @param {String} [spec.operator="AND"] The compare method to use
* @param {String} spec.key The metadata key
* @param {String} spec.value The value of the metadata to compare
*/
function stringEscapeRegexpCharacters(string) {
if (typeof string === "string") {
return string.replace(/([\\\.\$\[\]\(\)\{\}\^\?\*\+\-])/g, "\\$1");
}
throw new TypeError("complex_queries.stringEscapeRegexpCharacters(): " +
"Argument no 1 is not of type 'string'");
function ComplexQuery(spec) {
Query.call(this);
/**
* Logical operator to use to compare object values
*
* @attribute operator
* @type String
* @default "AND"
* @optional
*/
this.operator = spec.operator || "AND";
/**
* The sub Query list which are used to query an item.
*
* @attribute query_list
* @type Array
* @default []
* @optional
*/
this.query_list = spec.query_list || [];
this.query_list = this.query_list.map(QueryFactory.create);
}
inherits(ComplexQuery, Query);
_export("stringEscapeRegexpCharacters", stringEscapeRegexpCharacters);
/**
* #crossLink "Query/match:method"
*/
ComplexQuery.prototype.match = function (item, wildcard_character) {
return this[this.operator](item, wildcard_character);
};
/**
* Convert metadata values to array of strings. ex:
*
* "a" -> ["a"],
* {"content": "a"} -> ["a"]
*
* @param {Any} value The metadata value
* @return {Array} The value in string array format
* #crossLink "Query/toString:method"
*/
function metadataValueToStringArray(value) {
var i, new_value = [];
if (value === undefined) {
return undefined;
}
if (!Array.isArray(value)) {
value = [value];
}
for (i = 0; i < value.length; i += 1) {
if (typeof value[i] === 'object') {
new_value[i] = value[i].content;
} else {
new_value[i] = value[i];
}
}
return new_value;
}
ComplexQuery.prototype.toString = function () {
var str_list = ["("], this_operator = this.operator;
this.query_list.forEach(function (query) {
str_list.push(query.toString());
str_list.push(this_operator);
});
str_list.pop(); // remove last operator
str_list.push(")");
return str_list.join(" ");
};
/**
* A sort function to sort items by key
*
* @param {String} key The key to sort on
* @param {String} [way="ascending"] 'ascending' or 'descending'
* @return {Function} The sort function
* #crossLink "Query/serialized:method"
*/
function sortFunction(key, way) {
if (way === 'descending') {
return function (a, b) {
// this comparison is 5 times faster than json comparison
var i, l;
a = metadataValueToStringArray(a[key]) || [];
b = metadataValueToStringArray(b[key]) || [];
l = a.length > b.length ? a.length : b.length;
for (i = 0; i < l; i += 1) {
if (a[i] === undefined) {
return 1;
}
if (b[i] === undefined) {
return -1;
}
if (a[i] > b[i]) {
return -1;
}
if (a[i] < b[i]) {
return 1;
}
}
return 0;
};
}
if (way === 'ascending') {
return function (a, b) {
// this comparison is 5 times faster than json comparison
var i, l;
a = metadataValueToStringArray(a[key]) || [];
b = metadataValueToStringArray(b[key]) || [];
l = a.length > b.length ? a.length : b.length;
for (i = 0; i < l; i += 1) {
if (a[i] === undefined) {
return -1;
}
if (b[i] === undefined) {
return 1;
}
if (a[i] > b[i]) {
return 1;
}
if (a[i] < b[i]) {
return -1;
}
}
return 0;
};
}
throw new TypeError("complex_queries.sortFunction(): " +
"Argument 2 must be 'ascending' or 'descending'");
}
ComplexQuery.prototype.serialized = function () {
var s = {
"type": "complex",
"operator": this.operator,
"query_list": []
};
this.query_list.forEach(function (query) {
s.query_list.push(query.serialized());
});
return s;
};
/**
* Clones all native object in deep. Managed types: Object, Array, String,
* Number, Boolean, null.
* Comparison operator, test if all sub queries match the
* item value
*
* @param {A} object The object to clone
* @return {A} The cloned object
* @method AND
* @param {Object} item The item to match
* @param {String} wildcard_character The wildcard character
* @return {Boolean} true if all match, false otherwise
*/
function deepClone(object) {
var i, cloned;
if (Array.isArray(object)) {
cloned = [];
for (i = 0; i < object.length; i += 1) {
cloned[i] = deepClone(object[i]);
}
return cloned;
}
if (typeof object === "object") {
cloned = {};
for (i in object) {
if (object.hasOwnProperty(i)) {
cloned[i] = deepClone(object[i]);
}
ComplexQuery.prototype.AND = function (item, wildcard_character) {
var i;
for (i = 0; i < this.query_list.length; i += 1) {
if (!this.query_list[i].match(item, wildcard_character)) {
return false;
}
return cloned;
}
return object;
}
return true;
};
/**
* Inherits the prototype methods from one constructor into another. The
* prototype of `constructor` will be set to a new object created from
* `superConstructor`.
* Comparison operator, test if one of the sub queries matches the
* item value
*
* @param {Function} constructor The constructor which inherits the super one
* @param {Function} superConstructor The super constructor
* @method OR
* @param {Object} item The item to match
* @param {String} wildcard_character The wildcard character
* @return {Boolean} true if one match, false otherwise
*/
function inherits(constructor, superConstructor) {
constructor.super_ = superConstructor;
constructor.prototype = Object.create(superConstructor.prototype, {
"constructor": {
"configurable": true,
"enumerable": false,
"writable": true,
"value": constructor
ComplexQuery.prototype.OR = function (item, wildcard_character) {
var i;
for (i = 0; i < this.query_list.length; i += 1) {
if (this.query_list[i].match(item, wildcard_character)) {
return true;
}
});
}
}
return false;
};
/**
* Does nothing
* Comparison operator, test if the sub query does not match the
* item value
*
* @method NOT
* @param {Object} item The item to match
* @param {String} wildcard_character The wildcard character
* @return {Boolean} true if one match, false otherwise
*/
function emptyFunction() {}
ComplexQuery.prototype.NOT = function (item, wildcard_character) {
return !this.query_list[0].match(item, wildcard_character);
};
query_class_dict.complex = ComplexQuery;
_export("ComplexQuery", ComplexQuery);
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global parseStringToObject: true, emptyFunction: true, sortOn: true, limit:
true, select: true, _export: true, stringEscapeRegexpCharacters: true,
deepClone: true */
/**
* Filter a list of items, modifying them to select only wanted keys. If
* `clone` is true, then the method will act on a cloned list.
*
* @param {Array} select_option Key list to keep
* @param {Array} list The item list to filter
* @param {Boolean} [clone=false] If true, modifies a clone of the list
* @return {Array} The filtered list
*/
function select(select_option, list, clone) {
var i, j, new_item;
if (!Array.isArray(select_option)) {
throw new TypeError("complex_queries.select(): " +
"Argument 1 is not of type Array");
}
if (!Array.isArray(list)) {
throw new TypeError("complex_queries.select(): " +
"Argument 2 is not of type Array");
}
if (clone === true) {
list = deepClone(list);
}
for (i = 0; i < list.length; i += 1) {
new_item = {};
for (j = 0; j < select_option.length; j += 1) {
new_item[select_option[j]] = list[i][select_option[j]];
}
for (j in new_item) {
if (new_item.hasOwnProperty(j)) {
list[i] = new_item;
break;
}
}
}
return list;
}
_export('select', select);
/**
* Sort a list of items, according to keys and directions. If `clone` is true,
* then the method will act on a cloned list.
*
* @param {Array} sort_on_option List of couples [key, direction]
* @param {Array} list The item list to sort
* @param {Boolean} [clone=false] If true, modifies a clone of the list
* @return {Array} The filtered list
*/
function sortOn(sort_on_option, list, clone) {
var sort_index;
if (!Array.isArray(sort_on_option)) {
throw new TypeError("complex_queries.sortOn(): " +
"Argument 1 is not of type 'array'");
}
if (clone) {
list = deepClone(list);
}
for (sort_index = sort_on_option.length - 1; sort_index >= 0;
sort_index -= 1) {
list.sort(sortFunction(
sort_on_option[sort_index][0],
sort_on_option[sort_index][1]
));
}
return list;
}
_export('sortOn', sortOn);
/**
* Limit a list of items, according to index and length. If `clone` is true,
* then the method will act on a cloned list.
*
* @param {Array} limit_option A couple [from, length]
* @param {Array} list The item list to limit
* @param {Boolean} [clone=false] If true, modifies a clone of the list
* @return {Array} The filtered list
*/
function limit(limit_option, list, clone) {
if (!Array.isArray(limit_option)) {
throw new TypeError("complex_queries.limit(): " +
"Argument 1 is not of type 'array'");
}
if (!Array.isArray(list)) {
throw new TypeError("complex_queries.limit(): " +
"Argument 2 is not of type 'array'");
}
if (clone) {
list = deepClone(list);
}
list.splice(0, limit_option[0]);
if (limit_option[1]) {
list.splice(limit_option[1]);
}
return list;
}
_export('limit', limit);
/**
* Convert a search text to a regexp.
*
* @param {String} string The string to convert
* @param {String} [wildcard_character=undefined] The wildcard chararter
* @return {RegExp} The search text regexp
*/
function convertStringToRegExp(string, wildcard_character) {
if (typeof string !== 'string') {
throw new TypeError("complex_queries.convertStringToRegExp(): " +
"Argument 1 is not of type 'string'");
}
if (wildcard_character === undefined ||
wildcard_character === null || wildcard_character === '') {
return new RegExp("^" + stringEscapeRegexpCharacters(string) + "$");
}
if (typeof wildcard_character !== 'string' || wildcard_character.length > 1) {
throw new TypeError("complex_queries.convertStringToRegExp(): " +
"Optional argument 2 must be a string of length <= 1");
}
return new RegExp("^" + stringEscapeRegexpCharacters(string).replace(
new RegExp(stringEscapeRegexpCharacters(wildcard_character), 'g'),
'.*'
) + "$");
}
_export('convertStringToRegExp', convertStringToRegExp);
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global _export: true, ComplexQuery: true, SimpleQuery: true, Query: true,
parseStringToObject: true */
var query_class_dict = {};
/**
* Provides static methods to create Query object
*
* @class QueryFactory
*/
function QueryFactory() {}
/**
* Creates Query object from a search text string or a serialized version
* of a Query.
*
* @method create
* @static
* @param {Object,String} object The search text or the serialized version
* of a Query
* @return {Query} A Query object
*/
QueryFactory.create = function (object) {
if (object === "") {
return new Query();
}
if (typeof object === "string") {
object = parseStringToObject(object);
}
if (typeof (object || {}).type === "string" &&
query_class_dict[object.type]) {
return new query_class_dict[object.type](object);
}
throw new TypeError("QueryFactory.create(): " +
"Argument 1 is not a search text or a parsable object");
};
_export("QueryFactory", QueryFactory);
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global parseStringToObject: true, emptyFunction: true, sortOn: true, limit:
true, select: true, _export: true, stringEscapeRegexpCharacters: true,
deepClone: true */
/**
* The query to use to filter a list of objects.
* This is an abstract class.
* The query to use to filter a list of objects.
* This is an abstract class.
*
* @class Query
* @constructor
......@@ -1128,7 +954,7 @@ Query.prototype.exec = function (item_list, option) {
option.wildcard_character = '%';
}
while (i < item_list.length) {
if (!this.match(item_list[i], option.wildcard_character)) {
if (!item_list[i] || !this.match(item_list[i], option.wildcard_character)) {
item_list.splice(i, 1);
} else {
i += 1;
......@@ -1148,9 +974,10 @@ Query.prototype.exec = function (item_list, option) {
*
* @method match
* @param {Object} item The object to test
* @param {String} wildcard_character The wildcard character to use
* @return {Boolean} true if match, false otherwise
*/
Query.prototype.match = function (item, wildcard_character) {
Query.prototype.match = function () {
return true;
};
......@@ -1219,6 +1046,70 @@ Query.prototype.serialized = function () {
};
_export("Query", Query);
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global _export, ComplexQuery, SimpleQuery, Query, parseStringToObject,
query_class_dict */
/**
* Provides static methods to create Query object
*
* @class QueryFactory
*/
function QueryFactory() {
return;
}
/**
* Creates Query object from a search text string or a serialized version
* of a Query.
*
* @method create
* @static
* @param {Object,String} object The search text or the serialized version
* of a Query
* @return {Query} A Query object
*/
QueryFactory.create = function (object) {
if (object === "") {
return new Query();
}
if (typeof object === "string") {
object = parseStringToObject(object);
}
if (typeof (object || {}).type === "string" &&
query_class_dict[object.type]) {
return new query_class_dict[object.type](object);
}
throw new TypeError("QueryFactory.create(): " +
"Argument 1 is not a search text or a parsable object");
};
_export("QueryFactory", QueryFactory);
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global _export: true, to_export: true */
function objectToSearchText(query) {
var str_list = [];
if (query.type === "complex") {
str_list.push("(");
(query.query_list || []).forEach(function (sub_query) {
str_list.push(objectToSearchText(sub_query));
str_list.push(query.operator);
});
str_list.length -= 1;
str_list.push(")");
return str_list.join(" ");
}
if (query.type === "simple") {
return query.id + (query.id ? ": " : "") + (query.operator || "=") + ' "' +
query.value + '"';
}
throw new TypeError("This object is not a query");
}
_export("objectToSearchText", objectToSearchText);
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global Query: true, inherits: true, query_class_dict: true, _export: true,
convertStringToRegExp: true */
......@@ -1460,156 +1351,297 @@ SimpleQuery.prototype[">="] = function (object_value, comparison_value) {
query_class_dict.simple = SimpleQuery;
_export("SimpleQuery", SimpleQuery);
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global Query: true, query_class_dict: true, inherits: true,
_export: true, QueryFactory: true */
/*global _export: true */
/**
* The ComplexQuery inherits from Query, and compares one or several metadata
* values.
* Escapes regexp special chars from a string.
*
* @class ComplexQuery
* @extends Query
* @param {Object} [spec={}] The specifications
* @param {String} [spec.operator="AND"] The compare method to use
* @param {String} spec.key The metadata key
* @param {String} spec.value The value of the metadata to compare
* @param {String} string The string to escape
* @return {String} The escaped string
*/
function ComplexQuery(spec) {
Query.call(this);
/**
* Logical operator to use to compare object values
*
* @attribute operator
* @type String
* @default "AND"
* @optional
*/
this.operator = spec.operator || "AND";
function stringEscapeRegexpCharacters(string) {
if (typeof string === "string") {
return string.replace(/([\\\.\$\[\]\(\)\{\}\^\?\*\+\-])/g, "\\$1");
}
throw new TypeError("complex_queries.stringEscapeRegexpCharacters(): " +
"Argument no 1 is not of type 'string'");
}
/**
* The sub Query list which are used to query an item.
*
* @attribute query_list
* @type Array
* @default []
* @optional
*/
this.query_list = spec.query_list || [];
this.query_list = this.query_list.map(QueryFactory.create);
_export("stringEscapeRegexpCharacters", stringEscapeRegexpCharacters);
/**
* Convert metadata values to array of strings. ex:
*
* "a" -> ["a"],
* {"content": "a"} -> ["a"]
*
* @param {Any} value The metadata value
* @return {Array} The value in string array format
*/
function metadataValueToStringArray(value) {
var i, new_value = [];
if (value === undefined) {
return undefined;
}
if (!Array.isArray(value)) {
value = [value];
}
for (i = 0; i < value.length; i += 1) {
if (typeof value[i] === 'object') {
new_value[i] = value[i].content;
} else {
new_value[i] = value[i];
}
}
return new_value;
}
inherits(ComplexQuery, Query);
/**
* #crossLink "Query/match:method"
* A sort function to sort items by key
*
* @param {String} key The key to sort on
* @param {String} [way="ascending"] 'ascending' or 'descending'
* @return {Function} The sort function
*/
ComplexQuery.prototype.match = function (item, wildcard_character) {
return this[this.operator](item, wildcard_character);
};
function sortFunction(key, way) {
if (way === 'descending') {
return function (a, b) {
// this comparison is 5 times faster than json comparison
var i, l;
a = metadataValueToStringArray(a[key]) || [];
b = metadataValueToStringArray(b[key]) || [];
l = a.length > b.length ? a.length : b.length;
for (i = 0; i < l; i += 1) {
if (a[i] === undefined) {
return 1;
}
if (b[i] === undefined) {
return -1;
}
if (a[i] > b[i]) {
return -1;
}
if (a[i] < b[i]) {
return 1;
}
}
return 0;
};
}
if (way === 'ascending') {
return function (a, b) {
// this comparison is 5 times faster than json comparison
var i, l;
a = metadataValueToStringArray(a[key]) || [];
b = metadataValueToStringArray(b[key]) || [];
l = a.length > b.length ? a.length : b.length;
for (i = 0; i < l; i += 1) {
if (a[i] === undefined) {
return -1;
}
if (b[i] === undefined) {
return 1;
}
if (a[i] > b[i]) {
return 1;
}
if (a[i] < b[i]) {
return -1;
}
}
return 0;
};
}
throw new TypeError("complex_queries.sortFunction(): " +
"Argument 2 must be 'ascending' or 'descending'");
}
/**
* #crossLink "Query/toString:method"
* Clones all native object in deep. Managed types: Object, Array, String,
* Number, Boolean, null.
*
* @param {A} object The object to clone
* @return {A} The cloned object
*/
ComplexQuery.prototype.toString = function () {
var str_list = ["("], this_operator = this.operator;
this.query_list.forEach(function (query) {
str_list.push(query.toString());
str_list.push(this_operator);
});
str_list.pop(); // remove last operator
str_list.push(")");
return str_list.join(" ");
};
function deepClone(object) {
var i, cloned;
if (Array.isArray(object)) {
cloned = [];
for (i = 0; i < object.length; i += 1) {
cloned[i] = deepClone(object[i]);
}
return cloned;
}
if (typeof object === "object") {
cloned = {};
for (i in object) {
if (object.hasOwnProperty(i)) {
cloned[i] = deepClone(object[i]);
}
}
return cloned;
}
return object;
}
/**
* #crossLink "Query/serialized:method"
* Inherits the prototype methods from one constructor into another. The
* prototype of `constructor` will be set to a new object created from
* `superConstructor`.
*
* @param {Function} constructor The constructor which inherits the super one
* @param {Function} superConstructor The super constructor
*/
ComplexQuery.prototype.serialized = function () {
var s = {
"type": "complex",
"operator": this.operator,
"query_list": []
};
this.query_list.forEach(function (query) {
s.query_list.push(query.serialized());
function inherits(constructor, superConstructor) {
constructor.super_ = superConstructor;
constructor.prototype = Object.create(superConstructor.prototype, {
"constructor": {
"configurable": true,
"enumerable": false,
"writable": true,
"value": constructor
}
});
return s;
};
}
/**
* Comparison operator, test if all sub queries match the
* item value
* Does nothing
*/
function emptyFunction() {
return;
}
/**
* Filter a list of items, modifying them to select only wanted keys. If
* `clone` is true, then the method will act on a cloned list.
*
* @method AND
* @param {Object} item The item to match
* @param {String} wildcard_character The wildcard character
* @return {Boolean} true if all match, false otherwise
* @param {Array} select_option Key list to keep
* @param {Array} list The item list to filter
* @param {Boolean} [clone=false] If true, modifies a clone of the list
* @return {Array} The filtered list
*/
ComplexQuery.prototype.AND = function (item, wildcard_character) {
var i;
for (i = 0; i < this.query_list.length; i += 1) {
if (!this.query_list[i].match(item, wildcard_character)) {
return false;
function select(select_option, list, clone) {
var i, j, new_item;
if (!Array.isArray(select_option)) {
throw new TypeError("complex_queries.select(): " +
"Argument 1 is not of type Array");
}
if (!Array.isArray(list)) {
throw new TypeError("complex_queries.select(): " +
"Argument 2 is not of type Array");
}
if (clone === true) {
list = deepClone(list);
}
for (i = 0; i < list.length; i += 1) {
new_item = {};
for (j = 0; j < select_option.length; j += 1) {
if (list[i].hasOwnProperty([select_option[j]])) {
new_item[select_option[j]] = list[i][select_option[j]];
}
}
for (j in new_item) {
if (new_item.hasOwnProperty(j)) {
list[i] = new_item;
break;
}
}
}
return true;
};
return list;
}
_export('select', select);
/**
* Comparison operator, test if one of the sub queries matches the
* item value
* Sort a list of items, according to keys and directions. If `clone` is true,
* then the method will act on a cloned list.
*
* @method OR
* @param {Object} item The item to match
* @param {String} wildcard_character The wildcard character
* @return {Boolean} true if one match, false otherwise
* @param {Array} sort_on_option List of couples [key, direction]
* @param {Array} list The item list to sort
* @param {Boolean} [clone=false] If true, modifies a clone of the list
* @return {Array} The filtered list
*/
ComplexQuery.prototype.OR = function (item, wildcard_character) {
var i;
for (i = 0; i < this.query_list.length; i += 1) {
if (this.query_list[i].match(item, wildcard_character)) {
return true;
}
function sortOn(sort_on_option, list, clone) {
var sort_index;
if (!Array.isArray(sort_on_option)) {
throw new TypeError("complex_queries.sortOn(): " +
"Argument 1 is not of type 'array'");
}
return false;
};
if (clone) {
list = deepClone(list);
}
for (sort_index = sort_on_option.length - 1; sort_index >= 0;
sort_index -= 1) {
list.sort(sortFunction(
sort_on_option[sort_index][0],
sort_on_option[sort_index][1]
));
}
return list;
}
_export('sortOn', sortOn);
/**
* Comparison operator, test if the sub query does not match the
* item value
* Limit a list of items, according to index and length. If `clone` is true,
* then the method will act on a cloned list.
*
* @method NOT
* @param {Object} item The item to match
* @param {String} wildcard_character The wildcard character
* @return {Boolean} true if one match, false otherwise
* @param {Array} limit_option A couple [from, length]
* @param {Array} list The item list to limit
* @param {Boolean} [clone=false] If true, modifies a clone of the list
* @return {Array} The filtered list
*/
ComplexQuery.prototype.NOT = function (item, wildcard_character) {
return !this.query_list[0].match(item, wildcard_character);
};
query_class_dict.complex = ComplexQuery;
function limit(limit_option, list, clone) {
if (!Array.isArray(limit_option)) {
throw new TypeError("complex_queries.limit(): " +
"Argument 1 is not of type 'array'");
}
if (!Array.isArray(list)) {
throw new TypeError("complex_queries.limit(): " +
"Argument 2 is not of type 'array'");
}
if (clone) {
list = deepClone(list);
}
list.splice(0, limit_option[0]);
if (limit_option[1]) {
list.splice(limit_option[1]);
}
return list;
}
_export("ComplexQuery", ComplexQuery);
_export('limit', limit);
if (typeof define === "function" && define.amd) {
define(to_export);
} else if (typeof window === "object") {
Object.defineProperty(window, module_name, {
configurable: false,
enumerable: true,
writable: false,
value: to_export
});
} else if (typeof exports === "object") {
var i;
for (i in to_export) {
if (to_export.hasOwnProperty(i)) {
exports[i] = to_export[i];
}
}
} else {
complex_queries = to_export;
/**
* Convert a search text to a regexp.
*
* @param {String} string The string to convert
* @param {String} [wildcard_character=undefined] The wildcard chararter
* @return {RegExp} The search text regexp
*/
function convertStringToRegExp(string, wildcard_character) {
if (typeof string !== 'string') {
throw new TypeError("complex_queries.convertStringToRegExp(): " +
"Argument 1 is not of type 'string'");
}
if (wildcard_character === undefined ||
wildcard_character === null || wildcard_character === '') {
return new RegExp("^" + stringEscapeRegexpCharacters(string) + "$");
}
if (typeof wildcard_character !== 'string' || wildcard_character.length > 1) {
throw new TypeError("complex_queries.convertStringToRegExp(): " +
"Optional argument 2 must be a string of length <= 1");
}
}());
return new RegExp("^" + stringEscapeRegexpCharacters(string).replace(
new RegExp(stringEscapeRegexpCharacters(wildcard_character), 'g'),
'.*'
) + "$");
}
_export('convertStringToRegExp', convertStringToRegExp);
return to_export;
}));
This source diff could not be displayed because it is too large. You can view the blob instead.
/*
* Copyright 2013, Nexedi SA
* Released under the LGPL license.
* http://www.gnu.org/licenses/lgpl.html
*/
* Copyright 2013, Nexedi SA
* Released under the LGPL license.
* http://www.gnu.org/licenses/lgpl.html
*/
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global jIO: true, localStorage: true, setTimeout: true,
complex_queries: true */
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true, regexp: true */
/*global jIO, localStorage, setTimeout, complex_queries, window, define,
exports, require */
/**
* JIO Local Storage. Type = 'local'.
......@@ -16,6 +16,9 @@
*
* {
* "type": "local",
* "mode": <string>,
* // - "localStorage" // default
* // - "memory"
* "username": <non empty string>, // to define user space
* "application_name": <string> // default 'untitled'
* }
......@@ -43,12 +46,42 @@
*
* @class LocalStorage
*/
jIO.addStorageType('local', function (spec, my) {
spec = spec || {};
var that, priv, localstorage;
that = my.basicStorage(spec, my);
priv = {};
// define([module_name], [dependencies], module);
(function (dependencies, module) {
"use strict";
if (typeof define === 'function' && define.amd) {
return define(dependencies, module);
}
if (typeof exports === 'object') {
return module(exports, require('jio'), require('complex_queries'));
}
window.local_storage = {};
module(window.local_storage, jIO, complex_queries);
}([
'exports',
'jio',
'complex_queries'
], function (exports, jIO, complex_queries) {
"use strict";
/**
* Checks if an object has no enumerable keys
*
* @param {Object} obj The object
* @return {Boolean} true if empty, else false
*/
function objectIsEmpty(obj) {
var k;
for (k in obj) {
if (obj.hasOwnProperty(k)) {
return false;
}
}
return true;
}
var ram = {}, memorystorage, localstorage;
/*
* Wrapper for the localStorage used to simplify instion of any kind of
......@@ -67,371 +100,637 @@ jIO.addStorageType('local', function (spec, my) {
}
};
// attributes
priv.username = spec.username || '';
priv.application_name = spec.application_name || 'untitled';
priv.localpath = 'jio/localstorage/' + priv.username + '/' +
priv.application_name;
// ==================== Tools ====================
/**
* Generate a new uuid
* @method generateUuid
* @return {string} The new uuid
/*
* Wrapper for the localStorage used to simplify instion of any kind of
* values
*/
priv.generateUuid = function () {
var S4 = function () {
/* 65536 */
var i, string = Math.floor(
Math.random() * 0x10000
).toString(16);
for (i = string.length; i < 4; i += 1) {
string = '0' + string;
}
return string;
};
return S4() + S4() + "-" + S4() + "-" + S4() + "-" + S4() + "-" + S4() +
S4() + S4();
memorystorage = {
getItem: function (item) {
var value = ram[item];
return value === undefined ? null : JSON.parse(value);
},
setItem: function (item, value) {
ram[item] = JSON.stringify(value);
},
removeItem: function (item) {
delete ram[item];
}
};
/**
* Checks if an object has no enumerable keys
* @method objectIsEmpty
* @param {object} obj The object
* @return {boolean} true if empty, else false
* The JIO LocalStorage extension
*
* @class LocalStorage
* @constructor
*/
priv.objectIsEmpty = function (obj) {
var k;
for (k in obj) {
if (obj.hasOwnProperty(k)) {
return false;
}
function LocalStorage(spec) {
if (typeof spec.username !== 'string' && !spec.username) {
throw new TypeError("LocalStorage 'username' must be a string " +
"which contains more than one character.");
}
return true;
};
// ===================== overrides ======================
that.specToStore = function () {
return {
"application_name": priv.application_name,
"username": priv.username
};
};
that.validateState = function () {
if (typeof priv.username === "string" && priv.username !== '') {
return '';
this._localpath = 'jio/localstorage/' + spec.username + '/' + (
spec.application_name === null || spec.application_name ===
undefined ? 'untitled' : spec.application_name.toString()
);
switch (spec.mode) {
case "memory":
this._database = ram;
this._storage = memorystorage;
this._mode = "memory";
break;
default:
this._database = localStorage;
this._storage = localstorage;
this._mode = "localStorage";
break;
}
return 'Need at least one parameter: "username".';
};
}
// ==================== commands ====================
/**
* Create a document in local storage.
*
* @method post
* @param {object} command The JIO command
* @param {Object} command The JIO command
* @param {Object} metadata The metadata to store
* @param {Object} options The command options
*/
that.post = function (command) {
setTimeout(function () {
var doc, doc_id = command.getDocId();
if (!doc_id) {
doc_id = priv.generateUuid();
}
doc = localstorage.getItem(priv.localpath + "/" + doc_id);
if (doc === null) {
// the document does not exist
doc = command.cloneDoc();
doc._id = doc_id;
delete doc._attachments;
localstorage.setItem(priv.localpath + "/" + doc_id, doc);
that.success({
"ok": true,
"id": doc_id
});
} else {
// the document already exists
that.error({
"status": 409,
"statusText": "Conflicts",
"error": "conflicts",
"message": "Cannot create a new document",
"reason": "Document already exists"
});
}
});
LocalStorage.prototype.post = function (command, metadata) {
var doc, doc_id = metadata._id;
if (!doc_id) {
doc_id = jIO.util.generateUuid();
}
doc = this._storage.getItem(this._localpath + "/" + doc_id);
if (doc === null) {
// the document does not exist
doc = jIO.util.deepClone(metadata);
doc._id = doc_id;
delete doc._attachments;
this._storage.setItem(this._localpath + "/" + doc_id, doc);
command.success({"id": doc_id});
} else {
// the document already exists
command.error(
"conflict",
"document exists",
"Cannot create a new document"
);
}
};
/**
* Create or update a document in local storage.
*
* @method put
* @param {object} command The JIO command
* @param {Object} command The JIO command
* @param {Object} metadata The metadata to store
* @param {Object} options The command options
*/
that.put = function (command) {
setTimeout(function () {
var doc, tmp;
doc = localstorage.getItem(priv.localpath + "/" + command.getDocId());
if (doc === null) {
// the document does not exist
doc = command.cloneDoc();
delete doc._attachments;
} else {
// the document already exists
tmp = command.cloneDoc();
tmp._attachments = doc._attachments;
doc = tmp;
}
// write
localstorage.setItem(priv.localpath + "/" + command.getDocId(), doc);
that.success({
"ok": true,
"id": command.getDocId()
});
});
LocalStorage.prototype.put = function (command, metadata) {
var doc, tmp, status;
doc = this._storage.getItem(this._localpath + "/" + metadata._id);
if (doc === null) {
// the document does not exist
doc = jIO.util.deepClone(metadata);
delete doc._attachments;
status = "created";
} else {
// the document already exists
tmp = jIO.util.deepClone(metadata);
tmp._attachments = doc._attachments;
doc = tmp;
status = "no_content";
}
// write
this._storage.setItem(this._localpath + "/" + metadata._id, doc);
command.success(status);
};
/**
* Add an attachment to a document
* @method putAttachment
* @param {object} command The JIO command
*
* @method putAttachment
* @param {Object} command The JIO command
* @param {Object} param The given parameters
* @param {Object} options The command options
*/
that.putAttachment = function (command) {
setTimeout(function () {
var doc;
doc = localstorage.getItem(priv.localpath + "/" + command.getDocId());
if (doc === null) {
// the document does not exist
that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": "Impossible to add attachment",
"reason": "Document not found"
});
return;
}
LocalStorage.prototype.putAttachment = function (command, param) {
var that = this, doc, status = "created";
doc = this._storage.getItem(this._localpath + "/" + param._id);
if (doc === null) {
// the document does not exist
return command.error(
"not_found",
"missing",
"Impossible to add attachment"
);
}
// the document already exists
// the document already exists
// download data
jIO.util.readBlobAsBinaryString(param._blob).then(function (e) {
doc._attachments = doc._attachments || {};
doc._attachments[command.getAttachmentId()] = {
"content_type": command.getAttachmentMimeType(),
"digest": "md5-" + command.md5SumAttachmentData(),
"length": command.getAttachmentLength()
if (doc._attachments[param._attachment]) {
status = "no_content";
}
doc._attachments[param._attachment] = {
"content_type": param._blob.type,
"digest": jIO.util.makeBinaryStringDigest(e.target.result),
"length": param._blob.size
};
// upload data
localstorage.setItem(priv.localpath + "/" + command.getDocId() + "/" +
command.getAttachmentId(),
command.getAttachmentData());
// write document
localstorage.setItem(priv.localpath + "/" + command.getDocId(), doc);
that.success({
"ok": true,
"id": command.getDocId(),
"attachment": command.getAttachmentId()
});
that._storage.setItem(that._localpath + "/" + param._id + "/" +
param._attachment, e.target.result);
that._storage.setItem(that._localpath + "/" + param._id, doc);
command.success(status,
{"digest": doc._attachments[param._attachment].digest});
}, function (e) {
command.error(
"request_timeout",
"blob error",
"Error " + e.status + ", unable to get blob content"
);
}, function (e) {
command.notify((e.loaded / e.total) * 100);
});
};
/**
* Get a document
*
* @method get
* @param {object} command The JIO command
* @param {Object} command The JIO command
* @param {Object} param The given parameters
* @param {Object} options The command options
*/
that.get = function (command) {
setTimeout(function () {
var doc = localstorage.getItem(priv.localpath + "/" + command.getDocId());
if (doc !== null) {
that.success(doc);
} else {
that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": "Cannot find the document",
"reason": "Document does not exist"
});
}
});
LocalStorage.prototype.get = function (command, param) {
var doc = this._storage.getItem(
this._localpath + "/" + param._id
);
if (doc !== null) {
command.success({"data": doc});
} else {
command.error(
"not_found",
"missing",
"Cannot find document"
);
}
};
/**
* Get a attachment
* Get an attachment
*
* @method getAttachment
* @param {object} command The JIO command
* @param {Object} command The JIO command
* @param {Object} param The given parameters
* @param {Object} options The command options
*/
that.getAttachment = function (command) {
setTimeout(function () {
var doc = localstorage.getItem(priv.localpath + "/" + command.getDocId() +
"/" + command.getAttachmentId());
if (doc !== null) {
that.success(doc);
} else {
that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": "Cannot find the attachment",
"reason": "Attachment does not exist"
});
}
LocalStorage.prototype.getAttachment = function (command, param) {
var doc;
doc = this._storage.getItem(this._localpath + "/" + param._id);
if (doc === null) {
return command.error(
"not_found",
"missing document",
"Cannot find document"
);
}
if (typeof doc._attachments !== 'object' ||
typeof doc._attachments[param._attachment] !== 'object') {
return command.error(
"not_found",
"missing attachment",
"Cannot find attachment"
);
}
command.success({
"data": this._storage.getItem(
this._localpath + "/" + param._id +
"/" + param._attachment
) || "",
"digest": doc._attachments[param._attachment].digest,
"content_type": doc._attachments[param._attachment].content_type || ""
});
};
/**
* Remove a document
*
* @method remove
* @param {object} command The JIO command
* @param {Object} command The JIO command
* @param {Object} param The given parameters
* @param {Object} options The command options
*/
that.remove = function (command) {
setTimeout(function () {
var doc, i, attachment_list;
doc = localstorage.getItem(priv.localpath + "/" + command.getDocId());
attachment_list = [];
if (doc !== null && typeof doc === "object") {
if (typeof doc._attachments === "object") {
// prepare list of attachments
for (i in doc._attachments) {
if (doc._attachments.hasOwnProperty(i)) {
attachment_list.push(i);
}
LocalStorage.prototype.remove = function (command, param) {
var doc, i, attachment_list;
doc = this._storage.getItem(this._localpath + "/" + param._id);
attachment_list = [];
if (doc !== null && typeof doc === "object") {
if (typeof doc._attachments === "object") {
// prepare list of attachments
for (i in doc._attachments) {
if (doc._attachments.hasOwnProperty(i)) {
attachment_list.push(i);
}
}
} else {
return that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": "Document not found",
"reason": "missing"
});
}
localstorage.removeItem(priv.localpath + "/" + command.getDocId());
// delete all attachments
for (i = 0; i < attachment_list.length; i += 1) {
localstorage.removeItem(priv.localpath + "/" + command.getDocId() +
"/" + attachment_list[i]);
}
that.success({
"ok": true,
"id": command.getDocId()
});
});
} else {
return command.error(
"not_found",
"missing",
"Document not found"
);
}
this._storage.removeItem(this._localpath + "/" + param._id);
// delete all attachments
for (i = 0; i < attachment_list.length; i += 1) {
this._storage.removeItem(this._localpath + "/" + param._id +
"/" + attachment_list[i]);
}
command.success();
};
/**
* Remove an attachment
*
* @method removeAttachment
* @param {object} command The JIO command
* @param {Object} command The JIO command
* @param {Object} param The given parameters
* @param {Object} options The command options
*/
that.removeAttachment = function (command) {
setTimeout(function () {
var doc, error, i, attachment_list;
error = function (word) {
that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": word + " not found",
"reason": "missing"
});
};
doc = localstorage.getItem(priv.localpath + "/" + command.getDocId());
// remove attachment from document
if (doc !== null && typeof doc === "object" &&
typeof doc._attachments === "object") {
if (typeof doc._attachments[command.getAttachmentId()] ===
"object") {
delete doc._attachments[command.getAttachmentId()];
if (priv.objectIsEmpty(doc._attachments)) {
delete doc._attachments;
}
localstorage.setItem(priv.localpath + "/" + command.getDocId(),
doc);
localstorage.removeItem(priv.localpath + "/" + command.getDocId() +
"/" + command.getAttachmentId());
that.success({
"ok": true,
"id": command.getDocId(),
"attachment": command.getAttachmentId()
});
} else {
error("Attachment");
}
} else {
error("Document");
}
});
LocalStorage.prototype.removeAttachment = function (command, param) {
var doc = this._storage.getItem(this._localpath + "/" + param._id);
if (typeof doc !== 'object') {
return command.error(
"not_found",
"missing document",
"Document not found"
);
}
if (typeof doc._attachments !== "object" ||
typeof doc._attachments[param._attachment] !== "object") {
return command.error(
"not_found",
"missing attachment",
"Attachment not found"
);
}
delete doc._attachments[param._attachment];
if (objectIsEmpty(doc._attachments)) {
delete doc._attachments;
}
this._storage.setItem(this._localpath + "/" + param._id, doc);
this._storage.removeItem(this._localpath + "/" + param._id +
"/" + param._attachment);
command.success();
};
/**
* Get all filenames belonging to a user from the document index
*
* @method allDocs
* @param {object} command The JIO command
* @param {Object} command The JIO command
* @param {Object} param The given parameters
* @param {Object} options The command options
*/
that.allDocs = function (command) {
var i, row, path_re, rows = [], document_list = [], option, document_object;
LocalStorage.prototype.allDocs = function (command, param, options) {
var i, row, path_re, rows, document_list, document_object, delete_id;
param.unused = true;
rows = [];
document_list = [];
path_re = new RegExp(
"^" + complex_queries.stringEscapeRegexpCharacters(priv.localpath) +
"^" + complex_queries.stringEscapeRegexpCharacters(this._localpath) +
"/[^/]+$"
);
option = command.cloneOption();
if (typeof complex_queries !== "object" ||
(option.query === undefined && option.sort_on === undefined &&
option.select_list === undefined &&
option.include_docs === undefined)) {
if (options.query === undefined && options.sort_on === undefined &&
options.select_list === undefined &&
options.include_docs === undefined) {
rows = [];
for (i in localStorage) {
if (localStorage.hasOwnProperty(i)) {
for (i in this._database) {
if (this._database.hasOwnProperty(i)) {
// filter non-documents
if (path_re.test(i)) {
row = { value: {} };
row.id = i.split('/').slice(-1)[0];
row.key = row.id;
if (command.getOption('include_docs')) {
row.doc = JSON.parse(localStorage.getItem(i));
if (options.include_docs) {
row.doc = JSON.parse(this._storage.getItem(i));
}
rows.push(row);
}
}
}
that.success({"rows": rows, "total_rows": rows.length});
command.success({"data": {"rows": rows, "total_rows": rows.length}});
} else {
// create complex query object from returned results
for (i in localStorage) {
if (localStorage.hasOwnProperty(i)) {
for (i in this._database) {
if (this._database.hasOwnProperty(i)) {
if (path_re.test(i)) {
document_list.push(localstorage.getItem(i));
document_list.push(this._storage.getItem(i));
}
}
}
option.select_list = option.select_list || [];
option.select_list.push("_id");
if (option.include_docs === true) {
options.select_list = options.select_list || [];
if (options.select_list.indexOf("_id") === -1) {
options.select_list.push("_id");
delete_id = true;
}
if (options.include_docs === true) {
document_object = {};
document_list.forEach(function (meta) {
document_object[meta._id] = meta;
});
}
complex_queries.QueryFactory.create(option.query || "").
exec(document_list, option);
complex_queries.QueryFactory.create(options.query || "").
exec(document_list, options);
document_list = document_list.map(function (value) {
var o = {
"id": value._id,
"key": value._id
};
if (option.include_docs === true) {
if (options.include_docs === true) {
o.doc = document_object[value._id];
delete document_object[value._id];
}
delete value._id;
if (delete_id) {
delete value._id;
}
o.value = value;
return o;
});
that.success({"total_rows": document_list.length,
"rows": document_list});
command.success({"data": {
"total_rows": document_list.length,
"rows": document_list
}});
}
};
/**
* Check the storage or a specific document
*
* @method check
* @param {Object} command The JIO command
* @param {Object} param The command parameters
* @param {Object} options The command options
*/
LocalStorage.prototype.check = function (command, param) {
this.genericRepair(command, param, false);
};
/**
* Repair the storage or a specific document
*
* @method repair
* @param {Object} command The JIO command
* @param {Object} param The command parameters
* @param {Object} options The command options
*/
LocalStorage.prototype.repair = function (command, param) {
this.genericRepair(command, param, true);
};
/**
* A generic method that manage check or repair command
*
* @method genericRepair
* @param {Object} command The JIO command
* @param {Object} param The command parameters
* @param {Boolean} repair If true then repair else just check
*/
LocalStorage.prototype.genericRepair = function (command, param, repair) {
var that = this, result;
function referenceAttachment(param, attachment) {
if (param.referenced_attachments.indexOf(attachment) !== -1) {
return;
}
var i = param.unreferenced_attachments.indexOf(attachment);
if (i !== -1) {
param.unreferenced_attachments.splice(i, 1);
}
param.referenced_attachments[param.referenced_attachments.length] =
attachment;
}
function attachmentFound(param, attachment) {
if (param.referenced_attachments.indexOf(attachment) !== -1) {
return;
}
if (param.unreferenced_attachments.indexOf(attachment) !== -1) {
return;
}
param.unreferenced_attachments[param.unreferenced_attachments.length] =
attachment;
}
function repairOne(param, repair) {
var i, doc, modified;
doc = that._storage.getItem(that._localpath + "/" + param._id);
if (doc === null) {
return; // OK
}
// check document type
if (typeof doc !== 'object') {
// wrong document
if (!repair) {
return {"error": true, "answers": [
"conflict",
"corrupted",
"Document is unrecoverable"
]};
}
// delete the document
that._storage.removeItem(that._localpath + "/" + param._id);
return; // OK
}
// good document type
// repair json document
if (!repair) {
if (!(new jIO.Metadata(doc).check())) {
return {"error": true, "answers": [
"conflict",
"corrupted",
"Some metadata might be lost"
]};
}
} else {
modified = jIO.util.uniqueJSONStringify(doc) !==
jIO.util.uniqueJSONStringify(new jIO.Metadata(doc).format()._dict);
}
if (doc._attachments !== undefined) {
if (typeof doc._attachments !== 'object') {
if (!repair) {
return {"error": true, "answers": [
"conflict",
"corrupted",
"Attachments are unrecoverable"
]};
}
delete doc._attachments;
that._storage.setItem(that._localpath + "/" + param._id, doc);
return; // OK
}
for (i in doc._attachments) {
if (doc._attachments.hasOwnProperty(i)) {
// check attachment existence
if (that._storage.getItem(that._localpath + "/" + param._id + "/" +
i) !== 'string') {
if (!repair) {
return {"error": true, "answers": [
"conflict",
"missing attachment",
"Attachment \"" + i + "\" of \"" + param._id + "\" is missing"
]};
}
delete doc._attachments[i];
if (objectIsEmpty(doc._attachments)) {
delete doc._attachments;
}
modified = true;
} else {
// attachment exists
// check attachment metadata
// check length
referenceAttachment(param, param._id + "/" + doc._attachments[i]);
if (doc._attachments[i].length !== undefined &&
typeof doc._attachments[i].length !== 'number') {
if (!repair) {
return {"error": true, "answers": [
"conflict",
"corrupted",
"Attachment metadata length corrupted"
]};
}
// It could take a long time to get the length, no repair.
// length can be omited
delete doc._attachments[i].length;
}
// It could take a long time to regenerate the hash, no check.
// Impossible to discover the attachment content type.
}
}
}
}
if (modified) {
that._storage.setItem(that._localpath + "/" + param._id, doc);
}
// OK
}
function repairAll(param, repair) {
var i, result;
for (i in that._database) {
if (that._database.hasOwnProperty(i)) {
// browsing every entry
if (i.slice(0, that._localpath.length) === that._localpath) {
// is part of the user space
if (/^[^\/]+\/[^\/]+$/.test(i.slice(that._localpath.length + 1))) {
// this is an attachment
attachmentFound(param, i.slice(that._localpath.length + 1));
} else if (/^[^\/]+$/.test(i.slice(that._localpath.length + 1))) {
// this is a document
param._id = i.slice(that._localpath.length + 1);
result = repairOne(param, repair);
if (result) {
return result;
}
} else {
// this is pollution
that._storage.removeItem(i);
}
}
}
}
// remove unreferenced attachments
for (i = 0; i < param.unreferenced_attachments.length; i += 1) {
that._storage.removeItem(that._localpath + "/" +
param.unreferenced_attachments[i]);
}
}
param.referenced_attachments = [];
param.unreferenced_attachments = [];
if (typeof param._id === 'string') {
result = repairOne(param, repair) || {};
} else {
result = repairAll(param, repair) || {};
}
if (result.error) {
return command.error.apply(command, result.answers || []);
}
command.success.apply(command, result.answers || []);
};
return that;
});
jIO.addStorage('local', LocalStorage);
//////////////////////////////////////////////////////////////////////
// Tools
function createLocalDescription(username, application_name) {
if (typeof username !== 'string') {
throw new TypeError("LocalStorage username must be a string");
}
var description = {
"type": "local",
"username": username
};
if (typeof application_name === 'string') {
description.application_name = application_name;
}
return description;
}
function createMemoryDescription(username, application_name) {
var description = createLocalDescription(username, application_name);
description.mode = "memory";
return description;
}
/**
* Tool to help users to create local storage description for JIO
*
* @param {String} username The username
* @param {String} [application_name] The application_name
* @param {String} [mode="localStorage"] Use localStorage or memory
* @return {Object} The storage description
*/
function createDescription(username, application_name, mode) {
if (mode === undefined || mode.toString() === 'localStorage') {
return createLocalDescription(username, application_name);
}
if (mode.toString() === 'memory') {
return createMemoryDescription(username, application_name);
}
throw new TypeError("Unknown LocalStorage '" + mode.toString() + "' mode");
}
exports.createDescription = createDescription;
exports.createLocalDescription = createLocalDescription;
exports.createMemoryDescription = createMemoryDescription;
function clearLocalStorage() {
var k;
for (k in localStorage) {
if (localStorage.hasOwnProperty(k)) {
if (/^jio\/localstorage\//.test(k)) {
localStorage.removeItem(k);
}
}
}
}
function clearMemoryStorage() {
jIO.util.dictClear(ram);
}
exports.clear = clearLocalStorage;
exports.clearLocalStorage = clearLocalStorage;
exports.clearMemoryStorage = clearMemoryStorage;
}));
(function (dependencies, module) {
if (typeof define === 'function' && define.amd) {
return define(dependencies, module);
}
if (typeof exports === 'object') {
return module(exports);
}
module(window);
}(['exports'], function (exports) {
/*
* A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
* Digest Algorithm, as defined in RFC 1321.
......@@ -377,3 +386,11 @@ function bit_rol(num, cnt)
{
return (num << cnt) | (num >>> (32 - cnt));
}
exports.hex_md5 = hex_md5;
exports.b64_md5 = b64_md5;
exports.any_md5 = any_md5;
exports.hex_hmac_md5 = hex_hmac_md5;
exports.b64_hmac_md5 = b64_hmac_md5;
exports.any_hmac_md5 = any_hmac_md5;
}));
(function (dependencies, module) {
if (typeof define === 'function' && define.amd) {
return define(dependencies, module);
}
if (typeof exports === 'object') {
return module(exports);
}
module(window);
}(['exports'], function (window) {
/* A JavaScript implementation of the Secure Hash Algorithm, SHA-256
* Version 0.3 Copyright Angel Marin 2003-2004 - http://anmar.eu.org/
* Distributed under the BSD License
* Some bits taken from Paul Johnston's SHA-1 implementation
*/
(function () {
var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */
function safe_add (x, y) {
var lsw = (x & 0xFFFF) + (y & 0xFFFF);
var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
return (msw << 16) | (lsw & 0xFFFF);
}
function S (X, n) {return ( X >>> n ) | (X << (32 - n));}
function R (X, n) {return ( X >>> n );}
function Ch(x, y, z) {return ((x & y) ^ ((~x) & z));}
function Maj(x, y, z) {return ((x & y) ^ (x & z) ^ (y & z));}
function Sigma0256(x) {return (S(x, 2) ^ S(x, 13) ^ S(x, 22));}
function Sigma1256(x) {return (S(x, 6) ^ S(x, 11) ^ S(x, 25));}
function Gamma0256(x) {return (S(x, 7) ^ S(x, 18) ^ R(x, 3));}
function Gamma1256(x) {return (S(x, 17) ^ S(x, 19) ^ R(x, 10));}
function newArray (n) {
var a = [];
for (;n>0;n--) {
a.push(undefined);
}
return a;
}
function core_sha256 (m, l) {
var K = [0x428A2F98,0x71374491,0xB5C0FBCF,0xE9B5DBA5,0x3956C25B,0x59F111F1,0x923F82A4,0xAB1C5ED5,0xD807AA98,0x12835B01,0x243185BE,0x550C7DC3,0x72BE5D74,0x80DEB1FE,0x9BDC06A7,0xC19BF174,0xE49B69C1,0xEFBE4786,0xFC19DC6,0x240CA1CC,0x2DE92C6F,0x4A7484AA,0x5CB0A9DC,0x76F988DA,0x983E5152,0xA831C66D,0xB00327C8,0xBF597FC7,0xC6E00BF3,0xD5A79147,0x6CA6351,0x14292967,0x27B70A85,0x2E1B2138,0x4D2C6DFC,0x53380D13,0x650A7354,0x766A0ABB,0x81C2C92E,0x92722C85,0xA2BFE8A1,0xA81A664B,0xC24B8B70,0xC76C51A3,0xD192E819,0xD6990624,0xF40E3585,0x106AA070,0x19A4C116,0x1E376C08,0x2748774C,0x34B0BCB5,0x391C0CB3,0x4ED8AA4A,0x5B9CCA4F,0x682E6FF3,0x748F82EE,0x78A5636F,0x84C87814,0x8CC70208,0x90BEFFFA,0xA4506CEB,0xBEF9A3F7,0xC67178F2];
var HASH = [0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19];
var W = newArray(64);
var a, b, c, d, e, f, g, h, i, j;
var T1, T2;
/* append padding */
m[l >> 5] |= 0x80 << (24 - l % 32);
m[((l + 64 >> 9) << 4) + 15] = l;
for ( var i = 0; i<m.length; i+=16 ) {
a = HASH[0]; b = HASH[1]; c = HASH[2]; d = HASH[3];
e = HASH[4]; f = HASH[5]; g = HASH[6]; h = HASH[7];
for ( var j = 0; j<64; j++) {
if (j < 16) {
W[j] = m[j + i];
} else {
W[j] = safe_add(safe_add(safe_add(Gamma1256(
W[j - 2]), W[j - 7]), Gamma0256(W[j - 15])), W[j - 16]);
}
T1 = safe_add(safe_add(safe_add(
safe_add(h, Sigma1256(e)), Ch(e, f, g)), K[j]), W[j]);
T2 = safe_add(Sigma0256(a), Maj(a, b, c));
h = g; g = f; f = e; e = safe_add(d, T1);
d = c; c = b; b = a; a = safe_add(T1, T2);
}
HASH[0] = safe_add(a, HASH[0]); HASH[1] = safe_add(b, HASH[1]);
HASH[2] = safe_add(c, HASH[2]); HASH[3] = safe_add(d, HASH[3]);
HASH[4] = safe_add(e, HASH[4]); HASH[5] = safe_add(f, HASH[5]);
HASH[6] = safe_add(g, HASH[6]); HASH[7] = safe_add(h, HASH[7]);
}
return HASH;
}
function str2binb (str) {
var bin = Array();
var mask = (1 << chrsz) - 1;
for(var i = 0; i < str.length * chrsz; i += chrsz)
bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (24 - i%32);
return bin;
}
function binb2hex (binarray) {
var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
var str = "";
for (var i = 0; i < binarray.length * 4; i++) {
str += hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8+4)) & 0xF) +
hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8 )) & 0xF);
}
return str;
}
function hex_sha256(s){
return binb2hex(core_sha256(str2binb(s),s.length * chrsz));
}
window.hex_sha256 = hex_sha256;
}());
}));
(function(globals) {
var define, requireModule;
(function() {
var registry = {}, seen = {};
define = function(name, deps, callback) {
registry[name] = { deps: deps, callback: callback };
};
requireModule = function(name) {
if (seen[name]) { return seen[name]; }
seen[name] = {};
var mod = registry[name];
if (!mod) {
throw new Error("Module '" + name + "' not found.");
}
var deps = mod.deps,
callback = mod.callback,
reified = [],
exports;
for (var i=0, l=deps.length; i<l; i++) {
if (deps[i] === 'exports') {
reified.push(exports = {});
} else {
reified.push(requireModule(deps[i]));
}
}
var value = callback.apply(this, reified);
return seen[name] = exports || value;
};
})();
define("rsvp/all",
["rsvp/promise","exports"],
function(__dependency1__, __exports__) {
"use strict";
var Promise = __dependency1__.Promise;
/* global toString */
function promiseAtLeast(expected_count, promises) {
if (Object.prototype.toString.call(promises) !== "[object Array]") {
throw new TypeError('You must pass an array to all.');
}
function canceller() {
var promise;
for (var i = 0; i < promises.length; i++) {
promise = promises[i];
if (promise && typeof promise.then === 'function' &&
typeof promise.cancel === 'function') {
promise.cancel();
}
}
}
return new Promise(function(resolve, reject, notify) {
var results = [], remaining = promises.length,
promise, remaining_count = promises.length - expected_count;
if (remaining === 0) {
if (expected_count === 1) {
resolve();
} else {
resolve([]);
}
}
function resolver(index) {
return function(value) {
resolveAll(index, value);
};
}
function resolveAll(index, value) {
results[index] = value;
if (--remaining === remaining_count) {
if (remaining_count === 0) {
resolve(results);
} else {
resolve(value);
canceller();
}
}
}
function notifier(index) {
return function(value) {
notify({"index": index, "value": value});
};
}
function cancelAll(rejectionValue) {
reject(rejectionValue);
canceller();
}
for (var i = 0; i < promises.length; i++) {
promise = promises[i];
if (promise && typeof promise.then === 'function') {
promise.then(resolver(i), cancelAll, notifier(i));
} else {
resolveAll(i, promise);
}
}
}, canceller
);
}
function all(promises) {
return promiseAtLeast(promises.length, promises);
}
function any(promises) {
return promiseAtLeast(1, promises);
}
__exports__.all = all;
__exports__.any = any;
});
define("rsvp/async",
["exports"],
function(__exports__) {
"use strict";
var browserGlobal = (typeof window !== 'undefined') ? window : {};
var BrowserMutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver;
var async;
var local = (typeof global !== 'undefined') ? global : this;
// old node
function useNextTick() {
return function(callback, arg) {
process.nextTick(function() {
callback(arg);
});
};
}
// node >= 0.10.x
function useSetImmediate() {
return function(callback, arg) {
/* global setImmediate */
setImmediate(function(){
callback(arg);
});
};
}
function useMutationObserver() {
var queue = [];
var observer = new BrowserMutationObserver(function() {
var toProcess = queue.slice();
queue = [];
toProcess.forEach(function(tuple) {
var callback = tuple[0], arg= tuple[1];
callback(arg);
});
});
var element = document.createElement('div');
observer.observe(element, { attributes: true });
// Chrome Memory Leak: https://bugs.webkit.org/show_bug.cgi?id=93661
window.addEventListener('unload', function(){
observer.disconnect();
observer = null;
}, false);
return function(callback, arg) {
queue.push([callback, arg]);
element.setAttribute('drainQueue', 'drainQueue');
};
}
function useSetTimeout() {
return function(callback, arg) {
local.setTimeout(function() {
callback(arg);
}, 1);
};
}
if (typeof setImmediate === 'function') {
async = useSetImmediate();
} else if (typeof process !== 'undefined' && {}.toString.call(process) === '[object process]') {
async = useNextTick();
} else if (BrowserMutationObserver) {
async = useMutationObserver();
} else {
async = useSetTimeout();
}
__exports__.async = async;
});
define("rsvp/cancellation_error",
["exports"],
function(__exports__) {
"use strict";
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
function CancellationError(message) {
this.name = "cancel";
if ((message !== undefined) && (typeof message !== "string")) {
throw new TypeError('You must pass a string.');
}
this.message = message || "Default Message";
}
CancellationError.prototype = new Error();
CancellationError.prototype.constructor = CancellationError;
__exports__.CancellationError = CancellationError;
});
define("rsvp/config",
["rsvp/async","exports"],
function(__dependency1__, __exports__) {
"use strict";
var async = __dependency1__.async;
var config = {};
config.async = async;
__exports__.config = config;
});
define("rsvp/defer",
["rsvp/promise","exports"],
function(__dependency1__, __exports__) {
"use strict";
var Promise = __dependency1__.Promise;
function defer() {
var deferred = {
// pre-allocate shape
resolve: undefined,
reject: undefined,
promise: undefined
};
deferred.promise = new Promise(function(resolve, reject) {
deferred.resolve = resolve;
deferred.reject = reject;
});
return deferred;
}
__exports__.defer = defer;
});
define("rsvp/events",
["exports"],
function(__exports__) {
"use strict";
var Event = function(type, options) {
this.type = type;
for (var option in options) {
if (!options.hasOwnProperty(option)) { continue; }
this[option] = options[option];
}
};
var indexOf = function(callbacks, callback) {
for (var i=0, l=callbacks.length; i<l; i++) {
if (callbacks[i][0] === callback) { return i; }
}
return -1;
};
var callbacksFor = function(object) {
var callbacks = object._promiseCallbacks;
if (!callbacks) {
callbacks = object._promiseCallbacks = {};
}
return callbacks;
};
var EventTarget = {
mixin: function(object) {
object.on = this.on;
object.off = this.off;
object.trigger = this.trigger;
return object;
},
on: function(eventNames, callback, binding) {
var allCallbacks = callbacksFor(this), callbacks, eventName;
eventNames = eventNames.split(/\s+/);
binding = binding || this;
while (eventName = eventNames.shift()) {
callbacks = allCallbacks[eventName];
if (!callbacks) {
callbacks = allCallbacks[eventName] = [];
}
if (indexOf(callbacks, callback) === -1) {
callbacks.push([callback, binding]);
}
}
},
off: function(eventNames, callback) {
var allCallbacks = callbacksFor(this), callbacks, eventName, index;
eventNames = eventNames.split(/\s+/);
while (eventName = eventNames.shift()) {
if (!callback) {
allCallbacks[eventName] = [];
continue;
}
callbacks = allCallbacks[eventName];
index = indexOf(callbacks, callback);
if (index !== -1) { callbacks.splice(index, 1); }
}
},
trigger: function(eventName, options) {
var allCallbacks = callbacksFor(this),
callbacks, callbackTuple, callback, binding, event;
if (callbacks = allCallbacks[eventName]) {
// Don't cache the callbacks.length since it may grow
for (var i=0; i<callbacks.length; i++) {
callbackTuple = callbacks[i];
callback = callbackTuple[0];
binding = callbackTuple[1];
if (typeof options !== 'object') {
options = { detail: options };
}
event = new Event(eventName, options);
callback.call(binding, event);
}
}
}
};
__exports__.EventTarget = EventTarget;
});
define("rsvp/hash",
["rsvp/defer","exports"],
function(__dependency1__, __exports__) {
"use strict";
var defer = __dependency1__.defer;
function size(object) {
var s = 0;
for (var prop in object) {
s++;
}
return s;
}
function hash(promises) {
var results = {}, deferred = defer(), remaining = size(promises);
if (remaining === 0) {
deferred.resolve({});
}
var resolver = function(prop) {
return function(value) {
resolveAll(prop, value);
};
};
var resolveAll = function(prop, value) {
results[prop] = value;
if (--remaining === 0) {
deferred.resolve(results);
}
};
var rejectAll = function(error) {
deferred.reject(error);
};
for (var prop in promises) {
if (promises[prop] && typeof promises[prop].then === 'function') {
promises[prop].then(resolver(prop), rejectAll);
} else {
resolveAll(prop, promises[prop]);
}
}
return deferred.promise;
}
__exports__.hash = hash;
});
define("rsvp/node",
["rsvp/promise","rsvp/all","exports"],
function(__dependency1__, __dependency2__, __exports__) {
"use strict";
var Promise = __dependency1__.Promise;
var all = __dependency2__.all;
function makeNodeCallbackFor(resolve, reject) {
return function (error, value) {
if (error) {
reject(error);
} else if (arguments.length > 2) {
resolve(Array.prototype.slice.call(arguments, 1));
} else {
resolve(value);
}
};
}
function denodeify(nodeFunc) {
return function() {
var nodeArgs = Array.prototype.slice.call(arguments), resolve, reject;
var thisArg = this;
var promise = new Promise(function(nodeResolve, nodeReject) {
resolve = nodeResolve;
reject = nodeReject;
});
all(nodeArgs).then(function(nodeArgs) {
nodeArgs.push(makeNodeCallbackFor(resolve, reject));
try {
nodeFunc.apply(thisArg, nodeArgs);
} catch(e) {
reject(e);
}
});
return promise;
};
}
__exports__.denodeify = denodeify;
});
define("rsvp/promise",
["rsvp/config","rsvp/events","rsvp/cancellation_error","exports"],
function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
"use strict";
var config = __dependency1__.config;
var EventTarget = __dependency2__.EventTarget;
var CancellationError = __dependency3__.CancellationError;
function objectOrFunction(x) {
return isFunction(x) || (typeof x === "object" && x !== null);
}
function isFunction(x){
return typeof x === "function";
}
var Promise = function(resolver, canceller) {
var promise = this,
resolved = false;
if (typeof resolver !== 'function') {
throw new TypeError('You must pass a resolver function as the sole argument to the promise constructor');
}
if ((canceller !== undefined) && (typeof canceller !== 'function')) {
throw new TypeError('You can only pass a canceller function' +
' as the second argument to the promise constructor');
}
if (!(promise instanceof Promise)) {
return new Promise(resolver, canceller);
}
var resolvePromise = function(value) {
if (resolved) { return; }
resolved = true;
resolve(promise, value);
};
var rejectPromise = function(value) {
if (resolved) { return; }
resolved = true;
reject(promise, value);
};
var notifyPromise = function(value) {
if (resolved) { return; }
notify(promise, value);
};
this.on('promise:failed', function(event) {
this.trigger('error', { detail: event.detail });
}, this);
this.on('error', onerror);
this.cancel = function () {
// For now, simply reject the promise and does not propagate the cancel
// to parent or children
if (resolved) { return; }
if (canceller !== undefined) {
try {
canceller();
} catch (e) {
rejectPromise(e);
return;
}
}
// Trigger cancel?
rejectPromise(new CancellationError());
};
try {
resolver(resolvePromise, rejectPromise, notifyPromise);
} catch(e) {
rejectPromise(e);
}
};
function onerror(event) {
if (config.onerror) {
config.onerror(event.detail);
}
}
var invokeCallback = function(type, promise, callback, event) {
var hasCallback = isFunction(callback),
value, error, succeeded, failed;
if (promise.isFulfilled) { return; }
if (promise.isRejected) { return; }
if (hasCallback) {
try {
value = callback(event.detail);
succeeded = true;
} catch(e) {
failed = true;
error = e;
}
} else {
value = event.detail;
succeeded = true;
}
if (handleThenable(promise, value)) {
return;
} else if (hasCallback && succeeded) {
resolve(promise, value);
} else if (failed) {
reject(promise, error);
} else if (type === 'resolve') {
resolve(promise, value);
} else if (type === 'reject') {
reject(promise, value);
}
};
var invokeNotifyCallback = function(promise, callback, event) {
var value;
if (typeof callback === 'function') {
try {
value = callback(event.detail);
} catch (e) {
// stop propagating
return;
}
notify(promise, value);
} else {
notify(promise, event.detail);
}
};
Promise.prototype = {
constructor: Promise,
isRejected: undefined,
isFulfilled: undefined,
rejectedReason: undefined,
fulfillmentValue: undefined,
then: function(done, fail, progress) {
this.off('error', onerror);
var thenPromise = new this.constructor(function() {},
function () {
thenPromise.trigger('promise:cancelled', {});
});
if (this.isFulfilled) {
config.async(function(promise) {
invokeCallback('resolve', thenPromise, done, { detail: promise.fulfillmentValue });
}, this);
}
if (this.isRejected) {
config.async(function(promise) {
invokeCallback('reject', thenPromise, fail, { detail: promise.rejectedReason });
}, this);
}
this.on('promise:resolved', function(event) {
invokeCallback('resolve', thenPromise, done, event);
});
this.on('promise:failed', function(event) {
invokeCallback('reject', thenPromise, fail, event);
});
this.on('promise:notified', function (event) {
invokeNotifyCallback(thenPromise, progress, event);
});
return thenPromise;
},
fail: function(fail) {
return this.then(null, fail);
},
always: function(fail) {
return this.then(fail, fail);
}
};
EventTarget.mixin(Promise.prototype);
function resolve(promise, value) {
if (promise === value) {
fulfill(promise, value);
} else if (!handleThenable(promise, value)) {
fulfill(promise, value);
}
}
function handleThenable(promise, value) {
var then = null,
resolved;
try {
if (promise === value) {
throw new TypeError("A promises callback cannot return that same promise.");
}
if (objectOrFunction(value)) {
then = value.then;
if (isFunction(then)) {
promise.on('promise:cancelled', function(event) {
if (isFunction(value.cancel)) {
value.cancel();
}
});
then.call(value, function(val) {
if (resolved) { return true; }
resolved = true;
if (value !== val) {
resolve(promise, val);
} else {
fulfill(promise, val);
}
}, function(val) {
if (resolved) { return true; }
resolved = true;
reject(promise, val);
});
return true;
}
}
} catch (error) {
reject(promise, error);
return true;
}
return false;
}
function fulfill(promise, value) {
config.async(function() {
if (promise.isFulfilled) { return; }
if (promise.isRejected) { return; }
promise.trigger('promise:resolved', { detail: value });
promise.isFulfilled = true;
promise.fulfillmentValue = value;
});
}
function reject(promise, value) {
config.async(function() {
if (promise.isFulfilled) { return; }
if (promise.isRejected) { return; }
promise.trigger('promise:failed', { detail: value });
promise.isRejected = true;
promise.rejectedReason = value;
});
}
function notify(promise, value) {
config.async(function() {
promise.trigger('promise:notified', { detail: value });
});
}
__exports__.Promise = Promise;
});
define("rsvp/queue",
["rsvp/promise","rsvp/timeout","exports"],
function(__dependency1__, __dependency2__, __exports__) {
"use strict";
var Promise = __dependency1__.Promise;
var delay = __dependency2__.delay;
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
function ResolvedQueueError(message) {
this.name = "resolved";
if ((message !== undefined) && (typeof message !== "string")) {
throw new TypeError('You must pass a string.');
}
this.message = message || "Default Message";
}
ResolvedQueueError.prototype = new Error();
ResolvedQueueError.prototype.constructor = ResolvedQueueError;
var Queue = function() {
var queue = this,
promise_list = [],
promise,
fulfill,
reject,
resolved;
if (!(this instanceof Queue)) {
return new Queue();
}
function canceller() {
for (var i = 0; i < 2; i++) {
promise_list[i].cancel();
}
}
promise = new Promise(function(done, fail) {
fulfill = function (fulfillmentValue) {
if (resolved) {return;}
queue.isFulfilled = true;
queue.fulfillmentValue = fulfillmentValue;
resolved = true;
return done(fulfillmentValue);
};
reject = function (rejectedReason) {
if (resolved) {return;}
queue.isRejected = true;
queue.rejectedReason = rejectedReason ;
resolved = true;
return fail(rejectedReason);
};
}, canceller);
promise_list.push(delay());
promise_list.push(promise_list[0].then(function () {
promise_list.splice(0, 2);
if (promise_list.length === 0) {
fulfill();
}
}));
queue.cancel = function () {
if (resolved) {return;}
resolved = true;
promise.cancel();
promise.fail(function (rejectedReason) {
queue.isRejected = true;
queue.rejectedReason = rejectedReason;
});
};
queue.then = function () {
return promise.then.apply(promise, arguments);
};
queue.push = function(done, fail) {
var last_promise = promise_list[promise_list.length - 1],
next_promise;
if (resolved) {
throw new ResolvedQueueError();
}
next_promise = last_promise.then(done, fail);
promise_list.push(next_promise);
// Handle pop
promise_list.push(next_promise.then(function (fulfillmentValue) {
promise_list.splice(0, 2);
if (promise_list.length === 0) {
fulfill(fulfillmentValue);
} else {
return fulfillmentValue;
}
}, function (rejectedReason) {
promise_list.splice(0, 2);
if (promise_list.length === 0) {
reject(rejectedReason);
} else {
throw rejectedReason;
}
}));
return this;
};
};
Queue.prototype = Object.create(Promise.prototype);
Queue.prototype.constructor = Queue;
__exports__.Queue = Queue;
__exports__.ResolvedQueueError = ResolvedQueueError;
});
define("rsvp/reject",
["rsvp/promise","exports"],
function(__dependency1__, __exports__) {
"use strict";
var Promise = __dependency1__.Promise;
function reject(reason) {
return new Promise(function (resolve, reject) {
reject(reason);
});
}
__exports__.reject = reject;
});
define("rsvp/resolve",
["rsvp/promise","exports"],
function(__dependency1__, __exports__) {
"use strict";
var Promise = __dependency1__.Promise;
function resolve(thenable) {
return new Promise(function(resolve, reject) {
if (typeof thenable === "object" && thenable !== null) {
var then = thenable.then;
if ((then !== undefined) && (typeof then === "function")) {
return then.apply(thenable, [resolve, reject]);
}
}
return resolve(thenable);
}, function () {
if ((thenable !== undefined) && (thenable.cancel !== undefined)) {
thenable.cancel();
}
});
}
__exports__.resolve = resolve;
});
define("rsvp/rethrow",
["exports"],
function(__exports__) {
"use strict";
var local = (typeof global === "undefined") ? this : global;
function rethrow(reason) {
local.setTimeout(function() {
throw reason;
});
throw reason;
}
__exports__.rethrow = rethrow;
});
define("rsvp/timeout",
["rsvp/promise","exports"],
function(__dependency1__, __exports__) {
"use strict";
var Promise = __dependency1__.Promise;
function promiseSetTimeout(millisecond, should_reject, message) {
var timeout_id;
function resolver(resolve, reject) {
timeout_id = setTimeout(function () {
if (should_reject) {
reject(message);
} else {
resolve(message);
}
}, millisecond);
}
function canceller() {
clearTimeout(timeout_id);
}
return new Promise(resolver, canceller);
}
function delay(millisecond, message) {
return promiseSetTimeout(millisecond, false, message);
}
function timeout(millisecond) {
return promiseSetTimeout(millisecond, true,
"Timed out after " + millisecond + " ms");
}
Promise.prototype.delay = function(millisecond) {
return this.then(function (fulfillmentValue) {
return delay(millisecond, fulfillmentValue);
});
};
__exports__.delay = delay;
__exports__.timeout = timeout;
});
define("rsvp",
["rsvp/events","rsvp/cancellation_error","rsvp/promise","rsvp/node","rsvp/all","rsvp/queue","rsvp/timeout","rsvp/hash","rsvp/rethrow","rsvp/defer","rsvp/config","rsvp/resolve","rsvp/reject","exports"],
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __exports__) {
"use strict";
var EventTarget = __dependency1__.EventTarget;
var CancellationError = __dependency2__.CancellationError;
var Promise = __dependency3__.Promise;
var denodeify = __dependency4__.denodeify;
var all = __dependency5__.all;
var any = __dependency5__.any;
var Queue = __dependency6__.Queue;
var ResolvedQueueError = __dependency6__.ResolvedQueueError;
var delay = __dependency7__.delay;
var timeout = __dependency7__.timeout;
var hash = __dependency8__.hash;
var rethrow = __dependency9__.rethrow;
var defer = __dependency10__.defer;
var config = __dependency11__.config;
var resolve = __dependency12__.resolve;
var reject = __dependency13__.reject;
function configure(name, value) {
config[name] = value;
}
__exports__.CancellationError = CancellationError;
__exports__.Promise = Promise;
__exports__.EventTarget = EventTarget;
__exports__.all = all;
__exports__.any = any;
__exports__.Queue = Queue;
__exports__.ResolvedQueueError = ResolvedQueueError;
__exports__.delay = delay;
__exports__.timeout = timeout;
__exports__.hash = hash;
__exports__.rethrow = rethrow;
__exports__.defer = defer;
__exports__.denodeify = denodeify;
__exports__.configure = configure;
__exports__.resolve = resolve;
__exports__.reject = reject;
});
window.RSVP = requireModule("rsvp");
})(window);
\ No newline at end of file
/**
* sinon-qunit 1.0.0, 2010/12/09
*
* @author Christian Johansen (christian@cjohansen.no)
*
* (The BSD License)
*
* Copyright (c) 2010-2011, Christian Johansen, christian@cjohansen.no
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Christian Johansen nor the names of his contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*global sinon, QUnit, test*/
sinon.assert.fail = function (msg) {
QUnit.ok(false, msg);
};
sinon.assert.pass = function (assertion) {
QUnit.ok(true, assertion);
};
sinon.config = {
injectIntoThis: true,
injectInto: null,
properties: ["spy", "stub", "mock", "clock", "sandbox"],
useFakeTimers: true,
useFakeServer: false
};
(function (global) {
var qTest = QUnit.test;
QUnit.test = global.test = function (testName, expected, callback, async) {
if (arguments.length === 2) {
callback = expected;
expected = null;
}
return qTest(testName, expected, sinon.test(callback), async);
};
}(this));
/*! RenderJs v0.2 */
/*global jQuery, window, document, DOMParser, Channel */
/*! RenderJs v0.3 */
/*global RSVP, window, document, DOMParser, Channel, XMLHttpRequest, alert */
/*jslint unparam: true, maxlen: 150 */
"use strict";
/*
......@@ -22,7 +23,7 @@
// text/html parsing is natively supported
return;
}
} catch (ex) {}
} catch (ex) {console.warn(ex); }
DOMParser_proto.parseFromString = function (markup, type) {
var result, doc, doc_elt, first_elt;
......@@ -50,17 +51,20 @@
* renderJs - Generic Gadget library renderer.
* http://www.renderjs.org/documentation
*/
(function (document, window, $, DOMParser, Channel, undefined) {
(function (document, window, RSVP, DOMParser, Channel, undefined) {
var gadget_model_dict = {},
javascript_registration_dict = {},
stylesheet_registration_dict = {},
gadget_loading_klass,
methods,
loading_gadget_promise,
renderJS;
function RenderJSGadget() {}
function RenderJSGadget() {
if (!(this instanceof RenderJSGadget)) {
return new RenderJSGadget();
}
}
RenderJSGadget.prototype.title = "";
RenderJSGadget.prototype.interface_list = [];
RenderJSGadget.prototype.path = "";
......@@ -75,36 +79,14 @@
};
RenderJSGadget.declareMethod = function (name, callback) {
// // Register the potentially loading javascript
// var script_element = $('script').last(),
// src = script_element.attr('src');
// if (src !== undefined) {
// if (javascript_registration_dict[src] === undefined) {
// // First time loading the JS file.
// // Remember all declareMethod calls
// javascript_registration_dict[src] = {
// loaded: false,
// method_list: [[name, callback]],
// };
// script_element.load(function () {
// javascript_registration_dict[src].loaded = true;
// });
// } else if (!javascript_registration_dict[src].loaded) {
// javascript_registration_dict[src].method_list.push([name, callback]);
// }
// }
this.prototype[name] = function () {
var dfr = $.Deferred(),
gadget = this;
$.when(callback.apply(this, arguments))
.done(function () {
dfr.resolveWith(gadget, arguments);
})
.fail(function () {
dfr.rejectWith(gadget, arguments);
var context = this,
argument_list = arguments;
return new RSVP.Queue()
.push(function () {
return callback.apply(context, argument_list);
});
return dfr.promise();
};
// Allow chain
return this;
......@@ -131,400 +113,101 @@
// Returns the title of a gadget
return this.title;
})
.declareMethod('getHTML', function () {
// Returns the HTML of a gadget
return this.html;
.declareMethod('getElement', function () {
// Returns the DOM Element of a gadget
if (this.element === undefined) {
throw new Error("No element defined");
}
return this.element;
});
// Class inheritance
function RenderJSEmbeddedGadget() {
var root_gadget = this,
declare_method_count = 0,
gadget_ready = false,
// Create the communication channel
embedded_channel = Channel.build({
window: window.parent,
origin: "*",
scope: "renderJS"
});
RenderJSGadget.call(this);
// Bind calls to renderJS method on the instance
embedded_channel.bind("methodCall", function (trans, v) {
root_gadget[v[0]].apply(root_gadget, v[1]).done(function (g) {
trans.complete(g);
}).fail(function () {
trans.error(Array.prototype.slice.call(arguments, 0));
});
trans.delayReturn(true);
});
RenderJSGadget.prototype.declareGadget = function (url, options) {
var gadget_instance,
queue,
previous_loading_gadget_promise = loading_gadget_promise;
// Notify parent about gadget instanciation
function notifyReady() {
if ((declare_method_count === 0) && (gadget_ready === true)) {
embedded_channel.notify({method: "ready"});
}
if (options === undefined) {
options = {};
}
// Inform parent gadget about declareMethod calls here.
function notifyDeclareMethod(name) {
declare_method_count += 1;
embedded_channel.call({
method: "declareMethod",
params: name,
success: function () {
declare_method_count -= 1;
notifyReady();
},
error: function () {
declare_method_count -= 1;
// console.error(Array.prototype.slice.call(arguments, 0));
},
});
if (options.element === undefined) {
options.element = document.createElement("div");
}
notifyDeclareMethod("getInterfaceList");
notifyDeclareMethod("getRequiredCSSList");
notifyDeclareMethod("getRequiredJSList");
notifyDeclareMethod("getPath");
notifyDeclareMethod("getTitle");
notifyDeclareMethod("getHTML");
// Surcharge declareMethod to inform parent window
this.constructor.declareMethod = function (name, callback) {
notifyDeclareMethod(name);
return RenderJSGadget.declareMethod.apply(this, [name, callback]);
};
// Inform parent window that gadget is correctly loaded
loading_gadget_promise.done(function () {
gadget_ready = true;
notifyReady();
}).fail(function () {
embedded_channel.notify({method: "failed"});
});
return root_gadget;
}
RenderJSEmbeddedGadget.ready_list = [];
RenderJSEmbeddedGadget.ready =
RenderJSGadget.ready;
RenderJSEmbeddedGadget.prototype = new RenderJSGadget();
RenderJSEmbeddedGadget.prototype.constructor = RenderJSEmbeddedGadget;
// Class inheritance
function RenderJSIframeGadget() {
RenderJSGadget.call(this);
}
RenderJSIframeGadget.ready_list = [];
RenderJSIframeGadget.declareMethod =
RenderJSGadget.declareMethod;
RenderJSIframeGadget.ready =
RenderJSGadget.ready;
RenderJSIframeGadget.prototype = new RenderJSGadget();
RenderJSIframeGadget.prototype.constructor = RenderJSIframeGadget;
RenderJSGadget.prototype.declareIframedGadget =
function (url, jquery_context) {
var previous_loading_gadget_promise = loading_gadget_promise,
next_loading_gadget_deferred = $.Deferred();
// Change the global variable to update the loading queue
loading_gadget_promise = next_loading_gadget_deferred.promise();
// Wait for previous gadget loading to finish first
previous_loading_gadget_promise.always(function () {
// Instanciate iframe
var gadget = new RenderJSIframeGadget();
gadget.context = jquery_context;
// XXX Do not set this info on the instance!
gadget.path = url;
// XXX onload onerror
// $('iframe').load(function() {
// RunAfterIFrameLoaded();
// });
// Create the iframe
if (gadget.context !== undefined) {
$(gadget.context).html(
// Use encodeURI to prevent XSS
'<iframe src="' + encodeURI(url) + '"></iframe>'
);
gadget.chan = Channel.build({
window: gadget.context.find('iframe').first()[0].contentWindow,
origin: "*",
scope: "renderJS"
});
// gadget.getTitle = function () {
// var dfr = $.Deferred();
// gadget.chan.call({
// method: "getTitle",
// success: function (v) {
// dfr.resolve(v);
// }
// });
// return dfr.promise();
// };
gadget.chan.bind("declareMethod", function (trans, method_name) {
gadget[method_name] = function () {
var dfr = $.Deferred();
gadget.chan.call({
method: "methodCall",
params: [
method_name,
Array.prototype.slice.call(arguments, 0)],
success: function () {
dfr.resolveWith(gadget, arguments);
},
error: function () {
dfr.rejectWith(gadget, arguments);
}
// XXX Error callback
});
return dfr.promise();
};
return "OK";
});
// Wait for the iframe to be loaded before continuing
gadget.chan.bind("ready", function (trans) {
next_loading_gadget_deferred.resolve(gadget);
return "OK";
});
gadget.chan.bind("failed", function (trans) {
next_loading_gadget_deferred.reject();
return "OK";
});
} else {
next_loading_gadget_deferred.reject();
}
});
loading_gadget_promise
// Drop the current loading klass info used by selector
.done(function () {
gadget_loading_klass = undefined;
})
.fail(function () {
gadget_loading_klass = undefined;
})
.done(function (created_gadget) {
$.each(created_gadget.constructor.ready_list,
function (i, callback) {
callback.apply(created_gadget);
});
});
return loading_gadget_promise;
};
RenderJSGadget.prototype.declareGadget = function (url, jquery_context) {
var previous_loading_gadget_promise = loading_gadget_promise,
next_loading_gadget_deferred = $.Deferred();
// Change the global variable to update the loading queue
loading_gadget_promise = next_loading_gadget_deferred.promise();
// Wait for previous gadget loading to finish first
previous_loading_gadget_promise.always(function () {
queue = new RSVP.Queue()
// Wait for previous gadget loading to finish first
.push(function () {
return previous_loading_gadget_promise;
})
.push(function () {
return renderJS.declareGadgetKlass(url);
})
// Get the gadget class and instanciate it
renderJS.declareGadgetKlass(url).done(function (Klass) {
var gadget = new Klass();
gadget.context = jquery_context;
.push(function (Klass) {
var i,
template_node_list = Klass.template_element.body.childNodes;
gadget_loading_klass = Klass;
gadget_instance = new Klass();
gadget_instance.element = options.element;
for (i = 0; i < template_node_list.length; i += 1) {
gadget_instance.element.appendChild(template_node_list[i].cloneNode(true));
}
// Load dependencies if needed
$.when(gadget.getRequiredJSList(), gadget.getRequiredCSSList())
.done(function (js_list, css_list) {
var result_list = [],
first_deferred = $.Deferred(),
first_promise = first_deferred.promise();
gadget_loading_klass = Klass;
// Load JS and follow the dependency declaration defined in the
// head
function next(next_js_list) {
var next_js = next_js_list.shift();
if (next_js === undefined) {
first_deferred.resolve();
} else {
renderJS.declareJS(next_js)
.done(function () {
next(next_js_list);
})
.fail(function () {
first_deferred.reject.apply(
first_deferred,
arguments
);
});
}
}
next(js_list);
result_list.push(first_promise);
// Load CSS
$.each(css_list, function (i, required_url) {
result_list.push(renderJS.declareCSS(required_url));
});
$.when.apply(this, result_list)
.done(function () {
// Dependency correctly loaded. Fire instanciation success.
next_loading_gadget_deferred.resolve(gadget);
}).fail(function () {
// console.error(Array.prototype.slice.call(arguments, 0));
// One error during css/js loading
next_loading_gadget_deferred.reject.apply(
next_loading_gadget_deferred,
arguments
);
});
}).fail(function () {
// Failed to fetch dependencies information.
next_loading_gadget_deferred.reject.apply(
next_loading_gadget_deferred,
arguments
);
});
}).fail(function () {
// Klass not correctly loaded. Reject instanciation
next_loading_gadget_deferred.reject.apply(next_loading_gadget_deferred,
arguments);
});
});
loading_gadget_promise
// Drop the current loading klass info used by selector
.done(function () {
gadget_loading_klass = undefined;
return RSVP.all([
gadget_instance.getRequiredJSList(),
gadget_instance.getRequiredCSSList()
]);
})
.fail(function () {
gadget_loading_klass = undefined;
// Load all JS/CSS
.push(function (all_list) {
var parameter_list = [],
i;
// Load JS
for (i = 0; i < all_list[0].length; i += 1) {
parameter_list.push(renderJS.declareJS(all_list[0][i]));
}
// Load CSS
for (i = 0; i < all_list[1].length; i += 1) {
parameter_list.push(renderJS.declareCSS(all_list[1][i]));
}
return RSVP.all(parameter_list);
})
.done(function (created_gadget) {
// Set the content html and call the ready list if instance is
// correctly loaded
if (created_gadget.context !== undefined) {
$(created_gadget.context).html(
created_gadget.constructor.prototype.html
);
// Set the HTML context
.push(function () {
var i;
// Drop the current loading klass info used by selector
gadget_loading_klass = undefined;
// Trigger calling of all ready callback
function ready_wrapper() {
return gadget_instance;
}
$.each(created_gadget.constructor.ready_list, function (i, callback) {
callback.apply(created_gadget);
});
for (i = 0; i < gadget_instance.constructor.ready_list.length;
i += 1) {
// Put a timeout?
queue.push(gadget_instance.constructor.ready_list[i]);
// Always return the gadget instance after ready function
queue.push(ready_wrapper);
}
return gadget_instance;
})
.push(undefined, function (e) {
// Drop the current loading klass info used by selector
// even in case of error
gadget_loading_klass = undefined;
console.warn("failed to declare " + url);
console.warn(e);
throw e;
});
loading_gadget_promise = queue;
return loading_gadget_promise;
};
methods = {
loadGadgetFromDom: function () {
$(this).find('[data-gadget-path]').each(function (index, value) {
$(this).renderJS('declareGadget', $(this).attr('data-gadget-path'), {
scope: $(this).attr('data-gadget-scope'),
})
.done(function (value) {
var parsed_xml;
// Check that context is still attached to the DOM
// XXX Usefull?
if ($(this).closest(document.body).length) {
parsed_xml = $($.parseXML(value));
// Inject the css
// XXX Manage relative URL
$.each(parsed_xml.find('link[rel=stylesheet]'),
function (i, link) {
$('head').append(
'<link rel="stylesheet" href="' +
$(link).attr('href') +
'" type="text/css" />'
);
});
// Inject the js
// XXX Manage relative URL
$.each(parsed_xml.find('script[type="text/javascript"]'),
function (i, script) {
// $('head').append(
// '<script type="text/javascript" href="' +
// $(script).attr('src') +
// '" />'
// );
// Prevent infinite recursion if loading render.js
// more than once
if ($('head').find('script[src="' + $(script).attr('src')
+ '"]').length === 0) {
var headID = document.getElementsByTagName("head")[0],
newScript = document.createElement('script');
newScript.type = 'text/javascript';
newScript.src = $(script).attr('src');
headID.appendChild(newScript);
}
});
// Inject the html
// XXX parseXML does not support <div /> (without 2 tags)
$(this).html(parsed_xml.find('body').clone());
// XXX No idea why it is required to make it work
// Probably because of parseXML
$(this).html($(this).html())
.renderJS('loadGadgetFromDom');
}
});
});
},
};
// // Define a local copy of renderJS
// renderJS = function (selector) {
// // The renderJS object is actually just the init constructor 'enhanced'
// return new renderJS.fn.init(selector, rootrenderJS);
// };
// renderJS.fn = renderJS.prototype = {
// constructor: renderJS,
// init: function (selector, rootrenderJS) {
// var result;
// // HANDLE: $(""), $(null), $(undefined), $(false)
// if (!selector) {
// console.log("no selector");
// result = this;
// // // HANDLE: $(DOMElement)
// // } else if (selector.nodeType) {
// // this.context = this[0] = selector;
// // this.length = 1;
// // result = this;
// // } else if (selector === this) {
// // result = this.constructor();
// } else {
// // throw new Error("Not implemented selector " + selector);
// result = this.constructor();
// }
// return result;
// },
// };
// // Give the init function the renderJS prototype for later instantiation
// renderJS.fn.init.prototype = renderJS.fn;
//
// jQuery.fn.extend({
// attr: function (name, value) {
// return jQuery.access(this, jQuery.attr, name, value,
// arguments.length > 1);
// },
// });
renderJS = function (selector) {
var result;
// if (selector.nodeType) {
// console.log(selector);
// } else {
if (selector === window) {
// window is the this value when loading a javascript file
// window is the 'this' value when loading a javascript file
// In this case, use the current loading gadget constructor
result = gadget_loading_klass;
// } else if ($.isFunction(selector)) {
// console.log(selector);
} else if (selector instanceof RenderJSGadget) {
result = selector;
}
......@@ -535,38 +218,28 @@
};
renderJS.declareJS = function (url) {
// // Prevent infinite recursion if loading render.js
// // more than once
// if ($('head').find('script[src="' + $(script).attr('src')
// + '"]').length === 0) {
// var headID = document.getElementsByTagName("head")[0],
// newScript = document.createElement('script');
// newScript.type = 'text/javascript';
// newScript.src = $(script).attr('src');
// headID.appendChild(newScript);
// }
var dfr,
origin_dfr = $.Deferred(),
head_element,
script_element;
// Prevent infinite recursion if loading render.js
// more than once
var result;
if (javascript_registration_dict.hasOwnProperty(url)) {
setTimeout(function () {
origin_dfr.resolve();
});
dfr = origin_dfr.promise();
result = RSVP.resolve();
} else {
dfr = $.ajax({
url: url,
dataType: "script",
cache: true,
}).done(function (script, textStatus) {
javascript_registration_dict[url] = null;
// }).fail(function () {
// console.error(Array.prototype.slice.call(arguments, 0));
result = new RSVP.Promise(function (resolve, reject) {
var newScript;
newScript = document.createElement('script');
newScript.type = 'text/javascript';
newScript.src = url;
newScript.onload = function () {
javascript_registration_dict[url] = null;
resolve();
};
newScript.onerror = function (e) {
reject(e);
};
document.head.appendChild(newScript);
});
}
return dfr;
return result;
};
renderJS.declareCSS = function (url) {
......@@ -574,86 +247,116 @@
// No way to cleanly check if a css has been loaded
// So, always resolve the promise...
// http://requirejs.org/docs/faq-advanced.html#css
var origin_dfr = $.Deferred(),
origin_promise = origin_dfr.promise(),
head,
link;
var result;
if (stylesheet_registration_dict.hasOwnProperty(url)) {
setTimeout(function () {
origin_dfr.resolve();
});
result = RSVP.resolve();
} else {
head = document.getElementsByTagName('head')[0];
link = document.createElement('link');
result = new RSVP.Promise(function (resolve, reject) {
var link;
link = document.createElement('link');
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = url;
link.onload = function () {
stylesheet_registration_dict[url] = null;
resolve();
};
link.onerror = function (e) {
reject(e);
};
document.head.appendChild(link);
});
}
return result;
};
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = url;
renderJS.declareGadgetKlass = function (url) {
var result,
xhr;
function parse() {
var tmp_constructor,
key,
parsed_html;
if (!gadget_model_dict.hasOwnProperty(url)) {
// Class inheritance
tmp_constructor = function () {
RenderJSGadget.call(this);
};
tmp_constructor.ready_list = [];
tmp_constructor.declareMethod =
RenderJSGadget.declareMethod;
tmp_constructor.ready =
RenderJSGadget.ready;
tmp_constructor.prototype = new RenderJSGadget();
tmp_constructor.prototype.constructor = tmp_constructor;
tmp_constructor.prototype.path = url;
// https://developer.mozilla.org/en-US/docs/HTML_in_XMLHttpRequest
// https://developer.mozilla.org/en-US/docs/Web/API/DOMParser
// https://developer.mozilla.org/en-US/docs/Code_snippets/HTML_to_DOM
tmp_constructor.template_element =
(new DOMParser()).parseFromString(xhr.responseText, "text/html");
parsed_html = renderJS.parseGadgetHTMLDocument(
tmp_constructor.template_element
);
for (key in parsed_html) {
if (parsed_html.hasOwnProperty(key)) {
tmp_constructor.prototype[key] = parsed_html[key];
}
}
origin_promise.done(function () {
stylesheet_registration_dict[url] = null;
});
gadget_model_dict[url] = tmp_constructor;
}
head.appendChild(link);
return gadget_model_dict[url];
}
setTimeout(function () {
origin_dfr.resolve();
});
function resolver(resolve, reject) {
function handler() {
var tmp_result;
try {
if (xhr.readyState === 0) {
// UNSENT
reject(xhr);
} else if (xhr.readyState === 4) {
// DONE
if ((xhr.status < 200) || (xhr.status >= 300) ||
(!/^text\/html[;]?/.test(
xhr.getResponseHeader("Content-Type") || ""
))) {
reject(xhr);
} else {
tmp_result = parse();
resolve(tmp_result);
}
}
} catch (e) {
reject(e);
}
}
xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.onreadystatechange = handler;
xhr.setRequestHeader('Accept', 'text/html');
xhr.withCredentials = true;
xhr.send();
}
return origin_promise;
};
renderJS.declareGadgetKlass = function (url) {
var dfr = $.Deferred(),
parsed_html;
function canceller() {
if ((xhr !== undefined) && (xhr.readyState !== xhr.DONE)) {
xhr.abort();
}
}
if (gadget_model_dict.hasOwnProperty(url)) {
dfr.resolve(gadget_model_dict[url]);
// Return klass object if it already exists
result = RSVP.resolve(gadget_model_dict[url]);
} else {
$.ajax(url)
.done(function (value, textStatus, jqXHR) {
var klass, tmp_constructor, key;
if (/^text\/html[;]?/.test(
jqXHR.getResponseHeader("Content-Type") || ""
)) {
try {
if (!gadget_model_dict.hasOwnProperty(url)) {
// Class inheritance
tmp_constructor = function () {
RenderJSGadget.call(this);
};
tmp_constructor.ready_list = [];
tmp_constructor.declareMethod =
RenderJSGadget.declareMethod;
tmp_constructor.ready =
RenderJSGadget.ready;
tmp_constructor.prototype = new RenderJSGadget();
tmp_constructor.prototype.constructor = tmp_constructor;
tmp_constructor.prototype.path = url;
parsed_html = renderJS.parseGadgetHTML(value);
for (key in parsed_html) {
if (parsed_html.hasOwnProperty(key)) {
tmp_constructor.prototype[key] = parsed_html[key];
}
}
gadget_model_dict[url] = tmp_constructor;
}
dfr.resolve(gadget_model_dict[url]);
} catch (e) {
dfr.reject(jqXHR, "HTML Parsing failed");
}
} else {
dfr.reject(jqXHR, "Unexpected content type");
}
})
.fail(function () {
dfr.reject.apply(dfr, arguments);
});
// Fetch the HTML page and parse it
result = new RSVP.Promise(resolver, canceller);
}
return dfr.promise();
return result;
};
// For test purpose only
......@@ -663,52 +366,36 @@
stylesheet_registration_dict = {};
};
renderJS.parseGadgetHTML = function (html) {
var parsed_xml,
result,
settings = {
renderJS.parseGadgetHTMLDocument = function (document_element) {
var settings = {
title: "",
interface_list: [],
html: "",
required_css_list: [],
required_js_list: [],
};
if (html.constructor === String) {
// https://developer.mozilla.org/en-US/docs/HTML_in_XMLHttpRequest
// https://developer.mozilla.org/en-US/docs/Web/API/DOMParser
// https://developer.mozilla.org/en-US/docs/Code_snippets/HTML_to_DOM
// parsed_xml = $($.parseXML(html));
// parsed_xml = $('<div/>').html(html);
parsed_xml = $((new DOMParser()).parseFromString(html, "text/html"));
settings.title = parsed_xml.find('head > title').first().text();
// XXX Manage relative URL during extraction of URLs
$.each(parsed_xml.find('head > link[rel=stylesheet]'),
function (i, link) {
settings.required_css_list.push($(link).attr('href'));
});
$.each(parsed_xml.find('head > script[type="text/javascript"]'),
function (i, script) {
settings.required_js_list.push($(script).attr('src'));
});
$.each(parsed_xml.find(
'head > link[rel="http://www.renderjs.org/rel/interface"]'
), function (i, link) {
settings.interface_list.push($(link).attr('href'));
});
settings.html = parsed_xml.find('html > body').first().html();
if (settings.html === undefined) {
settings.html = "";
},
i,
element;
if (document_element.nodeType === 9) {
settings.title = document_element.title;
for (i = 0; i < document_element.head.children.length; i += 1) {
element = document_element.head.children[i];
if (element.href !== null) {
// XXX Manage relative URL during extraction of URLs
// element.href returns absolute URL in firefox but "" in chrome;
if (element.rel === "stylesheet") {
settings.required_css_list.push(element.getAttribute("href"));
} else if (element.type === "text/javascript") {
settings.required_js_list.push(element.getAttribute("src"));
} else if (element.rel === "http://www.renderjs.org/rel/interface") {
settings.interface_list.push(element.getAttribute("href"));
}
}
}
result = settings;
} else {
throw new Error(html + " is not a string");
throw new Error("The first parameter should be an HTMLDocument");
}
return result;
return settings;
};
window.rJS = window.renderJS = renderJS;
window.RenderJSGadget = RenderJSGadget;
......@@ -720,76 +407,89 @@
function bootstrap() {
var url = window.location.href,
tmp_constructor,
root_gadget,
loading_gadget_deferred = $.Deferred();
root_gadget;
// Create the gadget class for the current url
if (gadget_model_dict.hasOwnProperty(url)) {
throw new Error("bootstrap should not be called twice");
}
loading_gadget_promise = loading_gadget_deferred.promise();
if (window.self === window.top) {
// XXX Copy/Paste from declareGadgetKlass
tmp_constructor = function () {
RenderJSGadget.call(this);
};
tmp_constructor.declareMethod = RenderJSGadget.declareMethod;
tmp_constructor.ready_list = [];
tmp_constructor.ready = RenderJSGadget.ready;
tmp_constructor.prototype = new RenderJSGadget();
tmp_constructor.prototype.constructor = tmp_constructor;
tmp_constructor.prototype.path = url;
gadget_model_dict[url] = tmp_constructor;
// Create the root gadget instance and put it in the loading stack
root_gadget = new gadget_model_dict[url]();
} else {
// Create the root gadget instance and put it in the loading stack
tmp_constructor = RenderJSEmbeddedGadget;
root_gadget = new RenderJSEmbeddedGadget();
}
gadget_loading_klass = tmp_constructor;
loading_gadget_promise = new RSVP.Promise(function (resolve, reject) {
if (window.self === window.top) {
// XXX Copy/Paste from declareGadgetKlass
tmp_constructor = function () {
RenderJSGadget.call(this);
};
tmp_constructor.declareMethod = RenderJSGadget.declareMethod;
tmp_constructor.ready_list = [];
tmp_constructor.ready = RenderJSGadget.ready;
tmp_constructor.prototype = new RenderJSGadget();
tmp_constructor.prototype.constructor = tmp_constructor;
tmp_constructor.prototype.path = url;
gadget_model_dict[url] = tmp_constructor;
// Create the root gadget instance and put it in the loading stack
root_gadget = new gadget_model_dict[url]();
}
gadget_loading_klass = tmp_constructor;
// run on next tick so that if this was pulled in with requirejs,
// rJS.ready() can still be used.
// XXX: doesn't work with require(['renderjs', 'somethingElse'], ...
setTimeout(function () {
$(document).ready(function () {
function init() {
// XXX HTML properties can only be set when the DOM is fully loaded
var settings = renderJS.parseGadgetHTML($('html')[0].outerHTML),
promise,
var settings = renderJS.parseGadgetHTMLDocument(document),
j,
key;
for (key in settings) {
if (settings.hasOwnProperty(key)) {
tmp_constructor.prototype[key] = settings[key];
}
}
root_gadget.context = $('body');
promise = $.when(root_gadget.getRequiredJSList(),
root_gadget.getRequiredCSSList())
.done(function (js_list, css_list) {
$.each(js_list, function (i, required_url) {
javascript_registration_dict[required_url] = null;
});
$.each(css_list, function (i, required_url) {
stylesheet_registration_dict[url] = null;
});
$.each(tmp_constructor.ready_list, function (i, callback) {
callback.apply(root_gadget);
});
tmp_constructor.template_element = document.createElement("div");
root_gadget.element = document.body;
for (j = 0; j < root_gadget.element.childNodes.length; j += 1) {
tmp_constructor.template_element.appendChild(
root_gadget.element.childNodes[j].cloneNode(true)
);
}
RSVP.all([root_gadget.getRequiredJSList(),
root_gadget.getRequiredCSSList()])
.then(function (all_list) {
var i,
js_list = all_list[0],
css_list = all_list[1],
queue;
for (i = 0; i < js_list.length; i += 1) {
javascript_registration_dict[js_list[i]] = null;
}
for (i = 0; i < css_list.length; i += 1) {
stylesheet_registration_dict[css_list[i]] = null;
}
gadget_loading_klass = undefined;
loading_gadget_deferred.resolve();
}).fail(function () {
loading_gadget_deferred.reject.apply(loading_gadget_deferred,
arguments);
queue = new RSVP.Queue();
function ready_wrapper() {
return root_gadget;
}
queue.push(ready_wrapper);
for (i = 0; i < tmp_constructor.ready_list.length; i += 1) {
// Put a timeout?
queue.push(tmp_constructor.ready_list[i]);
// Always return the gadget instance after ready function
queue.push(ready_wrapper);
}
queue.push(resolve, function (e) {
reject(e);
console.warn(e);
throw e;
});
return queue;
}).fail(function (e) {
reject(e);
});
});
}
document.addEventListener('DOMContentLoaded', init, false);
});
}
bootstrap();
}(document, window, jQuery, DOMParser, Channel));
}(document, window, RSVP, DOMParser, Channel));
......@@ -6,10 +6,10 @@
<meta name="viewport" content="width=device-width, height=device-height"/>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="stylesheet" href="../lib/qunit/qunit.css" type="text/css" media="screen"/>
<script src="../lib/rsvp/rsvp.js" type="text/javascript"></script>
<script src="../lib/jquery/jquery.js" type="text/javascript"></script>
<script src="../lib/qunit/qunit.js" type="text/javascript"></script>
<script src="../lib/sinon/sinon.js" type="text/javascript"></script>
<script src="../sinon-qunit.js" type="text/javascript"></script>
<script src="../../lib/jschannel/jschannel.js" type="text/javascript"></script>
<script src="../renderjs.js" type="text/javascript"></script>
<script src="renderjs_test.js" type="text/javascript"></script>
......
/*global window, document, QUnit, jQuery, renderJS, RenderJSGadget, sinon */
/*jslint indent: 2, maxerr: 3, maxlen: 79 */
/*global window, document, QUnit, jQuery, renderJS, RenderJSGadget, sinon,
RSVP, DOMParser */
/*jslint unparam: true, maxlen: 150 */
"use strict";
(function (document, $, renderJS, QUnit, sinon) {
......@@ -8,63 +10,81 @@
start = QUnit.start,
ok = QUnit.ok,
equal = QUnit.equal,
expect = QUnit.expect,
throws = QUnit.throws,
deepEqual = QUnit.deepEqual;
deepEqual = QUnit.deepEqual,
root_gadget_klass = renderJS(window),
root_gadget_defer = RSVP.defer();
// Keep track of the root gadget
renderJS(window).ready(function (g) {
root_gadget_defer.resolve(g);
});
QUnit.config.testTimeout = 500;
// sinon.log = function (message) {
// console.log(message);
// };
function parseGadgetHTML(html) {
return renderJS.parseGadgetHTMLDocument(
(new DOMParser()).parseFromString(html, "text/html")
);
}
/////////////////////////////////////////////////////////////////
// parseGadgetHTML
// parseGadgetHTMLDocument
/////////////////////////////////////////////////////////////////
module("renderJS.parseGadgetHTML", {
module("renderJS.parseGadgetHTMLDocument", {
setup: function () {
renderJS.clearGadgetKlassList();
}
});
test('Not valid HTML string', function () {
// Check that parseGadgetHTML returns the default value if the string is
// Check that parseGadgetHTMLDocument returns the default value if the string is
// not a valid xml
deepEqual(renderJS.parseGadgetHTML(""), {
deepEqual(parseGadgetHTML(""), {
title: "",
interface_list: [],
required_css_list: [],
required_js_list: [],
html: "",
});
});
test('Not string', function () {
// Check that parseGadgetHTML throws an error if the parameter is not a
// string
test('Not HTML Document', function () {
// Check that parseGadgetHTMLDocument throws an error if the parameter is
// not a HTMLDocument
throws(function () {
renderJS.parseGadgetHTML({});
renderJS.parseGadgetHTMLDocument({});
});
});
test('Default result value', function () {
// Check default value returned by parseGadgetHTML
deepEqual(renderJS.parseGadgetHTML(""), {
// Check default value returned by parseGadgetHTMLDocument
deepEqual(renderJS.parseGadgetHTMLDocument(
document.implementation.createHTMLDocument("")
), {
title: "",
interface_list: [],
required_css_list: [],
required_js_list: [],
html: "",
});
});
test('Extract title', function () {
// Check that parseGadgetHTML correctly extract the title
// Check that parseGadgetHTMLDocument correctly extract the title
var settings,
html = "<html>" +
"<head>" +
"<title>Great title</title>" +
"</head></html>";
settings = renderJS.parseGadgetHTML(html);
settings = parseGadgetHTML(html);
equal(settings.title, 'Great title', 'Title extracted');
});
test('Extract only one title', function () {
// Check that parseGadgetHTML correctly extract the first title
// Check that parseGadgetHTMLDocument correctly extract the first title
var settings,
html = "<html>" +
"<head>" +
......@@ -72,61 +92,62 @@
"<title>Great title 2</title>" +
"</head></html>";
settings = renderJS.parseGadgetHTML(html);
settings = parseGadgetHTML(html);
equal(settings.title, 'Great title', 'First title extracted');
});
test('Extract title only from head', function () {
// Check that parseGadgetHTML only extract title from head
var settings,
html = "<html>" +
"<body>" +
"<title>Great title</title>" +
"</body></html>";
settings = renderJS.parseGadgetHTML(html);
equal(settings.title, '', 'Title not found');
});
test('Extract body', function () {
// Check that parseGadgetHTML correctly extract the body
var settings,
html = "<html>" +
"<body>" +
"<p>Foo</p>" +
"</body></html>";
settings = renderJS.parseGadgetHTML(html);
equal(settings.html, "<p>Foo</p>", "HTML extracted");
});
test('Extract all body', function () {
// Check that parseGadgetHTML correctly extracts all bodies
var settings,
html = "<html>" +
"<body>" +
"<p>Foo</p>" +
"</body><body>" +
"<p>Bar</p>" +
"</body></html>";
settings = renderJS.parseGadgetHTML(html);
equal(settings.html, '<p>Foo</p><p>Bar</p>', 'All bodies extracted');
});
test('Extract body only from html', function () {
// Check that parseGadgetHTML also extract body from head
var settings,
html = "<html>" +
"<head><body><p>Bar</p></body></head>" +
"</html>";
// test('Extract title only from head', function () {
// // Check that parseGadgetHTML only extract title from head
// var settings,
// html = "<html>" +
// "<body>" +
// "<title>Great title</title>" +
// "</body></html>";
//
// settings = renderJS.parseGadgetHTML(html);
// equal(settings.title, '', 'Title not found');
// });
settings = renderJS.parseGadgetHTML(html);
equal(settings.html, "<p>Bar</p>", "Body not found");
});
// XXX innerHTML is not extracted anymore
// test('Extract body', function () {
// // Check that parseGadgetHTML correctly extract the body
// var settings,
// html = "<html>" +
// "<body>" +
// "<p>Foo</p>" +
// "</body></html>";
//
// settings = renderJS.parseGadgetHTML(html);
// equal(settings.html, "<p>Foo</p>", "HTML extracted");
// });
//
// test('Extract all body', function () {
// // Check that parseGadgetHTML correctly extracts all bodies
// var settings,
// html = "<html>" +
// "<body>" +
// "<p>Foo</p>" +
// "</body><body>" +
// "<p>Bar</p>" +
// "</body></html>";
//
// settings = renderJS.parseGadgetHTML(html);
// equal(settings.html, '<p>Foo</p><p>Bar</p>', 'All bodies extracted');
// });
//
// test('Extract body only from html', function () {
// // Check that parseGadgetHTML also extract body from head
// var settings,
// html = "<html>" +
// "<head><body><p>Bar</p></body></head>" +
// "</html>";
//
// settings = renderJS.parseGadgetHTML(html);
// equal(settings.html, "<p>Bar</p>", "Body not found");
// });
test('Extract CSS', function () {
// Check that parseGadgetHTML correctly extract the CSS
// Check that parseGadgetHTMLDocument correctly extract the CSS
var settings,
html = "<html>" +
"<head>" +
......@@ -134,14 +155,14 @@
"type='text/css'/>" +
"</head></html>";
settings = renderJS.parseGadgetHTML(html);
settings = parseGadgetHTML(html);
deepEqual(settings.required_css_list,
['../lib/qunit/qunit.css'],
"CSS extracted");
});
test('Extract CSS order', function () {
// Check that parseGadgetHTML correctly keep CSS order
// Check that parseGadgetHTMLDocument correctly keep CSS order
var settings,
html = "<html>" +
"<head>" +
......@@ -151,14 +172,14 @@
"type='text/css'/>" +
"</head></html>";
settings = renderJS.parseGadgetHTML(html);
settings = parseGadgetHTML(html);
deepEqual(settings.required_css_list,
['../lib/qunit/qunit.css', '../lib/qunit/qunit2.css'],
"CSS order kept");
});
test('Extract CSS only from head', function () {
// Check that parseGadgetHTML only extract css from head
// Check that parseGadgetHTMLDocument only extract css from head
var settings,
html = "<html>" +
"<body>" +
......@@ -166,12 +187,12 @@
"type='text/css'/>" +
"</body></html>";
settings = renderJS.parseGadgetHTML(html);
settings = parseGadgetHTML(html);
deepEqual(settings.required_css_list, [], "CSS not found");
});
test('Extract interface', function () {
// Check that parseGadgetHTML correctly extract the interface
// Check that parseGadgetHTMLDocument correctly extract the interface
var settings,
html = "<html>" +
"<head>" +
......@@ -179,14 +200,14 @@
" href='./interface/renderable'/>" +
"</head></html>";
settings = renderJS.parseGadgetHTML(html);
settings = parseGadgetHTML(html);
deepEqual(settings.interface_list,
['./interface/renderable'],
"interface extracted");
});
test('Extract interface order', function () {
// Check that parseGadgetHTML correctly keep interface order
// Check that parseGadgetHTMLDocument correctly keep interface order
var settings,
html = "<html>" +
"<head>" +
......@@ -196,7 +217,7 @@
" href='./interface/field'/>" +
"</head></html>";
settings = renderJS.parseGadgetHTML(html);
settings = parseGadgetHTML(html);
deepEqual(settings.interface_list,
['./interface/renderable',
'./interface/field'],
......@@ -204,7 +225,7 @@
});
test('Extract interface only from head', function () {
// Check that parseGadgetHTML only extract interface from head
// Check that parseGadgetHTMLDocument only extract interface from head
var settings,
html = "<html>" +
"<body>" +
......@@ -212,12 +233,12 @@
" href='./interface/renderable'/>" +
"</body></html>";
settings = renderJS.parseGadgetHTML(html);
settings = parseGadgetHTML(html);
deepEqual(settings.interface_list, [], "interface not found");
});
test('Extract JS', function () {
// Check that parseGadgetHTML correctly extract the JS
// Check that parseGadgetHTMLDocument correctly extract the JS
var settings,
html = "<html>" +
"<head>" +
......@@ -225,14 +246,14 @@
"type='text/javascript'></script>" +
"</head></html>";
settings = renderJS.parseGadgetHTML(html);
settings = parseGadgetHTML(html);
deepEqual(settings.required_js_list,
['../lib/qunit/qunit.js'],
"JS extracted");
});
test('Extract JS order', function () {
// Check that parseGadgetHTML correctly keep JS order
// Check that parseGadgetHTMLDocument correctly keep JS order
var settings,
html = "<html>" +
"<head>" +
......@@ -242,14 +263,14 @@
"type='text/javascript'></script>" +
"</head></html>";
settings = renderJS.parseGadgetHTML(html);
settings = parseGadgetHTML(html);
deepEqual(settings.required_js_list,
['../lib/qunit/qunit.js', '../lib/qunit/qunit2.js'],
"JS order kept");
});
test('Extract JS only from head', function () {
// Check that parseGadgetHTML only extract js from head
// Check that parseGadgetHTMLDocument only extract js from head
var settings,
html = "<html>" +
"<body>" +
......@@ -257,13 +278,13 @@
"type='text/javascript'></script>" +
"</body></html>";
settings = renderJS.parseGadgetHTML(html);
settings = parseGadgetHTML(html);
deepEqual(settings.required_js_list, [], "JS not found");
});
test('Non valid XML (HTML in fact...)', function () {
// Check default value returned by parseGadgetHTML
deepEqual(renderJS.parseGadgetHTML('<!doctype html><html><head>' +
// Check default value returned by parseGadgetHTMLDocument
deepEqual(parseGadgetHTML('<!doctype html><html><head>' +
'<title>Test non valid XML</title>' +
'<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">' +
'</head><body><p>Non valid XML</p></body></html>'), {
......@@ -271,7 +292,7 @@
interface_list: [],
required_css_list: [],
required_js_list: [],
html: "<p>Non valid XML</p>",
// html: "<p>Non valid XML</p>",
});
});
......@@ -288,22 +309,26 @@
var server = sinon.fakeServer.create(),
url = 'https://example.org/files/qunittest/test';
server.autoRespond = true;
server.autoRespondAfter = 5;
server.respondWith("GET", url, [404, {
"Content-Type": "text/html",
}, "foo"]);
stop();
renderJS.declareGadgetKlass(url)
.done(function () {
.then(function () {
ok(false, "404 should fail");
})
.fail(function (jqXHR, textStatus) {
equal("404", jqXHR.status);
.fail(function (xhr) {
equal(xhr.status, 404);
equal(xhr.url, url);
})
.always(function () {
start();
server.restore();
});
server.respond();
});
test('Non HTML reject the promise', function () {
......@@ -311,24 +336,26 @@
var server = sinon.fakeServer.create(),
url = 'https://example.org/files/qunittest/test';
server.autoRespond = true;
server.autoRespondAfter = 5;
server.respondWith("GET", url, [200, {
"Content-Type": "text/plain",
}, "foo"]);
stop();
renderJS.declareGadgetKlass(url)
.done(function () {
.then(function () {
ok(false, "text/plain should fail");
})
.fail(function (jqXHR, textStatus) {
equal(jqXHR.status, "200");
equal(textStatus, "Unexpected content type");
.fail(function (jqXHR) {
equal(jqXHR.status, 200);
equal(jqXHR.getResponseHeader("Content-Type"), "text/plain");
})
.always(function () {
start();
server.restore();
});
server.respond();
});
test('HTML parsing failure reject the promise', function () {
......@@ -337,29 +364,32 @@
url = 'https://example.org/files/qunittest/test',
mock;
server.autoRespond = true;
server.autoRespondAfter = 5;
server.respondWith("GET", url, [200, {
"Content-Type": "text/html",
}, ""]);
mock = this.mock(renderJS, "parseGadgetHTML", function () {
throw new Error();
mock = sinon.mock(renderJS, "parseGadgetHTMLDocument", function () {
throw new Error("foo");
});
mock.expects("parseGadgetHTML").once().throws();
mock.expects("parseGadgetHTMLDocument").once().throws();
stop();
renderJS.declareGadgetKlass(url)
.done(function () {
.then(function () {
ok(false, "Non parsable HTML should fail");
})
.fail(function (jqXHR, textStatus) {
equal("200", jqXHR.status);
equal(textStatus, "HTML Parsing failed");
.fail(function (e) {
ok(e instanceof Error);
})
.always(function () {
mock.verify();
start();
mock.verify();
mock.restore();
server.restore();
});
server.respond();
});
test('Klass creation', function () {
......@@ -369,36 +399,41 @@
url = 'https://example.org/files/qunittest/test',
mock;
server.autoRespond = true;
server.autoRespondAfter = 5;
server.respondWith("GET", url, [200, {
"Content-Type": "text/html",
}, "foo"]);
mock = this.mock(renderJS, "parseGadgetHTML");
mock.expects("parseGadgetHTML").once().withArgs("foo").returns(
mock = sinon.mock(renderJS, "parseGadgetHTMLDocument");
mock.expects("parseGadgetHTMLDocument").once().returns(
{foo: 'bar'}
);
stop();
renderJS.declareGadgetKlass(url)
.done(function (Klass) {
.then(function (Klass) {
var instance;
equal(Klass.prototype.path, url);
equal(Klass.prototype.foo, 'bar');
equal(Klass.template_element.nodeType, 9);
instance = new Klass();
ok(instance instanceof RenderJSGadget);
ok(instance instanceof Klass);
ok(Klass !== RenderJSGadget);
})
.fail(function (jqXHR, textStatus) {
ok(false, "Failed to load " + textStatus + " " + jqXHR.status);
.fail(function (e) {
ok(false, JSON.stringify(e));
})
.always(function () {
mock.verify();
start();
mock.verify();
mock.restore();
server.restore();
});
server.respond();
});
test('Klass is not reloaded if called twice', function () {
......@@ -406,38 +441,39 @@
// if it has already been loaded
var server = sinon.fakeServer.create(),
url = 'https://example.org/files/qunittest/test',
klass1,
mock;
server.autoRespond = true;
server.autoRespondAfter = 5;
server.respondWith("GET", url, [200, {
"Content-Type": "text/html",
}, "foo"]);
mock = this.mock(renderJS, "parseGadgetHTML");
mock.expects("parseGadgetHTML").once().withArgs("foo").returns(
mock = sinon.mock(renderJS, "parseGadgetHTMLDocument");
mock.expects("parseGadgetHTMLDocument").once().returns(
{foo: 'bar'}
);
stop();
renderJS.declareGadgetKlass(url)
.done(function (Klass1) {
renderJS.declareGadgetKlass(url)
.done(function (Klass2) {
equal(Klass1, Klass2);
})
.fail(function (jqXHR, textStatus) {
ok(false, "Failed to load " + textStatus + " " + jqXHR.status);
})
.always(function () {
start();
mock.verify();
});
.then(function (Klass1) {
klass1 = Klass1;
return renderJS.declareGadgetKlass(url);
})
.then(function (Klass2) {
equal(klass1, Klass2);
})
.fail(function (jqXHR) {
ok(false, "Failed to load " + jqXHR.status);
})
.fail(function (jqXHR, textStatus) {
ok(false, "Failed to load " + textStatus + " " + jqXHR.status);
.always(function () {
start();
mock.verify();
mock.restore();
server.restore();
});
server.respond();
});
test('Content type parameter are supported', function () {
......@@ -446,13 +482,16 @@
var server = sinon.fakeServer.create(),
url = 'https://example.org/files/qunittest/test';
server.autoRespond = true;
server.autoRespondAfter = 5;
server.respondWith("GET", url, [200, {
"Content-Type": "text/html; charset=utf-8",
}, "<html></html>"]);
stop();
renderJS.declareGadgetKlass(url)
.done(function (Klass) {
.then(function (Klass) {
var instance;
equal(Klass.prototype.path, url);
......@@ -462,13 +501,13 @@
ok(instance instanceof Klass);
ok(Klass !== RenderJSGadget);
})
.fail(function (jqXHR, textStatus) {
ok(false, "Failed to load " + textStatus + " " + jqXHR.status);
.fail(function (jqXHR) {
ok(false, "Failed to load " + jqXHR.status);
})
.always(function () {
start();
server.restore();
});
server.respond();
});
/////////////////////////////////////////////////////////////////
......@@ -481,15 +520,16 @@
});
test('Download error reject the promise', function () {
// Check that declareJS fails if ajax fails
var url = 'foo://bar';
var url = 'http://0.0.0.0/bar';
stop();
renderJS.declareJS(url)
.done(function () {
.then(function () {
ok(false, "404 should fail");
})
.fail(function (jqXHR, textStatus) {
equal(jqXHR.status, "404");
.fail(function (e) {
equal(e.type, "error");
equal(e.target.getAttribute("src"), url);
})
.always(function () {
start();
......@@ -498,21 +538,22 @@
test('Ajax error reject the promise twice', function () {
// Check that failed declareJS is not cached
var url = 'foo://bar';
var url = 'http://0.0.0.0/bar2';
stop();
renderJS.declareJS(url)
.always(function () {
renderJS.declareJS(url)
.done(function () {
ok(false, "404 should fail");
})
.fail(function (jqXHR, textStatus) {
equal(jqXHR.status, "404");
})
.always(function () {
start();
});
return renderJS.declareJS(url);
})
.then(function () {
ok(false, "404 should fail");
})
.fail(function (e) {
equal(e.type, "error");
equal(e.target.getAttribute("src"), url);
})
.always(function () {
start();
});
});
......@@ -525,11 +566,11 @@
stop();
window.onerror = undefined;
renderJS.declareJS(url)
.done(function (value, textStatus, jqXHR) {
.then(function (value, textStatus, jqXHR) {
ok(ok, "Non JS mime type should load");
})
.fail(function (jqXHR, textStatus) {
ok(false, "Failed to load " + textStatus + " " + jqXHR.status);
.fail(function (jqXHR) {
ok(false, jqXHR);
})
.always(function () {
window.onerror = previousonerror;
......@@ -544,11 +585,11 @@
stop();
renderJS.declareJS(url)
.done(function () {
.then(function () {
equal($("#qunit-fixture").text(), "JS fetched and loaded");
})
.fail(function (jqXHR, textStatus) {
ok(false, "Failed to load " + textStatus + " " + jqXHR.status);
.fail(function (jqXHR) {
ok(false, "Failed to load " + jqXHR);
})
.always(function () {
start();
......@@ -564,11 +605,11 @@
stop();
window.onerror = undefined;
renderJS.declareJS(url)
.done(function (aaa) {
.then(function (aaa) {
ok(true, "JS with error cleanly loaded");
})
.fail(function (jqXHR, textStatus) {
ok(false, "Failed to load " + textStatus + " " + jqXHR.status);
.fail(function (jqXHR) {
ok(false, jqXHR);
})
.always(function () {
window.onerror = previousonerror;
......@@ -583,22 +624,18 @@
stop();
renderJS.declareJS(url)
.done(function () {
.then(function () {
equal($("#qunit-fixture").text(), "JS not fetched twice");
$("#qunit-fixture").text("");
renderJS.declareJS(url)
.done(function () {
equal($("#qunit-fixture").text(), "");
})
.fail(function (jqXHR, textStatus) {
ok(false, "Failed to load " + textStatus + " " + jqXHR.status);
})
.always(function () {
start();
});
return renderJS.declareJS(url);
})
.then(function () {
equal($("#qunit-fixture").text(), "");
})
.fail(function (jqXHR) {
ok(false, "Failed to load " + jqXHR);
})
.fail(function (jqXHR, textStatus) {
ok(false, "Failed to load " + textStatus + " " + jqXHR.status);
.always(function () {
start();
});
});
......@@ -614,34 +651,36 @@
test('Ajax error resolve the promise', function () {
// Check that declareCSS is resolved if ajax fails
var url = 'foo://bar';
var url = 'foo//://bar';
expect(1);
stop();
renderJS.declareCSS(url)
.done(function () {
ok(true, "404 should fail");
.then(function () {
ok(false, "404 should fail");
})
.fail(function (jqXHR, textStatus) {
ok(false);
.fail(function (e) {
equal(e.type, "error");
equal(e.target.getAttribute("href"), url);
})
.always(function () {
start();
});
});
test('Non CSS resolve the promise', function () {
test('Non CSS reject the promise', function () {
// Check that declareCSS is resolved if mime type is wrong
var url = "data:image/png;base64," +
window.btoa("= = =");
stop();
renderJS.declareCSS(url)
.done(function (value, textStatus, jqXHR) {
.then(function (value, textStatus, jqXHR) {
// Chrome accept the css
ok(true, "Non CSS mime type should load");
})
.fail(function (jqXHR, textStatus) {
ok(false);
.fail(function (e) {
equal(e.type, "error");
equal(e.target.getAttribute("href"), url);
})
.always(function () {
start();
......@@ -655,7 +694,7 @@
stop();
renderJS.declareCSS(url)
.done(function () {
.then(function () {
var found = false;
$('head').find('link[rel=stylesheet]').each(function (i, style) {
if (style.href === url) {
......@@ -665,8 +704,8 @@
ok(found, "CSS in the head");
equal($("#qunit-fixture").css("background-color"), "rgb(255, 0, 0)");
})
.fail(function (jqXHR, textStatus) {
ok(false, "Failed to load " + textStatus + " " + jqXHR.status);
.fail(function (e) {
ok(false, e);
})
.always(function () {
start();
......@@ -681,11 +720,12 @@
stop();
renderJS.declareCSS(url)
.done(function () {
.then(function () {
// Chrome does not consider this as error
ok(true, "CSS with error cleanly loaded");
})
.fail(function (jqXHR, textStatus) {
ok(false, "Failed to load " + textStatus + " " + jqXHR.status);
.fail(function (jqXHR) {
ok(true, jqXHR);
})
.always(function () {
start();
......@@ -699,7 +739,7 @@
stop();
renderJS.declareCSS(url)
.done(function () {
.then(function () {
equal($("#qunit-fixture").css("background-color"), "rgb(0, 0, 255)");
$('head').find('link[rel=stylesheet]').each(function (i, style) {
if (style.href === url) {
......@@ -708,27 +748,23 @@
});
ok($("#qunit-fixture").css("background-color") !== "rgb(0, 0, 255)");
renderJS.declareCSS(url)
.done(function () {
var found = false;
$('head').find('link[rel=stylesheet]').each(function (i, style) {
if (style.href === url) {
found = true;
}
});
ok($("#qunit-fixture").css("background-color") !==
"rgb(0, 0, 255)", $("#qunit-fixture").css("background-color"));
ok(!found);
})
.fail(function (jqXHR, textStatus) {
ok(false, "Failed to load " + textStatus + " " + jqXHR.status);
})
.always(function () {
start();
});
return renderJS.declareCSS(url);
})
.fail(function (jqXHR, textStatus) {
ok(false, "Failed to load " + textStatus + " " + jqXHR.status);
.then(function () {
var found = false;
$('head').find('link[rel=stylesheet]').each(function (i, style) {
if (style.href === url) {
found = true;
}
});
ok($("#qunit-fixture").css("background-color") !==
"rgb(0, 0, 255)", $("#qunit-fixture").css("background-color"));
ok(!found);
})
.fail(function (jqXHR) {
ok(false, "Failed to load " + jqXHR);
})
.always(function () {
start();
});
});
......@@ -747,41 +783,42 @@
// after clearGadgetKlassList is called
var server = sinon.fakeServer.create(),
url = 'https://example.org/files/qunittest/test',
klass1,
mock;
server.autoRespond = true;
server.autoRespondAfter = 5;
server.respondWith("GET", url, [200, {
"Content-Type": "text/html",
}, "foo"]);
mock = this.mock(renderJS, "parseGadgetHTML");
mock.expects("parseGadgetHTML").twice().withArgs("foo").returns(
mock = sinon.mock(renderJS, "parseGadgetHTMLDocument");
mock.expects("parseGadgetHTMLDocument").twice().returns(
{foo: 'bar'}
);
stop();
renderJS.declareGadgetKlass(url)
.done(function (Klass1) {
.then(function (Klass1) {
klass1 = Klass1;
renderJS.clearGadgetKlassList();
renderJS.declareGadgetKlass(url)
.done(function (Klass2) {
mock.verify();
ok(Klass1 !== Klass2);
})
.fail(function (jqXHR, textStatus) {
ok(false, "Failed to load " + textStatus + " " + jqXHR.status);
})
.always(function () {
start();
});
return renderJS.declareGadgetKlass(url);
})
.fail(function (jqXHR, textStatus) {
ok(false, "Failed to load " + textStatus + " " + jqXHR.status);
.then(function (Klass2) {
ok(klass1 !== Klass2);
})
.fail(function (jqXHR) {
ok(false, jqXHR);
})
.always(function () {
start();
server.restore();
mock.verify();
mock.restore();
});
server.respond();
});
test('clearGadgetKlassList leads to JS reload', function () {
......@@ -792,23 +829,19 @@
stop();
renderJS.declareJS(url)
.done(function () {
.then(function () {
renderJS.clearGadgetKlassList();
equal($("#qunit-fixture").text(), "JS not fetched twice");
$("#qunit-fixture").text("");
renderJS.declareJS(url)
.done(function () {
equal($("#qunit-fixture").text(), "JS not fetched twice");
})
.fail(function (jqXHR, textStatus) {
ok(false, "Failed to load " + textStatus + " " + jqXHR.status);
})
.always(function () {
start();
});
return renderJS.declareJS(url);
})
.then(function () {
equal($("#qunit-fixture").text(), "JS not fetched twice");
})
.fail(function (jqXHR, textStatus) {
ok(false, "Failed to load " + textStatus + " " + jqXHR.status);
.fail(function (jqXHR) {
ok(false, "Failed to load " + jqXHR);
})
.always(function () {
start();
});
});
......@@ -822,26 +855,60 @@
stop();
renderJS.declareCSS(url)
.done(function () {
.then(function () {
renderJS.clearGadgetKlassList();
equal($('head').find('link[rel=stylesheet]').length, count + 1);
renderJS.declareCSS(url)
.done(function () {
equal($('head').find('link[rel=stylesheet]').length, count + 2);
})
.fail(function (jqXHR, textStatus) {
ok(false, "Failed to load " + textStatus + " " + jqXHR.status);
})
.always(function () {
start();
});
return renderJS.declareCSS(url);
})
.fail(function (jqXHR, textStatus) {
ok(false, "Failed to load " + textStatus + " " + jqXHR.status);
.then(function () {
equal($('head').find('link[rel=stylesheet]').length, count + 2);
})
.fail(function (jqXHR) {
ok(false, "Failed to load " + jqXHR);
})
.always(function () {
start();
});
});
/////////////////////////////////////////////////////////////////
// RenderJSGadget
/////////////////////////////////////////////////////////////////
module("RenderJSGadget");
test('should be a constructor', function () {
var gadget = new RenderJSGadget();
equal(
Object.getPrototypeOf(gadget),
RenderJSGadget.prototype,
'[[Prototype]] equals RenderJSGadget.prototype'
);
equal(
gadget.constructor,
RenderJSGadget,
'constructor property of instances is set correctly'
);
equal(
RenderJSGadget.prototype.constructor,
RenderJSGadget,
'constructor property of prototype is set correctly'
);
});
test('should not accept parameter', function () {
equal(RenderJSGadget.length, 0);
});
test('should work without new', function () {
var gadgetKlass = RenderJSGadget,
gadget = gadgetKlass();
equal(
gadget.constructor,
RenderJSGadget,
'constructor property of instances is set correctly'
);
});
/////////////////////////////////////////////////////////////////
// RenderJSGadget.getInterfaceList
/////////////////////////////////////////////////////////////////
......@@ -856,7 +923,7 @@
gadget.interface_list = "foo";
stop();
gadget.getInterfaceList()
.done(function (result) {
.then(function (result) {
equal(result, "foo");
})
.always(function () {
......@@ -869,7 +936,7 @@
var gadget = new RenderJSGadget();
stop();
gadget.getInterfaceList()
.done(function (result) {
.then(function (result) {
deepEqual(result, []);
})
.always(function () {
......@@ -891,7 +958,7 @@
gadget.required_css_list = "foo";
stop();
gadget.getRequiredCSSList()
.done(function (result) {
.then(function (result) {
equal(result, "foo");
})
.always(function () {
......@@ -904,7 +971,7 @@
var gadget = new RenderJSGadget();
stop();
gadget.getRequiredCSSList()
.done(function (result) {
.then(function (result) {
deepEqual(result, []);
})
.always(function () {
......@@ -926,7 +993,7 @@
gadget.required_js_list = "foo";
stop();
gadget.getRequiredJSList()
.done(function (result) {
.then(function (result) {
equal(result, "foo");
})
.always(function () {
......@@ -939,7 +1006,7 @@
var gadget = new RenderJSGadget();
stop();
gadget.getRequiredJSList()
.done(function (result) {
.then(function (result) {
deepEqual(result, []);
})
.always(function () {
......@@ -961,7 +1028,7 @@
gadget.path = "foo";
stop();
gadget.getPath()
.done(function (result) {
.then(function (result) {
equal(result, "foo");
})
.always(function () {
......@@ -974,7 +1041,7 @@
var gadget = new RenderJSGadget();
stop();
gadget.getPath()
.done(function (result) {
.then(function (result) {
equal(result, "");
})
.always(function () {
......@@ -996,7 +1063,7 @@
gadget.title = "foo";
stop();
gadget.getTitle()
.done(function (result) {
.then(function (result) {
equal(result, "foo");
})
.always(function () {
......@@ -1009,7 +1076,7 @@
var gadget = new RenderJSGadget();
stop();
gadget.getTitle()
.done(function (result) {
.then(function (result) {
equal(result, "");
})
.always(function () {
......@@ -1018,20 +1085,20 @@
});
/////////////////////////////////////////////////////////////////
// RenderJSGadget.getHTML
// RenderJSGadget.getElement
/////////////////////////////////////////////////////////////////
module("RenderJSGadget.getHTML", {
module("RenderJSGadget.getElement", {
setup: function () {
renderJS.clearGadgetKlassList();
}
});
test('returns html', function () {
// Check that getHTML return a Promise
test('returns element property', function () {
// Check that getElement return a Promise
var gadget = new RenderJSGadget();
gadget.html = "foo";
gadget.element = "foo";
stop();
gadget.getHTML()
.done(function (result) {
gadget.getElement()
.then(function (result) {
equal(result, "foo");
})
.always(function () {
......@@ -1039,13 +1106,17 @@
});
});
test('default value', function () {
// Check that getHTML return a Promise
test('throw an error if no element is defined', function () {
// Check that getElement return a Promise
var gadget = new RenderJSGadget();
stop();
gadget.getHTML()
.done(function (result) {
equal(result, "");
gadget.getElement()
.then(function () {
ok(false, "getElement should fail");
})
.fail(function (e) {
console.log(e);
ok(e instanceof Error);
})
.always(function () {
start();
......@@ -1074,7 +1145,7 @@
gadget = new Klass();
equal(gadget.testFoo, undefined);
result = Klass.declareMethod('testFoo', function () {
var a;
console.log("");
});
// declareMethod is chainable
equal(result, Klass);
......@@ -1086,7 +1157,7 @@
// Subclass RenderJSGadget to not pollute its namespace
var Klass = function () {
RenderJSGadget.call(this);
}, gadget, called, result;
}, gadget, called;
Klass.prototype = new RenderJSGadget();
Klass.prototype.constructor = Klass;
Klass.declareMethod = RenderJSGadget.declareMethod;
......@@ -1102,9 +1173,18 @@
ok(Klass.prototype.testFoo !== undefined);
equal(Klass.prototype.testFoo, gadget.testFoo);
stop();
// method can be called
gadget.testFoo("Bar");
equal(called, "Bar");
gadget.testFoo("Bar")
.then(function (param) {
equal(called, "Bar");
})
.fail(function () {
ok(false, "Should propagate the parameters");
})
.always(function () {
start();
});
});
test('returns a promise when synchronous function', function () {
......@@ -1127,7 +1207,7 @@
// method can be called
stop();
gadget.testFoo("Bar")
.done(function (param) {
.then(function (param) {
equal(param, "Bar");
})
.fail(function () {
......@@ -1152,16 +1232,14 @@
gadget = new Klass();
Klass.declareMethod('testFoo', function (value) {
var dfr = $.Deferred();
setTimeout(function () {
dfr.reject(value);
});
dfr.reject(value);
return dfr.promise();
});
// method can be called
stop();
gadget.testFoo("Bar")
.done(function () {
.then(function () {
ok(false, "Callback promise is rejected");
})
.fail(function (param) {
......@@ -1186,15 +1264,14 @@
// Subclass RenderJSGadget to not pollute its namespace
var Klass = function () {
RenderJSGadget.call(this);
}, gadget, result;
}, result;
Klass.prototype = new RenderJSGadget();
Klass.prototype.constructor = Klass;
Klass.ready_list = [];
Klass.ready = RenderJSGadget.ready;
gadget = new Klass();
result = Klass.ready(function () {
var a;
console.log("");
});
// ready is chainable
equal(result, Klass);
......@@ -1206,14 +1283,13 @@
// Subclass RenderJSGadget to not pollute its namespace
var Klass = function () {
RenderJSGadget.call(this);
}, gadget, result,
callback = function () {var a; };
},
callback = function () {console.log(""); };
Klass.prototype = new RenderJSGadget();
Klass.prototype.constructor = Klass;
Klass.ready_list = [];
Klass.ready = RenderJSGadget.ready;
gadget = new Klass();
Klass.ready(callback);
// ready is chainable
deepEqual(Klass.ready_list, [callback]);
......@@ -1238,17 +1314,26 @@
"type='text/javascript'></script>" +
"</body></html>";
server.autoRespond = true;
server.autoRespondAfter = 5;
server.respondWith("GET", url, [200, {
"Content-Type": "text/html",
}, html]);
stop();
gadget.declareGadget(url, $('#qunit-fixture'))
.always(function () {
gadget.declareGadget(url)//, $('#qunit-fixture'))
.then(function () {
ok(true);
})
.fail(function (e) {
console.warn(e);
ok(false, e);
})
.always(function () {
start();
server.restore();
});
server.respond();
});
test('provide a gadget instance as callback parameter', function () {
......@@ -1262,19 +1347,22 @@
"type='text/javascript'></script>" +
"</body></html>";
server.autoRespond = true;
server.autoRespondAfter = 5;
server.respondWith("GET", url, [200, {
"Content-Type": "text/html",
}, html]);
stop();
gadget.declareGadget(url, $('#qunit-fixture'))
.done(function (new_gadget) {
gadget.declareGadget(url)//, $('#qunit-fixture'))
.then(function (new_gadget) {
equal(new_gadget.path, url);
})
.always(function () {
start();
server.restore();
});
server.respond();
});
// test('no parameter', function () {
......@@ -1303,7 +1391,7 @@
window.btoa(
"$('#qunit-fixture').find('div').first().text('youhou2');"
),
css1_url = "data:text/plain;base64," +
css1_url = "data:text/css;base64," +
window.btoa(""),
css2_url = css1_url,
html = "<html>" +
......@@ -1314,30 +1402,34 @@
"<link rel='stylesheet' href='" + css1_url + "' type='text/css'/>" +
"<link rel='stylesheet' href='" + css2_url + "' type='text/css'/>" +
"</head><body><p>Bar content</p></body></html>",
mock,
spy_js,
spy_css;
server.autoRespond = true;
server.autoRespondAfter = 5;
server.respondWith("GET", html_url, [200, {
"Content-Type": "text/html",
}, html]);
spy_js = this.spy(renderJS, "declareJS");
spy_css = this.spy(renderJS, "declareCSS");
spy_js = sinon.spy(renderJS, "declareJS");
spy_css = sinon.spy(renderJS, "declareCSS");
mock = this.mock(renderJS, "parseGadgetHTML");
mock.expects("parseGadgetHTML").once().withArgs(html).returns({
required_js_list: [js1_url, js2_url],
required_css_list: [css1_url, css2_url],
html: "<p>Bar content</p>",
});
// mock = sinon.mock(renderJS, "parseGadgetHTML");
// mock.expects("parseGadgetHTML").once().withArgs(html).returns({
// required_js_list: [js1_url, js2_url],
// required_css_list: [css1_url, css2_url],
// html: "<p>Bar content</p>",
// });
$('#qunit-fixture').html("<div></div><div></div>");
$('#qunit-fixture').html("<div></div><div>bar</div>");
stop();
gadget.declareGadget(html_url, $('#qunit-fixture').find("div").last())
.done(function (new_gadget) {
gadget.declareGadget(html_url)//, $('#qunit-fixture').find("div").last()[0])
.then(function (new_gadget) {
equal($('#qunit-fixture').html(),
"<div>youhou2</div><div><p>Bar content</p></div>");
"<div>youhou2</div><div>bar</div>");
equal(new_gadget.element.outerHTML,
"<div><p>Bar content</p></div>");
ok(spy_js.calledTwice, "JS count " + spy_js.callCount);
equal(spy_js.firstCall.args[0], js1_url, "First JS call");
equal(spy_js.secondCall.args[0], js2_url, "Second JS call");
......@@ -1345,13 +1437,16 @@
equal(spy_css.firstCall.args[0], css1_url, "First CSS call");
equal(spy_css.secondCall.args[0], css2_url, "Second CSS call");
})
.fail(function () {
.fail(function (e) {
console.warn(e);
ok(false);
})
.always(function () {
start();
server.restore();
spy_js.restore();
spy_css.restore();
});
server.respond();
});
// test('load dependency in the right order', function () {
......@@ -1368,135 +1463,274 @@
server = sinon.fakeServer.create(),
html_url = 'https://example.org/files/qunittest/test3.html';
server.autoRespond = true;
server.autoRespondAfter = 5;
server.respondWith("GET", html_url, [404, {
"Content-Type": "text/html",
}, ""]);
stop();
gadget.declareGadget(html_url, $('#qunit-fixture').find("div").last())
.done(function (new_gadget) {
gadget.declareGadget(html_url)//, $('#qunit-fixture').find("div").last())
.then(function (new_gadget) {
ok(false);
})
.fail(function (jqXHR, textStatus) {
equal("404", jqXHR.status);
.fail(function (jqXHR) {
equal(jqXHR.status, 404);
})
.always(function () {
start();
server.restore();
});
server.respond();
});
test('Fail if js can not be loaded', function () {
// Check that dependencies are loaded before gadget creation
var gadget = new RenderJSGadget(),
server = sinon.fakeServer.create(),
html_url = 'https://example.org/files/qunittest/test2.html',
js1_url = 'foo://bar2',
html_url = 'http://example.org/files/qunittest/test5.html',
js1_url = 'http://0.0.0.0/test.js',
mock;
server.autoRespond = true;
server.autoRespondAfter = 5;
server.respondWith("GET", html_url, [200, {
"Content-Type": "text/html",
}, "raw html"]);
mock = this.mock(renderJS, "parseGadgetHTML");
mock.expects("parseGadgetHTML").once().withArgs("raw html").returns({
mock = sinon.mock(renderJS, "parseGadgetHTMLDocument");
mock.expects("parseGadgetHTMLDocument").once().returns({
required_js_list: [js1_url]
});
stop();
gadget.declareGadget(html_url, $('#qunit-fixture'))
.done(function (new_gadget) {
gadget.declareGadget(html_url)//, $('#qunit-fixture'))
.then(function (new_gadget) {
ok(false);
})
.fail(function (jqXHR, textStatus) {
equal(jqXHR.status, 404);
equal(textStatus, "error");
.fail(function (e) {
ok(true);
})
.always(function () {
start();
server.restore();
mock.verify();
mock.restore();
});
server.respond();
});
test('Do not load gadget dependency twice', function () {
// Check that dependencies are not reloaded if 2 gadgets are created
var gadget = new RenderJSGadget(),
server = sinon.fakeServer.create(),
html_url = 'https://example.org/files/qunittest/test2.html',
html_url = 'https://example.org/files/qunittest/test254.html',
js1_url = "data:application/javascript;base64," +
window.btoa(
"$('#qunit-fixture').find('div').first().append('youhou');"
),
mock,
spy;
mock;
server.autoRespond = true;
server.autoRespondAfter = 5;
server.respondWith("GET", html_url, [200, {
"Content-Type": "text/html",
}, "raw html"]);
spy = this.spy($, "ajax");
mock = this.mock(renderJS, "parseGadgetHTML");
mock.expects("parseGadgetHTML").once().withArgs("raw html").returns({
mock = sinon.mock(renderJS, "parseGadgetHTMLDocument");
mock.expects("parseGadgetHTMLDocument").once().returns({
required_js_list: [js1_url]
});
stop();
$('#qunit-fixture').html("<div></div><div></div>");
gadget.declareGadget(html_url, $('#qunit-fixture').find("div").last())
gadget.declareGadget(html_url)//, $('#qunit-fixture').find("div").last()[0])
.always(function () {
equal($('#qunit-fixture').html(),
"<div>youhou</div><div></div>");
gadget.declareGadget(html_url, $('#qunit-fixture').find("div").last())
.done(function (new_gadget) {
equal($('#qunit-fixture').html(),
"<div>youhou</div><div></div>");
ok(spy.calledTwice, "Ajax count " + spy.callCount);
equal(spy.firstCall.args[0], html_url, "First ajax call");
deepEqual(spy.secondCall.args[0], {
"cache": true,
"dataType": "script",
"url": js1_url,
}, "Second ajax call");
})
.fail(function () {
ok(false);
})
.always(function () {
start();
});
return gadget.declareGadget(html_url);//,
//$('#qunit-fixture').find("div").last()[0]
// );
})
.then(function (new_gadget) {
equal($('#qunit-fixture').html(),
"<div>youhou</div><div></div>");
})
.fail(function () {
ok(false);
})
.always(function () {
start();
server.restore();
mock.verify();
mock.restore();
});
server.respond();
});
test('Load 2 concurrent gadgets in parallel', function () {
// Check that dependencies are loaded once if 2 gadgets are created
var gadget = new RenderJSGadget(),
server = sinon.fakeServer.create(),
html_url = 'https://example.org/files/qunittest/test2.html',
mock,
spy;
html_url = 'https://example.org/files/qunittest/test987.html',
mock;
server.autoRespond = true;
server.autoRespondAfter = 5;
server.respondWith("GET", html_url, [200, {
"Content-Type": "text/html",
}, "raw html"]);
spy = this.spy($, "ajax");
mock = sinon.mock(renderJS, "parseGadgetHTMLDocument");
mock.expects("parseGadgetHTMLDocument").once().returns({});
mock = this.mock(renderJS, "parseGadgetHTML");
mock.expects("parseGadgetHTML").once().withArgs("raw html").returns({});
stop();
RSVP.all([
gadget.declareGadget(html_url),// $('#qunit-fixture')),
gadget.declareGadget(html_url)//, $('#qunit-fixture'))
])
.then(function () {
ok(true);
})
.always(function () {
// Check that only one request has been done.
start();
mock.verify();
mock.restore();
server.restore();
});
});
test('Wait for ready callback before returning', function () {
// Subclass RenderJSGadget to not pollute its namespace
var called = false,
gadget = new RenderJSGadget(),
server = sinon.fakeServer.create(),
html_url = 'https://example.org/files/qunittest/test98.html';
server.autoRespond = true;
server.autoRespondAfter = 5;
server.respondWith("GET", html_url, [200, {
"Content-Type": "text/html",
}, "<html><body></body></html>"]);
stop();
$.when(
gadget.declareGadget(html_url, $('#qunit-fixture')),
gadget.declareGadget(html_url, $('#qunit-fixture'))
).always(function () {
// Check that only one request has been done.
ok(spy.calledOnce, "Ajax count " + spy.callCount);
equal(spy.firstCall.args[0], html_url, "First ajax call");
start();
});
server.respond();
renderJS.declareGadgetKlass(html_url)
.then(function (Klass) {
// Create a ready function
Klass.ready(function () {
return RSVP.delay(50).then(function () {
// Modify the value after 50ms
called = true;
});
});
return gadget.declareGadget(html_url);//, $('#qunit-fixture')[0]);
})
.then(function () {
ok(called);
})
.fail(function (e) {
console.warn(e);
ok(false);
})
.always(function () {
start();
server.restore();
});
});
test('Can take a DOM element options', function () {
// Subclass RenderJSGadget to not pollute its namespace
var gadget = new RenderJSGadget(),
server = sinon.fakeServer.create(),
html_url = 'https://example.org/files/qunittest/test98.html';
server.autoRespond = true;
server.autoRespondAfter = 5;
server.respondWith("GET", html_url, [200, {
"Content-Type": "text/html",
}, "<html><body><p>foo</p></body></html>"]);
$('#qunit-fixture').empty();
stop();
renderJS.declareGadgetKlass(html_url)
.then(function (Klass) {
return gadget.declareGadget(html_url, {element: $('#qunit-fixture')[0]});
})
.then(function () {
equal($('#qunit-fixture').html(), '<p>foo</p>');
})
.fail(function (e) {
console.warn(e);
ok(false);
})
.always(function () {
start();
server.restore();
});
});
/////////////////////////////////////////////////////////////////
// RenderJSGadget bootstrap
/////////////////////////////////////////////////////////////////
module("RenderJSGadget bootstrap");
// module("RenderJSGadget bootstrap", {
// setup: function () {
// renderJS.clearGadgetKlassList();
// }
// });
test('Check that the root gadget is cleanly implemented', function () {
stop();
console.log(root_gadget_klass);
root_gadget_defer.promise
.then(function (root_gadget) {
// Check instance
equal(root_gadget.path, window.location.href);
equal(root_gadget.title, document.title);
deepEqual(root_gadget.interface_list, []);
deepEqual(root_gadget.required_css_list, ["../lib/qunit/qunit.css"]);
deepEqual(root_gadget.required_js_list, [
"../lib/rsvp/rsvp.js",
"../lib/jquery/jquery.js",
"../lib/qunit/qunit.js",
"../lib/sinon/sinon.js",
"../../lib/jschannel/jschannel.js",
"../renderjs.js",
"renderjs_test.js",
]);
equal(root_gadget.element.outerHTML, document.body.outerHTML);
// Check klass
equal(root_gadget.constructor.prototype.path, window.location.href);
equal(root_gadget.constructor.prototype.title, document.title);
deepEqual(root_gadget.constructor.prototype.interface_list, []);
deepEqual(root_gadget.constructor.prototype.required_css_list,
["../lib/qunit/qunit.css"]);
deepEqual(root_gadget.constructor.prototype.required_js_list, [
"../lib/rsvp/rsvp.js",
"../lib/jquery/jquery.js",
"../lib/qunit/qunit.js",
"../lib/sinon/sinon.js",
"../../lib/jschannel/jschannel.js",
"../renderjs.js",
"renderjs_test.js",
]);
var html = root_gadget.constructor.template_element.outerHTML;
ok(/^<div>\s*<h1 id="qunit-header">/.test(html), html);
})
.fail(function (e) {
ok(false, e);
})
.always(function () {
start();
});
});
}(document, jQuery, renderJS, QUnit, sinon));
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment