Commit ac07b108 authored by Jérome Perrin's avatar Jérome Perrin

Repair graph editor

graph_editor was using `rsvp.Monitor` in order to dynamically add new promises to the chain of promise in its `declareService` promise. New DOM elements were added during gadget's lifetime and and event handler on these new elements was added to the chain of promise by using [monitor method](
 https://lab.nexedi.com/nexedi/erp5/blob/76ecef89d0b4f6aa6bc5/bt5/erp5_graph_editor/SkinTemplateItem/portal_skins/erp5_graph_editor/dream_graph_editor/jsplumb/jsplumb.js.js#L607) of a monitor instance which was [returned](https://lab.nexedi.com/nexedi/erp5/blob/76ecef89d0b4f6aa6bc5/bt5/erp5_graph_editor/SkinTemplateItem/portal_skins/erp5_graph_editor/dream_graph_editor/jsplumb/jsplumb.js.js#L835) in `declareService`.

`rsvp.js` included in `erp5_xhml_style` exported `Monitor`, but this was removed in  af9c57db . If I understand correctly, this is now included in renderjs, but it's only internal.

So this old way of doing is not longer possible. We realized that instead of dynamically setting `dblclick` event handlers to each graph node elements, we could  simply rely on event bubbling and use a event handler on the parent DOM element. Also, we used renderjs builtin `onEvent` that makes event callback function executed in the promise chain . 

At this stage we did not try to switch all event handling to this approach of using a "global" event handler on the parent DOM, because the goal here was just repairing the graph editor and making sure we have tests running. Also jsplumb uses its own event system.

To enable tests for this:
 - Running the existing qunit test through Zelenium. As far as I know we cannot run qunit test as part of ERP5 test suite.
 - Install the business template in testXHTML so that it is tested by `jsl`, which by the way produce different messages that the jshint integrated in ERP5's code mirror and jslint from `WebScript_checkSyntax`. For now, this passes jshint and jsl, but jslint complains about some indentation and space problems.

/cc  @romain @vincentB @xiaowu.zhang  @seb @gabriel 

