├── .github
└── FUNDING.yml
├── debug.js
├── LICENSE
├── jasmine
├── lib
│ └── jasmine-1.3.1
│ │ ├── MIT.LICENSE
│ │ ├── jasmine.css
│ │ └── jasmine-html.js
└── SpecRunner.html
├── flowchart
├── svg_class.js
├── dragging_service.js
├── mouse_capture_service.js
├── svg_class.spec.js
├── flowchart_template.html
├── flowchart_directive.js
├── flowchart_directive.spec.js
├── flowchart_viewmodel.js
└── flowchart_viewmodel.spec.js
├── server.js
├── app.css
├── index.html
├── README.md
└── app.js
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: ashleydavis
4 |
--------------------------------------------------------------------------------
/debug.js:
--------------------------------------------------------------------------------
1 | //
2 | // Debug utilities.
3 | //
4 |
5 | (function () {
6 |
7 | if (typeof debug !== "undefined") {
8 | throw new Error("debug object already defined!");
9 | }
10 |
11 | debug = {};
12 |
13 | //
14 | // Assert that an object is valid.
15 | //
16 | debug.assertObjectValid = function (obj) {
17 |
18 | if (!obj) {
19 | throw new Exception("Invalid object!");
20 | }
21 |
22 | if ($.isPlainObject(obj)) {
23 | throw new Error("Input is not an object! It is a " + typeof(obj));
24 | }
25 | };
26 |
27 | })();
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Ashley Davis
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/jasmine/lib/jasmine-1.3.1/MIT.LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2008-2011 Pivotal Labs
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/flowchart/svg_class.js:
--------------------------------------------------------------------------------
1 | //
2 | // http://www.justinmccandless.com/blog/Patching+jQuery's+Lack+of+SVG+Support
3 | //
4 | // Functions to add and remove SVG classes because jQuery doesn't support this.
5 | //
6 |
7 | // jQuery's removeClass doesn't work for SVG, but this does!
8 | // takes the object obj to remove from, and removes class remove
9 | // returns true if successful, false if remove does not exist in obj
10 | var removeClassSVG = function(obj, remove) {
11 | var classes = obj.attr('class');
12 | if (!classes) {
13 | return false;
14 | }
15 |
16 | var index = classes.search(remove);
17 |
18 | // if the class already doesn't exist, return false now
19 | if (index == -1) {
20 | return false;
21 | }
22 | else {
23 | // string manipulation to remove the class
24 | classes = classes.substring(0, index) + classes.substring((index + remove.length), classes.length);
25 |
26 | // set the new string as the object's class
27 | obj.attr('class', classes);
28 |
29 | return true;
30 | }
31 | };
32 |
33 | // jQuery's hasClass doesn't work for SVG, but this does!
34 | // takes an object obj and checks for class has
35 | // returns true if the class exits in obj, false otherwise
36 | var hasClassSVG = function(obj, has) {
37 | var classes = obj.attr('class');
38 | if (!classes) {
39 | return false;
40 | }
41 |
42 | var index = classes.search(has);
43 |
44 | if (index == -1) {
45 | return false;
46 | }
47 | else {
48 | return true;
49 | }
50 | };
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | //
2 | // Simple nodejs server for running the sample.
3 | //
4 | // http://stackoverflow.com/questions/6084360/node-js-as-a-simple-web-server
5 | //
6 |
7 | var http = require("http"),
8 | url = require("url"),
9 | path = require("path"),
10 | fs = require("fs")
11 | port = process.argv[2] || 8888;
12 |
13 | http.createServer(function(request, response) {
14 |
15 | var uri = url.parse(request.url).pathname
16 | , filename = path.join(process.cwd(), uri);
17 |
18 | fs.exists(filename, function(exists) {
19 | if(!exists) {
20 | response.writeHead(404, {"Content-Type": "text/plain"});
21 | response.write("404 Not Found\n");
22 | response.end();
23 | return;
24 | }
25 |
26 | if (fs.statSync(filename).isDirectory()) {
27 | filename += '/index.html';
28 | }
29 |
30 | fs.readFile(filename, "binary", function(err, file) {
31 | if(err) {
32 | response.writeHead(500, {"Content-Type": "text/plain"});
33 | response.write(err + "\n");
34 | response.end();
35 | return;
36 | }
37 |
38 | var contentType = "text/plain";
39 | var ext = path.extname(filename);
40 |
41 | switch (ext) {
42 | case ".html":
43 | contentType = "text/html";
44 | break;
45 | case ".css":
46 | contentType = "text/css";
47 | break;
48 | case ".js":
49 | contentType = "text/javascript";
50 | break;
51 | }
52 |
53 | console.log("Incoming ext: " + ext + ", content: " + contentType);
54 |
55 | response.writeHead(200, {"Content-Type": contentType});
56 | response.write(file, "binary");
57 | response.end();
58 | });
59 | });
60 | }).listen(parseInt(port, 10));
61 |
62 | console.log("Static file server running at\n => http://localhost:" + port + "/\nCTRL + C to shutdown");
--------------------------------------------------------------------------------
/app.css:
--------------------------------------------------------------------------------
1 | /*
2 | Generic reset.
3 | */
4 |
5 | * {
6 | padding: 0;
7 | margin: 0;
8 | }
9 |
10 | .test {
11 | border: 5px red solid;
12 | padding: 10;
13 | margin: 10;
14 | font-family: "Times New Roman";
15 | font-style: italic;
16 | }
17 |
18 | /*
19 | Styles for nodes and connectors.
20 | */
21 |
22 | .node-rect {
23 | stroke: black;
24 | stroke-width: 2;
25 | }
26 |
27 | .mouseover-node-rect {
28 | stroke: black;
29 | stroke-width: 4;
30 | }
31 |
32 | .selected-node-rect {
33 | stroke: red;
34 | stroke-width: 3;
35 | }
36 |
37 | .connector-circle {
38 | fill: white;
39 | stroke: black;
40 | stroke-width: 2;
41 | }
42 |
43 | .mouseover-connector-circle {
44 | fill: white;
45 | stroke: black;
46 | stroke-width: 3;
47 | }
48 |
49 | /*
50 | Style for connections.
51 | */
52 |
53 | .connection {
54 | }
55 |
56 | .connection-line {
57 | stroke: gray;
58 | stroke-width: 4;
59 | fill: transparent;
60 | }
61 |
62 | .mouseover-connection-line {
63 | stroke: gray;
64 | stroke-width: 6;
65 | fill: transparent;
66 | }
67 |
68 | .selected-connection-line {
69 | stroke: red;
70 | stroke-width: 4;
71 | fill: transparent;
72 | }
73 |
74 | .connection-endpoint {
75 | fill: gray;
76 | }
77 |
78 | .selected-connection-endpoint {
79 | fill: red;
80 | }
81 |
82 | .mouseover-connection-endpoint {
83 | fill: gray;
84 | }
85 |
86 | .connection-name{
87 | fill: black;
88 | }
89 |
90 | .selected-connection-name{
91 | fill: red;
92 | }
93 |
94 | .mouseover-connection-name{
95 | fill: gray;
96 | }
97 | /*
98 | Style for the connection being dragged out.
99 | */
100 |
101 | .dragging-connection {
102 | pointer-events: none;
103 | }
104 |
105 | .dragging-connection-line {
106 | stroke: gray;
107 | stroke-width: 3;
108 | fill: transparent;
109 | }
110 |
111 | .dragging-connection-endpoint {
112 | fill: gray;
113 | }
114 |
115 | /*
116 | The element (in this case the SVG element) that contains the draggable elements.
117 | */
118 |
119 | .draggable-container {
120 | border: solid 1px blue;
121 | }
122 |
123 | /*
124 | Drag selection rectangle.
125 | */
126 |
127 | .drag-selection-rect {
128 | stroke: blue;
129 | stroke-width: 2;
130 | fill: transparent;
131 | }
--------------------------------------------------------------------------------
/jasmine/SpecRunner.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Jasmine Spec Runner
5 |
6 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/flowchart/dragging_service.js:
--------------------------------------------------------------------------------
1 |
2 | angular.module('dragging', ['mouseCapture', ] )
3 |
4 | //
5 | // Service used to help with dragging and clicking on elements.
6 | //
7 | .factory('dragging', ['$rootScope', 'mouseCapture',function ($rootScope, mouseCapture) {
8 |
9 | //
10 | // Threshold for dragging.
11 | // When the mouse moves by at least this amount dragging starts.
12 | //
13 | var threshold = 5;
14 |
15 | return {
16 |
17 |
18 | //
19 | // Called by users of the service to register a mousedown event and start dragging.
20 | // Acquires the 'mouse capture' until the mouseup event.
21 | //
22 | startDrag: function (evt, config) {
23 |
24 | var dragging = false;
25 | var x = evt.pageX;
26 | var y = evt.pageY;
27 |
28 | //
29 | // Handler for mousemove events while the mouse is 'captured'.
30 | //
31 | var mouseMove = function (evt) {
32 |
33 | if (!dragging) {
34 | if (Math.abs(evt.pageX - x) > threshold ||
35 | Math.abs(evt.pageY - y) > threshold)
36 | {
37 | dragging = true;
38 |
39 | if (config.dragStarted) {
40 | config.dragStarted(x, y, evt);
41 | }
42 |
43 | if (config.dragging) {
44 | // First 'dragging' call to take into account that we have
45 | // already moved the mouse by a 'threshold' amount.
46 | config.dragging(evt.pageX, evt.pageY, evt);
47 | }
48 | }
49 | }
50 | else {
51 | if (config.dragging) {
52 | config.dragging(evt.pageX, evt.pageY, evt);
53 | }
54 |
55 | x = evt.pageX;
56 | y = evt.pageY;
57 | }
58 | };
59 |
60 | //
61 | // Handler for when mouse capture is released.
62 | //
63 | var released = function() {
64 |
65 | if (dragging) {
66 | if (config.dragEnded) {
67 | config.dragEnded();
68 | }
69 | }
70 | else {
71 | if (config.clicked) {
72 | config.clicked();
73 | }
74 | }
75 | };
76 |
77 | //
78 | // Handler for mouseup event while the mouse is 'captured'.
79 | // Mouseup releases the mouse capture.
80 | //
81 | var mouseUp = function (evt) {
82 |
83 | mouseCapture.release();
84 |
85 | evt.stopPropagation();
86 | evt.preventDefault();
87 | };
88 |
89 | //
90 | // Acquire the mouse capture and start handling mouse events.
91 | //
92 | mouseCapture.acquire(evt, {
93 | mouseMove: mouseMove,
94 | mouseUp: mouseUp,
95 | released: released,
96 | });
97 |
98 | evt.stopPropagation();
99 | evt.preventDefault();
100 | },
101 |
102 | };
103 |
104 | }])
105 |
106 | ;
107 |
108 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | AngularJS-FlowChart
4 |
5 |
9 |
10 |
11 |
12 |
19 |
20 |
21 |
22 |
28 |
29 |
30 |
36 |
43 |
50 |
57 |
58 |
61 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
--------------------------------------------------------------------------------
/flowchart/mouse_capture_service.js:
--------------------------------------------------------------------------------
1 |
2 | angular.module('mouseCapture', [])
3 |
4 | //
5 | // Service used to acquire 'mouse capture' then receive dragging events while the mouse is captured.
6 | //
7 | .factory('mouseCapture', [ '$rootScope', function ($rootScope) {
8 |
9 | //
10 | // Element that the mouse capture applies to, defaults to 'document'
11 | // unless the 'mouse-capture' directive is used.
12 | //
13 | var $element = $(document);
14 |
15 | //
16 | // Set when mouse capture is acquired to an object that contains
17 | // handlers for 'mousemove' and 'mouseup' events.
18 | //
19 | var mouseCaptureConfig = null;
20 |
21 | //
22 | // Handler for mousemove events while the mouse is 'captured'.
23 | //
24 | var mouseMove = function (evt) {
25 |
26 | if (mouseCaptureConfig && mouseCaptureConfig.mouseMove) {
27 |
28 | mouseCaptureConfig.mouseMove(evt);
29 |
30 | $rootScope.$digest();
31 | }
32 | };
33 |
34 | //
35 | // Handler for mouseup event while the mouse is 'captured'.
36 | //
37 | var mouseUp = function (evt) {
38 |
39 | if (mouseCaptureConfig && mouseCaptureConfig.mouseUp) {
40 |
41 | mouseCaptureConfig.mouseUp(evt);
42 |
43 | $rootScope.$digest();
44 | }
45 | };
46 |
47 | return {
48 |
49 | //
50 | // Register an element to use as the mouse capture element instead of
51 | // the default which is the document.
52 | //
53 | registerElement: function(element) {
54 |
55 | $element = element;
56 | },
57 |
58 | //
59 | // Acquire the 'mouse capture'.
60 | // After acquiring the mouse capture mousemove and mouseup events will be
61 | // forwarded to callbacks in 'config'.
62 | //
63 | acquire: function (evt, config) {
64 |
65 | //
66 | // Release any prior mouse capture.
67 | //
68 | this.release();
69 |
70 | mouseCaptureConfig = config;
71 |
72 | //
73 | // In response to the mousedown event register handlers for mousemove and mouseup
74 | // during 'mouse capture'.
75 | //
76 | $element.mousemove(mouseMove);
77 | $element.mouseup(mouseUp);
78 | },
79 |
80 | //
81 | // Release the 'mouse capture'.
82 | //
83 | release: function () {
84 |
85 | if (mouseCaptureConfig) {
86 |
87 | if (mouseCaptureConfig.released) {
88 | //
89 | // Let the client know that their 'mouse capture' has been released.
90 | //
91 | mouseCaptureConfig.released();
92 | }
93 |
94 | mouseCaptureConfig = null;
95 | }
96 |
97 | $element.unbind("mousemove", mouseMove);
98 | $element.unbind("mouseup", mouseUp);
99 | },
100 | };
101 | }]
102 | )
103 |
104 | //
105 | // Directive that marks the mouse capture element.
106 | //
107 | .directive('mouseCapture', function () {
108 | return {
109 | restrict: 'A',
110 |
111 | controller: ['$scope', '$element', '$attrs', 'mouseCapture',
112 | function($scope, $element, $attrs, mouseCapture) {
113 |
114 | //
115 | // Register the directives element as the mouse capture element.
116 | //
117 | mouseCapture.registerElement($element);
118 |
119 | }],
120 |
121 | };
122 | })
123 | ;
124 |
125 |
--------------------------------------------------------------------------------
/flowchart/svg_class.spec.js:
--------------------------------------------------------------------------------
1 |
2 | describe('svg_class', function () {
3 |
4 | it('removeClassSVG returns false when there is no classes attr', function () {
5 |
6 | var mockElement = {
7 | attr: function () {
8 | return null;
9 | },
10 | };
11 | var testClass = 'foo';
12 |
13 | expect(removeClassSVG(mockElement, testClass)).toBe(false);
14 |
15 | });
16 |
17 | it('removeClassSVG returns false when the element doesnt already have the class', function () {
18 |
19 | var mockElement = {
20 | attr: function () {
21 | return 'smeg';
22 | },
23 | };
24 | var testClass = 'foo';
25 |
26 | expect(removeClassSVG(mockElement, testClass)).toBe(false);
27 |
28 | });
29 |
30 | it('removeClassSVG returns true and removes the class when the element does have the class', function () {
31 |
32 | var testClass = 'foo';
33 |
34 | var mockElement = {
35 | attr: function () {
36 | return testClass;
37 | },
38 | };
39 |
40 | spyOn(mockElement, 'attr').andCallThrough();
41 |
42 | expect(removeClassSVG(mockElement, testClass)).toBe(true);
43 | expect(mockElement.attr).toHaveBeenCalledWith('class', '');
44 |
45 | });
46 |
47 | it('hasClassSVG returns false when attr returns null', function () {
48 |
49 | var mockElement = {
50 | attr: function () {
51 | return null;
52 | },
53 | };
54 |
55 | var testClass = 'foo';
56 |
57 | expect(hasClassSVG(mockElement, testClass)).toBe(false);
58 |
59 | });
60 |
61 | it('hasClassSVG returns false when element has no class', function () {
62 |
63 | var mockElement = {
64 | attr: function () {
65 | return '';
66 | },
67 | };
68 |
69 | var testClass = 'foo';
70 |
71 | expect(hasClassSVG(mockElement, testClass)).toBe(false);
72 |
73 | });
74 |
75 | it('hasClassSVG returns false when element has wrong class', function () {
76 |
77 | var mockElement = {
78 | attr: function () {
79 | return 'smeg';
80 | },
81 | };
82 |
83 | var testClass = 'foo';
84 |
85 | expect(hasClassSVG(mockElement, testClass)).toBe(false);
86 |
87 | });
88 |
89 | it('hasClassSVG returns true when element has correct class', function () {
90 |
91 | var testClass = 'foo';
92 |
93 | var mockElement = {
94 | attr: function () {
95 | return testClass;
96 | },
97 | };
98 |
99 | expect(hasClassSVG(mockElement, testClass)).toBe(true);
100 |
101 | });
102 |
103 | it('hasClassSVG returns true when element 1 correct class of many ', function () {
104 |
105 | var testClass = 'foo';
106 |
107 | var mockElement = {
108 | attr: function () {
109 | return "whar " + testClass + " smeg";
110 | },
111 | };
112 |
113 | expect(hasClassSVG(mockElement, testClass)).toBe(true);
114 | });
115 | });
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | AngularJS-FlowChart
2 | ===================
3 |
4 | A WebUI control for visualizing and editing flow charts.
5 |
6 | This isn't designed to be completely general purpose, but it will be a good basis if you need an SVG flowchart and you are willing to work with AngularJS.
7 |
8 | [Click here to support my work](https://www.codecapers.com.au/about#support-my-work)
9 |
10 | Code Project Article
11 | --------------------
12 |
13 | http://www.codeproject.com/Articles/709340/Implementing-a-Flowchart-with-SVG-and-AngularJS
14 |
15 |
16 | How to use it
17 | -------------
18 |
19 | Include the following Javascript in your HTML file:
20 |
21 | ```html
22 |
23 |
24 |
25 |
26 |
27 | ```
28 |
29 | Make a dependency on the the flowchart's AngularJS module from your application (or other module):
30 |
31 | ```javascript
32 | angular.module('app', ['flowChart', ])
33 | ```
34 |
35 | In your application (or other) controller setup a data-model for the initial flowchart (or AJAX the data-model in from a JSON resource):
36 |
37 | ```javascript
38 | var chartDataModel = {
39 |
40 | nodes: [
41 | {
42 | name: "Example Node 1",
43 | id: 0,
44 | x: 0,
45 | y: 0,
46 | inputConnectors: [
47 | {
48 | name: "A",
49 | },
50 | {
51 | name: "B",
52 | },
53 | {
54 | name: "C",
55 | },
56 | ],
57 | outputConnectors: [
58 | {
59 | name: "A",
60 | },
61 | {
62 | name: "B",
63 | },
64 | {
65 | name: "C",
66 | },
67 | ],
68 | },
69 |
70 | {
71 | name: "Example Node 2",
72 | id: 1,
73 | x: 400,
74 | y: 200,
75 | inputConnectors: [
76 | {
77 | name: "A",
78 | },
79 | {
80 | name: "B",
81 | },
82 | {
83 | name: "C",
84 | },
85 | ],
86 | outputConnectors: [
87 | {
88 | name: "A",
89 | },
90 | {
91 | name: "B",
92 | },
93 | {
94 | name: "C",
95 | },
96 | ],
97 | },
98 |
99 | ],
100 |
101 | connections: [
102 | {
103 | source: {
104 | nodeID: 0,
105 | connectorIndex: 1,
106 | },
107 |
108 | dest: {
109 | nodeID: 1,
110 | connectorIndex: 2,
111 | },
112 | },
113 |
114 |
115 | ]
116 | };
117 | ```
118 |
119 | Also in your controller, wrap the data-model in a view-model and add it to the AngularJS scope:
120 |
121 | ```javascript
122 | $scope.chartViewModel = new flowchart.ChartViewModel(chartDataModel);
123 | ```
124 |
125 | Your code is in direct control of creation of the view-model, so you can interact with it in almost anyway you want.
126 |
127 | Finally instantiate the flowchart's AngularJS directive in your HTML:
128 |
129 | ```html
130 |
134 |
135 | ```
136 |
137 | Be sure to bind your view-model as the 'chart' attribute!
138 |
139 |
140 | Have fun and please contribute!
141 |
--------------------------------------------------------------------------------
/app.js:
--------------------------------------------------------------------------------
1 |
2 | //
3 | // Define the 'app' module.
4 | //
5 | angular.module('app', ['flowChart', ])
6 |
7 | //
8 | // Simple service to create a prompt.
9 | //
10 | .factory('prompt', function () {
11 |
12 | /* Uncomment the following to test that the prompt service is working as expected.
13 | return function () {
14 | return "Test!";
15 | }
16 | */
17 |
18 | // Return the browsers prompt function.
19 | return prompt;
20 | })
21 |
22 | //
23 | // Application controller.
24 | //
25 | .controller('AppCtrl', ['$scope', 'prompt', function AppCtrl ($scope, prompt) {
26 |
27 | //
28 | // Code for the delete key.
29 | //
30 | var deleteKeyCode = 46;
31 |
32 | //
33 | // Code for control key.
34 | //
35 | var ctrlKeyCode = 17;
36 |
37 | //
38 | // Set to true when the ctrl key is down.
39 | //
40 | var ctrlDown = false;
41 |
42 | //
43 | // Code for A key.
44 | //
45 | var aKeyCode = 65;
46 |
47 | //
48 | // Code for esc key.
49 | //
50 | var escKeyCode = 27;
51 |
52 | //
53 | // Selects the next node id.
54 | //
55 | var nextNodeID = 10;
56 |
57 | //
58 | // Setup the data-model for the chart.
59 | //
60 | var chartDataModel = {
61 |
62 | nodes: [
63 | {
64 | name: "Example Node 1",
65 | id: 0,
66 | x: 0,
67 | y: 0,
68 | width: 350,
69 | inputConnectors: [
70 | {
71 | name: "A",
72 | },
73 | {
74 | name: "B",
75 | },
76 | {
77 | name: "C",
78 | },
79 | ],
80 | outputConnectors: [
81 | {
82 | name: "A",
83 | },
84 | {
85 | name: "B",
86 | },
87 | {
88 | name: "C",
89 | },
90 | ],
91 | },
92 |
93 | {
94 | name: "Example Node 2",
95 | id: 1,
96 | x: 400,
97 | y: 200,
98 | inputConnectors: [
99 | {
100 | name: "A",
101 | },
102 | {
103 | name: "B",
104 | },
105 | {
106 | name: "C",
107 | },
108 | ],
109 | outputConnectors: [
110 | {
111 | name: "A",
112 | },
113 | {
114 | name: "B",
115 | },
116 | {
117 | name: "C",
118 | },
119 | ],
120 | },
121 |
122 | ],
123 |
124 | connections: [
125 | {
126 | name:'Connection 1',
127 | source: {
128 | nodeID: 0,
129 | connectorIndex: 1,
130 | },
131 |
132 | dest: {
133 | nodeID: 1,
134 | connectorIndex: 2,
135 | },
136 | },
137 | {
138 | name:'Connection 2',
139 | source: {
140 | nodeID: 0,
141 | connectorIndex: 0,
142 | },
143 |
144 | dest: {
145 | nodeID: 1,
146 | connectorIndex: 0,
147 | },
148 | },
149 |
150 | ]
151 | };
152 |
153 | //
154 | // Event handler for key-down on the flowchart.
155 | //
156 | $scope.keyDown = function (evt) {
157 |
158 | if (evt.keyCode === ctrlKeyCode) {
159 |
160 | ctrlDown = true;
161 | evt.stopPropagation();
162 | evt.preventDefault();
163 | }
164 | };
165 |
166 | //
167 | // Event handler for key-up on the flowchart.
168 | //
169 | $scope.keyUp = function (evt) {
170 |
171 | if (evt.keyCode === deleteKeyCode) {
172 | //
173 | // Delete key.
174 | //
175 | $scope.chartViewModel.deleteSelected();
176 | }
177 |
178 | if (evt.keyCode == aKeyCode && ctrlDown) {
179 | //
180 | // Ctrl + A
181 | //
182 | $scope.chartViewModel.selectAll();
183 | }
184 |
185 | if (evt.keyCode == escKeyCode) {
186 | // Escape.
187 | $scope.chartViewModel.deselectAll();
188 | }
189 |
190 | if (evt.keyCode === ctrlKeyCode) {
191 | ctrlDown = false;
192 |
193 | evt.stopPropagation();
194 | evt.preventDefault();
195 | }
196 | };
197 |
198 | //
199 | // Add a new node to the chart.
200 | //
201 | $scope.addNewNode = function () {
202 |
203 | var nodeName = prompt("Enter a node name:", "New node");
204 | if (!nodeName) {
205 | return;
206 | }
207 |
208 | //
209 | // Template for a new node.
210 | //
211 | var newNodeDataModel = {
212 | name: nodeName,
213 | id: nextNodeID++,
214 | x: 0,
215 | y: 0,
216 | inputConnectors: [
217 | {
218 | name: "X"
219 | },
220 | {
221 | name: "Y"
222 | },
223 | {
224 | name: "Z"
225 | }
226 | ],
227 | outputConnectors: [
228 | {
229 | name: "1"
230 | },
231 | {
232 | name: "2"
233 | },
234 | {
235 | name: "3"
236 | }
237 | ],
238 | };
239 |
240 | $scope.chartViewModel.addNode(newNodeDataModel);
241 | };
242 |
243 | //
244 | // Add an input connector to selected nodes.
245 | //
246 | $scope.addNewInputConnector = function () {
247 | var connectorName = prompt("Enter a connector name:", "New connector");
248 | if (!connectorName) {
249 | return;
250 | }
251 |
252 | var selectedNodes = $scope.chartViewModel.getSelectedNodes();
253 | for (var i = 0; i < selectedNodes.length; ++i) {
254 | var node = selectedNodes[i];
255 | node.addInputConnector({
256 | name: connectorName,
257 | });
258 | }
259 | };
260 |
261 | //
262 | // Add an output connector to selected nodes.
263 | //
264 | $scope.addNewOutputConnector = function () {
265 | var connectorName = prompt("Enter a connector name:", "New connector");
266 | if (!connectorName) {
267 | return;
268 | }
269 |
270 | var selectedNodes = $scope.chartViewModel.getSelectedNodes();
271 | for (var i = 0; i < selectedNodes.length; ++i) {
272 | var node = selectedNodes[i];
273 | node.addOutputConnector({
274 | name: connectorName,
275 | });
276 | }
277 | };
278 |
279 | //
280 | // Delete selected nodes and connections.
281 | //
282 | $scope.deleteSelected = function () {
283 |
284 | $scope.chartViewModel.deleteSelected();
285 | };
286 |
287 | //
288 | // Create the view-model for the chart and attach to the scope.
289 | //
290 | $scope.chartViewModel = new flowchart.ChartViewModel(chartDataModel);
291 | }])
292 | ;
--------------------------------------------------------------------------------
/flowchart/flowchart_template.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/jasmine/lib/jasmine-1.3.1/jasmine.css:
--------------------------------------------------------------------------------
1 | body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; }
2 |
3 | #HTMLReporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; }
4 | #HTMLReporter a { text-decoration: none; }
5 | #HTMLReporter a:hover { text-decoration: underline; }
6 | #HTMLReporter p, #HTMLReporter h1, #HTMLReporter h2, #HTMLReporter h3, #HTMLReporter h4, #HTMLReporter h5, #HTMLReporter h6 { margin: 0; line-height: 14px; }
7 | #HTMLReporter .banner, #HTMLReporter .symbolSummary, #HTMLReporter .summary, #HTMLReporter .resultMessage, #HTMLReporter .specDetail .description, #HTMLReporter .alert .bar, #HTMLReporter .stackTrace { padding-left: 9px; padding-right: 9px; }
8 | #HTMLReporter #jasmine_content { position: fixed; right: 100%; }
9 | #HTMLReporter .version { color: #aaaaaa; }
10 | #HTMLReporter .banner { margin-top: 14px; }
11 | #HTMLReporter .duration { color: #aaaaaa; float: right; }
12 | #HTMLReporter .symbolSummary { overflow: hidden; *zoom: 1; margin: 14px 0; }
13 | #HTMLReporter .symbolSummary li { display: block; float: left; height: 7px; width: 14px; margin-bottom: 7px; font-size: 16px; }
14 | #HTMLReporter .symbolSummary li.passed { font-size: 14px; }
15 | #HTMLReporter .symbolSummary li.passed:before { color: #5e7d00; content: "\02022"; }
16 | #HTMLReporter .symbolSummary li.failed { line-height: 9px; }
17 | #HTMLReporter .symbolSummary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; }
18 | #HTMLReporter .symbolSummary li.skipped { font-size: 14px; }
19 | #HTMLReporter .symbolSummary li.skipped:before { color: #bababa; content: "\02022"; }
20 | #HTMLReporter .symbolSummary li.pending { line-height: 11px; }
21 | #HTMLReporter .symbolSummary li.pending:before { color: #aaaaaa; content: "-"; }
22 | #HTMLReporter .exceptions { color: #fff; float: right; margin-top: 5px; margin-right: 5px; }
23 | #HTMLReporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; }
24 | #HTMLReporter .runningAlert { background-color: #666666; }
25 | #HTMLReporter .skippedAlert { background-color: #aaaaaa; }
26 | #HTMLReporter .skippedAlert:first-child { background-color: #333333; }
27 | #HTMLReporter .skippedAlert:hover { text-decoration: none; color: white; text-decoration: underline; }
28 | #HTMLReporter .passingAlert { background-color: #a6b779; }
29 | #HTMLReporter .passingAlert:first-child { background-color: #5e7d00; }
30 | #HTMLReporter .failingAlert { background-color: #cf867e; }
31 | #HTMLReporter .failingAlert:first-child { background-color: #b03911; }
32 | #HTMLReporter .results { margin-top: 14px; }
33 | #HTMLReporter #details { display: none; }
34 | #HTMLReporter .resultsMenu, #HTMLReporter .resultsMenu a { background-color: #fff; color: #333333; }
35 | #HTMLReporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; }
36 | #HTMLReporter.showDetails .summaryMenuItem:hover { text-decoration: underline; }
37 | #HTMLReporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; }
38 | #HTMLReporter.showDetails .summary { display: none; }
39 | #HTMLReporter.showDetails #details { display: block; }
40 | #HTMLReporter .summaryMenuItem { font-weight: bold; text-decoration: underline; }
41 | #HTMLReporter .summary { margin-top: 14px; }
42 | #HTMLReporter .summary .suite .suite, #HTMLReporter .summary .specSummary { margin-left: 14px; }
43 | #HTMLReporter .summary .specSummary.passed a { color: #5e7d00; }
44 | #HTMLReporter .summary .specSummary.failed a { color: #b03911; }
45 | #HTMLReporter .description + .suite { margin-top: 0; }
46 | #HTMLReporter .suite { margin-top: 14px; }
47 | #HTMLReporter .suite a { color: #333333; }
48 | #HTMLReporter #details .specDetail { margin-bottom: 28px; }
49 | #HTMLReporter #details .specDetail .description { display: block; color: white; background-color: #b03911; }
50 | #HTMLReporter .resultMessage { padding-top: 14px; color: #333333; }
51 | #HTMLReporter .resultMessage span.result { display: block; }
52 | #HTMLReporter .stackTrace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; }
53 |
54 | #TrivialReporter { padding: 8px 13px; position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow-y: scroll; background-color: white; font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; /*.resultMessage {*/ /*white-space: pre;*/ /*}*/ }
55 | #TrivialReporter a:visited, #TrivialReporter a { color: #303; }
56 | #TrivialReporter a:hover, #TrivialReporter a:active { color: blue; }
57 | #TrivialReporter .run_spec { float: right; padding-right: 5px; font-size: .8em; text-decoration: none; }
58 | #TrivialReporter .banner { color: #303; background-color: #fef; padding: 5px; }
59 | #TrivialReporter .logo { float: left; font-size: 1.1em; padding-left: 5px; }
60 | #TrivialReporter .logo .version { font-size: .6em; padding-left: 1em; }
61 | #TrivialReporter .runner.running { background-color: yellow; }
62 | #TrivialReporter .options { text-align: right; font-size: .8em; }
63 | #TrivialReporter .suite { border: 1px outset gray; margin: 5px 0; padding-left: 1em; }
64 | #TrivialReporter .suite .suite { margin: 5px; }
65 | #TrivialReporter .suite.passed { background-color: #dfd; }
66 | #TrivialReporter .suite.failed { background-color: #fdd; }
67 | #TrivialReporter .spec { margin: 5px; padding-left: 1em; clear: both; }
68 | #TrivialReporter .spec.failed, #TrivialReporter .spec.passed, #TrivialReporter .spec.skipped { padding-bottom: 5px; border: 1px solid gray; }
69 | #TrivialReporter .spec.failed { background-color: #fbb; border-color: red; }
70 | #TrivialReporter .spec.passed { background-color: #bfb; border-color: green; }
71 | #TrivialReporter .spec.skipped { background-color: #bbb; }
72 | #TrivialReporter .messages { border-left: 1px dashed gray; padding-left: 1em; padding-right: 1em; }
73 | #TrivialReporter .passed { background-color: #cfc; display: none; }
74 | #TrivialReporter .failed { background-color: #fbb; }
75 | #TrivialReporter .skipped { color: #777; background-color: #eee; display: none; }
76 | #TrivialReporter .resultMessage span.result { display: block; line-height: 2em; color: black; }
77 | #TrivialReporter .resultMessage .mismatch { color: black; }
78 | #TrivialReporter .stackTrace { white-space: pre; font-size: .8em; margin-left: 10px; max-height: 5em; overflow: auto; border: 1px inset red; padding: 1em; background: #eef; }
79 | #TrivialReporter .finished-at { padding-left: 1em; font-size: .6em; }
80 | #TrivialReporter.show-passed .passed, #TrivialReporter.show-skipped .skipped { display: block; }
81 | #TrivialReporter #jasmine_content { position: fixed; right: 100%; }
82 | #TrivialReporter .runner { border: 1px solid gray; display: block; margin: 5px 0; padding: 2px 0 2px 10px; }
83 |
--------------------------------------------------------------------------------
/flowchart/flowchart_directive.js:
--------------------------------------------------------------------------------
1 | //
2 | // Flowchart module.
3 | //
4 | angular.module('flowChart', ['dragging'] )
5 |
6 | //
7 | // Directive that generates the rendered chart from the data model.
8 | //
9 | .directive('flowChart', function() {
10 | return {
11 | restrict: 'E',
12 | templateUrl: "flowchart/flowchart_template.html",
13 | replace: true,
14 | scope: {
15 | chart: "=chart",
16 | },
17 |
18 | //
19 | // Controller for the flowchart directive.
20 | // Having a separate controller is better for unit testing, otherwise
21 | // it is painful to unit test a directive without instantiating the DOM
22 | // (which is possible, just not ideal).
23 | //
24 | controller: 'FlowChartController',
25 | };
26 | })
27 |
28 | //
29 | // Directive that allows the chart to be edited as json in a textarea.
30 | //
31 | .directive('chartJsonEdit', function () {
32 | return {
33 | restrict: 'A',
34 | scope: {
35 | viewModel: "="
36 | },
37 | link: function (scope, elem, attr) {
38 |
39 | //
40 | // Serialize the data model as json and update the textarea.
41 | //
42 | var updateJson = function () {
43 | if (scope.viewModel) {
44 | var json = JSON.stringify(scope.viewModel.data, null, 4);
45 | $(elem).val(json);
46 | }
47 | };
48 |
49 | //
50 | // First up, set the initial value of the textarea.
51 | //
52 | updateJson();
53 |
54 | //
55 | // Watch for changes in the data model and update the textarea whenever necessary.
56 | //
57 | scope.$watch("viewModel.data", updateJson, true);
58 |
59 | //
60 | // Handle the change event from the textarea and update the data model
61 | // from the modified json.
62 | //
63 | $(elem).bind("input propertychange", function () {
64 | var json = $(elem).val();
65 | var dataModel = JSON.parse(json);
66 | scope.viewModel = new flowchart.ChartViewModel(dataModel);
67 |
68 | scope.$digest();
69 | });
70 | }
71 | }
72 |
73 | })
74 |
75 | //
76 | // Controller for the flowchart directive.
77 | // Having a separate controller is better for unit testing, otherwise
78 | // it is painful to unit test a directive without instantiating the DOM
79 | // (which is possible, just not ideal).
80 | //
81 | .controller('FlowChartController', ['$scope', 'dragging', '$element', function FlowChartController ($scope, dragging, $element) {
82 |
83 | var controller = this;
84 |
85 | //
86 | // Reference to the document and jQuery, can be overridden for testting.
87 | //
88 | this.document = document;
89 |
90 | //
91 | // Wrap jQuery so it can easily be mocked for testing.
92 | //
93 | this.jQuery = function (element) {
94 | return $(element);
95 | }
96 |
97 | //
98 | // Init data-model variables.
99 | //
100 | $scope.draggingConnection = false;
101 | $scope.connectorSize = 10;
102 | $scope.dragSelecting = false;
103 | /* Can use this to test the drag selection rect.
104 | $scope.dragSelectionRect = {
105 | x: 0,
106 | y: 0,
107 | width: 0,
108 | height: 0,
109 | };
110 | */
111 |
112 | //
113 | // Reference to the connection, connector or node that the mouse is currently over.
114 | //
115 | $scope.mouseOverConnector = null;
116 | $scope.mouseOverConnection = null;
117 | $scope.mouseOverNode = null;
118 |
119 | //
120 | // The class for connections and connectors.
121 | //
122 | this.connectionClass = 'connection';
123 | this.connectorClass = 'connector';
124 | this.nodeClass = 'node';
125 |
126 | //
127 | // Search up the HTML element tree for an element the requested class.
128 | //
129 | this.searchUp = function (element, parentClass) {
130 |
131 | //
132 | // Reached the root.
133 | //
134 | if (element == null || element.length == 0) {
135 | return null;
136 | }
137 |
138 | //
139 | // Check if the element has the class that identifies it as a connector.
140 | //
141 | if (hasClassSVG(element, parentClass)) {
142 | //
143 | // Found the connector element.
144 | //
145 | return element;
146 | }
147 |
148 | //
149 | // Recursively search parent elements.
150 | //
151 | return this.searchUp(element.parent(), parentClass);
152 | };
153 |
154 | //
155 | // Hit test and retreive node and connector that was hit at the specified coordinates.
156 | //
157 | this.hitTest = function (clientX, clientY) {
158 |
159 | //
160 | // Retreive the element the mouse is currently over.
161 | //
162 | return this.document.elementFromPoint(clientX, clientY);
163 | };
164 |
165 | //
166 | // Hit test and retreive node and connector that was hit at the specified coordinates.
167 | //
168 | this.checkForHit = function (mouseOverElement, whichClass) {
169 |
170 | //
171 | // Find the parent element, if any, that is a connector.
172 | //
173 | var hoverElement = this.searchUp(this.jQuery(mouseOverElement), whichClass);
174 | if (!hoverElement) {
175 | return null;
176 | }
177 |
178 | return hoverElement.scope();
179 | };
180 |
181 | //
182 | // Translate the coordinates so they are relative to the svg element.
183 | //
184 | this.translateCoordinates = function(x, y, evt) {
185 | var svg_elem = $element.get(0);
186 | var matrix = svg_elem.getScreenCTM();
187 | var point = svg_elem.createSVGPoint();
188 | point.x = x - evt.view.pageXOffset;
189 | point.y = y - evt.view.pageYOffset;
190 | return point.matrixTransform(matrix.inverse());
191 | };
192 |
193 | //
194 | // Called on mouse down in the chart.
195 | //
196 | $scope.mouseDown = function (evt) {
197 |
198 | $scope.chart.deselectAll();
199 |
200 | dragging.startDrag(evt, {
201 |
202 | //
203 | // Commence dragging... setup variables to display the drag selection rect.
204 | //
205 | dragStarted: function (x, y) {
206 | $scope.dragSelecting = true;
207 | var startPoint = controller.translateCoordinates(x, y, evt);
208 | $scope.dragSelectionStartPoint = startPoint;
209 | $scope.dragSelectionRect = {
210 | x: startPoint.x,
211 | y: startPoint.y,
212 | width: 0,
213 | height: 0,
214 | };
215 | },
216 |
217 | //
218 | // Update the drag selection rect while dragging continues.
219 | //
220 | dragging: function (x, y) {
221 | var startPoint = $scope.dragSelectionStartPoint;
222 | var curPoint = controller.translateCoordinates(x, y, evt);
223 |
224 | $scope.dragSelectionRect = {
225 | x: curPoint.x > startPoint.x ? startPoint.x : curPoint.x,
226 | y: curPoint.y > startPoint.y ? startPoint.y : curPoint.y,
227 | width: curPoint.x > startPoint.x ? curPoint.x - startPoint.x : startPoint.x - curPoint.x,
228 | height: curPoint.y > startPoint.y ? curPoint.y - startPoint.y : startPoint.y - curPoint.y,
229 | };
230 | },
231 |
232 | //
233 | // Dragging has ended... select all that are within the drag selection rect.
234 | //
235 | dragEnded: function () {
236 | $scope.dragSelecting = false;
237 | $scope.chart.applySelectionRect($scope.dragSelectionRect);
238 | delete $scope.dragSelectionStartPoint;
239 | delete $scope.dragSelectionRect;
240 | },
241 | });
242 | };
243 |
244 | //
245 | // Called for each mouse move on the svg element.
246 | //
247 | $scope.mouseMove = function (evt) {
248 |
249 | //
250 | // Clear out all cached mouse over elements.
251 | //
252 | $scope.mouseOverConnection = null;
253 | $scope.mouseOverConnector = null;
254 | $scope.mouseOverNode = null;
255 |
256 | var mouseOverElement = controller.hitTest(evt.clientX, evt.clientY);
257 | if (mouseOverElement == null) {
258 | // Mouse isn't over anything, just clear all.
259 | return;
260 | }
261 |
262 | if (!$scope.draggingConnection) { // Only allow 'connection mouse over' when not dragging out a connection.
263 |
264 | // Figure out if the mouse is over a connection.
265 | var scope = controller.checkForHit(mouseOverElement, controller.connectionClass);
266 | $scope.mouseOverConnection = (scope && scope.connection) ? scope.connection : null;
267 | if ($scope.mouseOverConnection) {
268 | // Don't attempt to mouse over anything else.
269 | return;
270 | }
271 | }
272 |
273 | // Figure out if the mouse is over a connector.
274 | var scope = controller.checkForHit(mouseOverElement, controller.connectorClass);
275 | $scope.mouseOverConnector = (scope && scope.connector) ? scope.connector : null;
276 | if ($scope.mouseOverConnector) {
277 | // Don't attempt to mouse over anything else.
278 | return;
279 | }
280 |
281 | // Figure out if the mouse is over a node.
282 | var scope = controller.checkForHit(mouseOverElement, controller.nodeClass);
283 | $scope.mouseOverNode = (scope && scope.node) ? scope.node : null;
284 | };
285 |
286 | //
287 | // Handle mousedown on a node.
288 | //
289 | $scope.nodeMouseDown = function (evt, node) {
290 |
291 | var chart = $scope.chart;
292 | var lastMouseCoords;
293 |
294 | dragging.startDrag(evt, {
295 |
296 | //
297 | // Node dragging has commenced.
298 | //
299 | dragStarted: function (x, y) {
300 |
301 | lastMouseCoords = controller.translateCoordinates(x, y, evt);
302 |
303 | //
304 | // If nothing is selected when dragging starts,
305 | // at least select the node we are dragging.
306 | //
307 | if (!node.selected()) {
308 | chart.deselectAll();
309 | node.select();
310 | }
311 | },
312 |
313 | //
314 | // Dragging selected nodes... update their x,y coordinates.
315 | //
316 | dragging: function (x, y) {
317 |
318 | var curCoords = controller.translateCoordinates(x, y, evt);
319 | var deltaX = curCoords.x - lastMouseCoords.x;
320 | var deltaY = curCoords.y - lastMouseCoords.y;
321 |
322 | chart.updateSelectedNodesLocation(deltaX, deltaY);
323 |
324 | lastMouseCoords = curCoords;
325 | },
326 |
327 | //
328 | // The node wasn't dragged... it was clicked.
329 | //
330 | clicked: function () {
331 | chart.handleNodeClicked(node, evt.ctrlKey);
332 | },
333 |
334 | });
335 | };
336 |
337 | //
338 | // Handle mousedown on a connection.
339 | //
340 | $scope.connectionMouseDown = function (evt, connection) {
341 | var chart = $scope.chart;
342 | chart.handleConnectionMouseDown(connection, evt.ctrlKey);
343 |
344 | // Don't let the chart handle the mouse down.
345 | evt.stopPropagation();
346 | evt.preventDefault();
347 | };
348 |
349 | //
350 | // Handle mousedown on an input connector.
351 | //
352 | $scope.connectorMouseDown = function (evt, node, connector, connectorIndex, isInputConnector) {
353 |
354 | //
355 | // Initiate dragging out of a connection.
356 | //
357 | dragging.startDrag(evt, {
358 |
359 | //
360 | // Called when the mouse has moved greater than the threshold distance
361 | // and dragging has commenced.
362 | //
363 | dragStarted: function (x, y) {
364 |
365 | var curCoords = controller.translateCoordinates(x, y, evt);
366 |
367 | $scope.draggingConnection = true;
368 | $scope.dragPoint1 = flowchart.computeConnectorPos(node, connectorIndex, isInputConnector);
369 | $scope.dragPoint2 = {
370 | x: curCoords.x,
371 | y: curCoords.y
372 | };
373 | $scope.dragTangent1 = flowchart.computeConnectionSourceTangent($scope.dragPoint1, $scope.dragPoint2);
374 | $scope.dragTangent2 = flowchart.computeConnectionDestTangent($scope.dragPoint1, $scope.dragPoint2);
375 | },
376 |
377 | //
378 | // Called on mousemove while dragging out a connection.
379 | //
380 | dragging: function (x, y, evt) {
381 | var startCoords = controller.translateCoordinates(x, y, evt);
382 | $scope.dragPoint1 = flowchart.computeConnectorPos(node, connectorIndex, isInputConnector);
383 | $scope.dragPoint2 = {
384 | x: startCoords.x,
385 | y: startCoords.y
386 | };
387 | $scope.dragTangent1 = flowchart.computeConnectionSourceTangent($scope.dragPoint1, $scope.dragPoint2);
388 | $scope.dragTangent2 = flowchart.computeConnectionDestTangent($scope.dragPoint1, $scope.dragPoint2);
389 | },
390 |
391 | //
392 | // Clean up when dragging has finished.
393 | //
394 | dragEnded: function () {
395 |
396 | if ($scope.mouseOverConnector &&
397 | $scope.mouseOverConnector !== connector) {
398 |
399 | //
400 | // Dragging has ended...
401 | // The mouse is over a valid connector...
402 | // Create a new connection.
403 | //
404 | $scope.chart.createNewConnection(connector, $scope.mouseOverConnector);
405 | }
406 |
407 | $scope.draggingConnection = false;
408 | delete $scope.dragPoint1;
409 | delete $scope.dragTangent1;
410 | delete $scope.dragPoint2;
411 | delete $scope.dragTangent2;
412 | },
413 |
414 | });
415 | };
416 | }])
417 | ;
418 |
--------------------------------------------------------------------------------
/flowchart/flowchart_directive.spec.js:
--------------------------------------------------------------------------------
1 |
2 | describe('flowchart-directive', function () {
3 |
4 | var testObject;
5 | var mockScope;
6 | var mockDragging;
7 | var mockSvgElement;
8 |
9 | //
10 | // Bring in the flowChart module before each test.
11 | //
12 | beforeEach(module('flowChart'));
13 |
14 | //
15 | // Helper function to create the controller for each test.
16 | //
17 | var createController = function ($rootScope, $controller) {
18 |
19 | mockScope = $rootScope.$new();
20 | mockDragging = createMockDragging();
21 | mockSvgElement = {
22 | get: function () {
23 | return createMockSvgElement();
24 | }
25 | };
26 |
27 | testObject = $controller('FlowChartController', {
28 | $scope: mockScope,
29 | dragging: mockDragging,
30 | $element: mockSvgElement,
31 | });
32 | };
33 |
34 | //
35 | // Setup the controller before each test.
36 | //
37 | beforeEach(inject(function ($rootScope, $controller) {
38 |
39 | createController($rootScope, $controller);
40 | }));
41 |
42 | //
43 | // Create a mock DOM element.
44 | //
45 | var createMockElement = function(attr, parent, scope) {
46 | return {
47 | attr: function() {
48 | return attr;
49 | },
50 |
51 | parent: function () {
52 | return parent;
53 | },
54 |
55 | scope: function () {
56 | return scope || {};
57 | },
58 |
59 | };
60 | }
61 |
62 | //
63 | // Create a mock node data model.
64 | //
65 | var createMockNode = function (inputConnectors, outputConnectors) {
66 | return {
67 | x: function () { return 0 },
68 | y: function () { return 0 },
69 | inputConnectors: inputConnectors || [],
70 | outputConnectors: outputConnectors || [],
71 | select: jasmine.createSpy(),
72 | selected: function () { return false; },
73 | };
74 | };
75 |
76 | //
77 | // Create a mock chart.
78 | //
79 | var createMockChart = function (mockNodes, mockConnections) {
80 | return {
81 | nodes: mockNodes,
82 | connections: mockConnections,
83 |
84 | handleNodeClicked: jasmine.createSpy(),
85 | handleConnectionMouseDown: jasmine.createSpy(),
86 | updateSelectedNodesLocation: jasmine.createSpy(),
87 | deselectAll: jasmine.createSpy(),
88 | createNewConnection: jasmine.createSpy(),
89 | applySelectionRect: jasmine.createSpy(),
90 | };
91 | };
92 |
93 | //
94 | // Create a mock dragging service.
95 | //
96 | var createMockDragging = function () {
97 |
98 | var mockDragging = {
99 | startDrag: function (evt, config) {
100 | mockDragging.evt = evt;
101 | mockDragging.config = config;
102 | },
103 | };
104 |
105 | return mockDragging;
106 | };
107 |
108 | //
109 | // Create a mock version of the SVG element.
110 | //
111 | var createMockSvgElement = function () {
112 | return {
113 | getScreenCTM: function () {
114 | return {
115 | inverse: function () {
116 | return this;
117 | },
118 | };
119 | },
120 |
121 | createSVGPoint: function () {
122 | return {
123 | x: 0,
124 | y: 0 ,
125 | matrixTransform: function () {
126 | return this;
127 | },
128 | };
129 | }
130 |
131 |
132 |
133 | };
134 | };
135 |
136 | it('searchUp returns null when at root 1', function () {
137 |
138 | expect(testObject.searchUp(null, "some-class")).toBe(null);
139 | });
140 |
141 |
142 | it('searchUp returns null when at root 2', function () {
143 |
144 | expect(testObject.searchUp([], "some-class")).toBe(null);
145 | });
146 |
147 | it('searchUp returns element when it has requested class', function () {
148 |
149 | var whichClass = "some-class";
150 | var mockElement = createMockElement(whichClass);
151 |
152 | expect(testObject.searchUp(mockElement, whichClass)).toBe(mockElement);
153 | });
154 |
155 | it('searchUp returns parent when it has requested class', function () {
156 |
157 | var whichClass = "some-class";
158 | var mockParent = createMockElement(whichClass);
159 | var mockElement = createMockElement('', mockParent);
160 |
161 | expect(testObject.searchUp(mockElement, whichClass)).toBe(mockParent);
162 | });
163 |
164 | it('hitTest returns result of elementFromPoint', function () {
165 |
166 | var mockElement = {};
167 |
168 | // Mock out the document.
169 | testObject.document = {
170 | elementFromPoint: function () {
171 | return mockElement;
172 | },
173 | };
174 |
175 | expect(testObject.hitTest(12, 30)).toBe(mockElement);
176 | });
177 |
178 | it('checkForHit returns null when the hit element has no parent with requested class', function () {
179 |
180 | var mockElement = createMockElement(null, null);
181 |
182 | testObject.jQuery = function (input) {
183 | return input;
184 | };
185 |
186 | expect(testObject.checkForHit(mockElement, "some-class")).toBe(null);
187 | });
188 |
189 | it('checkForHit returns the result of searchUp when found', function () {
190 |
191 | var mockConnectorScope = {};
192 |
193 | var whichClass = "some-class";
194 | var mockElement = createMockElement(whichClass, null, mockConnectorScope);
195 |
196 | testObject.jQuery = function (input) {
197 | return input;
198 | };
199 |
200 | expect(testObject.checkForHit(mockElement, whichClass)).toBe(mockConnectorScope);
201 | });
202 |
203 | it('checkForHit returns null when searchUp fails', function () {
204 |
205 | var mockElement = createMockElement(null, null, null);
206 |
207 | testObject.jQuery = function (input) {
208 | return input;
209 | };
210 |
211 | expect(testObject.checkForHit(mockElement, "some-class")).toBe(null);
212 | });
213 |
214 | it('test node dragging is started on node mouse down', function () {
215 |
216 | mockDragging.startDrag = jasmine.createSpy();
217 |
218 | var mockEvt = {};
219 | var mockNode = createMockNode();
220 |
221 | mockScope.nodeMouseDown(mockEvt, mockNode);
222 |
223 | expect(mockDragging.startDrag).toHaveBeenCalled();
224 |
225 | });
226 |
227 | it('test node click handling is forwarded to view model', function () {
228 |
229 | mockScope.chart = createMockChart([mockNode]);
230 |
231 | var mockEvt = {
232 | ctrlKey: false,
233 | };
234 | var mockNode = createMockNode();
235 |
236 | mockScope.nodeMouseDown(mockEvt, mockNode);
237 |
238 | mockDragging.config.clicked();
239 |
240 | expect(mockScope.chart.handleNodeClicked).toHaveBeenCalledWith(mockNode, false);
241 | });
242 |
243 | it('test control + node click handling is forwarded to view model', function () {
244 |
245 | var mockNode = createMockNode();
246 |
247 | mockScope.chart = createMockChart([mockNode]);
248 |
249 | var mockEvt = {
250 | ctrlKey: true,
251 | };
252 |
253 | mockScope.nodeMouseDown(mockEvt, mockNode);
254 |
255 | mockDragging.config.clicked();
256 |
257 | expect(mockScope.chart.handleNodeClicked).toHaveBeenCalledWith(mockNode, true);
258 | });
259 |
260 | it('test node dragging updates selected nodes location', function () {
261 |
262 | var mockEvt = {
263 | view: {
264 | pageXOffset: 0,
265 | pageYOffset: 0,
266 | },
267 | };
268 |
269 | mockScope.chart = createMockChart([createMockNode()]);
270 |
271 | mockScope.nodeMouseDown(mockEvt, mockScope.chart.nodes[0]);
272 |
273 | var xIncrement = 5;
274 | var yIncrement = 15;
275 |
276 | mockDragging.config.dragStarted(0, 0);
277 | mockDragging.config.dragging(xIncrement, yIncrement);
278 |
279 | expect(mockScope.chart.updateSelectedNodesLocation).toHaveBeenCalledWith(xIncrement, yIncrement);
280 | });
281 |
282 | it('test node dragging doesnt modify selection when node is already selected', function () {
283 |
284 | var mockNode1 = createMockNode();
285 | var mockNode2 = createMockNode();
286 |
287 | mockScope.chart = createMockChart([mockNode1, mockNode2]);
288 |
289 | mockNode2.selected = function () { return true; }
290 |
291 | var mockEvt = {
292 | view: {
293 | scrollX: 0,
294 | scrollY: 0,
295 | },
296 | };
297 |
298 | mockScope.nodeMouseDown(mockEvt, mockNode2);
299 |
300 | mockDragging.config.dragStarted(0, 0);
301 |
302 | expect(mockScope.chart.deselectAll).not.toHaveBeenCalled();
303 | });
304 |
305 | it('test node dragging selects node, when the node is not already selected', function () {
306 |
307 | var mockNode1 = createMockNode();
308 | var mockNode2 = createMockNode();
309 |
310 | mockScope.chart = createMockChart([mockNode1, mockNode2]);
311 |
312 | var mockEvt = {
313 | view: {
314 | scrollX: 0,
315 | scrollY: 0,
316 | },
317 | };
318 |
319 | mockScope.nodeMouseDown(mockEvt, mockNode2);
320 |
321 | mockDragging.config.dragStarted(0, 0);
322 |
323 | expect(mockScope.chart.deselectAll).toHaveBeenCalled();
324 | expect(mockNode2.select).toHaveBeenCalled();
325 | });
326 |
327 | it('test connection click handling is forwarded to view model', function () {
328 |
329 | var mockNode = createMockNode();
330 |
331 | var mockEvt = {
332 | stopPropagation: jasmine.createSpy(),
333 | preventDefault: jasmine.createSpy(),
334 | ctrlKey: false,
335 | };
336 | var mockConnection = {};
337 |
338 | mockScope.chart = createMockChart([mockNode]);
339 |
340 | mockScope.connectionMouseDown(mockEvt, mockConnection);
341 |
342 | expect(mockScope.chart.handleConnectionMouseDown).toHaveBeenCalledWith(mockConnection, false);
343 | expect(mockEvt.stopPropagation).toHaveBeenCalled();
344 | expect(mockEvt.preventDefault).toHaveBeenCalled();
345 | });
346 |
347 | it('test control + connection click handling is forwarded to view model', function () {
348 |
349 | var mockNode = createMockNode();
350 |
351 | var mockEvt = {
352 | stopPropagation: jasmine.createSpy(),
353 | preventDefault: jasmine.createSpy(),
354 | ctrlKey: true,
355 | };
356 | var mockConnection = {};
357 |
358 | mockScope.chart = createMockChart([mockNode]);
359 |
360 | mockScope.connectionMouseDown(mockEvt, mockConnection);
361 |
362 | expect(mockScope.chart.handleConnectionMouseDown).toHaveBeenCalledWith(mockConnection, true);
363 | });
364 |
365 | it('test selection is cleared when background is clicked', function () {
366 |
367 | var mockEvt = {};
368 |
369 | mockScope.chart = createMockChart([createMockNode()]);
370 |
371 | mockScope.chart.nodes[0].selected = true;
372 |
373 | mockScope.mouseDown(mockEvt);
374 |
375 | expect(mockScope.chart.deselectAll).toHaveBeenCalled();
376 | });
377 |
378 | it('test background mouse down commences selection dragging', function () {
379 |
380 | var mockNode = createMockNode();
381 | var mockEvt = {
382 | view: {
383 | scrollX: 0,
384 | scrollY: 0,
385 | },
386 | };
387 |
388 | mockScope.chart = createMockChart([mockNode]);
389 |
390 | mockScope.mouseDown(mockEvt);
391 |
392 | mockDragging.config.dragStarted(0, 0);
393 |
394 | expect(mockScope.dragSelecting).toBe(true);
395 | });
396 |
397 | it('test can end selection dragging', function () {
398 |
399 | var mockNode = createMockNode();
400 | var mockEvt = {
401 | view: {
402 | scrollX: 0,
403 | scrollY: 0,
404 | },
405 | };
406 |
407 | mockScope.chart = createMockChart([mockNode]);
408 |
409 | mockScope.mouseDown(mockEvt);
410 |
411 | mockDragging.config.dragStarted(0, 0, mockEvt);
412 | mockDragging.config.dragging(0, 0, mockEvt);
413 | mockDragging.config.dragEnded();
414 |
415 | expect(mockScope.dragSelecting).toBe(false);
416 | });
417 |
418 | it('test selection dragging ends by selecting nodes', function () {
419 |
420 | var mockNode = createMockNode();
421 | var mockEvt = {
422 | view: {
423 | scrollX: 0,
424 | scrollY: 0,
425 | },
426 | };
427 |
428 | mockScope.chart = createMockChart([mockNode]);
429 |
430 | mockScope.mouseDown(mockEvt);
431 |
432 | mockDragging.config.dragStarted(0, 0, mockEvt);
433 | mockDragging.config.dragging(0, 0, mockEvt);
434 |
435 | var selectionRect = {
436 | x: 1,
437 | y: 2,
438 | width: 3,
439 | height: 4,
440 | };
441 |
442 | mockScope.dragSelectionRect = selectionRect;
443 |
444 | mockDragging.config.dragEnded();
445 |
446 | expect(mockScope.chart.applySelectionRect).toHaveBeenCalledWith(selectionRect);
447 | });
448 |
449 | it('test mouse down commences connection dragging', function () {
450 |
451 | var mockNode = createMockNode();
452 | var mockEvt = {
453 | view: {
454 | scrollX: 0,
455 | scrollY: 0,
456 | },
457 | };
458 |
459 | mockScope.chart = createMockChart([mockNode]);
460 |
461 | mockScope.connectorMouseDown(mockEvt, mockScope.chart.nodes[0], mockScope.chart.nodes[0].inputConnectors[0], 0, false);
462 |
463 | mockDragging.config.dragStarted(0, 0);
464 |
465 | expect(mockScope.draggingConnection).toBe(true);
466 | });
467 |
468 | it('test can end connection dragging', function () {
469 |
470 | var mockNode = createMockNode();
471 | var mockEvt = {
472 | view: {
473 | scrollX: 0,
474 | scrollY: 0,
475 | },
476 | };
477 |
478 | mockScope.chart = createMockChart([mockNode]);
479 |
480 | mockScope.connectorMouseDown(mockEvt, mockScope.chart.nodes[0], mockScope.chart.nodes[0].inputConnectors[0], 0, false);
481 |
482 | mockDragging.config.dragStarted(0, 0, mockEvt);
483 | mockDragging.config.dragging(0, 0, mockEvt);
484 | mockDragging.config.dragEnded();
485 |
486 | expect(mockScope.draggingConnection).toBe(false);
487 | });
488 |
489 | it('test can make a connection by dragging', function () {
490 |
491 | var mockNode = createMockNode();
492 | var mockDraggingConnector = {};
493 | var mockDragOverConnector = {};
494 | var mockEvt = {
495 | view: {
496 | scrollX: 0,
497 | scrollY: 0,
498 | },
499 | };
500 |
501 | mockScope.chart = createMockChart([mockNode]);
502 |
503 | mockScope.connectorMouseDown(mockEvt, mockScope.chart.nodes[0], mockDraggingConnector, 0, false);
504 |
505 | mockDragging.config.dragStarted(0, 0, mockEvt);
506 | mockDragging.config.dragging(0, 0, mockEvt);
507 |
508 | // Fake out the mouse over connector.
509 | mockScope.mouseOverConnector = mockDragOverConnector;
510 |
511 | mockDragging.config.dragEnded();
512 |
513 | expect(mockScope.chart.createNewConnection).toHaveBeenCalledWith(mockDraggingConnector, mockDragOverConnector);
514 | });
515 |
516 | it('test connection creation by dragging is cancelled when dragged over invalid connector', function () {
517 |
518 | var mockNode = createMockNode();
519 | var mockDraggingConnector = {};
520 | var mockEvt = {
521 | view: {
522 | scrollX: 0,
523 | scrollY: 0,
524 | },
525 | };
526 |
527 | mockScope.chart = createMockChart([mockNode]);
528 |
529 | mockScope.connectorMouseDown(mockEvt, mockScope.chart.nodes[0], mockDraggingConnector, 0, false);
530 |
531 | mockDragging.config.dragStarted(0, 0, mockEvt);
532 | mockDragging.config.dragging(0, 0, mockEvt);
533 |
534 | // Fake out the invalid connector.
535 | mockScope.mouseOverConnector = null;
536 |
537 | mockDragging.config.dragEnded();
538 |
539 | expect(mockScope.chart.createNewConnection).not.toHaveBeenCalled();
540 | });
541 |
542 | it('mouse move over connection caches the connection', function () {
543 |
544 | var mockElement = {};
545 | var mockConnection = {};
546 | var mockConnectionScope = {
547 | connection: mockConnection
548 | };
549 | var mockEvent = {};
550 |
551 | //
552 | // Fake out the function that check if a connection has been hit.
553 | //
554 | testObject.checkForHit = function (element, whichClass) {
555 | if (whichClass === testObject.connectionClass) {
556 | return mockConnectionScope;
557 | }
558 |
559 | return null;
560 | };
561 |
562 | testObject.hitTest = function () {
563 | return mockElement;
564 | };
565 |
566 | mockScope.mouseMove(mockEvent);
567 |
568 | expect(mockScope.mouseOverConnection).toBe(mockConnection);
569 | });
570 |
571 | it('test mouse over connection clears mouse over connector and node', function () {
572 |
573 | var mockElement = {};
574 | var mockConnection = {};
575 | var mockConnectionScope = {
576 | connection: mockConnection
577 | };
578 | var mockEvent = {};
579 |
580 | //
581 | // Fake out the function that check if a connection has been hit.
582 | //
583 | testObject.checkForHit = function (element, whichClass) {
584 | if (whichClass === testObject.connectionClass) {
585 | return mockConnectionScope;
586 | }
587 |
588 | return null;
589 | };
590 |
591 | testObject.hitTest = function () {
592 | return mockElement;
593 | };
594 |
595 |
596 | mockScope.mouseOverConnector = {};
597 | mockScope.mouseOverNode = {};
598 |
599 | mockScope.mouseMove(mockEvent);
600 |
601 | expect(mockScope.mouseOverConnector).toBe(null);
602 | expect(mockScope.mouseOverNode).toBe(null);
603 | });
604 |
605 | it('test mouseMove handles mouse over connector', function () {
606 |
607 | var mockElement = {};
608 | var mockConnector = {};
609 | var mockConnectorScope = {
610 | connector: mockConnector
611 | };
612 | var mockEvent = {};
613 |
614 | //
615 | // Fake out the function that check if a connector has been hit.
616 | //
617 | testObject.checkForHit = function (element, whichClass) {
618 | if (whichClass === testObject.connectorClass) {
619 | return mockConnectorScope;
620 | }
621 |
622 | return null;
623 | };
624 |
625 | testObject.hitTest = function () {
626 | return mockElement;
627 | };
628 |
629 | mockScope.mouseMove(mockEvent);
630 |
631 | expect(mockScope.mouseOverConnector).toBe(mockConnector);
632 | });
633 |
634 | it('test mouseMove handles mouse over node', function () {
635 |
636 | var mockElement = {};
637 | var mockNode = {};
638 | var mockNodeScope = {
639 | node: mockNode
640 | };
641 | var mockEvent = {};
642 |
643 | //
644 | // Fake out the function that check if a connector has been hit.
645 | //
646 | testObject.checkForHit = function (element, whichClass) {
647 | if (whichClass === testObject.nodeClass) {
648 | return mockNodeScope;
649 | }
650 |
651 | return null;
652 | };
653 |
654 | testObject.hitTest = function () {
655 | return mockElement;
656 | };
657 |
658 | mockScope.mouseMove(mockEvent);
659 |
660 | expect(mockScope.mouseOverNode).toBe(mockNode);
661 | });
662 | });
663 |
--------------------------------------------------------------------------------
/flowchart/flowchart_viewmodel.js:
--------------------------------------------------------------------------------
1 |
2 | //
3 | // Global accessor.
4 | //
5 | var flowchart = {
6 |
7 | };
8 |
9 | // Module.
10 | (function () {
11 |
12 | //
13 | // Width of a node.
14 | //
15 | flowchart.defaultNodeWidth = 250;
16 |
17 | //
18 | // Amount of space reserved for displaying the node's name.
19 | //
20 | flowchart.nodeNameHeight = 40;
21 |
22 | //
23 | // Height of a connector in a node.
24 | //
25 | flowchart.connectorHeight = 35;
26 |
27 | //
28 | // Compute the Y coordinate of a connector, given its index.
29 | //
30 | flowchart.computeConnectorY = function (connectorIndex) {
31 | return flowchart.nodeNameHeight + (connectorIndex * flowchart.connectorHeight);
32 | }
33 |
34 | //
35 | // Compute the position of a connector in the graph.
36 | //
37 | flowchart.computeConnectorPos = function (node, connectorIndex, inputConnector) {
38 | return {
39 | x: node.x() + (inputConnector ? 0 : node.width ? node.width() : flowchart.defaultNodeWidth),
40 | y: node.y() + flowchart.computeConnectorY(connectorIndex),
41 | };
42 | };
43 |
44 | //
45 | // View model for a connector.
46 | //
47 | flowchart.ConnectorViewModel = function (connectorDataModel, x, y, parentNode) {
48 |
49 | this.data = connectorDataModel;
50 | this._parentNode = parentNode;
51 | this._x = x;
52 | this._y = y;
53 |
54 | //
55 | // The name of the connector.
56 | //
57 | this.name = function () {
58 | return this.data.name;
59 | }
60 |
61 | //
62 | // X coordinate of the connector.
63 | //
64 | this.x = function () {
65 | return this._x;
66 | };
67 |
68 | //
69 | // Y coordinate of the connector.
70 | //
71 | this.y = function () {
72 | return this._y;
73 | };
74 |
75 | //
76 | // The parent node that the connector is attached to.
77 | //
78 | this.parentNode = function () {
79 | return this._parentNode;
80 | };
81 | };
82 |
83 | //
84 | // Create view model for a list of data models.
85 | //
86 | var createConnectorsViewModel = function (connectorDataModels, x, parentNode) {
87 | var viewModels = [];
88 |
89 | if (connectorDataModels) {
90 | for (var i = 0; i < connectorDataModels.length; ++i) {
91 | var connectorViewModel =
92 | new flowchart.ConnectorViewModel(connectorDataModels[i], x, flowchart.computeConnectorY(i), parentNode);
93 | viewModels.push(connectorViewModel);
94 | }
95 | }
96 |
97 | return viewModels;
98 | };
99 |
100 | //
101 | // View model for a node.
102 | //
103 | flowchart.NodeViewModel = function (nodeDataModel) {
104 |
105 | this.data = nodeDataModel;
106 |
107 | // set the default width value of the node
108 | if (!this.data.width || this.data.width < 0) {
109 | this.data.width = flowchart.defaultNodeWidth;
110 | }
111 | this.inputConnectors = createConnectorsViewModel(this.data.inputConnectors, 0, this);
112 | this.outputConnectors = createConnectorsViewModel(this.data.outputConnectors, this.data.width, this);
113 |
114 | // Set to true when the node is selected.
115 | this._selected = false;
116 |
117 | //
118 | // Name of the node.
119 | //
120 | this.name = function () {
121 | return this.data.name || "";
122 | };
123 |
124 | //
125 | // X coordinate of the node.
126 | //
127 | this.x = function () {
128 | return this.data.x;
129 | };
130 |
131 | //
132 | // Y coordinate of the node.
133 | //
134 | this.y = function () {
135 | return this.data.y;
136 | };
137 |
138 | //
139 | // Width of the node.
140 | //
141 | this.width = function () {
142 | return this.data.width;
143 | }
144 |
145 | //
146 | // Height of the node.
147 | //
148 | this.height = function () {
149 | var numConnectors =
150 | Math.max(
151 | this.inputConnectors.length,
152 | this.outputConnectors.length);
153 | return flowchart.computeConnectorY(numConnectors);
154 | }
155 |
156 | //
157 | // Select the node.
158 | //
159 | this.select = function () {
160 | this._selected = true;
161 | };
162 |
163 | //
164 | // Deselect the node.
165 | //
166 | this.deselect = function () {
167 | this._selected = false;
168 | };
169 |
170 | //
171 | // Toggle the selection state of the node.
172 | //
173 | this.toggleSelected = function () {
174 | this._selected = !this._selected;
175 | };
176 |
177 | //
178 | // Returns true if the node is selected.
179 | //
180 | this.selected = function () {
181 | return this._selected;
182 | };
183 |
184 | //
185 | // Internal function to add a connector.
186 | this._addConnector = function (connectorDataModel, x, connectorsDataModel, connectorsViewModel) {
187 | var connectorViewModel =
188 | new flowchart.ConnectorViewModel(connectorDataModel, x,
189 | flowchart.computeConnectorY(connectorsViewModel.length), this);
190 |
191 | connectorsDataModel.push(connectorDataModel);
192 |
193 | // Add to node's view model.
194 | connectorsViewModel.push(connectorViewModel);
195 | }
196 |
197 | //
198 | // Add an input connector to the node.
199 | //
200 | this.addInputConnector = function (connectorDataModel) {
201 |
202 | if (!this.data.inputConnectors) {
203 | this.data.inputConnectors = [];
204 | }
205 | this._addConnector(connectorDataModel, 0, this.data.inputConnectors, this.inputConnectors);
206 | };
207 |
208 | //
209 | // Add an ouput connector to the node.
210 | //
211 | this.addOutputConnector = function (connectorDataModel) {
212 |
213 | if (!this.data.outputConnectors) {
214 | this.data.outputConnectors = [];
215 | }
216 | this._addConnector(connectorDataModel, this.data.width, this.data.outputConnectors, this.outputConnectors);
217 | };
218 | };
219 |
220 | //
221 | // Wrap the nodes data-model in a view-model.
222 | //
223 | var createNodesViewModel = function (nodesDataModel) {
224 | var nodesViewModel = [];
225 |
226 | if (nodesDataModel) {
227 | for (var i = 0; i < nodesDataModel.length; ++i) {
228 | nodesViewModel.push(new flowchart.NodeViewModel(nodesDataModel[i]));
229 | }
230 | }
231 |
232 | return nodesViewModel;
233 | };
234 |
235 | //
236 | // View model for a connection.
237 | //
238 | flowchart.ConnectionViewModel = function (connectionDataModel, sourceConnector, destConnector) {
239 |
240 | this.data = connectionDataModel;
241 | this.source = sourceConnector;
242 | this.dest = destConnector;
243 |
244 | // Set to true when the connection is selected.
245 | this._selected = false;
246 |
247 | this.name = function() {
248 | return this.data.name || "";
249 | }
250 |
251 | this.sourceCoordX = function () {
252 | return this.source.parentNode().x() + this.source.x();
253 | };
254 |
255 | this.sourceCoordY = function () {
256 | return this.source.parentNode().y() + this.source.y();
257 | };
258 |
259 | this.sourceCoord = function () {
260 | return {
261 | x: this.sourceCoordX(),
262 | y: this.sourceCoordY()
263 | };
264 | }
265 |
266 | this.sourceTangentX = function () {
267 | return flowchart.computeConnectionSourceTangentX(this.sourceCoord(), this.destCoord());
268 | };
269 |
270 | this.sourceTangentY = function () {
271 | return flowchart.computeConnectionSourceTangentY(this.sourceCoord(), this.destCoord());
272 | };
273 |
274 | this.destCoordX = function () {
275 | return this.dest.parentNode().x() + this.dest.x();
276 | };
277 |
278 | this.destCoordY = function () {
279 | return this.dest.parentNode().y() + this.dest.y();
280 | };
281 |
282 | this.destCoord = function () {
283 | return {
284 | x: this.destCoordX(),
285 | y: this.destCoordY()
286 | };
287 | }
288 |
289 | this.destTangentX = function () {
290 | return flowchart.computeConnectionDestTangentX(this.sourceCoord(), this.destCoord());
291 | };
292 |
293 | this.destTangentY = function () {
294 | return flowchart.computeConnectionDestTangentY(this.sourceCoord(), this.destCoord());
295 | };
296 |
297 | this.middleX = function(scale) {
298 | if(typeof(scale)=="undefined")
299 | scale = 0.5;
300 | return this.sourceCoordX()*(1-scale)+this.destCoordX()*scale;
301 | };
302 |
303 | this.middleY = function(scale) {
304 | if(typeof(scale)=="undefined")
305 | scale = 0.5;
306 | return this.sourceCoordY()*(1-scale)+this.destCoordY()*scale;
307 | };
308 |
309 |
310 | //
311 | // Select the connection.
312 | //
313 | this.select = function () {
314 | this._selected = true;
315 | };
316 |
317 | //
318 | // Deselect the connection.
319 | //
320 | this.deselect = function () {
321 | this._selected = false;
322 | };
323 |
324 | //
325 | // Toggle the selection state of the connection.
326 | //
327 | this.toggleSelected = function () {
328 | this._selected = !this._selected;
329 | };
330 |
331 | //
332 | // Returns true if the connection is selected.
333 | //
334 | this.selected = function () {
335 | return this._selected;
336 | };
337 | };
338 |
339 | //
340 | // Helper function.
341 | //
342 | var computeConnectionTangentOffset = function (pt1, pt2) {
343 |
344 | return (pt2.x - pt1.x) / 2;
345 | }
346 |
347 | //
348 | // Compute the tangent for the bezier curve.
349 | //
350 | flowchart.computeConnectionSourceTangentX = function (pt1, pt2) {
351 |
352 | return pt1.x + computeConnectionTangentOffset(pt1, pt2);
353 | };
354 |
355 | //
356 | // Compute the tangent for the bezier curve.
357 | //
358 | flowchart.computeConnectionSourceTangentY = function (pt1, pt2) {
359 |
360 | return pt1.y;
361 | };
362 |
363 | //
364 | // Compute the tangent for the bezier curve.
365 | //
366 | flowchart.computeConnectionSourceTangent = function(pt1, pt2) {
367 | return {
368 | x: flowchart.computeConnectionSourceTangentX(pt1, pt2),
369 | y: flowchart.computeConnectionSourceTangentY(pt1, pt2),
370 | };
371 | };
372 |
373 | //
374 | // Compute the tangent for the bezier curve.
375 | //
376 | flowchart.computeConnectionDestTangentX = function (pt1, pt2) {
377 |
378 | return pt2.x - computeConnectionTangentOffset(pt1, pt2);
379 | };
380 |
381 | //
382 | // Compute the tangent for the bezier curve.
383 | //
384 | flowchart.computeConnectionDestTangentY = function (pt1, pt2) {
385 |
386 | return pt2.y;
387 | };
388 |
389 | //
390 | // Compute the tangent for the bezier curve.
391 | //
392 | flowchart.computeConnectionDestTangent = function(pt1, pt2) {
393 | return {
394 | x: flowchart.computeConnectionDestTangentX(pt1, pt2),
395 | y: flowchart.computeConnectionDestTangentY(pt1, pt2),
396 | };
397 | };
398 |
399 | //
400 | // View model for the chart.
401 | //
402 | flowchart.ChartViewModel = function (chartDataModel) {
403 |
404 | //
405 | // Find a specific node within the chart.
406 | //
407 | this.findNode = function (nodeID) {
408 |
409 | for (var i = 0; i < this.nodes.length; ++i) {
410 | var node = this.nodes[i];
411 | if (node.data.id == nodeID) {
412 | return node;
413 | }
414 | }
415 |
416 | throw new Error("Failed to find node " + nodeID);
417 | };
418 |
419 | //
420 | // Find a specific input connector within the chart.
421 | //
422 | this.findInputConnector = function (nodeID, connectorIndex) {
423 |
424 | var node = this.findNode(nodeID);
425 |
426 | if (!node.inputConnectors || node.inputConnectors.length <= connectorIndex) {
427 | throw new Error("Node " + nodeID + " has invalid input connectors.");
428 | }
429 |
430 | return node.inputConnectors[connectorIndex];
431 | };
432 |
433 | //
434 | // Find a specific output connector within the chart.
435 | //
436 | this.findOutputConnector = function (nodeID, connectorIndex) {
437 |
438 | var node = this.findNode(nodeID);
439 |
440 | if (!node.outputConnectors || node.outputConnectors.length <= connectorIndex) {
441 | throw new Error("Node " + nodeID + " has invalid output connectors.");
442 | }
443 |
444 | return node.outputConnectors[connectorIndex];
445 | };
446 |
447 | //
448 | // Create a view model for connection from the data model.
449 | //
450 | this._createConnectionViewModel = function(connectionDataModel) {
451 |
452 | var sourceConnector = this.findOutputConnector(connectionDataModel.source.nodeID, connectionDataModel.source.connectorIndex);
453 | var destConnector = this.findInputConnector(connectionDataModel.dest.nodeID, connectionDataModel.dest.connectorIndex);
454 | return new flowchart.ConnectionViewModel(connectionDataModel, sourceConnector, destConnector);
455 | }
456 |
457 | //
458 | // Wrap the connections data-model in a view-model.
459 | //
460 | this._createConnectionsViewModel = function (connectionsDataModel) {
461 |
462 | var connectionsViewModel = [];
463 |
464 | if (connectionsDataModel) {
465 | for (var i = 0; i < connectionsDataModel.length; ++i) {
466 | connectionsViewModel.push(this._createConnectionViewModel(connectionsDataModel[i]));
467 | }
468 | }
469 |
470 | return connectionsViewModel;
471 | };
472 |
473 | // Reference to the underlying data.
474 | this.data = chartDataModel;
475 |
476 | // Create a view-model for nodes.
477 | this.nodes = createNodesViewModel(this.data.nodes);
478 |
479 | // Create a view-model for connections.
480 | this.connections = this._createConnectionsViewModel(this.data.connections);
481 |
482 | //
483 | // Create a view model for a new connection.
484 | //
485 | this.createNewConnection = function (startConnector, endConnector) {
486 |
487 | var connectionsDataModel = this.data.connections;
488 | if (!connectionsDataModel) {
489 | connectionsDataModel = this.data.connections = [];
490 | }
491 |
492 | var connectionsViewModel = this.connections;
493 | if (!connectionsViewModel) {
494 | connectionsViewModel = this.connections = [];
495 | }
496 |
497 | var startNode = startConnector.parentNode();
498 | var startConnectorIndex = startNode.outputConnectors.indexOf(startConnector);
499 | var startConnectorType = 'output';
500 | if (startConnectorIndex == -1) {
501 | startConnectorIndex = startNode.inputConnectors.indexOf(startConnector);
502 | startConnectorType = 'input';
503 | if (startConnectorIndex == -1) {
504 | throw new Error("Failed to find source connector within either inputConnectors or outputConnectors of source node.");
505 | }
506 | }
507 |
508 | var endNode = endConnector.parentNode();
509 | var endConnectorIndex = endNode.inputConnectors.indexOf(endConnector);
510 | var endConnectorType = 'input';
511 | if (endConnectorIndex == -1) {
512 | endConnectorIndex = endNode.outputConnectors.indexOf(endConnector);
513 | endConnectorType = 'output';
514 | if (endConnectorIndex == -1) {
515 | throw new Error("Failed to find dest connector within inputConnectors or outputConnectors of dest node.");
516 | }
517 | }
518 |
519 | if (startConnectorType == endConnectorType) {
520 | throw new Error("Failed to create connection. Only output to input connections are allowed.")
521 | }
522 |
523 | if (startNode == endNode) {
524 | throw new Error("Failed to create connection. Cannot link a node with itself.")
525 | }
526 |
527 | var startNode = {
528 | nodeID: startNode.data.id,
529 | connectorIndex: startConnectorIndex,
530 | }
531 |
532 | var endNode = {
533 | nodeID: endNode.data.id,
534 | connectorIndex: endConnectorIndex,
535 | }
536 |
537 | var connectionDataModel = {
538 | source: startConnectorType == 'output' ? startNode : endNode,
539 | dest: startConnectorType == 'output' ? endNode : startNode,
540 | };
541 | connectionsDataModel.push(connectionDataModel);
542 |
543 | var outputConnector = startConnectorType == 'output' ? startConnector : endConnector;
544 | var inputConnector = startConnectorType == 'output' ? endConnector : startConnector;
545 |
546 | var connectionViewModel = new flowchart.ConnectionViewModel(connectionDataModel, outputConnector, inputConnector);
547 | connectionsViewModel.push(connectionViewModel);
548 | };
549 |
550 | //
551 | // Add a node to the view model.
552 | //
553 | this.addNode = function (nodeDataModel) {
554 | if (!this.data.nodes) {
555 | this.data.nodes = [];
556 | }
557 |
558 | //
559 | // Update the data model.
560 | //
561 | this.data.nodes.push(nodeDataModel);
562 |
563 | //
564 | // Update the view model.
565 | //
566 | this.nodes.push(new flowchart.NodeViewModel(nodeDataModel));
567 | }
568 |
569 | //
570 | // Select all nodes and connections in the chart.
571 | //
572 | this.selectAll = function () {
573 |
574 | var nodes = this.nodes;
575 | for (var i = 0; i < nodes.length; ++i) {
576 | var node = nodes[i];
577 | node.select();
578 | }
579 |
580 | var connections = this.connections;
581 | for (var i = 0; i < connections.length; ++i) {
582 | var connection = connections[i];
583 | connection.select();
584 | }
585 | }
586 |
587 | //
588 | // Deselect all nodes and connections in the chart.
589 | //
590 | this.deselectAll = function () {
591 |
592 | var nodes = this.nodes;
593 | for (var i = 0; i < nodes.length; ++i) {
594 | var node = nodes[i];
595 | node.deselect();
596 | }
597 |
598 | var connections = this.connections;
599 | for (var i = 0; i < connections.length; ++i) {
600 | var connection = connections[i];
601 | connection.deselect();
602 | }
603 | };
604 |
605 | //
606 | // Update the location of the node and its connectors.
607 | //
608 | this.updateSelectedNodesLocation = function (deltaX, deltaY) {
609 |
610 | var selectedNodes = this.getSelectedNodes();
611 |
612 | for (var i = 0; i < selectedNodes.length; ++i) {
613 | var node = selectedNodes[i];
614 | node.data.x += deltaX;
615 | node.data.y += deltaY;
616 | }
617 | };
618 |
619 | //
620 | // Handle mouse click on a particular node.
621 | //
622 | this.handleNodeClicked = function (node, ctrlKey) {
623 |
624 | if (ctrlKey) {
625 | node.toggleSelected();
626 | }
627 | else {
628 | this.deselectAll();
629 | node.select();
630 | }
631 |
632 | // Move node to the end of the list so it is rendered after all the other.
633 | // This is the way Z-order is done in SVG.
634 |
635 | var nodeIndex = this.nodes.indexOf(node);
636 | if (nodeIndex == -1) {
637 | throw new Error("Failed to find node in view model!");
638 | }
639 | this.nodes.splice(nodeIndex, 1);
640 | this.nodes.push(node);
641 | };
642 |
643 | //
644 | // Handle mouse down on a connection.
645 | //
646 | this.handleConnectionMouseDown = function (connection, ctrlKey) {
647 |
648 | if (ctrlKey) {
649 | connection.toggleSelected();
650 | }
651 | else {
652 | this.deselectAll();
653 | connection.select();
654 | }
655 | };
656 |
657 | //
658 | // Delete all nodes and connections that are selected.
659 | //
660 | this.deleteSelected = function () {
661 |
662 | var newNodeViewModels = [];
663 | var newNodeDataModels = [];
664 |
665 | var deletedNodeIds = [];
666 |
667 | //
668 | // Sort nodes into:
669 | // nodes to keep and
670 | // nodes to delete.
671 | //
672 |
673 | for (var nodeIndex = 0; nodeIndex < this.nodes.length; ++nodeIndex) {
674 |
675 | var node = this.nodes[nodeIndex];
676 | if (!node.selected()) {
677 | // Only retain non-selected nodes.
678 | newNodeViewModels.push(node);
679 | newNodeDataModels.push(node.data);
680 | }
681 | else {
682 | // Keep track of nodes that were deleted, so their connections can also
683 | // be deleted.
684 | deletedNodeIds.push(node.data.id);
685 | }
686 | }
687 |
688 | var newConnectionViewModels = [];
689 | var newConnectionDataModels = [];
690 |
691 | //
692 | // Remove connections that are selected.
693 | // Also remove connections for nodes that have been deleted.
694 | //
695 | for (var connectionIndex = 0; connectionIndex < this.connections.length; ++connectionIndex) {
696 |
697 | var connection = this.connections[connectionIndex];
698 | if (!connection.selected() &&
699 | deletedNodeIds.indexOf(connection.data.source.nodeID) === -1 &&
700 | deletedNodeIds.indexOf(connection.data.dest.nodeID) === -1)
701 | {
702 | //
703 | // The nodes this connection is attached to, where not deleted,
704 | // so keep the connection.
705 | //
706 | newConnectionViewModels.push(connection);
707 | newConnectionDataModels.push(connection.data);
708 | }
709 | }
710 |
711 | //
712 | // Update nodes and connections.
713 | //
714 | this.nodes = newNodeViewModels;
715 | this.data.nodes = newNodeDataModels;
716 | this.connections = newConnectionViewModels;
717 | this.data.connections = newConnectionDataModels;
718 | };
719 |
720 | //
721 | // Select nodes and connections that fall within the selection rect.
722 | //
723 | this.applySelectionRect = function (selectionRect) {
724 |
725 | this.deselectAll();
726 |
727 | for (var i = 0; i < this.nodes.length; ++i) {
728 | var node = this.nodes[i];
729 | if (node.x() >= selectionRect.x &&
730 | node.y() >= selectionRect.y &&
731 | node.x() + node.width() <= selectionRect.x + selectionRect.width &&
732 | node.y() + node.height() <= selectionRect.y + selectionRect.height)
733 | {
734 | // Select nodes that are within the selection rect.
735 | node.select();
736 | }
737 | }
738 |
739 | for (var i = 0; i < this.connections.length; ++i) {
740 | var connection = this.connections[i];
741 | if (connection.source.parentNode().selected() &&
742 | connection.dest.parentNode().selected())
743 | {
744 | // Select the connection if both its parent nodes are selected.
745 | connection.select();
746 | }
747 | }
748 |
749 | };
750 |
751 | //
752 | // Get the array of nodes that are currently selected.
753 | //
754 | this.getSelectedNodes = function () {
755 | var selectedNodes = [];
756 |
757 | for (var i = 0; i < this.nodes.length; ++i) {
758 | var node = this.nodes[i];
759 | if (node.selected()) {
760 | selectedNodes.push(node);
761 | }
762 | }
763 |
764 | return selectedNodes;
765 | };
766 |
767 | //
768 | // Get the array of connections that are currently selected.
769 | //
770 | this.getSelectedConnections = function () {
771 | var selectedConnections = [];
772 |
773 | for (var i = 0; i < this.connections.length; ++i) {
774 | var connection = this.connections[i];
775 | if (connection.selected()) {
776 | selectedConnections.push(connection);
777 | }
778 | }
779 |
780 | return selectedConnections;
781 | };
782 |
783 |
784 | };
785 |
786 | })();
787 |
--------------------------------------------------------------------------------
/jasmine/lib/jasmine-1.3.1/jasmine-html.js:
--------------------------------------------------------------------------------
1 | jasmine.HtmlReporterHelpers = {};
2 |
3 | jasmine.HtmlReporterHelpers.createDom = function(type, attrs, childrenVarArgs) {
4 | var el = document.createElement(type);
5 |
6 | for (var i = 2; i < arguments.length; i++) {
7 | var child = arguments[i];
8 |
9 | if (typeof child === 'string') {
10 | el.appendChild(document.createTextNode(child));
11 | } else {
12 | if (child) {
13 | el.appendChild(child);
14 | }
15 | }
16 | }
17 |
18 | for (var attr in attrs) {
19 | if (attr == "className") {
20 | el[attr] = attrs[attr];
21 | } else {
22 | el.setAttribute(attr, attrs[attr]);
23 | }
24 | }
25 |
26 | return el;
27 | };
28 |
29 | jasmine.HtmlReporterHelpers.getSpecStatus = function(child) {
30 | var results = child.results();
31 | var status = results.passed() ? 'passed' : 'failed';
32 | if (results.skipped) {
33 | status = 'skipped';
34 | }
35 |
36 | return status;
37 | };
38 |
39 | jasmine.HtmlReporterHelpers.appendToSummary = function(child, childElement) {
40 | var parentDiv = this.dom.summary;
41 | var parentSuite = (typeof child.parentSuite == 'undefined') ? 'suite' : 'parentSuite';
42 | var parent = child[parentSuite];
43 |
44 | if (parent) {
45 | if (typeof this.views.suites[parent.id] == 'undefined') {
46 | this.views.suites[parent.id] = new jasmine.HtmlReporter.SuiteView(parent, this.dom, this.views);
47 | }
48 | parentDiv = this.views.suites[parent.id].element;
49 | }
50 |
51 | parentDiv.appendChild(childElement);
52 | };
53 |
54 |
55 | jasmine.HtmlReporterHelpers.addHelpers = function(ctor) {
56 | for(var fn in jasmine.HtmlReporterHelpers) {
57 | ctor.prototype[fn] = jasmine.HtmlReporterHelpers[fn];
58 | }
59 | };
60 |
61 | jasmine.HtmlReporter = function(_doc) {
62 | var self = this;
63 | var doc = _doc || window.document;
64 |
65 | var reporterView;
66 |
67 | var dom = {};
68 |
69 | // Jasmine Reporter Public Interface
70 | self.logRunningSpecs = false;
71 |
72 | self.reportRunnerStarting = function(runner) {
73 | var specs = runner.specs() || [];
74 |
75 | if (specs.length == 0) {
76 | return;
77 | }
78 |
79 | createReporterDom(runner.env.versionString());
80 | doc.body.appendChild(dom.reporter);
81 | setExceptionHandling();
82 |
83 | reporterView = new jasmine.HtmlReporter.ReporterView(dom);
84 | reporterView.addSpecs(specs, self.specFilter);
85 | };
86 |
87 | self.reportRunnerResults = function(runner) {
88 | reporterView && reporterView.complete();
89 | };
90 |
91 | self.reportSuiteResults = function(suite) {
92 | reporterView.suiteComplete(suite);
93 | };
94 |
95 | self.reportSpecStarting = function(spec) {
96 | if (self.logRunningSpecs) {
97 | self.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
98 | }
99 | };
100 |
101 | self.reportSpecResults = function(spec) {
102 | reporterView.specComplete(spec);
103 | };
104 |
105 | self.log = function() {
106 | var console = jasmine.getGlobal().console;
107 | if (console && console.log) {
108 | if (console.log.apply) {
109 | console.log.apply(console, arguments);
110 | } else {
111 | console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
112 | }
113 | }
114 | };
115 |
116 | self.specFilter = function(spec) {
117 | if (!focusedSpecName()) {
118 | return true;
119 | }
120 |
121 | return spec.getFullName().indexOf(focusedSpecName()) === 0;
122 | };
123 |
124 | return self;
125 |
126 | function focusedSpecName() {
127 | var specName;
128 |
129 | (function memoizeFocusedSpec() {
130 | if (specName) {
131 | return;
132 | }
133 |
134 | var paramMap = [];
135 | var params = jasmine.HtmlReporter.parameters(doc);
136 |
137 | for (var i = 0; i < params.length; i++) {
138 | var p = params[i].split('=');
139 | paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
140 | }
141 |
142 | specName = paramMap.spec;
143 | })();
144 |
145 | return specName;
146 | }
147 |
148 | function createReporterDom(version) {
149 | dom.reporter = self.createDom('div', { id: 'HTMLReporter', className: 'jasmine_reporter' },
150 | dom.banner = self.createDom('div', { className: 'banner' },
151 | self.createDom('span', { className: 'title' }, "Jasmine "),
152 | self.createDom('span', { className: 'version' }, version)),
153 |
154 | dom.symbolSummary = self.createDom('ul', {className: 'symbolSummary'}),
155 | dom.alert = self.createDom('div', {className: 'alert'},
156 | self.createDom('span', { className: 'exceptions' },
157 | self.createDom('label', { className: 'label', 'for': 'no_try_catch' }, 'No try/catch'),
158 | self.createDom('input', { id: 'no_try_catch', type: 'checkbox' }))),
159 | dom.results = self.createDom('div', {className: 'results'},
160 | dom.summary = self.createDom('div', { className: 'summary' }),
161 | dom.details = self.createDom('div', { id: 'details' }))
162 | );
163 | }
164 |
165 | function noTryCatch() {
166 | return window.location.search.match(/catch=false/);
167 | }
168 |
169 | function searchWithCatch() {
170 | var params = jasmine.HtmlReporter.parameters(window.document);
171 | var removed = false;
172 | var i = 0;
173 |
174 | while (!removed && i < params.length) {
175 | if (params[i].match(/catch=/)) {
176 | params.splice(i, 1);
177 | removed = true;
178 | }
179 | i++;
180 | }
181 | if (jasmine.CATCH_EXCEPTIONS) {
182 | params.push("catch=false");
183 | }
184 |
185 | return params.join("&");
186 | }
187 |
188 | function setExceptionHandling() {
189 | var chxCatch = document.getElementById('no_try_catch');
190 |
191 | if (noTryCatch()) {
192 | chxCatch.setAttribute('checked', true);
193 | jasmine.CATCH_EXCEPTIONS = false;
194 | }
195 | chxCatch.onclick = function() {
196 | window.location.search = searchWithCatch();
197 | };
198 | }
199 | };
200 | jasmine.HtmlReporter.parameters = function(doc) {
201 | var paramStr = doc.location.search.substring(1);
202 | var params = [];
203 |
204 | if (paramStr.length > 0) {
205 | params = paramStr.split('&');
206 | }
207 | return params;
208 | }
209 | jasmine.HtmlReporter.sectionLink = function(sectionName) {
210 | var link = '?';
211 | var params = [];
212 |
213 | if (sectionName) {
214 | params.push('spec=' + encodeURIComponent(sectionName));
215 | }
216 | if (!jasmine.CATCH_EXCEPTIONS) {
217 | params.push("catch=false");
218 | }
219 | if (params.length > 0) {
220 | link += params.join("&");
221 | }
222 |
223 | return link;
224 | };
225 | jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter);
226 | jasmine.HtmlReporter.ReporterView = function(dom) {
227 | this.startedAt = new Date();
228 | this.runningSpecCount = 0;
229 | this.completeSpecCount = 0;
230 | this.passedCount = 0;
231 | this.failedCount = 0;
232 | this.skippedCount = 0;
233 |
234 | this.createResultsMenu = function() {
235 | this.resultsMenu = this.createDom('span', {className: 'resultsMenu bar'},
236 | this.summaryMenuItem = this.createDom('a', {className: 'summaryMenuItem', href: "#"}, '0 specs'),
237 | ' | ',
238 | this.detailsMenuItem = this.createDom('a', {className: 'detailsMenuItem', href: "#"}, '0 failing'));
239 |
240 | this.summaryMenuItem.onclick = function() {
241 | dom.reporter.className = dom.reporter.className.replace(/ showDetails/g, '');
242 | };
243 |
244 | this.detailsMenuItem.onclick = function() {
245 | showDetails();
246 | };
247 | };
248 |
249 | this.addSpecs = function(specs, specFilter) {
250 | this.totalSpecCount = specs.length;
251 |
252 | this.views = {
253 | specs: {},
254 | suites: {}
255 | };
256 |
257 | for (var i = 0; i < specs.length; i++) {
258 | var spec = specs[i];
259 | this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom, this.views);
260 | if (specFilter(spec)) {
261 | this.runningSpecCount++;
262 | }
263 | }
264 | };
265 |
266 | this.specComplete = function(spec) {
267 | this.completeSpecCount++;
268 |
269 | if (isUndefined(this.views.specs[spec.id])) {
270 | this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom);
271 | }
272 |
273 | var specView = this.views.specs[spec.id];
274 |
275 | switch (specView.status()) {
276 | case 'passed':
277 | this.passedCount++;
278 | break;
279 |
280 | case 'failed':
281 | this.failedCount++;
282 | break;
283 |
284 | case 'skipped':
285 | this.skippedCount++;
286 | break;
287 | }
288 |
289 | specView.refresh();
290 | this.refresh();
291 | };
292 |
293 | this.suiteComplete = function(suite) {
294 | var suiteView = this.views.suites[suite.id];
295 | if (isUndefined(suiteView)) {
296 | return;
297 | }
298 | suiteView.refresh();
299 | };
300 |
301 | this.refresh = function() {
302 |
303 | if (isUndefined(this.resultsMenu)) {
304 | this.createResultsMenu();
305 | }
306 |
307 | // currently running UI
308 | if (isUndefined(this.runningAlert)) {
309 | this.runningAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "runningAlert bar" });
310 | dom.alert.appendChild(this.runningAlert);
311 | }
312 | this.runningAlert.innerHTML = "Running " + this.completeSpecCount + " of " + specPluralizedFor(this.totalSpecCount);
313 |
314 | // skipped specs UI
315 | if (isUndefined(this.skippedAlert)) {
316 | this.skippedAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "skippedAlert bar" });
317 | }
318 |
319 | this.skippedAlert.innerHTML = "Skipping " + this.skippedCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all";
320 |
321 | if (this.skippedCount === 1 && isDefined(dom.alert)) {
322 | dom.alert.appendChild(this.skippedAlert);
323 | }
324 |
325 | // passing specs UI
326 | if (isUndefined(this.passedAlert)) {
327 | this.passedAlert = this.createDom('span', { href: jasmine.HtmlReporter.sectionLink(), className: "passingAlert bar" });
328 | }
329 | this.passedAlert.innerHTML = "Passing " + specPluralizedFor(this.passedCount);
330 |
331 | // failing specs UI
332 | if (isUndefined(this.failedAlert)) {
333 | this.failedAlert = this.createDom('span', {href: "?", className: "failingAlert bar"});
334 | }
335 | this.failedAlert.innerHTML = "Failing " + specPluralizedFor(this.failedCount);
336 |
337 | if (this.failedCount === 1 && isDefined(dom.alert)) {
338 | dom.alert.appendChild(this.failedAlert);
339 | dom.alert.appendChild(this.resultsMenu);
340 | }
341 |
342 | // summary info
343 | this.summaryMenuItem.innerHTML = "" + specPluralizedFor(this.runningSpecCount);
344 | this.detailsMenuItem.innerHTML = "" + this.failedCount + " failing";
345 | };
346 |
347 | this.complete = function() {
348 | dom.alert.removeChild(this.runningAlert);
349 |
350 | this.skippedAlert.innerHTML = "Ran " + this.runningSpecCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all";
351 |
352 | if (this.failedCount === 0) {
353 | dom.alert.appendChild(this.createDom('span', {className: 'passingAlert bar'}, "Passing " + specPluralizedFor(this.passedCount)));
354 | } else {
355 | showDetails();
356 | }
357 |
358 | dom.banner.appendChild(this.createDom('span', {className: 'duration'}, "finished in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"));
359 | };
360 |
361 | return this;
362 |
363 | function showDetails() {
364 | if (dom.reporter.className.search(/showDetails/) === -1) {
365 | dom.reporter.className += " showDetails";
366 | }
367 | }
368 |
369 | function isUndefined(obj) {
370 | return typeof obj === 'undefined';
371 | }
372 |
373 | function isDefined(obj) {
374 | return !isUndefined(obj);
375 | }
376 |
377 | function specPluralizedFor(count) {
378 | var str = count + " spec";
379 | if (count > 1) {
380 | str += "s"
381 | }
382 | return str;
383 | }
384 |
385 | };
386 |
387 | jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.ReporterView);
388 |
389 |
390 | jasmine.HtmlReporter.SpecView = function(spec, dom, views) {
391 | this.spec = spec;
392 | this.dom = dom;
393 | this.views = views;
394 |
395 | this.symbol = this.createDom('li', { className: 'pending' });
396 | this.dom.symbolSummary.appendChild(this.symbol);
397 |
398 | this.summary = this.createDom('div', { className: 'specSummary' },
399 | this.createDom('a', {
400 | className: 'description',
401 | href: jasmine.HtmlReporter.sectionLink(this.spec.getFullName()),
402 | title: this.spec.getFullName()
403 | }, this.spec.description)
404 | );
405 |
406 | this.detail = this.createDom('div', { className: 'specDetail' },
407 | this.createDom('a', {
408 | className: 'description',
409 | href: '?spec=' + encodeURIComponent(this.spec.getFullName()),
410 | title: this.spec.getFullName()
411 | }, this.spec.getFullName())
412 | );
413 | };
414 |
415 | jasmine.HtmlReporter.SpecView.prototype.status = function() {
416 | return this.getSpecStatus(this.spec);
417 | };
418 |
419 | jasmine.HtmlReporter.SpecView.prototype.refresh = function() {
420 | this.symbol.className = this.status();
421 |
422 | switch (this.status()) {
423 | case 'skipped':
424 | break;
425 |
426 | case 'passed':
427 | this.appendSummaryToSuiteDiv();
428 | break;
429 |
430 | case 'failed':
431 | this.appendSummaryToSuiteDiv();
432 | this.appendFailureDetail();
433 | break;
434 | }
435 | };
436 |
437 | jasmine.HtmlReporter.SpecView.prototype.appendSummaryToSuiteDiv = function() {
438 | this.summary.className += ' ' + this.status();
439 | this.appendToSummary(this.spec, this.summary);
440 | };
441 |
442 | jasmine.HtmlReporter.SpecView.prototype.appendFailureDetail = function() {
443 | this.detail.className += ' ' + this.status();
444 |
445 | var resultItems = this.spec.results().getItems();
446 | var messagesDiv = this.createDom('div', { className: 'messages' });
447 |
448 | for (var i = 0; i < resultItems.length; i++) {
449 | var result = resultItems[i];
450 |
451 | if (result.type == 'log') {
452 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
453 | } else if (result.type == 'expect' && result.passed && !result.passed()) {
454 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
455 |
456 | if (result.trace.stack) {
457 | messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
458 | }
459 | }
460 | }
461 |
462 | if (messagesDiv.childNodes.length > 0) {
463 | this.detail.appendChild(messagesDiv);
464 | this.dom.details.appendChild(this.detail);
465 | }
466 | };
467 |
468 | jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SpecView);jasmine.HtmlReporter.SuiteView = function(suite, dom, views) {
469 | this.suite = suite;
470 | this.dom = dom;
471 | this.views = views;
472 |
473 | this.element = this.createDom('div', { className: 'suite' },
474 | this.createDom('a', { className: 'description', href: jasmine.HtmlReporter.sectionLink(this.suite.getFullName()) }, this.suite.description)
475 | );
476 |
477 | this.appendToSummary(this.suite, this.element);
478 | };
479 |
480 | jasmine.HtmlReporter.SuiteView.prototype.status = function() {
481 | return this.getSpecStatus(this.suite);
482 | };
483 |
484 | jasmine.HtmlReporter.SuiteView.prototype.refresh = function() {
485 | this.element.className += " " + this.status();
486 | };
487 |
488 | jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SuiteView);
489 |
490 | /* @deprecated Use jasmine.HtmlReporter instead
491 | */
492 | jasmine.TrivialReporter = function(doc) {
493 | this.document = doc || document;
494 | this.suiteDivs = {};
495 | this.logRunningSpecs = false;
496 | };
497 |
498 | jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) {
499 | var el = document.createElement(type);
500 |
501 | for (var i = 2; i < arguments.length; i++) {
502 | var child = arguments[i];
503 |
504 | if (typeof child === 'string') {
505 | el.appendChild(document.createTextNode(child));
506 | } else {
507 | if (child) { el.appendChild(child); }
508 | }
509 | }
510 |
511 | for (var attr in attrs) {
512 | if (attr == "className") {
513 | el[attr] = attrs[attr];
514 | } else {
515 | el.setAttribute(attr, attrs[attr]);
516 | }
517 | }
518 |
519 | return el;
520 | };
521 |
522 | jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) {
523 | var showPassed, showSkipped;
524 |
525 | this.outerDiv = this.createDom('div', { id: 'TrivialReporter', className: 'jasmine_reporter' },
526 | this.createDom('div', { className: 'banner' },
527 | this.createDom('div', { className: 'logo' },
528 | this.createDom('span', { className: 'title' }, "Jasmine"),
529 | this.createDom('span', { className: 'version' }, runner.env.versionString())),
530 | this.createDom('div', { className: 'options' },
531 | "Show ",
532 | showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }),
533 | this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "),
534 | showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }),
535 | this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped")
536 | )
537 | ),
538 |
539 | this.runnerDiv = this.createDom('div', { className: 'runner running' },
540 | this.createDom('a', { className: 'run_spec', href: '?' }, "run all"),
541 | this.runnerMessageSpan = this.createDom('span', {}, "Running..."),
542 | this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, ""))
543 | );
544 |
545 | this.document.body.appendChild(this.outerDiv);
546 |
547 | var suites = runner.suites();
548 | for (var i = 0; i < suites.length; i++) {
549 | var suite = suites[i];
550 | var suiteDiv = this.createDom('div', { className: 'suite' },
551 | this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"),
552 | this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description));
553 | this.suiteDivs[suite.id] = suiteDiv;
554 | var parentDiv = this.outerDiv;
555 | if (suite.parentSuite) {
556 | parentDiv = this.suiteDivs[suite.parentSuite.id];
557 | }
558 | parentDiv.appendChild(suiteDiv);
559 | }
560 |
561 | this.startedAt = new Date();
562 |
563 | var self = this;
564 | showPassed.onclick = function(evt) {
565 | if (showPassed.checked) {
566 | self.outerDiv.className += ' show-passed';
567 | } else {
568 | self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, '');
569 | }
570 | };
571 |
572 | showSkipped.onclick = function(evt) {
573 | if (showSkipped.checked) {
574 | self.outerDiv.className += ' show-skipped';
575 | } else {
576 | self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, '');
577 | }
578 | };
579 | };
580 |
581 | jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) {
582 | var results = runner.results();
583 | var className = (results.failedCount > 0) ? "runner failed" : "runner passed";
584 | this.runnerDiv.setAttribute("class", className);
585 | //do it twice for IE
586 | this.runnerDiv.setAttribute("className", className);
587 | var specs = runner.specs();
588 | var specCount = 0;
589 | for (var i = 0; i < specs.length; i++) {
590 | if (this.specFilter(specs[i])) {
591 | specCount++;
592 | }
593 | }
594 | var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s");
595 | message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s";
596 | this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild);
597 |
598 | this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString()));
599 | };
600 |
601 | jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) {
602 | var results = suite.results();
603 | var status = results.passed() ? 'passed' : 'failed';
604 | if (results.totalCount === 0) { // todo: change this to check results.skipped
605 | status = 'skipped';
606 | }
607 | this.suiteDivs[suite.id].className += " " + status;
608 | };
609 |
610 | jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) {
611 | if (this.logRunningSpecs) {
612 | this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
613 | }
614 | };
615 |
616 | jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) {
617 | var results = spec.results();
618 | var status = results.passed() ? 'passed' : 'failed';
619 | if (results.skipped) {
620 | status = 'skipped';
621 | }
622 | var specDiv = this.createDom('div', { className: 'spec ' + status },
623 | this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"),
624 | this.createDom('a', {
625 | className: 'description',
626 | href: '?spec=' + encodeURIComponent(spec.getFullName()),
627 | title: spec.getFullName()
628 | }, spec.description));
629 |
630 |
631 | var resultItems = results.getItems();
632 | var messagesDiv = this.createDom('div', { className: 'messages' });
633 | for (var i = 0; i < resultItems.length; i++) {
634 | var result = resultItems[i];
635 |
636 | if (result.type == 'log') {
637 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
638 | } else if (result.type == 'expect' && result.passed && !result.passed()) {
639 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
640 |
641 | if (result.trace.stack) {
642 | messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
643 | }
644 | }
645 | }
646 |
647 | if (messagesDiv.childNodes.length > 0) {
648 | specDiv.appendChild(messagesDiv);
649 | }
650 |
651 | this.suiteDivs[spec.suite.id].appendChild(specDiv);
652 | };
653 |
654 | jasmine.TrivialReporter.prototype.log = function() {
655 | var console = jasmine.getGlobal().console;
656 | if (console && console.log) {
657 | if (console.log.apply) {
658 | console.log.apply(console, arguments);
659 | } else {
660 | console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
661 | }
662 | }
663 | };
664 |
665 | jasmine.TrivialReporter.prototype.getLocation = function() {
666 | return this.document.location;
667 | };
668 |
669 | jasmine.TrivialReporter.prototype.specFilter = function(spec) {
670 | var paramMap = {};
671 | var params = this.getLocation().search.substring(1).split('&');
672 | for (var i = 0; i < params.length; i++) {
673 | var p = params[i].split('=');
674 | paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
675 | }
676 |
677 | if (!paramMap.spec) {
678 | return true;
679 | }
680 | return spec.getFullName().indexOf(paramMap.spec) === 0;
681 | };
682 |
--------------------------------------------------------------------------------
/flowchart/flowchart_viewmodel.spec.js:
--------------------------------------------------------------------------------
1 | describe('flowchart-viewmodel', function () {
2 |
3 | //
4 | // Create a mock data model from a simple definition.
5 | //
6 | var createMockDataModel = function (nodeIds, connections) {
7 |
8 | var nodeDataModels = null;
9 |
10 | if (nodeIds) {
11 | nodeDataModels = [];
12 |
13 | for (var i = 0; i < nodeIds.length; ++i) {
14 | nodeDataModels.push({
15 | id: nodeIds[i],
16 | x: 0,
17 | y: 0,
18 | inputConnectors: [ {}, {}, {} ],
19 | outputConnectors: [ {}, {}, {} ],
20 | });
21 | }
22 | }
23 |
24 | var connectionDataModels = null;
25 |
26 | if (connections) {
27 | connectionDataModels = [];
28 |
29 | for (var i = 0; i < connections.length; ++i) {
30 | connectionDataModels.push({
31 | source: {
32 | nodeID: connections[i][0][0],
33 | connectorIndex: connections[i][0][1],
34 | },
35 | dest: {
36 | nodeID: connections[i][1][0],
37 | connectorIndex: connections[i][1][1],
38 | },
39 | });
40 | }
41 | }
42 |
43 | var dataModel = {};
44 |
45 | if (nodeDataModels) {
46 | dataModel.nodes = nodeDataModels;
47 | }
48 |
49 | if (connectionDataModels) {
50 | dataModel.connections = connectionDataModels;
51 | }
52 |
53 | return dataModel;
54 | };
55 |
56 | it('compute input connector pos', function () {
57 |
58 | var mockNode = {
59 | x: function () { return 10 },
60 | y: function () { return 15 },
61 | };
62 |
63 | flowchart.computeConnectorPos(mockNode, 0, true);
64 | flowchart.computeConnectorPos(mockNode, 1, true);
65 | flowchart.computeConnectorPos(mockNode, 2, true);
66 | });
67 |
68 | it('compute output connector pos', function () {
69 |
70 | var mockNode = {
71 | x: function () { return 10 },
72 | y: function () { return 15 },
73 | };
74 |
75 | flowchart.computeConnectorPos(mockNode, 0, false);
76 | flowchart.computeConnectorPos(mockNode, 1, false);
77 | flowchart.computeConnectorPos(mockNode, 2, false);
78 | });
79 |
80 | it('construct ConnectorViewModel', function () {
81 |
82 | var mockDataModel = {
83 | name: "Fooey",
84 | };
85 |
86 | new flowchart.ConnectorViewModel(mockDataModel, 0, 10, 0);
87 | new flowchart.ConnectorViewModel(mockDataModel, 0, 10, 1);
88 | new flowchart.ConnectorViewModel(mockDataModel, 0, 10, 2);
89 |
90 | });
91 |
92 | it('ConnectorViewModel has reference to parent node', function () {
93 |
94 | var mockDataModel = {
95 | name: "Fooey",
96 | };
97 | var mockParentNodeViewModel = {};
98 |
99 | var testObject = new flowchart.ConnectorViewModel(mockDataModel, 0, 10, mockParentNodeViewModel);
100 |
101 | expect(testObject.parentNode()).toBe(mockParentNodeViewModel);
102 | });
103 |
104 | it('construct NodeViewModel with no connectors', function () {
105 |
106 | var mockDataModel = {
107 | x: 10,
108 | y: 12,
109 | name: "Woot",
110 | };
111 |
112 | new flowchart.NodeViewModel(mockDataModel);
113 | });
114 |
115 | it('construct NodeViewModel with empty connectors', function () {
116 |
117 | var mockDataModel = {
118 | x: 10,
119 | y: 12,
120 | name: "Woot",
121 | inputConnectors: [],
122 | outputConnectors: [],
123 | };
124 |
125 | new flowchart.NodeViewModel(mockDataModel);
126 | });
127 |
128 | it('construct NodeViewModel with connectors', function () {
129 |
130 | var mockInputConnector = {
131 | name: "Input",
132 | };
133 |
134 | var mockOutputConnector = {
135 | name: "Output",
136 | };
137 |
138 | var mockDataModel = {
139 | x: 10,
140 | y: 12,
141 | name: "Woot",
142 | inputConnectors: [
143 | mockInputConnector
144 | ],
145 | outputConnectors: [
146 | mockOutputConnector
147 | ],
148 | };
149 |
150 | new flowchart.NodeViewModel(mockDataModel);
151 | });
152 |
153 | it('test name of NodeViewModel', function () {
154 |
155 | var mockDataModel = {
156 | name: "Woot",
157 | };
158 |
159 | var testObject = new flowchart.NodeViewModel(mockDataModel);
160 |
161 | expect(testObject.name()).toBe(mockDataModel.name);
162 | });
163 |
164 | it('test name of NodeViewModel defaults to empty string', function () {
165 |
166 | var mockDataModel = {};
167 |
168 | var testObject = new flowchart.NodeViewModel(mockDataModel);
169 |
170 | expect(testObject.name()).toBe("");
171 | });
172 |
173 | it('test node is deselected by default', function () {
174 |
175 | var mockDataModel = {};
176 |
177 | var testObject = new flowchart.NodeViewModel(mockDataModel);
178 |
179 | expect(testObject.selected()).toBe(false);
180 | });
181 |
182 | it('test node width is set by default', function () {
183 |
184 | var mockDataModel = {};
185 |
186 | var testObject = new flowchart.NodeViewModel(mockDataModel);
187 |
188 | expect(testObject.width() === flowchart.defaultNodeWidth).toBe(true);
189 | });
190 |
191 | it('test node width is used', function () {
192 |
193 | var mockDataModel = {"width": 900 };
194 |
195 | var testObject = new flowchart.NodeViewModel(mockDataModel);
196 |
197 | expect(testObject.width()).toBe(900);
198 | });
199 |
200 | it('test computeConnectorPos uses node width', function () {
201 |
202 | var mockDataModel = {
203 | x: function () {
204 | return 10;
205 | },
206 | y: function () {
207 | return 15;
208 | },
209 | width: function () {
210 | return 900;
211 | },
212 | };
213 |
214 | var testObject = flowchart.computeConnectorPos(mockDataModel, 1, false);
215 |
216 | expect(testObject.x).toBe(910);
217 | });
218 |
219 | it('test computeConnectorPos uses default node width', function () {
220 |
221 | var mockDataModel = {
222 | x: function () {
223 | return 10
224 | },
225 | y: function () {
226 | return 15
227 | },
228 | };
229 |
230 | var testObject = flowchart.computeConnectorPos(mockDataModel, 1, false);
231 |
232 | expect(testObject.x).toBe(flowchart.defaultNodeWidth + 10);
233 | });
234 |
235 | it('test node can be selected', function () {
236 |
237 | var mockDataModel = {};
238 |
239 | var testObject = new flowchart.NodeViewModel(mockDataModel);
240 |
241 | testObject.select();
242 |
243 | expect(testObject.selected()).toBe(true);
244 | });
245 |
246 | it('test node can be deselected', function () {
247 |
248 | var mockDataModel = {};
249 |
250 | var testObject = new flowchart.NodeViewModel(mockDataModel);
251 |
252 | testObject.select();
253 |
254 | testObject.deselect();
255 |
256 | expect(testObject.selected()).toBe(false);
257 | });
258 |
259 | it('test node can be selection can be toggled', function () {
260 |
261 | var mockDataModel = {};
262 |
263 | var testObject = new flowchart.NodeViewModel(mockDataModel);
264 |
265 | testObject.toggleSelected();
266 |
267 | expect(testObject.selected()).toBe(true);
268 |
269 | testObject.toggleSelected();
270 |
271 | expect(testObject.selected()).toBe(false);
272 | });
273 |
274 | it('test can add input connector to node', function () {
275 |
276 | var mockDataModel = {};
277 |
278 | var testObject = new flowchart.NodeViewModel(mockDataModel);
279 |
280 | var name1 = "Connector1";
281 | var name2 = "Connector2";
282 | var data1 = {
283 | name: name1
284 | };
285 | var data2 = {
286 | name: name2
287 | }
288 | testObject.addInputConnector(data1);
289 | testObject.addInputConnector(data2);
290 |
291 | expect(testObject.inputConnectors.length).toBe(2);
292 | expect(testObject.inputConnectors[0].data).toBe(data1);
293 | expect(testObject.inputConnectors[1].data).toBe(data2);
294 |
295 | expect(testObject.data.inputConnectors.length).toBe(2);
296 | expect(testObject.data.inputConnectors[0]).toBe(data1);
297 | expect(testObject.data.inputConnectors[1]).toBe(data2);
298 | });
299 |
300 | it('test can add output connector to node', function () {
301 |
302 | var mockDataModel = {};
303 |
304 | var testObject = new flowchart.NodeViewModel(mockDataModel);
305 |
306 | var name1 = "Connector1";
307 | var name2 = "Connector2";
308 | var data1 = {
309 | name: name1
310 | };
311 | var data2 = {
312 | name: name2
313 | }
314 | testObject.addOutputConnector(data1);
315 | testObject.addOutputConnector(data2);
316 |
317 | expect(testObject.outputConnectors.length).toBe(2);
318 | expect(testObject.outputConnectors[0].data).toBe(data1);
319 | expect(testObject.outputConnectors[1].data).toBe(data2);
320 |
321 | expect(testObject.data.outputConnectors.length).toBe(2);
322 | expect(testObject.data.outputConnectors[0]).toBe(data1);
323 | expect(testObject.data.outputConnectors[1]).toBe(data2);
324 | });
325 |
326 | it('construct ChartViewModel with no nodes or connections', function () {
327 |
328 | var mockDataModel = {};
329 |
330 | new flowchart.ChartViewModel(mockDataModel);
331 |
332 | });
333 |
334 | it('construct ChartViewModel with empty nodes and connections', function () {
335 |
336 | var mockDataModel = {
337 | nodes: [],
338 | connections: [],
339 | };
340 |
341 | new flowchart.ChartViewModel(mockDataModel);
342 |
343 | });
344 |
345 | it('construct ConnectionViewModel', function () {
346 |
347 | var mockDataModel = {};
348 | var mockSourceConnector = {};
349 | var mockDestConnector = {};
350 |
351 | new flowchart.ConnectionViewModel(mockDataModel, mockSourceConnector, mockDestConnector);
352 | });
353 |
354 | it('retreive source and dest coordinates', function () {
355 |
356 | var mockDataModel = {
357 | };
358 |
359 | var mockSourceParentNode = {
360 | x: function () { return 5 },
361 | y: function () { return 10 },
362 | };
363 |
364 | var mockSourceConnector = {
365 | parentNode: function () {
366 | return mockSourceParentNode;
367 | },
368 |
369 | x: function() {
370 | return 5;
371 | },
372 |
373 | y: function() {
374 | return 15;
375 | },
376 | };
377 |
378 | var mockDestParentNode = {
379 | x: function () { return 50 },
380 | y: function () { return 30 },
381 | };
382 |
383 | var mockDestConnector = {
384 | parentNode: function () {
385 | return mockDestParentNode;
386 | },
387 |
388 | x: function() {
389 | return 25;
390 | },
391 |
392 | y: function() {
393 | return 35;
394 | },
395 | };
396 |
397 | var testObject = new flowchart.ConnectionViewModel(mockDataModel, mockSourceConnector, mockDestConnector);
398 |
399 | testObject.sourceCoord();
400 | expect(testObject.sourceCoordX()).toBe(10);
401 | expect(testObject.sourceCoordY()).toBe(25);
402 | testObject.sourceTangentX();
403 | testObject.sourceTangentY();
404 |
405 | testObject.destCoord();
406 | expect(testObject.destCoordX()).toBe(75);
407 | expect(testObject.destCoordY()).toBe(65);
408 | testObject.destTangentX();
409 | testObject.destTangentY();
410 | });
411 |
412 | it('test connection is deselected by default', function () {
413 |
414 | var mockDataModel = {};
415 |
416 | var testObject = new flowchart.ConnectionViewModel(mockDataModel);
417 |
418 | expect(testObject.selected()).toBe(false);
419 | });
420 |
421 | it('test connection can be selected', function () {
422 |
423 | var mockDataModel = {};
424 |
425 | var testObject = new flowchart.ConnectionViewModel(mockDataModel);
426 |
427 | testObject.select();
428 |
429 | expect(testObject.selected()).toBe(true);
430 | });
431 |
432 | it('test connection can be deselected', function () {
433 |
434 | var mockDataModel = {};
435 |
436 | var testObject = new flowchart.ConnectionViewModel(mockDataModel);
437 |
438 | testObject.select();
439 |
440 | testObject.deselect();
441 |
442 | expect(testObject.selected()).toBe(false);
443 | });
444 |
445 | it('test connection can be selection can be toggled', function () {
446 |
447 | var mockDataModel = {};
448 |
449 | var testObject = new flowchart.ConnectionViewModel(mockDataModel);
450 |
451 | testObject.toggleSelected();
452 |
453 | expect(testObject.selected()).toBe(true);
454 |
455 | testObject.toggleSelected();
456 |
457 | expect(testObject.selected()).toBe(false);
458 | });
459 |
460 | it('construct ChartViewModel with a node', function () {
461 |
462 | var mockDataModel = createMockDataModel([1]);
463 |
464 | var testObject = new flowchart.ChartViewModel(mockDataModel);
465 | expect(testObject.nodes.length).toBe(1);
466 | expect(testObject.nodes[0].data).toBe(mockDataModel.nodes[0]);
467 |
468 | });
469 |
470 | it('data model with existing connection creates a connection view model', function () {
471 |
472 | var mockDataModel = createMockDataModel(
473 | [ 5, 12 ],
474 | [
475 | [[ 5, 0 ], [ 12, 1 ]],
476 | ]
477 | );
478 |
479 | var testObject = new flowchart.ChartViewModel(mockDataModel);
480 |
481 | expect(testObject.connections.length).toBe(1);
482 | expect(testObject.connections[0].data).toBe(mockDataModel.connections[0]);
483 | expect(testObject.connections[0].source.data).toBe(mockDataModel.nodes[0].outputConnectors[0]);
484 | expect(testObject.connections[0].dest.data).toBe(mockDataModel.nodes[1].inputConnectors[1]);
485 | });
486 |
487 | it('test can add new node', function () {
488 |
489 | var mockDataModel = createMockDataModel();
490 |
491 | var testObject = new flowchart.ChartViewModel(mockDataModel);
492 |
493 | var nodeDataModel = {};
494 | testObject.addNode(nodeDataModel);
495 |
496 | expect(testObject.nodes.length).toBe(1);
497 | expect(testObject.nodes[0].data).toBe(nodeDataModel);
498 |
499 | expect(testObject.data.nodes.length).toBe(1);
500 | expect(testObject.data.nodes[0]).toBe(nodeDataModel);
501 | });
502 |
503 | it('test can select all', function () {
504 |
505 | var mockDataModel = createMockDataModel([1, 2], [[[1, 0], [2, 1]]]);
506 |
507 | var testObject = new flowchart.ChartViewModel(mockDataModel);
508 |
509 | var node1 = testObject.nodes[0];
510 | var node2 = testObject.nodes[1];
511 | var connection = testObject.connections[0];
512 |
513 | testObject.selectAll();
514 |
515 | expect(node1.selected()).toBe(true);
516 | expect(node2.selected()).toBe(true);
517 | expect(connection.selected()).toBe(true);
518 | });
519 |
520 | it('test can deselect all nodes', function () {
521 |
522 | var mockDataModel = createMockDataModel([1, 2]);
523 |
524 | var testObject = new flowchart.ChartViewModel(mockDataModel);
525 |
526 | var node1 = testObject.nodes[0];
527 | var node2 = testObject.nodes[1];
528 |
529 | node1.select();
530 | node2.select();
531 |
532 | testObject.deselectAll();
533 |
534 | expect(node1.selected()).toBe(false);
535 | expect(node2.selected()).toBe(false);
536 | });
537 |
538 | it('test can deselect all connections', function () {
539 |
540 | var mockDataModel = createMockDataModel(
541 | [ 5, 12 ],
542 | [
543 | [[ 5, 0 ], [ 12, 1 ]],
544 | [[ 5, 0 ], [ 12, 1 ]],
545 | ]
546 | );
547 |
548 | var testObject = new flowchart.ChartViewModel(mockDataModel);
549 |
550 | var connection1 = testObject.connections[0];
551 | var connection2 = testObject.connections[1];
552 |
553 | connection1.select();
554 | connection2.select();
555 |
556 | testObject.deselectAll();
557 |
558 | expect(connection1.selected()).toBe(false);
559 | expect(connection2.selected()).toBe(false);
560 | });
561 |
562 | it('test mouse down deselects nodes other than the one clicked', function () {
563 |
564 | var mockDataModel = createMockDataModel([ 1, 2, 3 ]);
565 |
566 | var testObject = new flowchart.ChartViewModel(mockDataModel);
567 |
568 | var node1 = testObject.nodes[0];
569 | var node2 = testObject.nodes[1];
570 | var node3 = testObject.nodes[2];
571 |
572 | // Fake out the nodes as selected.
573 | node1.select();
574 | node2.select();
575 | node3.select();
576 |
577 | testObject.handleNodeClicked(node2); // Doesn't matter which node is actually clicked.
578 |
579 | expect(node1.selected()).toBe(false);
580 | expect(node2.selected()).toBe(true);
581 | expect(node3.selected()).toBe(false);
582 | });
583 |
584 | it('test mouse down selects the clicked node', function () {
585 |
586 | var mockDataModel = createMockDataModel([ 1, 2, 3 ]);
587 |
588 | var testObject = new flowchart.ChartViewModel(mockDataModel);
589 |
590 | var node1 = testObject.nodes[0];
591 | var node2 = testObject.nodes[1];
592 | var node3 = testObject.nodes[2];
593 |
594 | testObject.handleNodeClicked(node3); // Doesn't matter which node is actually clicked.
595 |
596 | expect(node1.selected()).toBe(false);
597 | expect(node2.selected()).toBe(false);
598 | expect(node3.selected()).toBe(true);
599 | });
600 |
601 | it('test mouse down brings node to front', function () {
602 |
603 | var mockDataModel = createMockDataModel([ 1, 2 ]);
604 |
605 | var testObject = new flowchart.ChartViewModel(mockDataModel);
606 |
607 | var node1 = testObject.nodes[0];
608 | var node2 = testObject.nodes[1];
609 |
610 | testObject.handleNodeClicked(node1);
611 |
612 | expect(testObject.nodes[0]).toBe(node2); // Mock node 2 should be bought to front.
613 | expect(testObject.nodes[1]).toBe(node1);
614 | });
615 |
616 | it('test control + mouse down toggles node selection', function () {
617 |
618 | var mockDataModel = createMockDataModel([ 1, 2, 3 ]);
619 |
620 | var testObject = new flowchart.ChartViewModel(mockDataModel);
621 |
622 | var node1 = testObject.nodes[0];
623 | var node2 = testObject.nodes[1];
624 | var node3 = testObject.nodes[2];
625 |
626 | node1.select(); // Mark node 1 as already selected.
627 |
628 | testObject.handleNodeClicked(node2, true);
629 |
630 | expect(node1.selected()).toBe(true); // This node remains selected.
631 | expect(node2.selected()).toBe(true); // This node is being toggled.
632 | expect(node3.selected()).toBe(false); // This node remains unselected.
633 |
634 | testObject.handleNodeClicked(node2, true);
635 |
636 | expect(node1.selected()).toBe(true); // This node remains selected.
637 | expect(node2.selected()).toBe(false); // This node is being toggled.
638 | expect(node3.selected()).toBe(false); // This node remains unselected.
639 |
640 | testObject.handleNodeClicked(node2, true);
641 |
642 | expect(node1.selected()).toBe(true); // This node remains selected.
643 | expect(node2.selected()).toBe(true); // This node is being toggled.
644 | expect(node3.selected()).toBe(false); // This node remains unselected.
645 | });
646 |
647 | it('test mouse down deselects connections other than the one clicked', function () {
648 |
649 | var mockDataModel = createMockDataModel(
650 | [ 1, 2, 3 ],
651 | [
652 | [[ 1, 0 ], [ 3, 0 ]],
653 | [[ 2, 1 ], [ 3, 2 ]],
654 | [[ 1, 2 ], [ 3, 0 ]]
655 | ]
656 | );
657 |
658 | var testObject = new flowchart.ChartViewModel(mockDataModel);
659 |
660 | var connection1 = testObject.connections[0];
661 | var connection2 = testObject.connections[1];
662 | var connection3 = testObject.connections[2];
663 |
664 | // Fake out the connections as selected.
665 | connection1.select();
666 | connection2.select();
667 | connection3.select();
668 |
669 | testObject.handleConnectionMouseDown(connection2);
670 |
671 | expect(connection1.selected()).toBe(false);
672 | expect(connection2.selected()).toBe(true);
673 | expect(connection3.selected()).toBe(false);
674 | });
675 |
676 | it('test node mouse down selects the clicked connection', function () {
677 |
678 | var mockDataModel = createMockDataModel(
679 | [ 1, 2, 3 ],
680 | [
681 | [[ 1, 0 ], [ 3, 0 ]],
682 | [[ 2, 1 ], [ 3, 2 ]],
683 | [[ 1, 2 ], [ 3, 0 ]]
684 | ]
685 | );
686 |
687 | var testObject = new flowchart.ChartViewModel(mockDataModel);
688 |
689 | var connection1 = testObject.connections[0];
690 | var connection2 = testObject.connections[1];
691 | var connection3 = testObject.connections[2];
692 |
693 | testObject.handleConnectionMouseDown(connection3);
694 |
695 | expect(connection1.selected()).toBe(false);
696 | expect(connection2.selected()).toBe(false);
697 | expect(connection3.selected()).toBe(true);
698 | });
699 |
700 | it('test control + mouse down toggles connection selection', function () {
701 |
702 | var mockDataModel = createMockDataModel(
703 | [ 1, 2, 3 ],
704 | [
705 | [[ 1, 0 ], [ 3, 0 ]],
706 | [[ 2, 1 ], [ 3, 2 ]],
707 | [[ 1, 2 ], [ 3, 0 ]]
708 | ]
709 | );
710 |
711 | var testObject = new flowchart.ChartViewModel(mockDataModel);
712 |
713 | var connection1 = testObject.connections[0];
714 | var connection2 = testObject.connections[1];
715 | var connection3 = testObject.connections[2];
716 |
717 | connection1.select(); // Mark connection 1 as already selected.
718 |
719 | testObject.handleConnectionMouseDown(connection2, true);
720 |
721 | expect(connection1.selected()).toBe(true); // This connection remains selected.
722 | expect(connection2.selected()).toBe(true); // This connection is being toggle.
723 | expect(connection3.selected()).toBe(false); // This connection remains unselected.
724 |
725 | testObject.handleConnectionMouseDown(connection2, true);
726 |
727 | expect(connection1.selected()).toBe(true); // This connection remains selected.
728 | expect(connection2.selected()).toBe(false); // This connection is being toggle.
729 | expect(connection3.selected()).toBe(false); // This connection remains unselected.
730 |
731 | testObject.handleConnectionMouseDown(connection2, true);
732 |
733 | expect(connection1.selected()).toBe(true); // This connection remains selected.
734 | expect(connection2.selected()).toBe(true); // This connection is being toggle.
735 | expect(connection3.selected()).toBe(false); // This connection remains unselected.
736 | });
737 |
738 | it('test data-model is wrapped in view-model', function () {
739 |
740 | var mockDataModel = createMockDataModel([ 1, 2 ], [[[1, 0], [2, 0]]]);
741 | var mockNode = mockDataModel.nodes[0];
742 | var mockInputConnector = mockNode.inputConnectors[0];
743 | var mockOutputConnector = mockNode.outputConnectors[0];
744 |
745 | var testObject = new flowchart.ChartViewModel(mockDataModel);
746 |
747 | // Chart
748 |
749 | expect(testObject).toBeDefined();
750 | expect(testObject).toNotBe(mockDataModel);
751 | expect(testObject.data).toBe(mockDataModel);
752 | expect(testObject.nodes).toBeDefined();
753 | expect(testObject.nodes.length).toBe(2);
754 |
755 | // Node
756 |
757 | var node = testObject.nodes[0];
758 |
759 | expect(node).toNotBe(mockNode);
760 | expect(node.data).toBe(mockNode);
761 |
762 | // Connectors
763 |
764 | expect(node.inputConnectors.length).toBe(3);
765 | expect(node.inputConnectors[0].data).toBe(mockInputConnector);
766 |
767 | expect(node.outputConnectors.length).toBe(3);
768 | expect(node.outputConnectors[0].data).toBe(mockOutputConnector);
769 |
770 | // Connection
771 |
772 | expect(testObject.connections.length).toBe(1);
773 | expect(testObject.connections[0].source).toBe(testObject.nodes[0].outputConnectors[0]);
774 | expect(testObject.connections[0].dest).toBe(testObject.nodes[1].inputConnectors[0]);
775 | });
776 |
777 | it('test can delete 1st selected node', function () {
778 |
779 | var mockDataModel = createMockDataModel([ 1, 2 ]);
780 |
781 | var testObject = new flowchart.ChartViewModel(mockDataModel);
782 |
783 | expect(testObject.nodes.length).toBe(2);
784 |
785 | testObject.nodes[0].select();
786 |
787 | var mockNode2 = mockDataModel.nodes[1];
788 |
789 | testObject.deleteSelected();
790 |
791 | expect(testObject.nodes.length).toBe(1);
792 | expect(mockDataModel.nodes.length).toBe(1);
793 | expect(testObject.nodes[0].data).toBe(mockNode2);
794 | });
795 |
796 | it('test can delete 2nd selected nodes', function () {
797 |
798 | var mockDataModel = createMockDataModel([ 1, 2 ]);
799 |
800 | var testObject = new flowchart.ChartViewModel(mockDataModel);
801 |
802 | expect(testObject.nodes.length).toBe(2);
803 |
804 | testObject.nodes[1].select();
805 |
806 | var mockNode1 = mockDataModel.nodes[0];
807 |
808 | testObject.deleteSelected();
809 |
810 | expect(testObject.nodes.length).toBe(1);
811 | expect(mockDataModel.nodes.length).toBe(1);
812 | expect(testObject.nodes[0].data).toBe(mockNode1);
813 | });
814 |
815 | it('test can delete multiple selected nodes', function () {
816 |
817 | var mockDataModel = createMockDataModel([ 1, 2, 3, 4 ]);
818 |
819 | var testObject = new flowchart.ChartViewModel(mockDataModel);
820 |
821 | expect(testObject.nodes.length).toBe(4);
822 |
823 | testObject.nodes[1].select();
824 | testObject.nodes[2].select();
825 |
826 | var mockNode1 = mockDataModel.nodes[0];
827 | var mockNode4 = mockDataModel.nodes[3];
828 |
829 | testObject.deleteSelected();
830 |
831 | expect(testObject.nodes.length).toBe(2);
832 | expect(mockDataModel.nodes.length).toBe(2);
833 | expect(testObject.nodes[0].data).toBe(mockNode1);
834 | expect(testObject.nodes[1].data).toBe(mockNode4);
835 | });
836 |
837 | it('deleting a node also deletes its connections', function () {
838 |
839 | var mockDataModel = createMockDataModel(
840 | [ 1, 2, 3 ],
841 | [
842 | [[ 1, 0 ], [ 2, 0 ]],
843 | [[ 2, 0 ], [ 3, 0 ]],
844 | ]
845 | );
846 |
847 | var testObject = new flowchart.ChartViewModel(mockDataModel);
848 |
849 | expect(testObject.connections.length).toBe(2);
850 |
851 | // Select the middle node.
852 | testObject.nodes[1].select();
853 |
854 | testObject.deleteSelected();
855 |
856 | expect(testObject.connections.length).toBe(0);
857 | });
858 |
859 | it('deleting a node doesnt delete other connections', function () {
860 |
861 | var mockDataModel = createMockDataModel(
862 | [ 1, 2, 3 ],
863 | [
864 | [[ 1, 0 ], [ 3, 0 ],]
865 | ]
866 | );
867 |
868 | var testObject = new flowchart.ChartViewModel(mockDataModel);
869 |
870 | expect(testObject.connections.length).toBe(1);
871 |
872 | // Select the middle node.
873 | testObject.nodes[1].select();
874 |
875 | testObject.deleteSelected();
876 |
877 | expect(testObject.connections.length).toBe(1);
878 | });
879 |
880 | it('test can delete 1st selected connection', function () {
881 |
882 | var mockDataModel = createMockDataModel(
883 | [ 1, 2 ],
884 | [
885 | [[ 1, 0 ], [ 2, 0 ]],
886 | [[ 2, 1 ], [ 1, 2 ]]
887 | ]
888 | );
889 |
890 | var mockRemainingConnectionDataModel = mockDataModel.connections[1];
891 |
892 | var testObject = new flowchart.ChartViewModel(mockDataModel);
893 |
894 | expect(testObject.connections.length).toBe(2);
895 |
896 | testObject.connections[0].select();
897 |
898 | testObject.deleteSelected();
899 |
900 | expect(testObject.connections.length).toBe(1);
901 | expect(mockDataModel.connections.length).toBe(1);
902 | expect(testObject.connections[0].data).toBe(mockRemainingConnectionDataModel);
903 | });
904 |
905 | it('test can delete 2nd selected connection', function () {
906 |
907 | var mockDataModel = createMockDataModel(
908 | [ 1, 2 ],
909 | [
910 | [[ 1, 0 ], [ 2, 0 ]],
911 | [[ 2, 1 ], [ 1, 2 ]]
912 | ]
913 | );
914 |
915 | var mockRemainingConnectionDataModel = mockDataModel.connections[0];
916 |
917 | var testObject = new flowchart.ChartViewModel(mockDataModel);
918 |
919 | expect(testObject.connections.length).toBe(2);
920 |
921 | testObject.connections[1].select();
922 |
923 | testObject.deleteSelected();
924 |
925 | expect(testObject.connections.length).toBe(1);
926 | expect(mockDataModel.connections.length).toBe(1);
927 | expect(testObject.connections[0].data).toBe(mockRemainingConnectionDataModel);
928 | });
929 |
930 |
931 | it('test can delete multiple selected connections', function () {
932 |
933 | var mockDataModel = createMockDataModel(
934 | [ 1, 2, 3 ],
935 | [
936 | [[ 1, 0 ], [ 2, 0 ]],
937 | [[ 2, 1 ], [ 1, 2 ]],
938 | [[ 1, 1 ], [ 3, 0 ]],
939 | [[ 3, 2 ], [ 2, 1 ]]
940 | ]
941 | );
942 |
943 | var mockRemainingConnectionDataModel1 = mockDataModel.connections[0];
944 | var mockRemainingConnectionDataModel2 = mockDataModel.connections[3];
945 |
946 | var testObject = new flowchart.ChartViewModel(mockDataModel);
947 |
948 | expect(testObject.connections.length).toBe(4);
949 |
950 | testObject.connections[1].select();
951 | testObject.connections[2].select();
952 |
953 | testObject.deleteSelected();
954 |
955 | expect(testObject.connections.length).toBe(2);
956 | expect(mockDataModel.connections.length).toBe(2);
957 | expect(testObject.connections[0].data).toBe(mockRemainingConnectionDataModel1);
958 | expect(testObject.connections[1].data).toBe(mockRemainingConnectionDataModel2);
959 | });
960 |
961 | it('can select nodes via selection rect', function () {
962 |
963 | var mockDataModel = createMockDataModel([ 1, 2, 3 ]);
964 | mockDataModel.nodes[0].x = 0;
965 | mockDataModel.nodes[0].y = 0;
966 | mockDataModel.nodes[1].x = 1020;
967 | mockDataModel.nodes[1].y = 1020;
968 | mockDataModel.nodes[2].x = 3000;
969 | mockDataModel.nodes[2].y = 3000;
970 |
971 | var testObject = new flowchart.ChartViewModel(mockDataModel);
972 |
973 | testObject.nodes[0].select(); // Select a nodes, to ensure it is correctly deselected.
974 |
975 | testObject.applySelectionRect({ x: 1000, y: 1000, width: 1000, height: 1000 });
976 |
977 | expect(testObject.nodes[0].selected()).toBe(false);
978 | expect(testObject.nodes[1].selected()).toBe(true);
979 | expect(testObject.nodes[2].selected()).toBe(false);
980 | });
981 |
982 | it('can select connections via selection rect', function () {
983 |
984 | var mockDataModel = createMockDataModel(
985 | [ 1, 2, 3, 4 ],
986 | [
987 | [[ 1, 0 ], [ 2, 0 ]],
988 | [[ 2, 1 ], [ 3, 2 ]],
989 | [[ 3, 2 ], [ 4, 1 ]]
990 | ]
991 | );
992 | mockDataModel.nodes[0].x = 0;
993 | mockDataModel.nodes[0].y = 0;
994 | mockDataModel.nodes[1].x = 1020;
995 | mockDataModel.nodes[1].y = 1020;
996 | mockDataModel.nodes[2].x = 1500;
997 | mockDataModel.nodes[2].y = 1500;
998 | mockDataModel.nodes[3].x = 3000;
999 | mockDataModel.nodes[3].y = 3000;
1000 |
1001 | var testObject = new flowchart.ChartViewModel(mockDataModel);
1002 |
1003 | testObject.connections[0].select(); // Select a connection, to ensure it is correctly deselected.
1004 |
1005 | testObject.applySelectionRect({ x: 1000, y: 1000, width: 1000, height: 1000 });
1006 |
1007 | expect(testObject.connections[0].selected()).toBe(false);
1008 | expect(testObject.connections[1].selected()).toBe(true);
1009 | expect(testObject.connections[2].selected()).toBe(false);
1010 | });
1011 |
1012 | it('test update selected nodes location', function () {
1013 | var mockDataModel = createMockDataModel([1]);
1014 | var testObject = new flowchart.ChartViewModel(mockDataModel);
1015 |
1016 | var node = testObject.nodes[0];
1017 | node.select();
1018 |
1019 | var xInc = 5;
1020 | var yInc = 15;
1021 |
1022 | testObject.updateSelectedNodesLocation(xInc, yInc);
1023 |
1024 | expect(node.x()).toBe(xInc);
1025 | expect(node.y()).toBe(yInc);
1026 | });
1027 |
1028 | it('test update selected nodes location, ignores unselected nodes', function () {
1029 | var mockDataModel = createMockDataModel([1]);
1030 | var testObject = new flowchart.ChartViewModel(mockDataModel);
1031 |
1032 | var node = testObject.nodes[0];
1033 |
1034 | var xInc = 5;
1035 | var yInc = 15;
1036 |
1037 | testObject.updateSelectedNodesLocation(xInc, yInc);
1038 |
1039 | expect(node.x()).toBe(0);
1040 | expect(node.y()).toBe(0);
1041 | });
1042 |
1043 | it('test find node throws when there are no nodes', function () {
1044 | var mockDataModel = createMockDataModel();
1045 |
1046 | var testObject = new flowchart.ChartViewModel(mockDataModel);
1047 |
1048 | expect(function () { testObject.findNode(150); }).toThrow();
1049 | });
1050 |
1051 | it('test find node throws when node is not found', function () {
1052 | var mockDataModel = createMockDataModel([5, 25, 15, 30]);
1053 |
1054 | var testObject = new flowchart.ChartViewModel(mockDataModel);
1055 |
1056 | expect(function () { testObject.findNode(150); }).toThrow();
1057 | });
1058 |
1059 | it('test find node retreives correct node', function () {
1060 | var mockDataModel = createMockDataModel([5, 25, 15, 30]);
1061 |
1062 | var testObject = new flowchart.ChartViewModel(mockDataModel);
1063 |
1064 | expect(testObject.findNode(15)).toBe(testObject.nodes[2]);
1065 | });
1066 |
1067 | it('test find input connector throws when there are no nodes', function () {
1068 | var mockDataModel = createMockDataModel();
1069 |
1070 | var testObject = new flowchart.ChartViewModel(mockDataModel);
1071 |
1072 | expect(function () { testObject.findInputConnector(150, 1); }).toThrow();
1073 | });
1074 |
1075 | it('test find input connector throws when the node is not found', function () {
1076 | var mockDataModel = createMockDataModel([ 1, 2, 3]);
1077 |
1078 | var testObject = new flowchart.ChartViewModel(mockDataModel);
1079 |
1080 | expect(function () { testObject.findInputConnector(150, 1); }).toThrow();
1081 | });
1082 |
1083 | it('test find input connector throws when there are no connectors', function () {
1084 | var mockDataModel = createMockDataModel([ 1 ]);
1085 |
1086 | mockDataModel.nodes[0].inputConnectors = [];
1087 |
1088 | var testObject = new flowchart.ChartViewModel(mockDataModel);
1089 |
1090 | expect(function () { testObject.findInputConnector(1, 1); }).toThrow();
1091 | });
1092 |
1093 | it('test find input connector throws when connector is not found', function () {
1094 | var mockDataModel = createMockDataModel([5]);
1095 |
1096 | mockDataModel.nodes[0].inputConnectors = [
1097 | {} // Only 1 input connector.
1098 | ];
1099 |
1100 | var testObject = new flowchart.ChartViewModel(mockDataModel);
1101 |
1102 | expect(function () { testObject.findInputConnector(5, 1); }).toThrow();
1103 | });
1104 |
1105 | it('test find input connector retreives correct connector', function () {
1106 | var mockDataModel = createMockDataModel([5, 25, 15, 30]);
1107 |
1108 | var testObject = new flowchart.ChartViewModel(mockDataModel);
1109 |
1110 | expect(testObject.findInputConnector(15, 1)).toBe(testObject.nodes[2].inputConnectors[1]);
1111 | });
1112 |
1113 | it('test find output connector throws when there are no nodes', function () {
1114 | var mockDataModel = createMockDataModel();
1115 |
1116 | var testObject = new flowchart.ChartViewModel(mockDataModel);
1117 |
1118 | expect(function () { testObject.findOutputConnector(150, 1); }).toThrow();
1119 | });
1120 |
1121 | it('test find output connector throws when the node is not found', function () {
1122 | var mockDataModel = createMockDataModel([ 1, 2, 3]);
1123 |
1124 | var testObject = new flowchart.ChartViewModel(mockDataModel);
1125 |
1126 | expect(function () { testObject.findOutputConnector(150, 1); }).toThrow();
1127 | });
1128 |
1129 | it('test find output connector throws when there are no connectors', function () {
1130 | var mockDataModel = createMockDataModel([ 1 ]);
1131 |
1132 | mockDataModel.nodes[0].outputConnectors = [];
1133 |
1134 | var testObject = new flowchart.ChartViewModel(mockDataModel);
1135 |
1136 | expect(function () { testObject.findOutputConnector(1, 1); }).toThrow();
1137 | });
1138 |
1139 | it('test find output connector throws when connector is not found', function () {
1140 | var mockDataModel = createMockDataModel([5]);
1141 |
1142 | mockDataModel.nodes[0].outputConnectors = [
1143 | {} // Only 1 input connector.
1144 | ];
1145 |
1146 | var testObject = new flowchart.ChartViewModel(mockDataModel);
1147 |
1148 | expect(function () { testObject.findOutputConnector(5, 1); }).toThrow();
1149 | });
1150 |
1151 | it('test find output connector retreives correct connector', function () {
1152 | var mockDataModel = createMockDataModel([5, 25, 15, 30]);
1153 |
1154 | var testObject = new flowchart.ChartViewModel(mockDataModel);
1155 |
1156 | expect(testObject.findOutputConnector(15, 1)).toBe(testObject.nodes[2].outputConnectors[1]);
1157 | });
1158 |
1159 |
1160 | it('test create new connection', function () {
1161 |
1162 | var mockDataModel = createMockDataModel([5, 25]);
1163 |
1164 | var testObject = new flowchart.ChartViewModel(mockDataModel);
1165 |
1166 | var startConnector = testObject.nodes[0].outputConnectors[0];
1167 | var endConnector = testObject.nodes[1].inputConnectors[1];
1168 |
1169 | testObject.createNewConnection(startConnector, endConnector);
1170 |
1171 | expect(testObject.connections.length).toBe(1);
1172 | var connection = testObject.connections[0];
1173 | expect(connection.source).toBe(startConnector);
1174 | expect(connection.dest).toBe(endConnector);
1175 |
1176 | expect(testObject.data.connections.length).toBe(1);
1177 | var connectionData = testObject.data.connections[0];
1178 | expect(connection.data).toBe(connectionData);
1179 |
1180 | expect(connectionData.source.nodeID).toBe(5);
1181 | expect(connectionData.source.connectorIndex).toBe(0);
1182 | expect(connectionData.dest.nodeID).toBe(25);
1183 | expect(connectionData.dest.connectorIndex).toBe(1);
1184 | });
1185 |
1186 | it('test create new connection from input to output', function () {
1187 |
1188 | var mockDataModel = createMockDataModel([5, 25]);
1189 |
1190 | var testObject = new flowchart.ChartViewModel(mockDataModel);
1191 |
1192 | var startConnector = testObject.nodes[1].inputConnectors[1];
1193 | var endConnector = testObject.nodes[0].outputConnectors[0];
1194 |
1195 | testObject.createNewConnection(startConnector, endConnector);
1196 |
1197 | expect(testObject.connections.length).toBe(1);
1198 | var connection = testObject.connections[0];
1199 | expect(connection.source).toBe(endConnector);
1200 | expect(connection.dest).toBe(startConnector);
1201 |
1202 | expect(testObject.data.connections.length).toBe(1);
1203 | var connectionData = testObject.data.connections[0];
1204 | expect(connection.data).toBe(connectionData);
1205 |
1206 | expect(connectionData.source.nodeID).toBe(5);
1207 | expect(connectionData.source.connectorIndex).toBe(0);
1208 | expect(connectionData.dest.nodeID).toBe(25);
1209 | expect(connectionData.dest.connectorIndex).toBe(1);
1210 | });
1211 |
1212 | it('test get selected nodes results in empty array when there are no nodes', function () {
1213 |
1214 | var mockDataModel = createMockDataModel();
1215 |
1216 | var testObject = new flowchart.ChartViewModel(mockDataModel);
1217 |
1218 | var selectedNodes = testObject.getSelectedNodes();
1219 |
1220 | expect(selectedNodes.length).toBe(0);
1221 | });
1222 |
1223 | it('test get selected nodes results in empty array when none selected', function () {
1224 |
1225 | var mockDataModel = createMockDataModel([1, 2, 3, 4]);
1226 |
1227 | var testObject = new flowchart.ChartViewModel(mockDataModel);
1228 |
1229 | var selectedNodes = testObject.getSelectedNodes();
1230 |
1231 | expect(selectedNodes.length).toBe(0);
1232 | });
1233 |
1234 | it('test can get selected nodes', function () {
1235 |
1236 | var mockDataModel = createMockDataModel([1, 2, 3, 4]);
1237 |
1238 | var testObject = new flowchart.ChartViewModel(mockDataModel);
1239 |
1240 | var node1 = testObject.nodes[0];
1241 | var node2 = testObject.nodes[1];
1242 | var node3 = testObject.nodes[2];
1243 | var node4 = testObject.nodes[3];
1244 |
1245 | node2.select();
1246 | node3.select();
1247 |
1248 | var selectedNodes = testObject.getSelectedNodes();
1249 |
1250 | expect(selectedNodes.length).toBe(2);
1251 | expect(selectedNodes[0]).toBe(node2);
1252 | expect(selectedNodes[1]).toBe(node3);
1253 | });
1254 |
1255 | it('test can get selected connections', function () {
1256 |
1257 | var mockDataModel = createMockDataModel(
1258 | [ 1, 2, 3 ],
1259 | [
1260 | [[ 1, 0 ], [ 2, 0 ]],
1261 | [[ 2, 1 ], [ 1, 2 ]],
1262 | [[ 1, 1 ], [ 3, 0 ]],
1263 | [[ 3, 2 ], [ 2, 1 ]]
1264 | ]
1265 | );
1266 | var testObject = new flowchart.ChartViewModel(mockDataModel);
1267 |
1268 | var connection1 = testObject.connections[0];
1269 | var connection2 = testObject.connections[1];
1270 | var connection3 = testObject.connections[2];
1271 | var connection4 = testObject.connections[3];
1272 |
1273 | connection2.select();
1274 | connection3.select();
1275 |
1276 | var selectedConnections = testObject.getSelectedConnections();
1277 |
1278 | expect(selectedConnections.length).toBe(2);
1279 | expect(selectedConnections[0]).toBe(connection2);
1280 | expect(selectedConnections[1]).toBe(connection3);
1281 | });
1282 | });
1283 |
--------------------------------------------------------------------------------