瀏覽代碼

Merge pull request #43 from AToMPM/dsl-test

Create tests to build a DSL
BentleyJOakes 7 年之前
父節點
當前提交
4077f1d8ed

+ 3 - 6
.travis.yml

@@ -5,19 +5,16 @@ node_js:
 cache:
   directories:
     - "node_modules"
+    - "$HOME/.cache/pip"
 
 sudo: required
 addons:
   chrome: stable
-
+ 
 before_script:
- - "export DISPLAY=:99.0"
- - "sh -e /etc/init.d/xvfb start"
- - sleep 3 # give xvfb some time to start
-
  - google-chrome-stable --headless --disable-gpu --remote-debugging-port=9222 http://localhost &
  - pip install --user python-igraph
     
 script:
- - ./run_tests.sh
+ - xvfb-run --server-args="-screen 0 2880x1800x24" ./run_tests.sh
     

+ 46 - 20
client/compile_utils.js

@@ -269,6 +269,7 @@ CompileUtils = function(){
 				icon.setAttr('__r',0);			
 				icon.setAttr('__sx',1);
 				icon.setAttr('__sy',1);
+				icon.setAttr('id', id);
 			}
 	
 			if( 'attrs' in options )
@@ -342,26 +343,51 @@ CompileUtils = function(){
 	/**
 	 * Compile the current model to an Abstract Syntax Metamodel
 	 */
-	this.compileToASMM = function(fname){
-		if( ! __isAbstractSyntaxMetamodel(fname) )
-			WindowManagement.openDialog(
-				_ERROR,
-				'invalid extension... abstract syntax metamodels are "*.metamodel" files');
-		else
-			HttpUtils.httpReq('PUT', HttpUtils.url(fname,__FORCE_GET));
-	};
-	
-	/**
-	 * Compile the current model to a Concrete Syntax Metamodel
-	 */
-	this.compileToCSMM = function(fname){
-		if( ! __isIconMetamodel(fname) )
-			WindowManagement.openDialog(
-				_ERROR,
-				'invalid extension... icon definition metamodels are "*Icons.metamodel" files');
-		else
-			HttpUtils.httpReq('PUT', HttpUtils.url(fname,__FORCE_GET));
-	};
+    this.compileToASMM = function (fname) {
+        if (!__isAbstractSyntaxMetamodel(fname))
+            WindowManagement.openDialog(
+                _ERROR,
+                'invalid extension... abstract syntax metamodels are "*.metamodel" files');
+        else
+            HttpUtils.httpReq('PUT', HttpUtils.url(fname, __FORCE_GET), null,
+                function (status, text) {
+                    //there was a problem
+                    if (!utils.isHttpSuccessCode(status)) {
+                        //let resp = JSON.parse(text);
+                        WindowManagement.openDialog(_ERROR, JSON.stringify(text));
+                    }
+
+                });
+    };
+
+    /**
+     * Compile the current model to a Concrete Syntax Metamodel
+     */
+    this.compileToCSMM = function (fname) {
+        if (!__isIconMetamodel(fname))
+            WindowManagement.openDialog(
+                _ERROR,
+                'invalid extension... icon definition metamodels are "*Icons.metamodel" files');
+        else
+            HttpUtils.httpReq('PUT', HttpUtils.url(fname, __FORCE_GET), null,
+                function (status, text) {
+                    //there was a problem
+                    if (!utils.isHttpSuccessCode(status)) {
+                        let resp = text;
+                        if (!(resp.startsWith("500"))) {
+                            resp = JSON.parse(text);
+                        }
+
+                        if (resp["code"] && resp["code"].includes("ENOENT")) {
+                            let msg = "ERROR: Corresponding metamodel could not be found in same directory!";
+                            WindowManagement.openDialog(_ERROR, msg);
+                        } else {
+                            WindowManagement.openDialog(_ERROR, JSON.stringify(text));
+                        }
+                    }
+
+                });
+    };
 	
 	/**
 	 * Compiles the current model to an Icon Pattern Metamodel

+ 3 - 1
client/file_browser.js

@@ -26,7 +26,8 @@ class FileBrowser{
                     fileb =
                         FileBrowser.getFileBrowser(fnames, false, manualInput, __getRecentDir(startDir));
 
-                new_folder_b.html('new folder')
+                new_folder_b.attr('id', 'new_folder')
+                    .html('new folder')
                     .click(function (ev) {
                         var folder_name = prompt("please fill in a name for the folder");
                         if (folder_name != null) {
@@ -367,6 +368,7 @@ class FileBrowser{
                     subfolders.forEach(function (subfolder) {
                         var navbutton = $('<button>');
                         navbutton.html(subfolder);
+                        navbutton.attr('id', 'navbar_' + subfolder);
                         navbutton.click(navbuttononclick);
                         navdiv.append(navbutton);
                     });

+ 2 - 0
client/geometry_utils.js

@@ -305,6 +305,7 @@ GeometryUtils = function(){
 					var img = $('<img>');
 					img.attr('class', 'geometry_ctrl');
 					img.attr('src', 'client/media/'+x+'.png');
+					img.attr('id', x + "_btn");
 
 					let wheelFunc = function(event)
 					{
@@ -327,6 +328,7 @@ GeometryUtils = function(){
 			var img = $('<img>');
 			img.attr('class', 'geometry_ctrl');
 			img.attr('src', 'client/media/ok.png');
+			img.attr('id', "ok_btn");
 			img.click(function(event) {GeometryUtils.transformSelection(__GEOM_TRANSF);});
 			geometryControlsOverlay.append(img);
 		}

+ 2 - 1
client/gui_utils.js

@@ -208,7 +208,7 @@ GUIUtils = function(){
 	 * 
 	 * @param choices - the choices for the <select> element
 	 * @param multipleChoice - if true, allows for multiple options to be selected
-	 * @param defaultSelect - sets the default selection for the list
+	 * @param defaultSelection - sets the default selection for the list
 	 * @param numVisibleOptions - sets the number of visible options
 	 */
 	this.getSelector = function(choices,multipleChoice,defaultSelection,numVisibleOptions){
@@ -223,6 +223,7 @@ GUIUtils = function(){
 					var option = $('<option>');
 					option.val( choice ); 
 					option.html( choice );
+					option.attr('id', "choice_" + choice);
 					select.append(option);
 					if( defaultSelection != undefined && 
 						 utils.contains(defaultSelection,choice) )

+ 3 - 0
client/http_utils.js

@@ -109,6 +109,8 @@ HttpUtils = function(){
 		span.attr("class", 'fileb_icon');
 //		img.attr("class", 'clickable');
 		txt.css("padding", '5px');
+
+		txt.attr('id', fname.replace("/", ""));
 		
 		span.append(img);
 		span.append(txt);
@@ -135,6 +137,7 @@ HttpUtils = function(){
 		txt.attr("contentEditable", true);
 		// JQuery does not support HTML5 oninput
 		txt.keyup( oninput );
+		txt.attr('id', 'new_file');
 		span.append(img);
 		span.append(txt);
 		return span;

+ 14 - 5
client/window_management.js

@@ -460,11 +460,20 @@ WindowManagement = function(){
 				if( args['ignoreKey'] != undefined && 
 					 args['ignoreKey'](attr,args['data'][attr]['value']) )
 					continue;
-	
-				var tr = $('<tr>');
-				var ii = GUIUtils.getInputField(
-						args['data'][attr]['type'],
-						args['data'][attr]['value']);
+
+                var tr = $('<tr>');
+                tr.attr("id", "tr_" + attr);
+                var ii = null;
+                try {
+                    ii = GUIUtils.getInputField(
+                        args['data'][attr]['type'],
+                        args['data'][attr]['value']);
+                } catch (err) {
+                    console.log(args['data'][attr]);
+                    WindowManagement.openDialog(
+                        _ERROR,
+                        'unexpected error in editing mode ::\n ' + err + '\n' + utils.jsons(args['data'][attr]));
+                }
 //				var tr = table.append( $('<tr>') ),
 //					 ii = GUIUtils.getInputField(
 //							 args['data'][attr]['type'],

+ 3 - 2
doc/new_language.rst

@@ -235,8 +235,9 @@ The events that can trigger are:
 * **post-disconnect**, which triggers just after a link between two instances is deleted
 * **post-delete**, which triggers just after an instance is deleted
 * **post-edit**, which triggers just after an instance is edited
+* **verify**, which triggers when the user presses the *verify* button on the *MainMenu* toolbar
 
-.. note:: A constraint without a trigger is evaluated when the user presses the *verify* button on the *MainMenu* toolbar.
+.. note:: A constraint/action with no defined triggers will execute on the *verify* event. These constraints/actions should be updated to select the event explicitly.
 
 .. _action-library:
 
@@ -365,4 +366,4 @@ To compile your concrete syntax, make sure the current active model is the concr
 
 .. image:: img/compile_cs.png
 
-Each time you make a change to your abstract or concrete syntax, recompile them before using them.
+Each time you make a change to your abstract or concrete syntax, recompile them before using them.

+ 62 - 40
mmmk.js

@@ -813,41 +813,55 @@ module.exports = {
 				types2ids[type].push(id);
 			}
 
-			for( var i in allHandlers )
-			{
-				var handler = allHandlers[i];
-				if( _utils.contains(events,handler['event']) )
-				{
-					if( handler['targetType'] == '*' )
-					{
-						for( var j in ids )
-							if( (res = this.__runDesignerCode(
-														handler['code'],
-														handler['event']+' '+handler['name'],
-														handlerType,
-														ids[j])) )
-								return res;
-
-						if( ids.length == 0 )
-							if( (res = this.__runDesignerCode(
-														handler['code'],
-														handler['event']+' '+handler['name'],
-														handlerType)) )
-								return res;
-					}
-					else
-						for( var j in types2ids[handler['targetType']] )
-						{
-							var id = types2ids[handler['targetType']][j];
-							if( (res = this.__runDesignerCode(
-														handler['code'],
-														handler['event']+' '+handler['name'],
-														handlerType,
-														id)) )
-								return res;
-						}
-				}
-			}
+            for (let i in allHandlers) {
+                let handler = allHandlers[i];
+
+                let handled = _utils.contains(events, handler['event']) ||
+                    (_utils.contains(events, "validate") && handler['event'] == ""); //handle legacy events
+
+                if (!handled) {
+                    continue;
+                }
+                if (handler['targetType'] == '*') {
+                    let result = null;
+                    for (let j in ids) {
+                        result = this.__runDesignerCode(
+                            handler['code'],
+                            handler['event'] + ' ' + handler['name'],
+                            handlerType,
+                            ids[j]);
+                        if (result) {
+                            return result;
+                        }
+                    }
+
+                    if (ids.length == 0) {
+                        result = this.__runDesignerCode(
+                            handler['code'],
+                            handler['event'] + ' ' + handler['name'],
+                            handlerType);
+
+                        if (result) {
+                            return result;
+                        }
+                    }
+                }
+                else {
+                    for (let j in types2ids[handler['targetType']]) {
+                        let id = types2ids[handler['targetType']][j];
+                        let result = this.__runDesignerCode(
+                            handler['code'],
+                            handler['event'] + ' ' + handler['name'],
+                            handlerType,
+                            id);
+
+                        if (result) {
+                            return result;
+                        }
+                    }
+                }
+
+            }
 		},
 
 
@@ -954,10 +968,13 @@ module.exports = {
                     checked_for_loops= checked_for_loops.concat(visited);
                 }
 			}
-			
-			for( var metamodel in this.metamodels )
-				if( (err=this.__runEventHandlers(this.metamodels[metamodel]['constraints'], [''], [], 'constraint')) )
-					return err;
+
+            for (let metamodel in this.metamodels) {
+
+                let err = this.__runEventHandlers(this.metamodels[metamodel]['constraints'], ['validate'], [], 'constraint');
+                if (err)
+                    return err;
+            }
 		},
 
 
@@ -1409,6 +1426,11 @@ module.exports = {
 					types2legalNeighborTypes[type].forEach(
 							function(ntype)
 							{
+								if (types2legalNeighborTypes[ntype] == undefined){
+									let msg = "Error! Problem with edges for class: " + type +"\nFound constraints: " + JSON.stringify(types2legalNeighborTypes[type]);
+									throw msg;
+								}
+
 								types2legalNeighborTypes[ntype].forEach(
 									function(nntype)
 									{
@@ -1426,7 +1448,7 @@ module.exports = {
 			}
 			catch(err)
 			{
-				return {'$err':'invalid metamodel model, crashed on :: '+err};
+				return {'$err':'invalid metamodel model, crashed on :: ' + err};
 			}
 		},
 

文件差異過大導致無法顯示
+ 1087 - 0
tests/05_creating_dsl.js


+ 248 - 0
tests/06_transformation_test.js

@@ -0,0 +1,248 @@
+//NOTE: REQUIRES DSL FROM PREVIOUS TEST
+
+let test_utils = require('./test_utils');
+let model_building_utils = require('./model_building_utils');
+let user = "./users/testuser/";
+
+let rule_toolbars = [
+    "/Formalisms/__Transformations__/TransformationRule/TransformationRule.defaultIcons.metamodel",
+    "/autotest/autotest.defaultIcons.pattern.metamodel"
+];
+
+module.exports = {
+
+    beforeEach: function (client, done) {
+        client.url('http://localhost:8124/atompm').pause(300).maximizeWindow(done);
+    },
+
+    'Login': function (client) {
+
+        test_utils.login(client);
+    },
+
+    'Compile Pattern MM': function (client) {
+
+        let folder_name = "autotest";
+        let model_name = "autotest.metamodel";
+        model_building_utils.compile_model(client, "pattern", folder_name, model_name);
+    },
+
+    'Create Transformation': function (client) {
+
+        let trans_formalism = "/Formalisms/__Transformations__/Transformation/MoTif.defaultIcons.metamodel";
+
+        test_utils.load_toolbar(client, [trans_formalism]);
+
+
+        //BUILD ELEMENTS
+
+        let x_coord = 300;
+        let y_coords = [200, 320, 440, 560, 680];
+
+        let btn_prefix = "#\\2f Formalisms\\2f __Transformations__\\2f Transformation\\2f MoTif\\2e defaultIcons\\2e metamodel\\2f ";
+        let type_prefix = "#\\2f Formalisms\\2f __Transformations__\\2f Transformation\\2f MoTif\\2e defaultIcons\\2f ";
+
+        let to_create = ["StartIcon", "FRuleIcon", "ARuleIcon", "EndSuccessIcon", "EndFailIcon"];
+
+        let ele_map = {};
+        let num_elements = 0;
+        for (let ele of to_create) {
+            client.waitForElementPresent(btn_prefix + ele, 2000, "Button present: " + btn_prefix + ele);
+            client.click(btn_prefix + ele);
+
+            let built_div = model_building_utils.create_class(client,
+                x_coord, y_coords[num_elements], num_elements, type_prefix + ele + "\\2f ");
+
+            ele_map[ele] = built_div;
+
+            if (ele.includes("Rule")) {
+                let rule_name = num_elements + "_" + ele.replace("Icon", "");
+                let name_field = "#tr_name > td:nth-child(2) > textarea:nth-child(1)";
+                let rule_field = "#tr_rule > td:nth-child(2) > textarea:nth-child(1)";
+                let rule_prefix = "/autotest/R_";
+
+
+                let attribs = {};
+                attribs[name_field] = rule_name;
+                attribs[rule_field] = rule_prefix + rule_name + ".model";
+                model_building_utils.set_attribs(client, num_elements, attribs, type_prefix + ele + "\\2f ");
+            }
+            num_elements++;
+        }
+
+        let assocs = [
+            [0, 1, ""],
+            [1, 2, "success"],
+            [2, 3, "success"],
+            [1, 4, "fail"],
+            [2, 4, "fail"]
+        ];
+
+
+        for (let assoc of assocs) {
+
+            let start_ele = to_create[assoc[0]];
+            let end_ele = to_create[assoc[1]];
+
+            let start = ele_map[start_ele];
+            let end = ele_map[end_ele];
+
+            //TODO: Have path come from check/x mark
+
+            let relation_div = "";
+            if (assoc[2] == "success") {
+                relation_div = "#choice_\\2f Formalisms\\2f __Transformations__\\2f Transformation\\2f MoTif\\2e defaultIcons\\2f success";
+                //start += " > path:nth-child(3)";
+            } else if (assoc[2] == "fail") {
+                relation_div = "#choice_\\2f Formalisms\\2f __Transformations__\\2f Transformation\\2f MoTif\\2e defaultIcons\\2f fail";
+                //start += " > path:nth-child(5)";
+            }
+
+            let offset = 5 * (assoc[0] + assoc[1]);
+            model_building_utils.create_assoc(client, start, end, relation_div, offset);
+        }
+
+
+        model_building_utils.save_model(client, "autotest", "T_autotest.model");
+
+    },
+
+    'Create Rule 1': function (client) {
+
+        test_utils.load_toolbar(client, rule_toolbars);
+
+        // BUILD LHS AND RHS
+        let LHS_btn = "#\\2f Formalisms\\2f __Transformations__\\2f TransformationRule\\2f TransformationRule\\2e defaultIcons\\2e metamodel\\2f LHSIcon";
+        let RHS_btn = "#\\2f Formalisms\\2f __Transformations__\\2f TransformationRule\\2f TransformationRule\\2e defaultIcons\\2e metamodel\\2f RHSIcon";
+
+        let ele_map = {};
+
+        client.waitForElementPresent(LHS_btn, 2000, "LHS button").click(LHS_btn);
+        let LHS_div = "#\\2f Formalisms\\2f __Transformations__\\2f TransformationRule\\2f TransformationRule\\2e defaultIcons\\2f LHSIcon\\2f ";
+        ele_map["LHS"] = model_building_utils.create_class(client, 150, 200, 0, LHS_div);
+
+        client.waitForElementPresent(RHS_btn, 2000, "RHS button").click(RHS_btn);
+        let RHS_div = "#\\2f Formalisms\\2f __Transformations__\\2f TransformationRule\\2f TransformationRule\\2e defaultIcons\\2f RHSIcon\\2f ";
+        ele_map["RHS"] = model_building_utils.create_class(client, 650, 200, 1, RHS_div);
+
+        model_building_utils.click_off(client);
+
+        //BUILD ELEMENTS INSIDE
+        let c_btn = "#\\2f autotest\\2f autotest\\2e defaultIcons\\2e pattern\\2e metamodel\\2f __pClassCIcon";
+        let d_btn = "#\\2f autotest\\2f autotest\\2e defaultIcons\\2e pattern\\2e metamodel\\2f __pClassDIcon";
+
+        client.waitForElementPresent(c_btn, 2000, "C button").click(c_btn);
+        let c_div = "#\\2f autotest\\2f autotest\\2e defaultIcons\\2e pattern\\2f __pClassCIcon\\2f ";
+        ele_map["C"] = model_building_utils.create_class(client, 50, 200, 2, c_div);
+
+        client.waitForElementPresent(d_btn, 2000, "D button").click(d_btn);
+        let d_div = "#\\2f autotest\\2f autotest\\2e defaultIcons\\2e pattern\\2f __pClassDIcon\\2f ";
+        ele_map["D"] = model_building_utils.create_class(client, 50, 400, 3, d_div);
+
+        model_building_utils.move_element(client, ele_map["C"] + " > text:nth-child(1)", ele_map["LHS"], [50, 50], [50, 50]);
+        model_building_utils.move_element(client, ele_map["D"] + " > text:nth-child(1)", ele_map["RHS"], [50, 50], [50, 50]);
+
+
+        model_building_utils.save_model(client, "autotest", "R_1_FRule.model");
+    },
+
+    'Create Rule 2': function (client) {
+
+        test_utils.load_toolbar(client, rule_toolbars);
+
+        // BUILD LHS AND RHS
+        let LHS_btn = "#\\2f Formalisms\\2f __Transformations__\\2f TransformationRule\\2f TransformationRule\\2e defaultIcons\\2e metamodel\\2f LHSIcon";
+        let RHS_btn = "#\\2f Formalisms\\2f __Transformations__\\2f TransformationRule\\2f TransformationRule\\2e defaultIcons\\2e metamodel\\2f RHSIcon";
+
+        let ele_map = {};
+
+        client.waitForElementPresent(LHS_btn, 2000, "LHS button").click(LHS_btn);
+        let LHS_div = "#\\2f Formalisms\\2f __Transformations__\\2f TransformationRule\\2f TransformationRule\\2e defaultIcons\\2f LHSIcon\\2f ";
+        ele_map["LHS"] = model_building_utils.create_class(client, 150, 200, 0, LHS_div);
+
+        client.waitForElementPresent(RHS_btn, 2000, "RHS button").click(RHS_btn);
+        let RHS_div = "#\\2f Formalisms\\2f __Transformations__\\2f TransformationRule\\2f TransformationRule\\2e defaultIcons\\2f RHSIcon\\2f ";
+        ele_map["RHS"] = model_building_utils.create_class(client, 650, 200, 1, RHS_div);
+
+        model_building_utils.click_off(client);
+
+        //BUILD ELEMENTS INSIDE
+        let a_btn = "#\\2f autotest\\2f autotest\\2e defaultIcons\\2e pattern\\2e metamodel\\2f __pClassAIcon";
+        let a_div = "#\\2f autotest\\2f autotest\\2e defaultIcons\\2e pattern\\2f __pClassAIcon\\2f ";
+
+        //BUILD A
+        client.waitForElementPresent(a_btn, 2000, "A button").click(a_btn);
+        ele_map["A_lhs"] = model_building_utils.create_class(client, 50, 200, 2, a_div);
+        ele_map["A_rhs"] = model_building_utils.create_class(client, 50, 400, 3, a_div);
+
+        model_building_utils.move_element(client, ele_map["A_lhs"] + " > text:nth-child(1)", ele_map["LHS"], [50, 50], [50, 20]);
+        model_building_utils.move_element(client, ele_map["A_rhs"] + " > text:nth-child(1)", ele_map["RHS"], [50, 50], [50, 20]);
+
+
+        model_building_utils.click_off(client);
+
+        let b_btn = "#\\2f autotest\\2f autotest\\2e defaultIcons\\2e pattern\\2e metamodel\\2f __pClassBIcon";
+        let b_div = "#\\2f autotest\\2f autotest\\2e defaultIcons\\2e pattern\\2f __pClassBIcon\\2f ";
+
+        //BUILD B
+        client.waitForElementPresent(b_btn, 2000, "B button").click(b_btn);
+        ele_map["B_lhs"] = model_building_utils.create_class(client, 50, 200, 6, b_div);
+        ele_map["B_rhs"] = model_building_utils.create_class(client, 50, 400, 7, b_div);
+
+        model_building_utils.move_element(client, ele_map["B_lhs"] + " > text:nth-child(1)", ele_map["LHS"], [50, 50], [50, 70]);
+        model_building_utils.move_element(client, ele_map["B_rhs"] + " > text:nth-child(1)", ele_map["RHS"], [50, 50], [50, 70]);
+
+        model_building_utils.click_off(client);
+
+        //BUILD ASSOCS
+        client.pause(300);
+        model_building_utils.create_assoc(client,
+            ele_map["A_lhs"] + " > text:nth-child(1)", ele_map["B_lhs"] + " > text:nth-child(1)", "", 0);
+
+        client.pause(300);
+        model_building_utils.create_assoc(client,
+            ele_map["A_rhs"] + " > text:nth-child(1)", ele_map["B_rhs"] + " > text:nth-child(1)", "", 0);
+
+        let test_field = "#tr_test > td:nth-child(2) > textarea";
+        let attrs = {};
+        attrs[test_field] = "result = \"bonjour world!\"";
+        model_building_utils.set_attribs(client, 3, attrs, a_div, " > text:nth-child(1)", [5, 5]);
+
+        model_building_utils.save_model(client, "autotest", "R_2_ARule.model");
+    },
+
+    'Execute Transformation': function (client) {
+        model_building_utils.load_model(client, "autotest", "autotest_instance.model");
+
+        model_building_utils.compile_model(client, "transform", "autotest", "T_autotest.model");
+
+        let run_button = "#\\2f Toolbars\\2f TransformationController\\2f TransformationController\\2e buttons\\2e model\\2f play";
+
+        client.click(run_button);
+
+        let created_D_1 = "#\\2f autotest\\2f autotest\\2e defaultIcons\\2f ClassDIcon\\2f 15\\2e instance";
+        let created_D_2 = "#\\2f autotest\\2f autotest\\2e defaultIcons\\2f ClassDIcon\\2f 15\\2e instance";
+        client.waitForElementPresent(created_D_1, 5000, "First D element created");
+        client.waitForElementPresent(created_D_2, 5000, "Second D element created");
+
+        //TODO:Test for A element's attribute
+
+        //CHECK CONSTRAINT
+        let verify_btn = "#\\/Toolbars\\/MainMenu\\/MainMenu\\.buttons\\.model\\/validateM";
+        let dialog_btn = "#dialog_btn";
+
+        client.pause(500);
+
+        client.waitForElementPresent(verify_btn, 2000, "Find verify button")
+            .click(verify_btn).pause(500)
+            .waitForElementPresent(dialog_btn, 2000, "Constraint violation")
+            .click(dialog_btn);
+    },
+
+    after: function (client) {
+        client.end();
+    },
+
+
+};
+

+ 312 - 0
tests/model_building_utils.js

@@ -0,0 +1,312 @@
+let canvas = "#div_canvas";
+
+function build_div(element_type, num) {
+    return element_type + (num) + "\\2e instance";
+}
+
+function get_element_div(type, num) {
+    return "#\\2f Formalisms\\2f __LanguageSyntax__\\2f SimpleClassDiagram\\2f SimpleClassDiagram\\2e umlIcons\\2f " + (type) + "\\2f " + (num) + "\\2e instance";
+}
+
+function get_class_div(num) {
+    return get_element_div("ClassIcon", num);
+}
+
+function get_assoc_div(num) {
+
+    return get_element_div("AssociationLink", num) + " > text:nth-child(1)";
+}
+
+function fix_selector(name) {
+    return name.replace(".", "\\.");
+}
+
+function create_class(client, x, y, i, element_type) {
+
+    let class_div = "";
+    if (element_type != undefined) {
+        class_div = this.build_div(element_type, i);
+    } else {
+        class_div = this.get_class_div(i);
+    }
+
+    client
+        .moveToElement(canvas, x, y)
+        .mouseButtonClick('right')
+        .pause(300)
+        .waitForElementPresent(class_div, 500, "Created class: " + class_div);
+
+    return class_div;
+
+}
+
+function create_classes(client, x_coords, y_coords, curr_num_elements, element_type) {
+    for (let x of x_coords) {
+        for (let y of y_coords) {
+
+            this.create_class(client, x, y, curr_num_elements, element_type);
+
+            curr_num_elements++;
+        }
+    }
+
+    return curr_num_elements;
+}
+
+function create_assoc(client, start_div, end_div, relation_div, offset) {
+
+    this.click_off(client);
+
+    this.move_to_element_ratio(client, start_div, 50 + offset, 50 + offset);
+    client.mouseButtonDown('right');
+    this.move_to_element_ratio(client, end_div, 50 + offset, 50 + offset);
+    client.mouseButtonUp('right').pause(300);
+
+    if (relation_div.length > 0) {
+        client.waitForElementPresent(relation_div, 1000, "Relation option present: " + relation_div);
+        client.click(relation_div);
+        client.waitForElementPresent("#dialog_btn", 1000, "Assoc menu opens")
+            .click("#dialog_btn")
+            .pause(300)
+            .waitForElementNotPresent("#dialog_btn", 1000, "Assoc menu closes");
+    }
+
+    this.click_off(client);
+    client.pause(300);
+
+}
+
+function move_element(client, from_div, to_div, from_offset, to_offset) {
+
+    this.click_off(client);
+    this.move_to_element_ratio(client, from_div, from_offset[0], from_offset[1]);
+    client.mouseButtonClick('left').pause(300);
+    client.mouseButtonDown('left');
+    this.move_to_element_ratio(client, to_div, to_offset[0], to_offset[1]);
+    client.mouseButtonUp('left').pause(300);
+}
+
+function set_attribs(client, num, attrs, element_type, div_suffix, offset) {
+
+    let element_div = "";
+    if (element_type != undefined) {
+        element_div = this.build_div(element_type, num);
+    } else {
+        element_div = this.get_class_div(num);
+    }
+
+    if (div_suffix != undefined) {
+        element_div += div_suffix;
+    }
+
+    this.click_off(client);
+
+    if (offset == undefined) {
+        offset = [50, 50];
+    }
+
+    client.waitForElementPresent(element_div, 1000, "Find element for attrib set: " + element_div);
+    this.move_to_element_ratio(client, element_div, offset[0], offset[1]);
+    client.mouseButtonClick('middle')
+        .waitForElementPresent("#dialog_btn", 1000, "Editing menu opens");
+
+    for (const [key, value] of Object.entries(attrs)) {
+
+        client.element('css selector', key, function (result) {
+            //if not found, assume checkbox
+            if (result.status == -1) {
+                let attrib_name = key.split(" ")[0];
+                let checkbox_div = attrib_name + " > td:nth-child(2) > input:nth-child(1)";
+                client.click(checkbox_div);
+            } else {
+                client
+                    .clearValue(key)
+                    .setValue(key, value);
+            }
+        });
+    }
+
+    client
+        .click("#dialog_btn")
+        .waitForElementNotPresent("#dialog_btn", 1000, "Editing menu closes")
+        .moveToElement(canvas, 0, 100)
+        .mouseButtonClick('left')
+        .pause(300)
+    ;
+}
+
+function move_to_element_ratio(client, element, x_ratio, y_ratio) {
+
+    client.getElementSize(element, function (result) {
+        let x_pos = Math.trunc(x_ratio / 100 * result.value.width);
+        let y_pos = Math.trunc(y_ratio / 100 * result.value.height);
+        //console.log("X: " + x_pos + " Y: " + y_pos);
+        client.moveToElement(element, x_pos, y_pos);
+    });
+}
+
+function click_off(client) {
+    client
+        .moveToElement(canvas, 0, 100)
+        .mouseButtonClick('left');
+}
+
+function load_model(client, folder_name, model_name) {
+
+    let load_button = "#\\2f Toolbars\\2f MainMenu\\2f MainMenu\\2e buttons\\2e model\\2f loadModel";
+
+    client.waitForElementPresent(load_button, 1000, "Looking for load button")
+        .click(load_button)
+        .waitForElementPresent("#dialog_btn", 1000, "Load menu opens");
+
+    let root_button = "#navbar_\\2f";
+    client.waitForElementPresent(root_button, 1000, "Find root button")
+        .click(root_button);
+
+    let folder_name_div = "#" + folder_name;
+    client.click(folder_name_div);
+
+    client.click("#" + fix_selector(model_name))
+        .pause(200)
+        .click("#dialog_btn");
+
+    client.waitForElementNotPresent("#dialog_btn", 1000, "Save menu closes");
+
+}
+
+function save_model(client, folder_name, model_name) {
+    let save_button = "#\\2f Toolbars\\2f MainMenu\\2f MainMenu\\2e buttons\\2e model\\2f saveModel";
+    let new_file_text = "#new_file";
+
+    client.waitForElementPresent(save_button, 1000, "Looking for save button")
+        .click(save_button)
+        .waitForElementPresent("#dialog_btn", 1000, "Save menu opens");
+
+    let root_button = "#navbar_\\2f";
+    client.waitForElementPresent(root_button, 1000, "Find root button")
+        .click(root_button);
+
+    let folder_name_div = "#" + folder_name;
+    client.element('css selector', folder_name_div, function (result) {
+            if (result.status == -1) {
+                let new_folder_btn = "#new_folder";
+                client.click(new_folder_btn)
+                    .setAlertText(folder_name)
+                    .acceptAlert();
+            }
+            client.click(folder_name_div);
+
+            client.element('css selector', "#" + model_name, function (result) {
+                    if (result.status == -1) {
+                        client.click(new_file_text)
+                            .clearValue(new_file_text)
+                            .setValue(new_file_text, model_name)
+                            .pause(200)
+                            .click("#dialog_btn");
+                    } else {
+                        client.click("#" + model_name)
+                            .pause(200)
+                            .click("#dialog_btn");
+                    }
+
+                    client.waitForElementNotPresent("#dialog_btn", 1000, "Save menu closes");
+                }
+            );
+        }
+    );
+}
+
+function compile_model(client, compile_type, folder_name, model_name) {
+
+    let button = "";
+    let button_name = compile_type;
+
+    if (button_name == "AS") {
+        button = "#\\2f Toolbars\\2f CompileMenu\\2f CompileMenu\\2e buttons\\2e model\\2f compileToASMM";
+    } else if (button_name == "CS") {
+        button = "#\\2f Toolbars\\2f CompileMenu\\2f CompileMenu\\2e buttons\\2e model\\2f compileToCSMM";
+    } else if (button_name == "pattern") {
+        button = "#\\2f Toolbars\\2f CompileMenu\\2f CompileMenu\\2e buttons\\2e model\\2f compileToPatternMM";
+    } else if (button_name == "transform") {
+        button = "#\\2f Toolbars\\2f TransformationController\\2f TransformationController\\2e buttons\\2e model\\2f load";
+    }
+
+    client.waitForElementPresent(button, 1000, "Looking for " + button_name + " button")
+        .click(button)
+        .waitForElementPresent("#dialog_btn", 2000, button_name + " menu opens");
+
+    let root_button = "#navbar_\\2f";
+    client.waitForElementPresent(root_button, 1000, "Find root button")
+        .click(root_button);
+
+    let folder_div = "#" + folder_name;
+    client.element('css selector', folder_div, function (result) {
+        if (result.status != -1) {
+            client.click(folder_div);
+        }
+    });
+
+    let new_file_text = "#new_file";
+    let model_div = "#" + fix_selector(model_name);
+    client.element('css selector', model_div, function (result) {
+
+            if (result.status == -1) {
+                //don't create new file with pattern compilation
+                if (button_name == "pattern" || button_name == "transform") {
+                    client.assert.ok(false, "File found: " + model_name);
+                }
+
+                client.click(new_file_text)
+                    .clearValue(new_file_text)
+                    .setValue(new_file_text, model_name)
+                    .click("#dialog_btn");
+            } else {
+                client.click(model_div)
+                    .click("#dialog_btn");
+            }
+
+            client.waitForElementNotPresent("#dialog_btn", 2000, button_name + " menu closes");
+        }
+    );
+}
+
+function delete_element(client, element) {
+    client.moveToElement(element, 10, 10);
+    client.mouseButtonClick('left');
+    client.keys(client.Keys.DELETE);
+    this.click_off(client);
+}
+
+function scroll_geometry_element(client, element, scrollAmount, scrollTimes) {
+    client.waitForElementPresent(element, 2000, element + " present");
+    this.move_to_element_ratio(client, element, 50, 50);
+    client.execute(function (btn_div, scrollAmount, scrollTimes) {
+        let element = $(btn_div);
+        for (let i = 0; i < scrollTimes; i++) {
+            element.get(0).onwheel(scrollAmount);
+        }
+    }, [element, scrollAmount, scrollTimes], null);
+
+    client.pause(300);
+}
+
+
+module.exports = {
+    '@disabled': true,
+    get_element_div,
+    get_assoc_div,
+    get_class_div,
+    build_div,
+    create_class,
+    create_classes,
+    create_assoc,
+    delete_element,
+    set_attribs,
+    move_to_element_ratio,
+    click_off,
+    save_model,
+    load_model,
+    compile_model,
+    scroll_geometry_element,
+    move_element
+};

+ 1 - 1
types.js

@@ -8,7 +8,7 @@ __specialTypes = {
 
 	'$CARDINALITY':'map<[dir,type,min,max],[string,string,string,string]>',
 
-	'$EVENT':'ENUM(pre-connect,pre-create,pre-disconnect,pre-delete,pre-edit,post-connect,post-create,post-disconnect,post-delete,post-edit)',
+	'$EVENT':'ENUM(pre-connect,pre-create,pre-disconnect,pre-delete,pre-edit,post-connect,post-create,post-disconnect,post-delete,post-edit,validate)',
 
 	'$EVENT_HANDLER':'map<[name,event,code],[string,$EVENT,code]>',