/reviewed-on nexedi/erp5!321
parents ff931799 a6cef4a8
...@@ -9,13 +9,17 @@ ...@@ -9,13 +9,17 @@
<script src="renderjs.js" type="text/javascript"></script> <script src="renderjs.js" type="text/javascript"></script>
<script src="rsvp.js" type="text/javascript"></script> <script src="rsvp.js" type="text/javascript"></script>
--> -->
<!--
FIXME: Including jQuery twice cause the jsplumb to be loaded twice.
For now we assume that it has already been loaded at this point.
<script src="../lib/jquery.js" type="text/javascript"></script> <script src="../lib/jquery.js" type="text/javascript"></script>
-->
<script src="../lib/jquery-ui.js" type="text/javascript"></script> <script src="../lib/jquery-ui.js" type="text/javascript"></script>
<script src="../lib/jquery.jsplumb.js" type="text/javascript"></script> <script src="../lib/jquery.jsplumb.js" type="text/javascript"></script>
<script src="../lib/handlebars.min.js" type="text/javascript"></script> <script src="../lib/handlebars.min.js" type="text/javascript"></script>
<script src="../lib/springy.js" type="text/javascript"></script>
<script src="../dream/mixin_promise.js" type="text/javascript"></script> <script src="../dream/mixin_promise.js" type="text/javascript"></script>
<script src="springy.js" type="text/javascript"></script>
<script src="jsplumb.js" type="text/javascript"></script> <script src="jsplumb.js" type="text/javascript"></script>
<script id="node-template" type="text/x-handlebars-template"> <script id="node-template" type="text/x-handlebars-template">
......
/* =========================================================================== /* ===========================================================================
* Copyright 2013-2015 Nexedi SA and Contributors * Copyright 2013-2015 Nexedi SA and Contributors
* *
* This file is part of DREAM. * This file is part of DREAM.
...@@ -16,63 +16,77 @@ ...@@ -16,63 +16,77 @@
* You should have received a copy of the GNU Lesser General Public License * You should have received a copy of the GNU Lesser General Public License
* along with DREAM. If not, see <http://www.gnu.org/licenses/>. * along with DREAM. If not, see <http://www.gnu.org/licenses/>.
* ==========================================================================*/ * ==========================================================================*/
/*global console, window, RSVP, rJS, $, jsPlumb, Handlebars, /*global console, window, Node, RSVP, rJS, $, jsPlumb, Handlebars,
loopEventListener, promiseEventListener, DOMParser, Springy */ loopEventListener, promiseEventListener, DOMParser, Springy */
/*jslint unparam: true todo: true */ /*jslint vars: true unparam: true nomen: true todo: true */
(function(RSVP, rJS, $, jsPlumb, Handlebars, loopEventListener, promiseEventListener, DOMParser, Springy) { (function (RSVP, rJS, $, jsPlumb, Handlebars, loopEventListener, promiseEventListener, DOMParser, Springy) {
"use strict"; "use strict";
/* TODO: /* TODO:
* less dependancies ( promise event listner ? ) * less dependancies ( promise event listener ? )
* no more handlebars * no more handlebars
* id should not always be modifiable * id should not always be modifiable
* drop zoom level * drop zoom level
* rename draggable() * rename draggable()
* factorize node & edge popup edition * factorize node & edge popup edition
*/ */
/*jslint nomen: true */ var gadget_klass = rJS(window);
var gadget_klass = rJS(window), var domParser = new DOMParser();
domParser = new DOMParser(), var node_template_source = gadget_klass.__template_element.getElementById("node-template").innerHTML;
node_template_source = gadget_klass.__template_element.getElementById("node-template").innerHTML, var node_template = Handlebars.compile(node_template_source);
node_template = Handlebars.compile(node_template_source), var popup_edit_template = gadget_klass.__template_element.getElementById("popup-edit-template").innerHTML;
popup_edit_template = gadget_klass.__template_element.getElementById("popup-edit-template").innerHTML;
function layoutGraph(graph_data) { function layoutGraph(graph_data) {
// Promise returning the graph once springy calculated the layout. // Promise returning the graph once springy calculated the layout.
// If the graph already contain layout, return it as is. // If the graph already contain layout, return it as is.
function resolver(resolve, reject) { function resolver(resolve, reject) {
try { try {
var springy_graph = new Springy.Graph(), var springy_graph = new Springy.Graph();
max_iterations = 100, // we stop layout after 100 iterations. var max_iterations = 100; // we stop layout after 100 iterations.
loop = 0, var loop = 0;
springy_nodes = {}, var springy_nodes = {};
drawn_nodes = {}, var drawn_nodes = {};
min_x=100, max_x=0, min_y=100, max_y=0; var min_x = 100;
var max_x = 0;
var min_y = 100;
var max_y = 0;
// if graph is empty, no need to layout
if (Object.keys(graph_data.edge).length === 0) {
resolve(graph_data);
return;
}
// make a Springy graph with our graph // make a Springy graph with our graph
$.each(graph_data.node, function(key, value) { $.each(graph_data.node, function (key, value) {
if (value.coordinate) { if (value.coordinate && value.coordinate.top && value.coordinate.left) {
// graph already has a layout, no need to layout again // graph already has a layout, no need to layout again
return resolve(graph_data); resolve(graph_data);
return;
} }
springy_nodes[key] = springy_graph.newNode({node_id: key}); springy_nodes[key] = springy_graph.newNode({node_id: key});
}); });
$.each(graph_data.edge, function(key, value) { $.each(graph_data.edge, function (ignore, value) {
springy_graph.newEdge(springy_nodes[value.source], springy_nodes[value.destination]); springy_graph.newEdge(springy_nodes[value.source], springy_nodes[value.destination]);
}); });
var layout = new Springy.Layout.ForceDirected(springy_graph, 400.0, 400.0, 0.5); var layout = new Springy.Layout.ForceDirected(springy_graph, 400.0, 400.0, 0.5);
var renderer = new Springy.Renderer( var renderer;
renderer = new Springy.Renderer(
layout, layout,
function clear() {}, function clear() {
function drawEdge(edge, p1, p2) {}, return;
},
function drawEdge() {
return;
},
function drawNode(node, p) { function drawNode(node, p) {
drawn_nodes[node.data.node_id] = p; drawn_nodes[node.data.node_id] = p;
if ( ++loop > max_iterations) { loop += 1;
if (loop > max_iterations) {
renderer.stop(); renderer.stop();
} }
}, },
function onRenderStop() { function onRenderStop() {
// calculate the min and max of x and y // calculate the min and max of x and y
$.each(graph_data.node, function(key, value) { $.each(graph_data.node, function (key) {
if (drawn_nodes[key].x > max_x) { if (drawn_nodes[key].x > max_x) {
max_x = drawn_nodes[key].x; max_x = drawn_nodes[key].x;
} }
...@@ -88,7 +102,7 @@ ...@@ -88,7 +102,7 @@
}); });
// "resample" the positions from 0 to 1, the scale used by this gadget. // "resample" the positions from 0 to 1, the scale used by this gadget.
// We keep a 5% margin // We keep a 5% margin
$.each(graph_data.node, function(key, value) { $.each(graph_data.node, function (key) {
graph_data.node[key].coordinate = { graph_data.node[key].coordinate = {
left: 0.05 + 0.9 * (drawn_nodes[key].x - min_x) / (max_x - min_x), left: 0.05 + 0.9 * (drawn_nodes[key].x - min_x) / (max_x - min_x),
top: 0.05 + 0.9 * (drawn_nodes[key].y - min_y) / (max_y - min_y) top: 0.05 + 0.9 * (drawn_nodes[key].y - min_y) / (max_y - min_y)
...@@ -110,7 +124,9 @@ ...@@ -110,7 +124,9 @@
// Infinite event listener (promise is never resolved) // Infinite event listener (promise is never resolved)
// eventListener is removed when promise is cancelled/rejected // eventListener is removed when promise is cancelled/rejected
////////////////////////// //////////////////////////
var handle_event_callback, callback_promise, jsplumb_instance = gadget.props.jsplumb_instance; var handle_event_callback;
var callback_promise;
var jsplumb_instance = gadget.props.jsplumb_instance;
function cancelResolver() { function cancelResolver() {
if (callback_promise !== undefined && typeof callback_promise.cancel === "function") { if (callback_promise !== undefined && typeof callback_promise.cancel === "function") {
...@@ -125,13 +141,13 @@ ...@@ -125,13 +141,13 @@
cancelResolver(); cancelResolver();
} }
function resolver(resolve, reject) { function resolver(ignore, reject) {
handle_event_callback = function() { handle_event_callback = function () {
var args = arguments; var args = arguments;
cancelResolver(); cancelResolver();
callback_promise = new RSVP.Queue().push(function() { callback_promise = new RSVP.Queue().push(function () {
return callback.apply(jsplumb_instance, args); return callback.apply(jsplumb_instance, args);
}).push(undefined, function(error) { }).push(undefined, function (error) {
if (!(error instanceof RSVP.CancellationError)) { if (!(error instanceof RSVP.CancellationError)) {
canceller(); canceller();
reject(error); reject(error);
...@@ -146,7 +162,7 @@ ...@@ -146,7 +162,7 @@
function getNodeId(gadget, element_id) { function getNodeId(gadget, element_id) {
// returns the ID of the node in the graph from its DOM element id // returns the ID of the node in the graph from its DOM element id
var node_id; var node_id;
$.each(gadget.props.node_id_to_dom_element_id, function(k, v) { $.each(gadget.props.node_id_to_dom_element_id, function (k, v) {
if (v === element_id) { if (v === element_id) {
node_id = k; node_id = k;
return false; return false;
...@@ -157,9 +173,9 @@ ...@@ -157,9 +173,9 @@
function generateNodeId(gadget, element) { function generateNodeId(gadget, element) {
// Generate a node id // Generate a node id
var n = 1, var n = 1;
class_def = gadget.props.data.class_definition[element._class], var class_def = gadget.props.data.class_definition[element._class];
id = class_def.short_id || element._class; var id = class_def.short_id || element._class;
while (gadget.props.data.graph.node[id + n] !== undefined) { while (gadget.props.data.graph.node[id + n] !== undefined) {
n += 1; n += 1;
} }
...@@ -177,8 +193,9 @@ ...@@ -177,8 +193,9 @@
function getDefaultEdgeClass(gadget) { function getDefaultEdgeClass(gadget) {
var class_definition = gadget.props.data.class_definition; var class_definition = gadget.props.data.class_definition;
for (var key in class_definition) { var key;
if (class_definition.hasOwnProperty(key) && class_definition[key]._class === 'edge') { for (key in class_definition) {
if (class_definition.hasOwnProperty(key) && class_definition[key]._class === "edge") {
return key; return key;
} }
} }
...@@ -205,31 +222,31 @@ ...@@ -205,31 +222,31 @@
} }
function convertToAbsolutePosition(gadget, x, y) { function convertToAbsolutePosition(gadget, x, y) {
var zoom_level = gadget.props.zoom_level, var zoom_level = gadget.props.zoom_level;
canvas_size_x = $(gadget.props.main).width(), var canvas_size_x = $(gadget.props.main).width();
canvas_size_y = $(gadget.props.main).height(), var canvas_size_y = $(gadget.props.main).height();
size_x = $(gadget.props.element).find(".dummy_window").width() * zoom_level, var size_x = $(gadget.props.element).find(".dummy_window").width() * zoom_level;
size_y = $(gadget.props.element).find(".dummy_window").height() * zoom_level, var size_y = $(gadget.props.element).find(".dummy_window").height() * zoom_level;
top = Math.floor(y * (canvas_size_y - size_y)) + "px", var top = Math.floor(y * (canvas_size_y - size_y)) + "px";
left = Math.floor(x * (canvas_size_x - size_x)) + "px"; var left = Math.floor(x * (canvas_size_x - size_x)) + "px";
return [left, top]; return [left, top];
} }
function convertToRelativePosition(gadget, x, y) { function convertToRelativePosition(gadget, x, y) {
var zoom_level = gadget.props.zoom_level, var zoom_level = gadget.props.zoom_level;
canvas_size_x = $(gadget.props.main).width(), var canvas_size_x = $(gadget.props.main).width();
canvas_size_y = $(gadget.props.main).height(), var canvas_size_y = $(gadget.props.main).height();
size_x = $(gadget.props.element).find(".dummy_window").width() * zoom_level, var size_x = $(gadget.props.element).find(".dummy_window").width() * zoom_level;
size_y = $(gadget.props.element).find(".dummy_window").height() * zoom_level, var size_y = $(gadget.props.element).find(".dummy_window").height() * zoom_level;
top = Math.max(Math.min(y.replace("px", "") / (canvas_size_y - size_y), 1), 0), var top = Math.max(Math.min(y.replace("px", "") / (canvas_size_y - size_y), 1), 0);
left = Math.max(Math.min(x.replace("px", "") / (canvas_size_x - size_x), 1), 0); var left = Math.max(Math.min(x.replace("px", "") / (canvas_size_x - size_x), 1), 0);
return [left, top]; return [left, top];
} }
function updateElementCoordinate(gadget, node_id, coordinate) { function updateElementCoordinate(gadget, node_id, coordinate) {
var element_id = gadget.props.node_id_to_dom_element_id[node_id], var element_id = gadget.props.node_id_to_dom_element_id[node_id];
element, var element;
relative_position; var relative_position;
if (coordinate === undefined) { if (coordinate === undefined) {
element = $(gadget.props.element).find("#" + element_id); element = $(gadget.props.element).find("#" + element_id);
relative_position = convertToRelativePosition(gadget, element.css("left"), element.css("top")); relative_position = convertToRelativePosition(gadget, element.css("left"), element.css("top"));
...@@ -244,8 +261,8 @@ ...@@ -244,8 +261,8 @@
} }
function draggable(gadget) { function draggable(gadget) {
var jsplumb_instance = gadget.props.jsplumb_instance, var jsplumb_instance = gadget.props.jsplumb_instance;
stop = function(element) { var stop = function (element) {
updateElementCoordinate(gadget, getNodeId(gadget, element.target.id)); updateElementCoordinate(gadget, getNodeId(gadget, element.target.id));
}; };
...@@ -279,10 +296,10 @@ ...@@ -279,10 +296,10 @@
function updateNodeStyle(gadget, element_id) { function updateNodeStyle(gadget, element_id) {
// Update node size according to the zoom level // Update node size according to the zoom level
// XXX does nothing for now // XXX does nothing for now
var zoom_level = gadget.props.zoom_level, var zoom_level = gadget.props.zoom_level;
element = $(gadget.props.element).find("#" + element_id), var element = $(gadget.props.element).find("#" + element_id);
new_value; var new_value;
$.each(gadget.props.style_attr_list, function(i, j) { $.each(gadget.props.style_attr_list, function (ignore, j) {
new_value = element.css(j).replace("px", "") * zoom_level + "px"; new_value = element.css(j).replace("px", "") * zoom_level + "px";
element.css(j, new_value); element.css(j, new_value);
}); });
...@@ -294,7 +311,7 @@ ...@@ -294,7 +311,7 @@
$(gadget.props.element).find("#" + element_id).remove(); $(gadget.props.element).find("#" + element_id).remove();
delete gadget.props.data.graph.node[node_id]; delete gadget.props.data.graph.node[node_id];
delete gadget.props.node_id_to_dom_element_id[node_id]; delete gadget.props.node_id_to_dom_element_id[node_id];
$.each(gadget.props.data.graph.edge, function(k, v) { $.each(gadget.props.data.graph.edge, function (k, v) {
if (node_id === v.source || node_id === v.destination) { if (node_id === v.source || node_id === v.destination) {
delete gadget.props.data.graph.edge[k]; delete gadget.props.data.graph.edge[k];
} }
...@@ -303,11 +320,11 @@ ...@@ -303,11 +320,11 @@
} }
function updateElementData(gadget, node_id, data) { function updateElementData(gadget, node_id, data) {
var element_id = gadget.props.node_id_to_dom_element_id[node_id], var element_id = gadget.props.node_id_to_dom_element_id[node_id];
new_id = data.id || data.data.id; var new_id = data.id || data.data.id;
$(gadget.props.element).find("#" + element_id).text(data.data.name || new_id) $(gadget.props.element).find("#" + element_id).text(data.data.name || new_id)
.attr("title", data.data.name || new_id) .attr("title", data.data.name || new_id)
.append('<div class="ep"></div></div>'); .append("<div class='ep'></div></div>");
delete data.id; delete data.id;
...@@ -320,7 +337,7 @@ ...@@ -320,7 +337,7 @@
delete gadget.props.node_id_to_dom_element_id[node_id]; delete gadget.props.node_id_to_dom_element_id[node_id];
delete gadget.props.data.graph.node[new_id].id; delete gadget.props.data.graph.node[new_id].id;
$.each(gadget.props.data.graph.edge, function (k, v) { $.each(gadget.props.data.graph.edge, function (ignore, v) {
if (v.source === node_id) { if (v.source === node_id) {
v.source = new_id; v.source = new_id;
} }
...@@ -334,8 +351,8 @@ ...@@ -334,8 +351,8 @@
function addEdge(gadget, edge_id, edge_data) { function addEdge(gadget, edge_id, edge_data) {
var overlays = [], var overlays = [];
connection; var connection;
if (edge_data.name) { if (edge_data.name) {
overlays = [ overlays = [
["Label", { ["Label", {
...@@ -363,7 +380,8 @@ ...@@ -363,7 +380,8 @@
// endpoints on nodes. // endpoints on nodes.
if (edge_data.jsplumb_connector === "Flowchart") { if (edge_data.jsplumb_connector === "Flowchart") {
connection = gadget.props.jsplumb_instance.connect({ connection = gadget.props.jsplumb_instance.connect({
uuids: [edge_data.source + ".flowChart" + edge_data.jsplumb_source_endpoint, uuids: [
edge_data.source + ".flowChart" + edge_data.jsplumb_source_endpoint,
edge_data.destination + ".flowChart" + edge_data.jsplumb_destination_endpoint edge_data.destination + ".flowChart" + edge_data.jsplumb_destination_endpoint
], ],
overlays: overlays overlays: overlays
...@@ -389,11 +407,10 @@ ...@@ -389,11 +407,10 @@
// references // references
// XXX this should probably be moved to fieldset ( and not handle // XXX this should probably be moved to fieldset ( and not handle
// class_definition here) // class_definition here)
function resolveReference(ref, schema) { function resolveReference(ref, schema) {
// 2 here is for #/ var i;
var i, ref_path = ref.substr(2, ref.length), var ref_path = ref.substr(2, ref.length); // 2 here is for #/
parts = ref_path.split("/"); var parts = ref_path.split("/");
for (i = 0; i < parts.length; i += 1) { for (i = 0; i < parts.length; i += 1) {
schema = schema[parts[i]]; schema = schema[parts[i]];
} }
...@@ -404,10 +421,10 @@ ...@@ -404,10 +421,10 @@
return JSON.parse(JSON.stringify(obj)); return JSON.parse(JSON.stringify(obj));
} }
var referenced, var referenced;
i, var i;
property, var property;
expanded_class_definition = clone(class_definition) || {}; var expanded_class_definition = clone(class_definition) || {};
if (!expanded_class_definition.properties) { if (!expanded_class_definition.properties) {
...@@ -461,12 +478,12 @@ ...@@ -461,12 +478,12 @@
} }
function openEdgeEditionDialog(gadget, connection) { function openEdgeEditionDialog(gadget, connection) {
var edge_id = connection.id, var edge_id = connection.id;
edge_data = gadget.props.data.graph.edge[edge_id], var edge_data = gadget.props.data.graph.edge[edge_id];
edit_popup = $(gadget.props.element).find("#popup-edit-template"), var edit_popup = $(gadget.props.element).find("#popup-edit-template");
schema, var schema;
fieldset_element, var fieldset_element;
delete_promise; var delete_promise;
schema = expandSchema(gadget.props.data.class_definition[edge_data._class], gadget.props.data); schema = expandSchema(gadget.props.data.class_definition[edge_data._class], gadget.props.data);
// We do not edit source & destination on edge this way. // We do not edit source & destination on edge this way.
delete schema.properties.source; delete schema.properties.source;
...@@ -478,15 +495,15 @@ ...@@ -478,15 +495,15 @@
edit_popup.dialog(); edit_popup.dialog();
edit_popup.show(); edit_popup.show();
function save_promise(fieldset_gadget, edge_id) { function save_promise(fieldset_gadget) {
return RSVP.Queue().push(function() { return new RSVP.Queue().push(function () {
return promiseEventListener(edit_popup.find(".graph_editor_validate_button")[0], "click", false); return promiseEventListener(edit_popup.find(".graph_editor_validate_button")[0], "click", false);
}).push(function(evt) { }).push(function (evt) {
var data = { var data = {
id: $(evt.target[1]).val(), id: $(evt.target[1]).val(),
data: {} data: {}
}; };
return fieldset_gadget.getContent().then(function(r) { return fieldset_gadget.getContent().then(function (r) {
$.extend(data.data, gadget.props.data.graph.edge[connection.id]); $.extend(data.data, gadget.props.data.graph.edge[connection.id]);
$.extend(data.data, r); $.extend(data.data, r);
// to redraw, we remove the edge and add again. // to redraw, we remove the edge and add again.
...@@ -500,32 +517,32 @@ ...@@ -500,32 +517,32 @@
}); });
}); });
} }
delete_promise = new RSVP.Queue().push(function() { delete_promise = new RSVP.Queue().push(function () {
return promiseEventListener(edit_popup.find(".graph_editor_delete_button")[0], "click", false); return promiseEventListener(edit_popup.find(".graph_editor_delete_button")[0], "click", false);
}).push(function() { }).push(function () {
// connectionDetached event will remove the edge from data // connectionDetached event will remove the edge from data
gadget.props.jsplumb_instance.detach(connection); gadget.props.jsplumb_instance.detach(connection);
}); });
return gadget.declareGadget("../fieldset/index.html", { return gadget.declareGadget("../fieldset/index.html", {
element: fieldset_element, element: fieldset_element,
scope: "fieldset" scope: "fieldset"
}).push(function(fieldset_gadget) { }).push(function (fieldset_gadget) {
return RSVP.all([fieldset_gadget, fieldset_gadget.render({ return RSVP.all([fieldset_gadget, fieldset_gadget.render({
value: edge_data, value: edge_data,
property_definition: schema property_definition: schema
}, edge_id)]); }, edge_id)]);
}).push(function(fieldset_gadget) { }).push(function (fieldset_gadget) {
edit_popup.dialog("open"); edit_popup.dialog("open");
return fieldset_gadget[0]; return fieldset_gadget[0];
}).push(function(fieldset_gadget) { }).push(function (fieldset_gadget) {
fieldset_gadget.startService(); // XXX fieldset_gadget.startService(); // XXX
return fieldset_gadget; return fieldset_gadget;
}).push(function(fieldset_gadget) { }).push(function (fieldset_gadget) {
// Expose the dialog handling promise so that we can wait for it in // Expose the dialog handling promise so that we can wait for it in
// test. // test.
gadget.props.dialog_promise = RSVP.any([save_promise(fieldset_gadget, edge_id), delete_promise]); gadget.props.dialog_promise = RSVP.any([save_promise(fieldset_gadget, edge_id), delete_promise]);
return gadget.props.dialog_promise; return gadget.props.dialog_promise;
}).push(function() { }).push(function () {
edit_popup.dialog("close"); edit_popup.dialog("close");
edit_popup.remove(); edit_popup.remove();
delete gadget.props.dialog_promise; delete gadget.props.dialog_promise;
...@@ -533,17 +550,17 @@ ...@@ -533,17 +550,17 @@
} }
function openNodeEditionDialog(gadget, element) { function openNodeEditionDialog(gadget, element) {
var node_id = getNodeId(gadget, element.id), var node_id = getNodeId(gadget, element.id);
node_data = gadget.props.data.graph.node[node_id], var node_data = gadget.props.data.graph.node[node_id];
node_edit_popup = $(gadget.props.element).find("#popup-edit-template"), var node_edit_popup = $(gadget.props.element).find("#popup-edit-template");
schema, var schema;
fieldset_element, var fieldset_element;
delete_promise; var delete_promise;
// If we have no definition for this, we do not allow edition. // If we have no definition for this, we do not allow edition.
// XXX incorrect, we need to display this dialog to be able // XXX incorrect, we need to display this dialog to be able
// to delete a node // to delete a node
if (gadget.props.data.class_definition[node_data._class] === undefined) { if (gadget.props.data.class_definition[node_data._class] === undefined) {
return; return false;
} }
schema = expandSchema(gadget.props.data.class_definition[node_data._class], gadget.props.data); schema = expandSchema(gadget.props.data.class_definition[node_data._class], gadget.props.data);
if (node_edit_popup.length !== 0) { if (node_edit_popup.length !== 0) {
...@@ -558,81 +575,91 @@ ...@@ -558,81 +575,91 @@
node_data.id = node_id; node_data.id = node_id;
function save_promise(fieldset_gadget, node_id) { function save_promise(fieldset_gadget, node_id) {
return RSVP.Queue().push(function() { return new RSVP.Queue().push(function () {
return promiseEventListener(node_edit_popup.find(".graph_editor_validate_button")[0], "click", false); return promiseEventListener(
}).push(function(evt) { node_edit_popup.find(".graph_editor_validate_button")[0],
"click",
false
);
}).push(function (evt) {
var data = { var data = {
// XXX id should not be handled differently ... // XXX id should not be handled differently ...
id: $(evt.target[1]).val(), id: $(evt.target[1]).val(),
data: {} data: {}
}; };
return fieldset_gadget.getContent().then(function(r) { return fieldset_gadget.getContent().then(function (r) {
$.extend(data.data, r); $.extend(data.data, r);
updateElementData(gadget, node_id, data); updateElementData(gadget, node_id, data);
}); });
}); });
} }
delete_promise = new RSVP.Queue().push(function() { delete_promise = new RSVP.Queue().push(function () {
return promiseEventListener(node_edit_popup.find(".graph_editor_delete_button")[0], "click", false); return promiseEventListener(
}).push(function() { node_edit_popup.find(".graph_editor_delete_button")[0],
"click",
false
);
}).push(function () {
return removeElement(gadget, node_id); return removeElement(gadget, node_id);
}); });
return gadget.declareGadget("../fieldset/index.html", { return gadget.declareGadget("../fieldset/index.html", {
element: fieldset_element, element: fieldset_element,
scope: "fieldset" scope: "fieldset"
}).push(function(fieldset_gadget) { }).push(function (fieldset_gadget) {
return RSVP.all([fieldset_gadget, fieldset_gadget.render({ return RSVP.all([
fieldset_gadget,
fieldset_gadget.render(
{
value: node_data, value: node_data,
property_definition: schema property_definition: schema
}, node_id)]); },
}).push(function(fieldset_gadget) { node_id
)
]);
}).push(function (fieldset_gadget) {
node_edit_popup.dialog("open"); node_edit_popup.dialog("open");
return fieldset_gadget[0]; return fieldset_gadget[0];
}).push(function(fieldset_gadget) { }).push(function (fieldset_gadget) {
fieldset_gadget.startService(); // XXX this should not be needed anymore. fieldset_gadget.startService(); // XXX this should not be needed anymore.
return fieldset_gadget; return fieldset_gadget;
}).push(function(fieldset_gadget) { }).push(function (fieldset_gadget) {
// Expose the dialog handling promise so that we can wait for it in // Expose the dialog handling promise so that we can wait for it in
// test. // test.
gadget.props.dialog_promise = RSVP.any([save_promise(fieldset_gadget, node_id), delete_promise]); gadget.props.dialog_promise = RSVP.any([save_promise(fieldset_gadget, node_id), delete_promise]);
return gadget.props.dialog_promise; return gadget.props.dialog_promise;
}).push(function() { }).push(function () {
node_edit_popup.dialog("close"); node_edit_popup.dialog("close");
node_edit_popup.remove(); node_edit_popup.remove();
delete gadget.props.dialog_promise; delete gadget.props.dialog_promise;
}); });
} }
function waitForNodeClick(gadget, node) {
gadget.props.nodes_click_monitor.monitor(loopEventListener(node, "dblclick", false, openNodeEditionDialog.bind(null, gadget, node)));
}
function waitForConnection(gadget) { function waitForConnection(gadget) {
return loopJsplumbBind(gadget, "connection", function(info, originalEvent) { return loopJsplumbBind(gadget, "connection", function (info) {
updateConnectionData(gadget, info.connection, false); updateConnectionData(gadget, info.connection, false);
}); });
} }
function waitForConnectionDetached(gadget) { function waitForConnectionDetached(gadget) {
return loopJsplumbBind(gadget, "connectionDetached", function(info, originalEvent) { return loopJsplumbBind(gadget, "connectionDetached", function (info) {
updateConnectionData(gadget, info.connection, true); updateConnectionData(gadget, info.connection, true);
}); });
} }
function waitForConnectionClick(gadget) { function waitForConnectionClick(gadget) {
return loopJsplumbBind(gadget, "click", function(connection) { return loopJsplumbBind(gadget, "click", function (connection) {
return openEdgeEditionDialog(gadget, connection); return openEdgeEditionDialog(gadget, connection);
}); });
} }
function addNode(gadget, node_id, node_data) { function addNode(gadget, node_id, node_data) {
var render_element = $(gadget.props.main), var render_element = $(gadget.props.main);
class_definition = gadget.props.data.class_definition[node_data._class], var class_definition = gadget.props.data.class_definition[node_data._class];
coordinate = node_data.coordinate, var coordinate = node_data.coordinate;
dom_element_id, var dom_element_id;
box, var box;
absolute_position, var absolute_position;
domElement; var domElement;
dom_element_id = generateDomElementId(gadget.props.element); dom_element_id = generateDomElementId(gadget.props.element);
gadget.props.node_id_to_dom_element_id[node_id] = dom_element_id; gadget.props.node_id_to_dom_element_id[node_id] = dom_element_id;
...@@ -645,7 +672,6 @@ ...@@ -645,7 +672,6 @@
}; };
} }
node_data.coordinate = updateElementCoordinate(gadget, node_id, coordinate); node_data.coordinate = updateElementCoordinate(gadget, node_id, coordinate);
/*jslint nomen: true*/
domElement = domParser.parseFromString(node_template({ domElement = domParser.parseFromString(node_template({
"class": node_data._class.replace(".", "-"), "class": node_data._class.replace(".", "-"),
element_id: dom_element_id, element_id: dom_element_id,
...@@ -653,7 +679,6 @@ ...@@ -653,7 +679,6 @@
name: node_data.name || node_data.id name: node_data.name || node_data.id
}), "text/html").querySelector(".window"); }), "text/html").querySelector(".window");
render_element.append(domElement); render_element.append(domElement);
waitForNodeClick(gadget, domElement);
box = $(gadget.props.element).find("#" + dom_element_id); box = $(gadget.props.element).find("#" + dom_element_id);
absolute_position = convertToAbsolutePosition(gadget, coordinate.left, coordinate.top); absolute_position = convertToAbsolutePosition(gadget, coordinate.left, coordinate.top);
if (class_definition && class_definition.css) { if (class_definition && class_definition.css) {
...@@ -697,16 +722,16 @@ ...@@ -697,16 +722,16 @@
gadget.props.main.removeEventListener("drop", callback, false); gadget.props.main.removeEventListener("drop", callback, false);
} }
} }
/*jslint unparam: true*/ function resolver(ignore, reject) {
function resolver(resolve, reject) { callback = function (evt) {
callback = function(evt) {
try { try {
var class_name, offset = $(gadget.props.main).offset(), var class_name;
relative_position = convertToRelativePosition(gadget, evt.clientX - offset.left + "px", evt.clientY - offset.top + "px"); var offset = $(gadget.props.main).offset();
var relative_position = convertToRelativePosition(gadget, evt.clientX - offset.left + "px", evt.clientY - offset.top + "px");
try { try {
// html5 compliant browser // html5 compliant browser
class_name = JSON.parse(evt.dataTransfer.getData("application/json")); class_name = JSON.parse(evt.dataTransfer.getData("application/json"));
} catch (e) { } catch (error_from_drop) {
// internet explorer // internet explorer
class_name = JSON.parse(evt.dataTransfer.getData("text")); class_name = JSON.parse(evt.dataTransfer.getData("text"));
} }
...@@ -725,33 +750,30 @@ ...@@ -725,33 +750,30 @@
}; };
gadget.props.main.addEventListener("drop", callback, false); gadget.props.main.addEventListener("drop", callback, false);
} }
return new RSVP.all([ // loopEventListener adds an event listener that will prevent default for return RSVP.all([ // loopEventListener adds an event listener that will prevent default for
// dragover // dragover
loopEventListener(gadget.props.main, "dragover", false, function() { loopEventListener(gadget.props.main, "dragover", false, function () {
return undefined; return undefined;
}), RSVP.Promise(resolver, canceller) }), new RSVP.Promise(resolver, canceller)
]); ]);
} }
gadget_klass.ready(function (g) { gadget_klass.ready(function (g) {
g.props = {}; g.props = {};
}) }).ready(function (g) {
.ready(function (g) {
return g.getElement().push(function (element) { return g.getElement().push(function (element) {
g.props.element = element; g.props.element = element;
}); });
}) }).ready(function (g) {
.ready(function(g) {
g.props.node_id_to_dom_element_id = {}; g.props.node_id_to_dom_element_id = {};
g.props.zoom_level = 1; g.props.zoom_level = 1;
g.props.style_attr_list = ["width", "height", "padding-top", "line-height"]; g.props.style_attr_list = ["width", "height", "padding-top", "line-height"];
g.getElement().then(function(element) { g.getElement().then(function (element) {
g.props.element = element; g.props.element = element;
}); });
}) }).declareAcquiredMethod("notifyDataChanged", "notifyDataChanged")
.declareAcquiredMethod("notifyDataChanged", "notifyDataChanged") .declareMethod("render", function (data) {
.declareMethod("render", function(data) { var gadget = this;
var gadget = this, jsplumb_instance;
this.props.data = {}; this.props.data = {};
if (data.key) { if (data.key) {
...@@ -761,30 +783,30 @@ ...@@ -761,30 +783,30 @@
} }
this.props.main = this.props.element.querySelector(".graph_container"); this.props.main = this.props.element.querySelector(".graph_container");
/* /*
$(this.props.main).resizable({ $(this.props.main).resizable({
resize : function(event, ui) { resize : function (event, ui) {
jsplumb_instance.repaint(ui.helper); jsplumb_instance.repaint(ui.helper);
} }
}); });
*/ */
if (data) { if (data) {
this.props.data = JSON.parse(data); this.props.data = JSON.parse(data);
// XXX how to make queue ?? // XXX how to make queue ??
return layoutGraph(this.props.data.graph).then(function(graph_data) { return layoutGraph(this.props.data.graph).then(function (graph_data) {
gadget.props.data.graph = graph_data; gadget.props.data.graph = graph_data;
// load the data // load the data
$.each(gadget.props.data.graph.node, function(key, value) { $.each(gadget.props.data.graph.node, function (key, value) {
addNode(gadget, key, value); addNode(gadget, key, value);
}); });
$.each(gadget.props.data.graph.edge, function(key, value) { $.each(gadget.props.data.graph.edge, function (key, value) {
addEdge(gadget, key, value); addEdge(gadget, key, value);
}); });
}); });
} }
}) })
.declareMethod("getContent", function() { .declareMethod("getContent", function () {
var ret = {}; var ret = {};
if (this.props.erp5_key) { if (this.props.erp5_key) {
// ERP5 // ERP5
...@@ -793,16 +815,26 @@ ...@@ -793,16 +815,26 @@
} }
return JSON.stringify(this.props.data); return JSON.stringify(this.props.data);
}) })
.declareService(function() { .onEvent("dblclick", function (evt) {
var gadget = this, jsplumb_instance; var node = evt.target;
if (
(node.nodeType === Node.ELEMENT_NODE) &&
(node.tagName === "DIV") && node.classList.contains(["window"])
) {
return openNodeEditionDialog(this, node);
}
})
.declareService(function () {
var gadget = this;
var jsplumb_instance;
this.props.main = this.props.element.querySelector(".graph_container"); this.props.main = this.props.element.querySelector(".graph_container");
this.props.jsplumb_instance = jsplumb_instance = jsPlumb.getInstance(); this.props.jsplumb_instance = jsplumb_instance = jsPlumb.getInstance();
if (this.props.data) { if (this.props.data) {
// load the data // load the data
$.each(this.props.data.graph.node, function(key, value) { $.each(this.props.data.graph.node, function (key, value) {
addNode(gadget, key, value); addNode(gadget, key, value);
}); });
$.each(this.props.data.graph.edge, function(key, value) { $.each(this.props.data.graph.edge, function (key, value) {
addEdge(gadget, key, value); addEdge(gadget, key, value);
}); });
} }
...@@ -827,13 +859,12 @@ ...@@ -827,13 +859,12 @@
}); });
draggable(gadget); draggable(gadget);
this.props.nodes_click_monitor = RSVP.Monitor(); return RSVP.all([
return RSVP.all([waitForDrop(gadget), waitForDrop(gadget),
waitForConnection(gadget), waitForConnection(gadget),
waitForConnectionDetached(gadget), waitForConnectionDetached(gadget),
waitForConnectionClick(gadget), waitForConnectionClick(gadget)
gadget.props.nodes_click_monitor
]); ]);
}); });
})(RSVP, rJS, $, jsPlumb, Handlebars, loopEventListener, promiseEventListener, DOMParser, Springy); }(RSVP, rJS, $, jsPlumb, Handlebars, loopEventListener, promiseEventListener, DOMParser, Springy));
\ No newline at end of file \ No newline at end of file
...@@ -15,6 +15,8 @@ ...@@ -15,6 +15,8 @@
<body> <body>
<div id="qunit"></div> <div id="qunit"></div>
<div id="qunit-fixture"></div> <div id="qunit-fixture">
<div id="test-element"/>
</div>
</body> </body>
</html> </html>
/*global window, document, rJS, JSON, QUnit, jQuery, RSVP, console, setTimeout /*jslint vars:true nomen:true */ /* these two options are for compatibility with jslint 2014-04-21 . We'll remove them once we switch to more recent jslint */
/*global window, document, rJS, JSON, QUnit, jQuery, RSVP, console, setTimeout */
*/ (function (rJS, JSON, QUnit, RSVP, $) {
(function(rJS, JSON, QUnit, RSVP, $) {
"use strict"; "use strict";
var start = QUnit.start, var start = QUnit.start;
stop = QUnit.stop, var stop = QUnit.stop;
test = QUnit.test, var test = QUnit.test;
equal = QUnit.equal, var equal = QUnit.equal;
ok = QUnit.ok, var ok = QUnit.ok;
error_handler = function(e) { var error_handler = function (e) {
window.console.error(e); window.console.error(e);
ok(false, e); ok(false, e);
}, };
sample_class_definition = { var sample_class_definition = {
edge: { edge: {
description: "Base definition for edge", description: "Base definition for edge",
properties: { properties: {
_class: { "_class": {
type: "string" type: "string"
}, },
destination: { destination: {
...@@ -25,7 +24,7 @@ ...@@ -25,7 +24,7 @@
name: { name: {
type: "string" type: "string"
}, },
required: [ "name", "_class", "source", "destination" ], required: ["name", "_class", "source", "destination"],
source: { source: {
type: "string" type: "string"
} }
...@@ -33,35 +32,35 @@ ...@@ -33,35 +32,35 @@
type: "object" type: "object"
}, },
"Example.Edge": { "Example.Edge": {
_class: "edge", "_class": "edge",
allOf: [ { allOf: [{
$ref: "#/edge" "$ref": "#/edge"
}, { }, {
properties: { properties: {
color: { color: {
"enum": [ "red", "green", "blue" ] "enum": ["red", "green", "blue"]
} }
} }
} ], }],
description: "An example edge with a color property" description: "An example edge with a color property"
}, },
"Example.Node": { "Example.Node": {
_class: "node", "_class": "node",
allOf: [ { allOf: [{
$ref: "#/node" "$ref": "#/node"
}, { }, {
properties: { properties: {
shape: { shape: {
type: "string" type: "string"
} }
} }
} ], }],
description: "An example node with a shape property" description: "An example node with a shape property"
}, },
node: { node: {
description: "Base definition for node", description: "Base definition for node",
properties: { properties: {
_class: { "_class": {
type: "string" type: "string"
}, },
coordinate: { coordinate: {
...@@ -74,14 +73,15 @@ ...@@ -74,14 +73,15 @@
name: { name: {
type: "string" type: "string"
}, },
required: [ "name", "_class" ] required: ["name", "_class"]
}, },
type: "object" type: "object"
} }
}, sample_graph = { };
var sample_graph = {
edge: { edge: {
edge1: { edge1: {
_class: "Example.Edge", "_class": "Example.Edge",
source: "N1", source: "N1",
destination: "N2", destination: "N2",
color: "blue" color: "blue"
...@@ -89,7 +89,7 @@ ...@@ -89,7 +89,7 @@
}, },
node: { node: {
N1: { N1: {
_class: "Example.Node", "_class": "Example.Node",
name: "Node 1", name: "Node 1",
coordinate: { coordinate: {
top: 0, top: 0,
...@@ -98,7 +98,7 @@ ...@@ -98,7 +98,7 @@
shape: "square" shape: "square"
}, },
N2: { N2: {
_class: "Example.Node", "_class": "Example.Node",
name: "Node 2", name: "Node 2",
shape: "circle", shape: "circle",
coordinate: { coordinate: {
...@@ -107,127 +107,161 @@ ...@@ -107,127 +107,161 @@
} }
} }
} }
}, sample_graph_not_connected = { };
var sample_graph_no_node_coodinate = {
edge: {
edge1: {
"_class": "Example.Edge",
source: "N1",
destination: "N2",
color: "blue"
}
},
node: {
N1: {
"_class": "Example.Node",
name: "Node 1",
shape: "square"
},
N2: {
"_class": "Example.Node",
name: "Node 2",
shape: "circle"
}
}
};
var sample_graph_not_connected = {
edge: {}, edge: {},
node: { node: {
N1: { N1: {
_class: "Example.Node", "_class": "Example.Node",
name: "Node 1", name: "Node 1",
shape: "square" shape: "square"
}, },
N2: { N2: {
_class: "Example.Node", "_class": "Example.Node",
name: "Node 2", name: "Node 2",
shape: "circle" shape: "circle"
} }
} }
}, sample_data_graph = JSON.stringify({ };
var sample_data_graph = JSON.stringify({
class_definition: sample_class_definition, class_definition: sample_class_definition,
graph: sample_graph graph: sample_graph
}), sample_data_graph_not_connected = JSON.stringify({ });
var sample_data_graph_no_node_coordinate = JSON.stringify({
class_definition: sample_class_definition,
graph: sample_graph_no_node_coodinate
});
var sample_data_graph_not_connected = JSON.stringify({
class_definition: sample_class_definition, class_definition: sample_class_definition,
graph: sample_graph_not_connected graph: sample_graph_not_connected
}), sample_data_empty_graph = JSON.stringify({ });
var sample_data_empty_graph = JSON.stringify({
class_definition: sample_class_definition, class_definition: sample_class_definition,
graph: { graph: {
node: {}, node: {},
edge: {} edge: {}
} }
}); });
QUnit.config.testTimeout = 60000; QUnit.config.testTimeout = 60000;
rJS(window).ready(function(g) { rJS(window).ready(function (g) {
test("Sample graph can be loaded and output is equal to input", function() { test("Sample graph can be loaded and output is equal to input", function () {
var jsplumb_gadget; var jsplumb_gadget;
stop(); stop();
g.declareGadget("./index.html", { g.declareGadget("./index.html", {
element: document.querySelector("#qunit-fixture") element: document.querySelector("#test-element")
}).then(function(new_gadget) { }).then(function (new_gadget) {
jsplumb_gadget = new_gadget; jsplumb_gadget = new_gadget;
return jsplumb_gadget.render(sample_data_graph); return jsplumb_gadget.render(sample_data_graph);
}).then(function() { }).then(function () {
return jsplumb_gadget.getContent(); return jsplumb_gadget.getContent();
}).then(function(content) { }).then(function (content) {
equal(content, sample_data_graph); equal(content, sample_data_graph);
}).fail(error_handler).always(start); }).fail(error_handler).always(start);
}); });
test("New node can be drag & dropped", function() { test("New node can be drag & dropped", function () {
var jsplumb_gadget; var jsplumb_gadget;
stop(); stop();
function runTest() { function runTest() {
// XXX here I used getContent to have a promise, but there must be a // XXX here I used getContent to have a promise, but there must be a
// more elegant way. // more elegant way.
return jsplumb_gadget.getContent().then(function() { return jsplumb_gadget.getContent().then(function () {
// fake a drop event // fake a drop event
var e = new window.Event("drop"); var e = new window.Event("drop");
e.dataTransfer = { e.dataTransfer = {
getData: function(type) { getData: function (type) {
// make sure we are called properly // make sure we are called properly
equal("application/json", type, "The drag&dropped element must have data type application/json"); equal("application/json", type, "The drag&dropped element must have data type application/json");
return JSON.stringify("Example.Node"); return JSON.stringify("Example.Node");
} }
}; };
jsplumb_gadget.props.main.dispatchEvent(e); jsplumb_gadget.props.main.dispatchEvent(e);
}).then(function() { }).then(function () {
return jsplumb_gadget.getContent(); return jsplumb_gadget.getContent();
}).then(function(content) { }).then(function (content) {
var node, graph = JSON.parse(content).graph; var node;
var graph = JSON.parse(content).graph;
equal(1, Object.keys(graph.node).length, "There is one new node class"); equal(1, Object.keys(graph.node).length, "There is one new node class");
node = graph.node[Object.keys(graph.node)[0]]; node = graph.node[Object.keys(graph.node)[0]];
equal("Example.Node", node._class, "Node class is set to Example.?ode"); equal("Example.Node", node._class, "Node class is set to Example.Node");
}); });
} }
g.declareGadget("./index.html", { g.declareGadget("./index.html", {
element: document.querySelector("#qunit-fixture") element: document.querySelector("#test-element")
}).then(function(new_gadget) { }).then(function (new_gadget) {
jsplumb_gadget = new_gadget; jsplumb_gadget = new_gadget;
jsplumb_gadget.render(sample_data_empty_graph); jsplumb_gadget.render(sample_data_empty_graph);
}).then(runTest).fail(error_handler).always(start); }).then(runTest).fail(error_handler).always(start);
}); });
test("Node can be dragged", function() { test("Node can be dragged", function () {
var jsplumb_gadget; var jsplumb_gadget;
stop(); stop();
function runTest() { function runTest() {
return jsplumb_gadget.getContent().then(function() { return jsplumb_gadget.getContent().then(function () {
// 100 and 60 are about 10% of the .graph_container div ( set by css, so this // 100 and 60 are about 10% of the .graph_container div ( set by css, so this
// might change ) // might change )
$("div[title='Node 1']").simulate("drag", { $("div[title='Node 1']").simulate("drag", {
dx: 100, dx: 100,
dy: 60 dy: 60
}); });
}).then(function() { }).then(function () {
return jsplumb_gadget.getContent(); return jsplumb_gadget.getContent();
}).then(function(content) { }).then(function (content) {
var graph = JSON.parse(content).graph, node_coordinate = graph.node.N1.coordinate; var graph = JSON.parse(content).graph;
var node_coordinate = graph.node.N1.coordinate;
// Since original coordinates where 0,0 we are now about 0.1,0.1 // Since original coordinates where 0,0 we are now about 0.1,0.1
// as we moved 10% // as we moved 10%
ok(node_coordinate.top - .1 < .1, "Top is ok"); ok(node_coordinate.top - 0.1 < 0.1, "Top is ok");
ok(node_coordinate.left - .1 < .1, "Left is ok"); ok(node_coordinate.left - 0.1 < 0.1, "Left is ok");
}); });
} }
g.declareGadget("./index.html", { g.declareGadget("./index.html", {
element: document.querySelector("#qunit-fixture") element: document.querySelector("#test-element")
}).then(function(new_gadget) { }).then(function (new_gadget) {
jsplumb_gadget = new_gadget; jsplumb_gadget = new_gadget;
jsplumb_gadget.render(sample_data_graph); jsplumb_gadget.render(sample_data_graph);
}).then(runTest).fail(error_handler).always(start); }).then(runTest).fail(error_handler).always(start);
}); });
test("Node properties can be edited", function() { test("Node properties can be edited", function () {
var jsplumb_gadget; var jsplumb_gadget;
stop(); stop();
function runTest() { function runTest() {
return jsplumb_gadget.getContent().then(function() { return jsplumb_gadget.getContent().then(function () {
// click on a node to see display the popup // click on a node to see display the popup
$("div[title='Node 1']").simulate("dblclick"); $("div[title='Node 1']").simulate("dblclick");
// Promises that handle the dialog actions are not available // Promises that handle the dialog actions are not available
// immediately after clicking. // immediately after clicking.
var promise = RSVP.Promise(function(resolve) { var promise = new RSVP.Promise(function (resolve) {
var fillDialog = function() { function fillDialog() {
if (!jsplumb_gadget.props.dialog_promise) { if (!jsplumb_gadget.props.dialog_promise) {
// Dialog not ready. Let's retry later. // Dialog not ready. Let's retry later.
// XXX this condition is actually incorrect. We need to wait // XXX this condition is actually incorrect. We need to wait
// for the event listener to have been registered for the // for the event listener to have been registered for the
// dialog buttons. This setTimeout is good enough for now. // dialog buttons. This setTimeout is good enough for now.
return setTimeout(fillDialog, 1e3); setTimeout(fillDialog, 1e3);
return;
} }
// check displayed values // check displayed values
equal($("input[name='id']").val(), "N1"); equal($("input[name='id']").val(), "N1");
...@@ -241,12 +275,13 @@ ...@@ -241,12 +275,13 @@
// resolve our test promise once the dialog handling promise is // resolve our test promise once the dialog handling promise is
// finished. // finished.
jsplumb_gadget.props.dialog_promise.then(resolve); jsplumb_gadget.props.dialog_promise.then(resolve);
}; }
fillDialog(); fillDialog();
}); });
return promise.then(function() { return promise.then(function () {
return jsplumb_gadget.getContent().then(function(content) { return jsplumb_gadget.getContent().then(function (content) {
var graph = JSON.parse(content).graph, node = graph.node.N1; var graph = JSON.parse(content).graph;
var node = graph.node.N1;
equal("Modified Name", node.name, "Data is modified"); equal("Modified Name", node.name, "Data is modified");
equal("Modified Name", $("div#" + jsplumb_gadget.props.node_id_to_dom_element_id.N1).text(), "DOM is modified"); equal("Modified Name", $("div#" + jsplumb_gadget.props.node_id_to_dom_element_id.N1).text(), "DOM is modified");
equal(1, $("div[title='Modified Name']").length, "DOM title attribute is modified"); equal(1, $("div[title='Modified Name']").length, "DOM title attribute is modified");
...@@ -255,27 +290,29 @@ ...@@ -255,27 +290,29 @@
}); });
} }
g.declareGadget("./index.html", { g.declareGadget("./index.html", {
element: document.querySelector("#qunit-fixture") element: document.querySelector("#test-element")
}).then(function(new_gadget) { }).then(function (new_gadget) {
jsplumb_gadget = new_gadget; jsplumb_gadget = new_gadget;
jsplumb_gadget.render(sample_data_graph); jsplumb_gadget.render(sample_data_graph);
}).then(runTest).fail(error_handler).always(start); }).then(runTest).fail(error_handler).always(start);
}); });
test("Node can be connected", function() { test("Node can be connected", function () {
var jsplumb_gadget; var jsplumb_gadget;
stop(); stop();
function runTest() { function runTest() {
return jsplumb_gadget.getContent().then(function(content) { return jsplumb_gadget.getContent().then(function (content) {
var node1 = jsplumb_gadget.props.main.querySelector("div[title='Node 1']"), node2 = jsplumb_gadget.props.main.querySelector("div[title='Node 2']"); var node1 = jsplumb_gadget.props.main.querySelector("div[title='Node 1']");
var node2 = jsplumb_gadget.props.main.querySelector("div[title='Node 2']");
equal(0, Object.keys(JSON.parse(content).graph.edge).length, "There are no edge at the beginning"); equal(0, Object.keys(JSON.parse(content).graph.edge).length, "There are no edge at the beginning");
jsplumb_gadget.props.jsplumb_instance.connect({ jsplumb_gadget.props.jsplumb_instance.connect({
source: node1.id, source: node1.id,
target: node2.id target: node2.id
}); });
}).then(function() { }).then(function () {
return jsplumb_gadget.getContent(); return jsplumb_gadget.getContent();
}).then(function(content) { }).then(function (content) {
var edge, graph = JSON.parse(content).graph; var edge;
var graph = JSON.parse(content).graph;
equal(2, Object.keys(graph.node).length, "We still have 2 nodes"); equal(2, Object.keys(graph.node).length, "We still have 2 nodes");
equal(1, Object.keys(graph.edge).length, "We have 1 edge"); equal(1, Object.keys(graph.edge).length, "We have 1 edge");
edge = graph.edge[Object.keys(graph.edge)[0]]; edge = graph.edge[Object.keys(graph.edge)[0]];
...@@ -286,42 +323,43 @@ ...@@ -286,42 +323,43 @@
}); });
} }
g.declareGadget("./index.html", { g.declareGadget("./index.html", {
element: document.querySelector("#qunit-fixture") element: document.querySelector("#test-element")
}).then(function(new_gadget) { }).then(function (new_gadget) {
jsplumb_gadget = new_gadget; jsplumb_gadget = new_gadget;
jsplumb_gadget.render(sample_data_graph_not_connected); jsplumb_gadget.render(sample_data_graph_not_connected);
}).then(runTest).fail(error_handler).always(start); }).then(runTest).fail(error_handler).always(start);
}); });
test("Node can be deleted", function() { test("Node can be deleted", function () {
var jsplumb_gadget; var jsplumb_gadget;
stop(); stop();
function runTest() { function runTest() {
return jsplumb_gadget.getContent().then(function() { return jsplumb_gadget.getContent().then(function () {
equal(1, $("div[title='Node 1']").length, "node 1 is visible"); equal(1, $("div[title='Node 1']").length, "node 1 is visible");
equal(1, $("._jsPlumb_connector").length, "there is 1 connection"); equal(1, $("._jsPlumb_connector").length, "there is 1 connection");
// click on node 1 to see display the popup // click on node 1 to see display the popup
$("div[title='Node 1']").simulate("dblclick"); $("div[title='Node 1']").simulate("dblclick");
// Promises that handle the dialog actions are not available // Promises that handle the dialog actions are not available
// immediately after clicking. // immediately after clicking.
var promise = RSVP.Promise(function(resolve) { var promise = new RSVP.Promise(function (resolve) {
var waitForDialogAndDelete = function() { function waitForDialogAndDelete() {
if (!jsplumb_gadget.props.dialog_promise) { if (!jsplumb_gadget.props.dialog_promise) {
// Dialog not ready. Let's retry later. // Dialog not ready. Let's retry later.
// XXX this condition is actually incorrect. We need to wait // XXX this condition is actually incorrect. We need to wait
// for the event listener to have been registered for the // for the event listener to have been registered for the
// dialog buttons. This setTimeout is good enough for now. // dialog buttons. This setTimeout is good enough for now.
return setTimeout(waitForDialogAndDelete, 1e3); setTimeout(waitForDialogAndDelete, 1e3);
return;
} }
equal(1, $("input[value='Delete']").length, "There should be one delete button"); equal(1, $("input[value='Delete']").length, "There should be one delete button");
$("input[value='Delete']").click(); $("input[value='Delete']").click();
// resolve our test promise once the dialog handling promise is // resolve our test promise once the dialog handling promise is
// finished. // finished.
jsplumb_gadget.props.dialog_promise.then(resolve); jsplumb_gadget.props.dialog_promise.then(resolve);
}; }
waitForDialogAndDelete(); waitForDialogAndDelete();
}); });
return promise.then(function() { return promise.then(function () {
return jsplumb_gadget.getContent().then(function(content) { return jsplumb_gadget.getContent().then(function (content) {
var graph = JSON.parse(content).graph; var graph = JSON.parse(content).graph;
equal(1, Object.keys(graph.node).length, "node is removed from data"); equal(1, Object.keys(graph.node).length, "node is removed from data");
equal(0, Object.keys(graph.edge).length, "edge referencing this node is also removed"); equal(0, Object.keys(graph.edge).length, "edge referencing this node is also removed");
...@@ -332,29 +370,30 @@ ...@@ -332,29 +370,30 @@
}); });
} }
g.declareGadget("./index.html", { g.declareGadget("./index.html", {
element: document.querySelector("#qunit-fixture") element: document.querySelector("#test-element")
}).then(function(new_gadget) { }).then(function (new_gadget) {
jsplumb_gadget = new_gadget; jsplumb_gadget = new_gadget;
jsplumb_gadget.render(sample_data_graph); jsplumb_gadget.render(sample_data_graph);
}).then(runTest).fail(error_handler).always(start); }).then(runTest).fail(error_handler).always(start);
}); });
test("Node id can be changed (connections are updated and node can be edited afterwards)", function() { test("Node id can be changed (connections are updated and node can be edited afterwards)", function () {
var jsplumb_gadget; var jsplumb_gadget;
stop(); stop();
function runTest() { function runTest() {
return jsplumb_gadget.getContent().then(function() { return jsplumb_gadget.getContent().then(function () {
// click on a node to see display the popup // click on a node to see display the popup
$("div[title='Node 1']").simulate("dblclick"); $("div[title='Node 1']").simulate("dblclick");
// Promises that handle the dialog actions are not available // Promises that handle the dialog actions are not available
// immediately after clicking. // immediately after clicking.
var promise = RSVP.Promise(function(resolve) { var promise = new RSVP.Promise(function (resolve) {
var fillDialog = function() { function fillDialog() {
if (!jsplumb_gadget.props.dialog_promise) { if (!jsplumb_gadget.props.dialog_promise) {
// Dialog not ready. Let's retry later. // Dialog not ready. Let's retry later.
// XXX this condition is actually incorrect. We need to wait // XXX this condition is actually incorrect. We need to wait
// for the event listener to have been registered for the // for the event listener to have been registered for the
// dialog buttons. This setTimeout is good enough for now. // dialog buttons. This setTimeout is good enough for now.
return setTimeout(fillDialog, 1e3); setTimeout(fillDialog, 1e3);
return;
} }
equal($("input[name='id']").val(), "N1"); equal($("input[name='id']").val(), "N1");
// change the id // change the id
...@@ -364,11 +403,11 @@ ...@@ -364,11 +403,11 @@
// resolve our test promise once the dialog handling promise is // resolve our test promise once the dialog handling promise is
// finished. // finished.
jsplumb_gadget.props.dialog_promise.then(resolve); jsplumb_gadget.props.dialog_promise.then(resolve);
}; }
fillDialog(); fillDialog();
}); });
return promise.then(function() { return promise.then(function () {
return jsplumb_gadget.getContent().then(function(content) { return jsplumb_gadget.getContent().then(function (content) {
var graph = JSON.parse(content).graph; var graph = JSON.parse(content).graph;
equal(2, Object.keys(graph.node).length, "We still have two nodes"); equal(2, Object.keys(graph.node).length, "We still have two nodes");
ok(graph.node.N1b !== undefined, "Node Id changed"); ok(graph.node.N1b !== undefined, "Node Id changed");
...@@ -379,52 +418,55 @@ ...@@ -379,52 +418,55 @@
}); });
} }
g.declareGadget("./index.html", { g.declareGadget("./index.html", {
element: document.querySelector("#qunit-fixture") element: document.querySelector("#test-element")
}).then(function(new_gadget) { }).then(function (new_gadget) {
jsplumb_gadget = new_gadget; jsplumb_gadget = new_gadget;
jsplumb_gadget.render(sample_data_graph); jsplumb_gadget.render(sample_data_graph);
}).then(runTest).fail(error_handler).always(start); }).then(runTest).fail(error_handler).always(start);
}); });
test("New node can be edited", function() { test("New node can be edited", function () {
var jsplumb_gadget, node_id; var jsplumb_gadget;
var node_id;
stop(); stop();
function runTest() { function runTest() {
// XXX here I used getContent to have a promise, but there must be a // XXX here I used getContent to have a promise, but there must be a
// more elegant way. // more elegant way.
return jsplumb_gadget.getContent().then(function() { return jsplumb_gadget.getContent().then(function () {
// fake a drop event // fake a drop event
var e = new window.Event("drop"); var e = new window.Event("drop");
e.dataTransfer = { e.dataTransfer = {
getData: function(type) { getData: function (type) {
// make sure we are called properly // make sure we are called properly
equal("application/json", type, "The drag&dropped element must have data type application/json"); equal("application/json", type, "The drag&dropped element must have data type application/json");
return JSON.stringify("Example.Node"); return JSON.stringify("Example.Node");
} }
}; };
jsplumb_gadget.props.main.dispatchEvent(e); jsplumb_gadget.props.main.dispatchEvent(e);
}).then(function() { }).then(function () {
return jsplumb_gadget.getContent(); return jsplumb_gadget.getContent();
}).then(function(content) { }).then(function (content) {
var node, graph = JSON.parse(content).graph; var node;
var graph = JSON.parse(content).graph;
equal(1, Object.keys(graph.node).length); equal(1, Object.keys(graph.node).length);
node_id = Object.keys(graph.node)[0]; node_id = Object.keys(graph.node)[0];
node = graph.node[node_id]; node = graph.node[node_id];
equal("Example.Node", node._class); equal("Example.Node", node._class);
}).then(function() { }).then(function () {
// click the new node to see display the popup // click the new node to see display the popup
// XXX at the moment nodes have class window // XXX at the moment nodes have class window
equal(1, $("div.window").length, "We have a new node"); equal(1, $("div.window").length, "We have a new node");
$("div.window").simulate("dblclick"); $("div.window").simulate("dblclick");
// Promises that handle the dialog actions are not available // Promises that handle the dialog actions are not available
// immediately after clicking. // immediately after clicking.
var promise = RSVP.Promise(function(resolve) { var promise = new RSVP.Promise(function (resolve) {
var fillDialog = function() { function fillDialog() {
if (!jsplumb_gadget.props.dialog_promise) { if (!jsplumb_gadget.props.dialog_promise) {
// Dialog not ready. Let's retry later. // Dialog not ready. Let's retry later.
// XXX this condition is actually incorrect. We need to wait // XXX this condition is actually incorrect. We need to wait
// for the event listener to have been registered for the // for the event listener to have been registered for the
// dialog buttons. This setTimeout is good enough for now. // dialog buttons. This setTimeout is good enough for now.
return setTimeout(fillDialog, 1e3); setTimeout(fillDialog, 1e3);
return;
} }
// check displayed values // check displayed values
equal($("input[name='id']").val(), node_id); equal($("input[name='id']").val(), node_id);
...@@ -438,12 +480,13 @@ ...@@ -438,12 +480,13 @@
// resolve our test promise once the dialog handling promise is // resolve our test promise once the dialog handling promise is
// finished. // finished.
jsplumb_gadget.props.dialog_promise.then(resolve); jsplumb_gadget.props.dialog_promise.then(resolve);
}; }
fillDialog(); fillDialog();
}); });
return promise.then(function() { return promise.then(function () {
return jsplumb_gadget.getContent().then(function(content) { return jsplumb_gadget.getContent().then(function (content) {
var graph = JSON.parse(content).graph, node = graph.node[node_id]; var graph = JSON.parse(content).graph;
var node = graph.node[node_id];
equal("Modified Name", node.name, "Data is modified"); equal("Modified Name", node.name, "Data is modified");
equal("Modified Name", $("div.window").text(), "DOM is modified"); equal("Modified Name", $("div.window").text(), "DOM is modified");
}); });
...@@ -451,63 +494,66 @@ ...@@ -451,63 +494,66 @@
}); });
} }
g.declareGadget("./index.html", { g.declareGadget("./index.html", {
element: document.querySelector("#qunit-fixture") element: document.querySelector("#test-element")
}).then(function(new_gadget) { }).then(function (new_gadget) {
jsplumb_gadget = new_gadget; jsplumb_gadget = new_gadget;
jsplumb_gadget.render(sample_data_empty_graph); jsplumb_gadget.render(sample_data_empty_graph);
}).then(runTest).fail(error_handler).always(start); }).then(runTest).fail(error_handler).always(start);
}); });
test("New node can be deleted", function() { test("New node can be deleted", function () {
var jsplumb_gadget, node_id; var jsplumb_gadget;
var node_id;
stop(); stop();
function runTest() { function runTest() {
// XXX here I used getContent to have a promise, but there must be a // XXX here I used getContent to have a promise, but there must be a
// more elegant way. // more elegant way.
return jsplumb_gadget.getContent().then(function() { return jsplumb_gadget.getContent().then(function () {
// fake a drop event // fake a drop event
var e = new window.Event("drop"); var e = new window.Event("drop");
e.dataTransfer = { e.dataTransfer = {
getData: function(type) { getData: function (type) {
// make sure we are called properly // make sure we are called properly
equal("application/json", type, "The drag&dropped element must have data type application/json"); equal("application/json", type, "The drag&dropped element must have data type application/json");
return JSON.stringify("Example.Node"); return JSON.stringify("Example.Node");
} }
}; };
jsplumb_gadget.props.main.dispatchEvent(e); jsplumb_gadget.props.main.dispatchEvent(e);
}).then(function() { }).then(function () {
return jsplumb_gadget.getContent(); return jsplumb_gadget.getContent();
}).then(function(content) { }).then(function (content) {
var node, graph = JSON.parse(content).graph; var node;
var graph = JSON.parse(content).graph;
equal(1, Object.keys(graph.node).length); equal(1, Object.keys(graph.node).length);
node_id = Object.keys(graph.node)[0]; node_id = Object.keys(graph.node)[0];
node = graph.node[node_id]; node = graph.node[node_id];
equal("Example.Node", node._class); equal("Example.Node", node._class);
}).then(function() { }).then(function () {
// click the new node to see display the popup // click the new node to see display the popup
// XXX at the moment nodes have class window // XXX at the moment nodes have class window
equal(1, $("div.window").length, "We have a new node"); equal(1, $("div.window").length, "We have a new node");
$("div.window").simulate("dblclick"); $("div.window").simulate("dblclick");
// Promises that handle the dialog actions are not available // Promises that handle the dialog actions are not available
// immediately after clicking. // immediately after clicking.
var promise = RSVP.Promise(function(resolve) { var promise = new RSVP.Promise(function (resolve) {
var waitForDialogAndDelete = function() { function waitForDialogAndDelete() {
if (!jsplumb_gadget.props.dialog_promise) { if (!jsplumb_gadget.props.dialog_promise) {
// Dialog not ready. Let's retry later. // Dialog not ready. Let's retry later.
// XXX this condition is actually incorrect. We need to wait // XXX this condition is actually incorrect. We need to wait
// for the event listener to have been registered for the // for the event listener to have been registered for the
// dialog buttons. This setTimeout is good enough for now. // dialog buttons. This setTimeout is good enough for now.
return setTimeout(waitForDialogAndDelete, 1e3); setTimeout(waitForDialogAndDelete, 1e3);
return;
} }
equal(1, $("input[value='Delete']").length, "There should be one delete button"); equal(1, $("input[value='Delete']").length, "There should be one delete button");
$("input[value='Delete']").click(); $("input[value='Delete']").click();
// resolve our test promise once the dialog handling promise is // resolve our test promise once the dialog handling promise is
// finished. // finished.
jsplumb_gadget.props.dialog_promise.then(resolve); jsplumb_gadget.props.dialog_promise.then(resolve);
}; }
waitForDialogAndDelete(); waitForDialogAndDelete();
}); });
return promise.then(function() { return promise.then(function () {
return jsplumb_gadget.getContent().then(function(content) { return jsplumb_gadget.getContent().then(function (content) {
var graph = JSON.parse(content).graph; var graph = JSON.parse(content).graph;
equal(0, Object.keys(graph.node).length, "node is removed from data"); equal(0, Object.keys(graph.node).length, "node is removed from data");
equal(0, $("div.window").length, "DOM is modified"); equal(0, $("div.window").length, "DOM is modified");
...@@ -516,11 +562,31 @@ ...@@ -516,11 +562,31 @@
}); });
} }
g.declareGadget("./index.html", { g.declareGadget("./index.html", {
element: document.querySelector("#qunit-fixture") element: document.querySelector("#test-element")
}).then(function(new_gadget) { }).then(function (new_gadget) {
jsplumb_gadget = new_gadget; jsplumb_gadget = new_gadget;
jsplumb_gadget.render(sample_data_empty_graph); jsplumb_gadget.render(sample_data_empty_graph);
}).then(runTest).fail(error_handler).always(start); }).then(runTest).fail(error_handler).always(start);
}); });
test("Graph is automatically layout", function () {
var jsplumb_gadget;
stop();
g.declareGadget("./index.html", {
element: document.querySelector("#test-element")
}).then(function (new_gadget) {
jsplumb_gadget = new_gadget;
return jsplumb_gadget.render(sample_data_graph_no_node_coordinate);
}).then(function () {
return jsplumb_gadget.getContent();
}).then(function (content) {
/*jslint unparam: true */
$.each(JSON.parse(content).graph.node, function (ignore, node) {
ok(node.coordinate.top !== undefined, "Node have top coordinate");
ok((0 <= node.coordinate.top) && (node.coordinate.top <= 1), "Node top coordinate is between [0..1]");
ok(node.coordinate.left !== undefined, "Node have left coordinate");
ok((0 <= node.coordinate.left) && (node.coordinate.left <= 1), "Node left coordinate is between [0..1]");
});
}).fail(error_handler).always(start);
});
}); });
})(rJS, JSON, QUnit, RSVP, jQuery); }(rJS, JSON, QUnit, RSVP, jQuery));
\ No newline at end of file \ No newline at end of file
...@@ -14,16 +14,16 @@ ...@@ -14,16 +14,16 @@
}).declareMethod("render", function(options) { }).declareMethod("render", function(options) {
var select = this.element.getElementsByTagName("select")[0], i, template, tmp = ""; var select = this.element.getElementsByTagName("select")[0], i, template, tmp = "";
select.setAttribute("name", options.key); select.setAttribute("name", options.key);
for (i = 0; i < options.property_definition.enum.length; i += 1) { for (i = 0; i < options.property_definition['enum'].length; i += 1) {
if (options.property_definition.enum[i] === options.value) { if (options.property_definition['enum'][i] === options.value) {
template = selected_option_template; template = selected_option_template;
} else { } else {
template = option_template; template = option_template;
} }
// XXX value and text are always same in json schema // XXX value and text are always same in json schema
tmp += template({ tmp += template({
value: options.property_definition.enum[i], value: options.property_definition['enum'][i],
text: options.property_definition.enum[i] text: options.property_definition['enum'][i]
}); });
} }
select.innerHTML += tmp; select.innerHTML += tmp;
......
erp5_trade
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Zuite" module="Products.Zelenium.zuite"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_objects</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>graph_editor_zuite</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ZopePageTemplate" module="Products.PageTemplates.ZopePageTemplate"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>text/html</string> </value>
</item>
<item>
<key> <string>expand</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>testQunit</string> </value>
</item>
<item>
<key> <string>output_encoding</string> </key>
<value> <string>utf-8</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <unicode>Graph Editor Qunit Test</unicode> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<html>
<head><title>Graph Editor Qunit Test</title></head>
<body>
<table cellpadding="1" cellspacing="1" border="1">
<thead>
<tr><td rowspan="1" colspan="4">
Run existing qunit test in zelenium framework, to easily integrate it in current test suite.
</td></tr>
</thead><tbody>
<tr>
<td>open</td>
<td tal:content="string:${context/portal_url}/dream_graph_editor/jsplumb/test.html"></td>
<td></td>
</tr>
<tr>
<td>waitForTextPresent</td>
<td>Tests completed in </td>
<td>30000</td>
</tr>
<tr>
<td>assertText</td>
<td>css=#qunit-testresult span.failed</td>
<td>0</td>
</tr>
</tbody></table>
</body>
</html>
\ No newline at end of file
##############################################################################
#
# Copyright (c) 2017 Nexedi SA and Contributors. All Rights Reserved.
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
import unittest
from Products.ERP5Type.tests.ERP5TypeFunctionalTestCase import ERP5TypeFunctionalTestCase
class TestGraphEditor(ERP5TypeFunctionalTestCase):
run_only = "graph_editor_zuite"
def getBusinessTemplateList(self):
return (
'erp5_graph_editor',
'erp5_graph_editor_ui_test',
'erp5_ui_test_core',
)
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestGraphEditor))
return suite
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Test Component" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_recorded_property_dict</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>testFunctionalGraphEditor</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>test.erp5.testFunctionalGraphEditor</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Test Component</string> </value>
</item>
<item>
<key> <string>sid</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>text_content_error_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>text_content_warning_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>version</string> </key>
<value> <string>erp5</string> </value>
</item>
<item>
<key> <string>workflow_history</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary/>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary>
<item>
<key> <string>component_validation_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="4" aka="AAAAAAAAAAQ=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.patches.WorkflowTool"/>
</pickle>
<pickle>
<tuple>
<none/>
<list>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>validate</string> </value>
</item>
<item>
<key> <string>validation_state</string> </key>
<value> <string>validated</string> </value>
</item>
</dictionary>
</list>
</tuple>
</pickle>
</record>
</ZopeData>
erp5_graph_editor
erp5_ui_test_core
\ No newline at end of file
portal_tests/graph_editor_zuite
portal_tests/graph_editor_zuite/**
\ No newline at end of file
test.erp5.testFunctionalGraphEditor
\ No newline at end of file
erp5_full_text_mroonga_catalog
\ No newline at end of file
erp5_graph_editor_ui_test
\ No newline at end of file
...@@ -53,7 +53,15 @@ class TestXHTMLMixin(ERP5TypeTestCase): ...@@ -53,7 +53,15 @@ class TestXHTMLMixin(ERP5TypeTestCase):
'renderjs.js','jio.js','rsvp.js','handlebars.js', 'renderjs.js','jio.js','rsvp.js','handlebars.js',
'pdf_js/build/pdf.js', 'pdf_js/build/pdf.worker.js', 'pdf_js/build/pdf.js', 'pdf_js/build/pdf.worker.js',
'pdf_js/compatibility.js', 'pdf_js/debugger.js', 'pdf_js/compatibility.js', 'pdf_js/debugger.js',
'pdf_js/viewer.js', 'pdf_js/l10n.js') 'pdf_js/viewer.js', 'pdf_js/l10n.js',
'dream_graph_editor/lib/handlebars.min.js',
'dream_graph_editor/lib/jquery-ui.js',
'dream_graph_editor/lib/jquery.js',
'dream_graph_editor/lib/jquery.jsplumb.js',
'dream_graph_editor/lib/jquery.simulate.js',
'dream_graph_editor/lib/qunit.js',
'dream_graph_editor/lib/springy.js',
)
JSL_IGNORE_SKIN_LIST = ('erp5_ace_editor', 'erp5_code_mirror', JSL_IGNORE_SKIN_LIST = ('erp5_ace_editor', 'erp5_code_mirror',
'erp5_fckeditor', 'erp5_jquery', 'erp5_jquery_ui', 'erp5_fckeditor', 'erp5_jquery', 'erp5_jquery_ui',
'erp5_svg_editor', 'erp5_xinha_editor') 'erp5_svg_editor', 'erp5_xinha_editor')
...@@ -429,6 +437,7 @@ class TestXHTML(TestXHTMLMixin): ...@@ -429,6 +437,7 @@ class TestXHTML(TestXHTMLMixin):
'erp5_xinha_editor', 'erp5_xinha_editor',
'erp5_svg_editor', 'erp5_svg_editor',
'erp5_jquery_sheet_editor', 'erp5_jquery_sheet_editor',
'erp5_graph_editor',
'erp5_web_ung_core', 'erp5_web_ung_core',
'erp5_web_ung_theme', 'erp5_web_ung_theme',
'erp5_web_ung_role', 'erp5_web_ung_role',
......
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