Showing preview only (1,588K chars total). Download the full file or copy to clipboard to get everything.
Repository: codecapers/AngularJS-FlowChart
Branch: master
Commit: ad925f651b36
Files: 25
Total size: 1.5 MB
Directory structure:
gitextract_jh45o0se/
├── .github/
│ └── FUNDING.yml
├── LICENSE
├── README.md
├── app.css
├── app.js
├── debug.js
├── flowchart/
│ ├── dragging_service.js
│ ├── flowchart_directive.js
│ ├── flowchart_directive.spec.js
│ ├── flowchart_template.html
│ ├── flowchart_viewmodel.js
│ ├── flowchart_viewmodel.spec.js
│ ├── mouse_capture_service.js
│ ├── svg_class.js
│ └── svg_class.spec.js
├── index.html
├── jasmine/
│ ├── SpecRunner.html
│ └── lib/
│ └── jasmine-1.3.1/
│ ├── MIT.LICENSE
│ ├── jasmine-html.js
│ ├── jasmine.css
│ └── jasmine.js
├── lib/
│ ├── angular-mocks.js
│ ├── angular.js
│ └── jquery-2.0.2.js
└── server.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github: ashleydavis
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2014 Ashley Davis
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: README.md
================================================
AngularJS-FlowChart
===================
A WebUI control for visualizing and editing flow charts.
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.
[Click here to support my work](https://www.codecapers.com.au/about#support-my-work)
Code Project Article
--------------------
http://www.codeproject.com/Articles/709340/Implementing-a-Flowchart-with-SVG-and-AngularJS
How to use it
-------------
Include the following Javascript in your HTML file:
```html
<script src="flowchart/svg_class.js" type="text/javascript"></script>
<script src="flowchart/mouse_capture_service.js" type="text/javascript"></script>
<script src="flowchart/dragging_service.js" type="text/javascript"></script>
<script src="flowchart/flowchart_viewmodel.js" type="text/javascript"></script>
<script src="flowchart/flowchart_directive.js" type="text/javascript"></script>
```
Make a dependency on the the flowchart's AngularJS module from your application (or other module):
```javascript
angular.module('app', ['flowChart', ])
```
In your application (or other) controller setup a data-model for the initial flowchart (or AJAX the data-model in from a JSON resource):
```javascript
var chartDataModel = {
nodes: [
{
name: "Example Node 1",
id: 0,
x: 0,
y: 0,
inputConnectors: [
{
name: "A",
},
{
name: "B",
},
{
name: "C",
},
],
outputConnectors: [
{
name: "A",
},
{
name: "B",
},
{
name: "C",
},
],
},
{
name: "Example Node 2",
id: 1,
x: 400,
y: 200,
inputConnectors: [
{
name: "A",
},
{
name: "B",
},
{
name: "C",
},
],
outputConnectors: [
{
name: "A",
},
{
name: "B",
},
{
name: "C",
},
],
},
],
connections: [
{
source: {
nodeID: 0,
connectorIndex: 1,
},
dest: {
nodeID: 1,
connectorIndex: 2,
},
},
]
};
```
Also in your controller, wrap the data-model in a view-model and add it to the AngularJS scope:
```javascript
$scope.chartViewModel = new flowchart.ChartViewModel(chartDataModel);
```
Your code is in direct control of creation of the view-model, so you can interact with it in almost anyway you want.
Finally instantiate the flowchart's AngularJS directive in your HTML:
```html
<flow-chart
style="margin: 5px; width: 100%; height: 100%;"
chart="chartViewModel"
>
</flow-chart>
```
Be sure to bind your view-model as the 'chart' attribute!
Have fun and please contribute!
================================================
FILE: app.css
================================================
/*
Generic reset.
*/
* {
padding: 0;
margin: 0;
}
.test {
border: 5px red solid;
padding: 10;
margin: 10;
font-family: "Times New Roman";
font-style: italic;
}
/*
Styles for nodes and connectors.
*/
.node-rect {
stroke: black;
stroke-width: 2;
}
.mouseover-node-rect {
stroke: black;
stroke-width: 4;
}
.selected-node-rect {
stroke: red;
stroke-width: 3;
}
.connector-circle {
fill: white;
stroke: black;
stroke-width: 2;
}
.mouseover-connector-circle {
fill: white;
stroke: black;
stroke-width: 3;
}
/*
Style for connections.
*/
.connection {
}
.connection-line {
stroke: gray;
stroke-width: 4;
fill: transparent;
}
.mouseover-connection-line {
stroke: gray;
stroke-width: 6;
fill: transparent;
}
.selected-connection-line {
stroke: red;
stroke-width: 4;
fill: transparent;
}
.connection-endpoint {
fill: gray;
}
.selected-connection-endpoint {
fill: red;
}
.mouseover-connection-endpoint {
fill: gray;
}
.connection-name{
fill: black;
}
.selected-connection-name{
fill: red;
}
.mouseover-connection-name{
fill: gray;
}
/*
Style for the connection being dragged out.
*/
.dragging-connection {
pointer-events: none;
}
.dragging-connection-line {
stroke: gray;
stroke-width: 3;
fill: transparent;
}
.dragging-connection-endpoint {
fill: gray;
}
/*
The element (in this case the SVG element) that contains the draggable elements.
*/
.draggable-container {
border: solid 1px blue;
}
/*
Drag selection rectangle.
*/
.drag-selection-rect {
stroke: blue;
stroke-width: 2;
fill: transparent;
}
================================================
FILE: app.js
================================================
//
// Define the 'app' module.
//
angular.module('app', ['flowChart', ])
//
// Simple service to create a prompt.
//
.factory('prompt', function () {
/* Uncomment the following to test that the prompt service is working as expected.
return function () {
return "Test!";
}
*/
// Return the browsers prompt function.
return prompt;
})
//
// Application controller.
//
.controller('AppCtrl', ['$scope', 'prompt', function AppCtrl ($scope, prompt) {
//
// Code for the delete key.
//
var deleteKeyCode = 46;
//
// Code for control key.
//
var ctrlKeyCode = 17;
//
// Set to true when the ctrl key is down.
//
var ctrlDown = false;
//
// Code for A key.
//
var aKeyCode = 65;
//
// Code for esc key.
//
var escKeyCode = 27;
//
// Selects the next node id.
//
var nextNodeID = 10;
//
// Setup the data-model for the chart.
//
var chartDataModel = {
nodes: [
{
name: "Example Node 1",
id: 0,
x: 0,
y: 0,
width: 350,
inputConnectors: [
{
name: "A",
},
{
name: "B",
},
{
name: "C",
},
],
outputConnectors: [
{
name: "A",
},
{
name: "B",
},
{
name: "C",
},
],
},
{
name: "Example Node 2",
id: 1,
x: 400,
y: 200,
inputConnectors: [
{
name: "A",
},
{
name: "B",
},
{
name: "C",
},
],
outputConnectors: [
{
name: "A",
},
{
name: "B",
},
{
name: "C",
},
],
},
],
connections: [
{
name:'Connection 1',
source: {
nodeID: 0,
connectorIndex: 1,
},
dest: {
nodeID: 1,
connectorIndex: 2,
},
},
{
name:'Connection 2',
source: {
nodeID: 0,
connectorIndex: 0,
},
dest: {
nodeID: 1,
connectorIndex: 0,
},
},
]
};
//
// Event handler for key-down on the flowchart.
//
$scope.keyDown = function (evt) {
if (evt.keyCode === ctrlKeyCode) {
ctrlDown = true;
evt.stopPropagation();
evt.preventDefault();
}
};
//
// Event handler for key-up on the flowchart.
//
$scope.keyUp = function (evt) {
if (evt.keyCode === deleteKeyCode) {
//
// Delete key.
//
$scope.chartViewModel.deleteSelected();
}
if (evt.keyCode == aKeyCode && ctrlDown) {
//
// Ctrl + A
//
$scope.chartViewModel.selectAll();
}
if (evt.keyCode == escKeyCode) {
// Escape.
$scope.chartViewModel.deselectAll();
}
if (evt.keyCode === ctrlKeyCode) {
ctrlDown = false;
evt.stopPropagation();
evt.preventDefault();
}
};
//
// Add a new node to the chart.
//
$scope.addNewNode = function () {
var nodeName = prompt("Enter a node name:", "New node");
if (!nodeName) {
return;
}
//
// Template for a new node.
//
var newNodeDataModel = {
name: nodeName,
id: nextNodeID++,
x: 0,
y: 0,
inputConnectors: [
{
name: "X"
},
{
name: "Y"
},
{
name: "Z"
}
],
outputConnectors: [
{
name: "1"
},
{
name: "2"
},
{
name: "3"
}
],
};
$scope.chartViewModel.addNode(newNodeDataModel);
};
//
// Add an input connector to selected nodes.
//
$scope.addNewInputConnector = function () {
var connectorName = prompt("Enter a connector name:", "New connector");
if (!connectorName) {
return;
}
var selectedNodes = $scope.chartViewModel.getSelectedNodes();
for (var i = 0; i < selectedNodes.length; ++i) {
var node = selectedNodes[i];
node.addInputConnector({
name: connectorName,
});
}
};
//
// Add an output connector to selected nodes.
//
$scope.addNewOutputConnector = function () {
var connectorName = prompt("Enter a connector name:", "New connector");
if (!connectorName) {
return;
}
var selectedNodes = $scope.chartViewModel.getSelectedNodes();
for (var i = 0; i < selectedNodes.length; ++i) {
var node = selectedNodes[i];
node.addOutputConnector({
name: connectorName,
});
}
};
//
// Delete selected nodes and connections.
//
$scope.deleteSelected = function () {
$scope.chartViewModel.deleteSelected();
};
//
// Create the view-model for the chart and attach to the scope.
//
$scope.chartViewModel = new flowchart.ChartViewModel(chartDataModel);
}])
;
================================================
FILE: debug.js
================================================
//
// Debug utilities.
//
(function () {
if (typeof debug !== "undefined") {
throw new Error("debug object already defined!");
}
debug = {};
//
// Assert that an object is valid.
//
debug.assertObjectValid = function (obj) {
if (!obj) {
throw new Exception("Invalid object!");
}
if ($.isPlainObject(obj)) {
throw new Error("Input is not an object! It is a " + typeof(obj));
}
};
})();
================================================
FILE: flowchart/dragging_service.js
================================================
angular.module('dragging', ['mouseCapture', ] )
//
// Service used to help with dragging and clicking on elements.
//
.factory('dragging', ['$rootScope', 'mouseCapture',function ($rootScope, mouseCapture) {
//
// Threshold for dragging.
// When the mouse moves by at least this amount dragging starts.
//
var threshold = 5;
return {
//
// Called by users of the service to register a mousedown event and start dragging.
// Acquires the 'mouse capture' until the mouseup event.
//
startDrag: function (evt, config) {
var dragging = false;
var x = evt.pageX;
var y = evt.pageY;
//
// Handler for mousemove events while the mouse is 'captured'.
//
var mouseMove = function (evt) {
if (!dragging) {
if (Math.abs(evt.pageX - x) > threshold ||
Math.abs(evt.pageY - y) > threshold)
{
dragging = true;
if (config.dragStarted) {
config.dragStarted(x, y, evt);
}
if (config.dragging) {
// First 'dragging' call to take into account that we have
// already moved the mouse by a 'threshold' amount.
config.dragging(evt.pageX, evt.pageY, evt);
}
}
}
else {
if (config.dragging) {
config.dragging(evt.pageX, evt.pageY, evt);
}
x = evt.pageX;
y = evt.pageY;
}
};
//
// Handler for when mouse capture is released.
//
var released = function() {
if (dragging) {
if (config.dragEnded) {
config.dragEnded();
}
}
else {
if (config.clicked) {
config.clicked();
}
}
};
//
// Handler for mouseup event while the mouse is 'captured'.
// Mouseup releases the mouse capture.
//
var mouseUp = function (evt) {
mouseCapture.release();
evt.stopPropagation();
evt.preventDefault();
};
//
// Acquire the mouse capture and start handling mouse events.
//
mouseCapture.acquire(evt, {
mouseMove: mouseMove,
mouseUp: mouseUp,
released: released,
});
evt.stopPropagation();
evt.preventDefault();
},
};
}])
;
================================================
FILE: flowchart/flowchart_directive.js
================================================
//
// Flowchart module.
//
angular.module('flowChart', ['dragging'] )
//
// Directive that generates the rendered chart from the data model.
//
.directive('flowChart', function() {
return {
restrict: 'E',
templateUrl: "flowchart/flowchart_template.html",
replace: true,
scope: {
chart: "=chart",
},
//
// Controller for the flowchart directive.
// Having a separate controller is better for unit testing, otherwise
// it is painful to unit test a directive without instantiating the DOM
// (which is possible, just not ideal).
//
controller: 'FlowChartController',
};
})
//
// Directive that allows the chart to be edited as json in a textarea.
//
.directive('chartJsonEdit', function () {
return {
restrict: 'A',
scope: {
viewModel: "="
},
link: function (scope, elem, attr) {
//
// Serialize the data model as json and update the textarea.
//
var updateJson = function () {
if (scope.viewModel) {
var json = JSON.stringify(scope.viewModel.data, null, 4);
$(elem).val(json);
}
};
//
// First up, set the initial value of the textarea.
//
updateJson();
//
// Watch for changes in the data model and update the textarea whenever necessary.
//
scope.$watch("viewModel.data", updateJson, true);
//
// Handle the change event from the textarea and update the data model
// from the modified json.
//
$(elem).bind("input propertychange", function () {
var json = $(elem).val();
var dataModel = JSON.parse(json);
scope.viewModel = new flowchart.ChartViewModel(dataModel);
scope.$digest();
});
}
}
})
//
// Controller for the flowchart directive.
// Having a separate controller is better for unit testing, otherwise
// it is painful to unit test a directive without instantiating the DOM
// (which is possible, just not ideal).
//
.controller('FlowChartController', ['$scope', 'dragging', '$element', function FlowChartController ($scope, dragging, $element) {
var controller = this;
//
// Reference to the document and jQuery, can be overridden for testting.
//
this.document = document;
//
// Wrap jQuery so it can easily be mocked for testing.
//
this.jQuery = function (element) {
return $(element);
}
//
// Init data-model variables.
//
$scope.draggingConnection = false;
$scope.connectorSize = 10;
$scope.dragSelecting = false;
/* Can use this to test the drag selection rect.
$scope.dragSelectionRect = {
x: 0,
y: 0,
width: 0,
height: 0,
};
*/
//
// Reference to the connection, connector or node that the mouse is currently over.
//
$scope.mouseOverConnector = null;
$scope.mouseOverConnection = null;
$scope.mouseOverNode = null;
//
// The class for connections and connectors.
//
this.connectionClass = 'connection';
this.connectorClass = 'connector';
this.nodeClass = 'node';
//
// Search up the HTML element tree for an element the requested class.
//
this.searchUp = function (element, parentClass) {
//
// Reached the root.
//
if (element == null || element.length == 0) {
return null;
}
//
// Check if the element has the class that identifies it as a connector.
//
if (hasClassSVG(element, parentClass)) {
//
// Found the connector element.
//
return element;
}
//
// Recursively search parent elements.
//
return this.searchUp(element.parent(), parentClass);
};
//
// Hit test and retreive node and connector that was hit at the specified coordinates.
//
this.hitTest = function (clientX, clientY) {
//
// Retreive the element the mouse is currently over.
//
return this.document.elementFromPoint(clientX, clientY);
};
//
// Hit test and retreive node and connector that was hit at the specified coordinates.
//
this.checkForHit = function (mouseOverElement, whichClass) {
//
// Find the parent element, if any, that is a connector.
//
var hoverElement = this.searchUp(this.jQuery(mouseOverElement), whichClass);
if (!hoverElement) {
return null;
}
return hoverElement.scope();
};
//
// Translate the coordinates so they are relative to the svg element.
//
this.translateCoordinates = function(x, y, evt) {
var svg_elem = $element.get(0);
var matrix = svg_elem.getScreenCTM();
var point = svg_elem.createSVGPoint();
point.x = x - evt.view.pageXOffset;
point.y = y - evt.view.pageYOffset;
return point.matrixTransform(matrix.inverse());
};
//
// Called on mouse down in the chart.
//
$scope.mouseDown = function (evt) {
$scope.chart.deselectAll();
dragging.startDrag(evt, {
//
// Commence dragging... setup variables to display the drag selection rect.
//
dragStarted: function (x, y) {
$scope.dragSelecting = true;
var startPoint = controller.translateCoordinates(x, y, evt);
$scope.dragSelectionStartPoint = startPoint;
$scope.dragSelectionRect = {
x: startPoint.x,
y: startPoint.y,
width: 0,
height: 0,
};
},
//
// Update the drag selection rect while dragging continues.
//
dragging: function (x, y) {
var startPoint = $scope.dragSelectionStartPoint;
var curPoint = controller.translateCoordinates(x, y, evt);
$scope.dragSelectionRect = {
x: curPoint.x > startPoint.x ? startPoint.x : curPoint.x,
y: curPoint.y > startPoint.y ? startPoint.y : curPoint.y,
width: curPoint.x > startPoint.x ? curPoint.x - startPoint.x : startPoint.x - curPoint.x,
height: curPoint.y > startPoint.y ? curPoint.y - startPoint.y : startPoint.y - curPoint.y,
};
},
//
// Dragging has ended... select all that are within the drag selection rect.
//
dragEnded: function () {
$scope.dragSelecting = false;
$scope.chart.applySelectionRect($scope.dragSelectionRect);
delete $scope.dragSelectionStartPoint;
delete $scope.dragSelectionRect;
},
});
};
//
// Called for each mouse move on the svg element.
//
$scope.mouseMove = function (evt) {
//
// Clear out all cached mouse over elements.
//
$scope.mouseOverConnection = null;
$scope.mouseOverConnector = null;
$scope.mouseOverNode = null;
var mouseOverElement = controller.hitTest(evt.clientX, evt.clientY);
if (mouseOverElement == null) {
// Mouse isn't over anything, just clear all.
return;
}
if (!$scope.draggingConnection) { // Only allow 'connection mouse over' when not dragging out a connection.
// Figure out if the mouse is over a connection.
var scope = controller.checkForHit(mouseOverElement, controller.connectionClass);
$scope.mouseOverConnection = (scope && scope.connection) ? scope.connection : null;
if ($scope.mouseOverConnection) {
// Don't attempt to mouse over anything else.
return;
}
}
// Figure out if the mouse is over a connector.
var scope = controller.checkForHit(mouseOverElement, controller.connectorClass);
$scope.mouseOverConnector = (scope && scope.connector) ? scope.connector : null;
if ($scope.mouseOverConnector) {
// Don't attempt to mouse over anything else.
return;
}
// Figure out if the mouse is over a node.
var scope = controller.checkForHit(mouseOverElement, controller.nodeClass);
$scope.mouseOverNode = (scope && scope.node) ? scope.node : null;
};
//
// Handle mousedown on a node.
//
$scope.nodeMouseDown = function (evt, node) {
var chart = $scope.chart;
var lastMouseCoords;
dragging.startDrag(evt, {
//
// Node dragging has commenced.
//
dragStarted: function (x, y) {
lastMouseCoords = controller.translateCoordinates(x, y, evt);
//
// If nothing is selected when dragging starts,
// at least select the node we are dragging.
//
if (!node.selected()) {
chart.deselectAll();
node.select();
}
},
//
// Dragging selected nodes... update their x,y coordinates.
//
dragging: function (x, y) {
var curCoords = controller.translateCoordinates(x, y, evt);
var deltaX = curCoords.x - lastMouseCoords.x;
var deltaY = curCoords.y - lastMouseCoords.y;
chart.updateSelectedNodesLocation(deltaX, deltaY);
lastMouseCoords = curCoords;
},
//
// The node wasn't dragged... it was clicked.
//
clicked: function () {
chart.handleNodeClicked(node, evt.ctrlKey);
},
});
};
//
// Handle mousedown on a connection.
//
$scope.connectionMouseDown = function (evt, connection) {
var chart = $scope.chart;
chart.handleConnectionMouseDown(connection, evt.ctrlKey);
// Don't let the chart handle the mouse down.
evt.stopPropagation();
evt.preventDefault();
};
//
// Handle mousedown on an input connector.
//
$scope.connectorMouseDown = function (evt, node, connector, connectorIndex, isInputConnector) {
//
// Initiate dragging out of a connection.
//
dragging.startDrag(evt, {
//
// Called when the mouse has moved greater than the threshold distance
// and dragging has commenced.
//
dragStarted: function (x, y) {
var curCoords = controller.translateCoordinates(x, y, evt);
$scope.draggingConnection = true;
$scope.dragPoint1 = flowchart.computeConnectorPos(node, connectorIndex, isInputConnector);
$scope.dragPoint2 = {
x: curCoords.x,
y: curCoords.y
};
$scope.dragTangent1 = flowchart.computeConnectionSourceTangent($scope.dragPoint1, $scope.dragPoint2);
$scope.dragTangent2 = flowchart.computeConnectionDestTangent($scope.dragPoint1, $scope.dragPoint2);
},
//
// Called on mousemove while dragging out a connection.
//
dragging: function (x, y, evt) {
var startCoords = controller.translateCoordinates(x, y, evt);
$scope.dragPoint1 = flowchart.computeConnectorPos(node, connectorIndex, isInputConnector);
$scope.dragPoint2 = {
x: startCoords.x,
y: startCoords.y
};
$scope.dragTangent1 = flowchart.computeConnectionSourceTangent($scope.dragPoint1, $scope.dragPoint2);
$scope.dragTangent2 = flowchart.computeConnectionDestTangent($scope.dragPoint1, $scope.dragPoint2);
},
//
// Clean up when dragging has finished.
//
dragEnded: function () {
if ($scope.mouseOverConnector &&
$scope.mouseOverConnector !== connector) {
//
// Dragging has ended...
// The mouse is over a valid connector...
// Create a new connection.
//
$scope.chart.createNewConnection(connector, $scope.mouseOverConnector);
}
$scope.draggingConnection = false;
delete $scope.dragPoint1;
delete $scope.dragTangent1;
delete $scope.dragPoint2;
delete $scope.dragTangent2;
},
});
};
}])
;
================================================
FILE: flowchart/flowchart_directive.spec.js
================================================
describe('flowchart-directive', function () {
var testObject;
var mockScope;
var mockDragging;
var mockSvgElement;
//
// Bring in the flowChart module before each test.
//
beforeEach(module('flowChart'));
//
// Helper function to create the controller for each test.
//
var createController = function ($rootScope, $controller) {
mockScope = $rootScope.$new();
mockDragging = createMockDragging();
mockSvgElement = {
get: function () {
return createMockSvgElement();
}
};
testObject = $controller('FlowChartController', {
$scope: mockScope,
dragging: mockDragging,
$element: mockSvgElement,
});
};
//
// Setup the controller before each test.
//
beforeEach(inject(function ($rootScope, $controller) {
createController($rootScope, $controller);
}));
//
// Create a mock DOM element.
//
var createMockElement = function(attr, parent, scope) {
return {
attr: function() {
return attr;
},
parent: function () {
return parent;
},
scope: function () {
return scope || {};
},
};
}
//
// Create a mock node data model.
//
var createMockNode = function (inputConnectors, outputConnectors) {
return {
x: function () { return 0 },
y: function () { return 0 },
inputConnectors: inputConnectors || [],
outputConnectors: outputConnectors || [],
select: jasmine.createSpy(),
selected: function () { return false; },
};
};
//
// Create a mock chart.
//
var createMockChart = function (mockNodes, mockConnections) {
return {
nodes: mockNodes,
connections: mockConnections,
handleNodeClicked: jasmine.createSpy(),
handleConnectionMouseDown: jasmine.createSpy(),
updateSelectedNodesLocation: jasmine.createSpy(),
deselectAll: jasmine.createSpy(),
createNewConnection: jasmine.createSpy(),
applySelectionRect: jasmine.createSpy(),
};
};
//
// Create a mock dragging service.
//
var createMockDragging = function () {
var mockDragging = {
startDrag: function (evt, config) {
mockDragging.evt = evt;
mockDragging.config = config;
},
};
return mockDragging;
};
//
// Create a mock version of the SVG element.
//
var createMockSvgElement = function () {
return {
getScreenCTM: function () {
return {
inverse: function () {
return this;
},
};
},
createSVGPoint: function () {
return {
x: 0,
y: 0 ,
matrixTransform: function () {
return this;
},
};
}
};
};
it('searchUp returns null when at root 1', function () {
expect(testObject.searchUp(null, "some-class")).toBe(null);
});
it('searchUp returns null when at root 2', function () {
expect(testObject.searchUp([], "some-class")).toBe(null);
});
it('searchUp returns element when it has requested class', function () {
var whichClass = "some-class";
var mockElement = createMockElement(whichClass);
expect(testObject.searchUp(mockElement, whichClass)).toBe(mockElement);
});
it('searchUp returns parent when it has requested class', function () {
var whichClass = "some-class";
var mockParent = createMockElement(whichClass);
var mockElement = createMockElement('', mockParent);
expect(testObject.searchUp(mockElement, whichClass)).toBe(mockParent);
});
it('hitTest returns result of elementFromPoint', function () {
var mockElement = {};
// Mock out the document.
testObject.document = {
elementFromPoint: function () {
return mockElement;
},
};
expect(testObject.hitTest(12, 30)).toBe(mockElement);
});
it('checkForHit returns null when the hit element has no parent with requested class', function () {
var mockElement = createMockElement(null, null);
testObject.jQuery = function (input) {
return input;
};
expect(testObject.checkForHit(mockElement, "some-class")).toBe(null);
});
it('checkForHit returns the result of searchUp when found', function () {
var mockConnectorScope = {};
var whichClass = "some-class";
var mockElement = createMockElement(whichClass, null, mockConnectorScope);
testObject.jQuery = function (input) {
return input;
};
expect(testObject.checkForHit(mockElement, whichClass)).toBe(mockConnectorScope);
});
it('checkForHit returns null when searchUp fails', function () {
var mockElement = createMockElement(null, null, null);
testObject.jQuery = function (input) {
return input;
};
expect(testObject.checkForHit(mockElement, "some-class")).toBe(null);
});
it('test node dragging is started on node mouse down', function () {
mockDragging.startDrag = jasmine.createSpy();
var mockEvt = {};
var mockNode = createMockNode();
mockScope.nodeMouseDown(mockEvt, mockNode);
expect(mockDragging.startDrag).toHaveBeenCalled();
});
it('test node click handling is forwarded to view model', function () {
mockScope.chart = createMockChart([mockNode]);
var mockEvt = {
ctrlKey: false,
};
var mockNode = createMockNode();
mockScope.nodeMouseDown(mockEvt, mockNode);
mockDragging.config.clicked();
expect(mockScope.chart.handleNodeClicked).toHaveBeenCalledWith(mockNode, false);
});
it('test control + node click handling is forwarded to view model', function () {
var mockNode = createMockNode();
mockScope.chart = createMockChart([mockNode]);
var mockEvt = {
ctrlKey: true,
};
mockScope.nodeMouseDown(mockEvt, mockNode);
mockDragging.config.clicked();
expect(mockScope.chart.handleNodeClicked).toHaveBeenCalledWith(mockNode, true);
});
it('test node dragging updates selected nodes location', function () {
var mockEvt = {
view: {
pageXOffset: 0,
pageYOffset: 0,
},
};
mockScope.chart = createMockChart([createMockNode()]);
mockScope.nodeMouseDown(mockEvt, mockScope.chart.nodes[0]);
var xIncrement = 5;
var yIncrement = 15;
mockDragging.config.dragStarted(0, 0);
mockDragging.config.dragging(xIncrement, yIncrement);
expect(mockScope.chart.updateSelectedNodesLocation).toHaveBeenCalledWith(xIncrement, yIncrement);
});
it('test node dragging doesnt modify selection when node is already selected', function () {
var mockNode1 = createMockNode();
var mockNode2 = createMockNode();
mockScope.chart = createMockChart([mockNode1, mockNode2]);
mockNode2.selected = function () { return true; }
var mockEvt = {
view: {
scrollX: 0,
scrollY: 0,
},
};
mockScope.nodeMouseDown(mockEvt, mockNode2);
mockDragging.config.dragStarted(0, 0);
expect(mockScope.chart.deselectAll).not.toHaveBeenCalled();
});
it('test node dragging selects node, when the node is not already selected', function () {
var mockNode1 = createMockNode();
var mockNode2 = createMockNode();
mockScope.chart = createMockChart([mockNode1, mockNode2]);
var mockEvt = {
view: {
scrollX: 0,
scrollY: 0,
},
};
mockScope.nodeMouseDown(mockEvt, mockNode2);
mockDragging.config.dragStarted(0, 0);
expect(mockScope.chart.deselectAll).toHaveBeenCalled();
expect(mockNode2.select).toHaveBeenCalled();
});
it('test connection click handling is forwarded to view model', function () {
var mockNode = createMockNode();
var mockEvt = {
stopPropagation: jasmine.createSpy(),
preventDefault: jasmine.createSpy(),
ctrlKey: false,
};
var mockConnection = {};
mockScope.chart = createMockChart([mockNode]);
mockScope.connectionMouseDown(mockEvt, mockConnection);
expect(mockScope.chart.handleConnectionMouseDown).toHaveBeenCalledWith(mockConnection, false);
expect(mockEvt.stopPropagation).toHaveBeenCalled();
expect(mockEvt.preventDefault).toHaveBeenCalled();
});
it('test control + connection click handling is forwarded to view model', function () {
var mockNode = createMockNode();
var mockEvt = {
stopPropagation: jasmine.createSpy(),
preventDefault: jasmine.createSpy(),
ctrlKey: true,
};
var mockConnection = {};
mockScope.chart = createMockChart([mockNode]);
mockScope.connectionMouseDown(mockEvt, mockConnection);
expect(mockScope.chart.handleConnectionMouseDown).toHaveBeenCalledWith(mockConnection, true);
});
it('test selection is cleared when background is clicked', function () {
var mockEvt = {};
mockScope.chart = createMockChart([createMockNode()]);
mockScope.chart.nodes[0].selected = true;
mockScope.mouseDown(mockEvt);
expect(mockScope.chart.deselectAll).toHaveBeenCalled();
});
it('test background mouse down commences selection dragging', function () {
var mockNode = createMockNode();
var mockEvt = {
view: {
scrollX: 0,
scrollY: 0,
},
};
mockScope.chart = createMockChart([mockNode]);
mockScope.mouseDown(mockEvt);
mockDragging.config.dragStarted(0, 0);
expect(mockScope.dragSelecting).toBe(true);
});
it('test can end selection dragging', function () {
var mockNode = createMockNode();
var mockEvt = {
view: {
scrollX: 0,
scrollY: 0,
},
};
mockScope.chart = createMockChart([mockNode]);
mockScope.mouseDown(mockEvt);
mockDragging.config.dragStarted(0, 0, mockEvt);
mockDragging.config.dragging(0, 0, mockEvt);
mockDragging.config.dragEnded();
expect(mockScope.dragSelecting).toBe(false);
});
it('test selection dragging ends by selecting nodes', function () {
var mockNode = createMockNode();
var mockEvt = {
view: {
scrollX: 0,
scrollY: 0,
},
};
mockScope.chart = createMockChart([mockNode]);
mockScope.mouseDown(mockEvt);
mockDragging.config.dragStarted(0, 0, mockEvt);
mockDragging.config.dragging(0, 0, mockEvt);
var selectionRect = {
x: 1,
y: 2,
width: 3,
height: 4,
};
mockScope.dragSelectionRect = selectionRect;
mockDragging.config.dragEnded();
expect(mockScope.chart.applySelectionRect).toHaveBeenCalledWith(selectionRect);
});
it('test mouse down commences connection dragging', function () {
var mockNode = createMockNode();
var mockEvt = {
view: {
scrollX: 0,
scrollY: 0,
},
};
mockScope.chart = createMockChart([mockNode]);
mockScope.connectorMouseDown(mockEvt, mockScope.chart.nodes[0], mockScope.chart.nodes[0].inputConnectors[0], 0, false);
mockDragging.config.dragStarted(0, 0);
expect(mockScope.draggingConnection).toBe(true);
});
it('test can end connection dragging', function () {
var mockNode = createMockNode();
var mockEvt = {
view: {
scrollX: 0,
scrollY: 0,
},
};
mockScope.chart = createMockChart([mockNode]);
mockScope.connectorMouseDown(mockEvt, mockScope.chart.nodes[0], mockScope.chart.nodes[0].inputConnectors[0], 0, false);
mockDragging.config.dragStarted(0, 0, mockEvt);
mockDragging.config.dragging(0, 0, mockEvt);
mockDragging.config.dragEnded();
expect(mockScope.draggingConnection).toBe(false);
});
it('test can make a connection by dragging', function () {
var mockNode = createMockNode();
var mockDraggingConnector = {};
var mockDragOverConnector = {};
var mockEvt = {
view: {
scrollX: 0,
scrollY: 0,
},
};
mockScope.chart = createMockChart([mockNode]);
mockScope.connectorMouseDown(mockEvt, mockScope.chart.nodes[0], mockDraggingConnector, 0, false);
mockDragging.config.dragStarted(0, 0, mockEvt);
mockDragging.config.dragging(0, 0, mockEvt);
// Fake out the mouse over connector.
mockScope.mouseOverConnector = mockDragOverConnector;
mockDragging.config.dragEnded();
expect(mockScope.chart.createNewConnection).toHaveBeenCalledWith(mockDraggingConnector, mockDragOverConnector);
});
it('test connection creation by dragging is cancelled when dragged over invalid connector', function () {
var mockNode = createMockNode();
var mockDraggingConnector = {};
var mockEvt = {
view: {
scrollX: 0,
scrollY: 0,
},
};
mockScope.chart = createMockChart([mockNode]);
mockScope.connectorMouseDown(mockEvt, mockScope.chart.nodes[0], mockDraggingConnector, 0, false);
mockDragging.config.dragStarted(0, 0, mockEvt);
mockDragging.config.dragging(0, 0, mockEvt);
// Fake out the invalid connector.
mockScope.mouseOverConnector = null;
mockDragging.config.dragEnded();
expect(mockScope.chart.createNewConnection).not.toHaveBeenCalled();
});
it('mouse move over connection caches the connection', function () {
var mockElement = {};
var mockConnection = {};
var mockConnectionScope = {
connection: mockConnection
};
var mockEvent = {};
//
// Fake out the function that check if a connection has been hit.
//
testObject.checkForHit = function (element, whichClass) {
if (whichClass === testObject.connectionClass) {
return mockConnectionScope;
}
return null;
};
testObject.hitTest = function () {
return mockElement;
};
mockScope.mouseMove(mockEvent);
expect(mockScope.mouseOverConnection).toBe(mockConnection);
});
it('test mouse over connection clears mouse over connector and node', function () {
var mockElement = {};
var mockConnection = {};
var mockConnectionScope = {
connection: mockConnection
};
var mockEvent = {};
//
// Fake out the function that check if a connection has been hit.
//
testObject.checkForHit = function (element, whichClass) {
if (whichClass === testObject.connectionClass) {
return mockConnectionScope;
}
return null;
};
testObject.hitTest = function () {
return mockElement;
};
mockScope.mouseOverConnector = {};
mockScope.mouseOverNode = {};
mockScope.mouseMove(mockEvent);
expect(mockScope.mouseOverConnector).toBe(null);
expect(mockScope.mouseOverNode).toBe(null);
});
it('test mouseMove handles mouse over connector', function () {
var mockElement = {};
var mockConnector = {};
var mockConnectorScope = {
connector: mockConnector
};
var mockEvent = {};
//
// Fake out the function that check if a connector has been hit.
//
testObject.checkForHit = function (element, whichClass) {
if (whichClass === testObject.connectorClass) {
return mockConnectorScope;
}
return null;
};
testObject.hitTest = function () {
return mockElement;
};
mockScope.mouseMove(mockEvent);
expect(mockScope.mouseOverConnector).toBe(mockConnector);
});
it('test mouseMove handles mouse over node', function () {
var mockElement = {};
var mockNode = {};
var mockNodeScope = {
node: mockNode
};
var mockEvent = {};
//
// Fake out the function that check if a connector has been hit.
//
testObject.checkForHit = function (element, whichClass) {
if (whichClass === testObject.nodeClass) {
return mockNodeScope;
}
return null;
};
testObject.hitTest = function () {
return mockElement;
};
mockScope.mouseMove(mockEvent);
expect(mockScope.mouseOverNode).toBe(mockNode);
});
});
================================================
FILE: flowchart/flowchart_template.html
================================================
<svg
class="draggable-container"
xmlns="http://www.w3.org/2000/svg"
ng-mousedown="mouseDown($event)"
ng-mousemove="mouseMove($event)"
>
<defs>
<linearGradient
spreadMethod="pad"
y2="0"
x2="0"
y1="1"
x1="0"
id="nodeBackgroundGradient"
>
<stop
offset="0"
stop-opacity="0.99609"
stop-color="#56aaff"
/>
<stop
offset="0.63934"
stop-opacity="0.99219"
stop-color="#d0d0e5"
/>
</linearGradient>
</defs>
<g
ng-repeat="node in chart.nodes"
ng-mousedown="nodeMouseDown($event, node)"
ng-attr-transform="translate({{node.x()}}, {{node.y()}})"
>
<rect
ng-attr-class="{{node.selected() && 'selected-node-rect' || (node == mouseOverNode && 'mouseover-node-rect' || 'node-rect')}}"
ry="10"
rx="10"
x="0"
y="0"
ng-attr-width="{{node.width()}}"
ng-attr-height="{{node.height()}}"
fill="url(#nodeBackgroundGradient)"
>
</rect>
<text
ng-attr-x="{{node.width()/2}}"
y="25"
text-anchor="middle"
alignment-baseline="middle"
>
{{node.name()}}
</text>
<g
ng-repeat="connector in node.inputConnectors"
ng-mousedown="connectorMouseDown($event, node, connector, $index, true)"
class="connector input-connector"
>
<text
ng-attr-x="{{connector.x() + 20}}"
ng-attr-y="{{connector.y()}}"
text-anchor="left"
alignment-baseline="middle"
>
{{connector.name()}}
</text>
<circle
ng-attr-class="{{connector == mouseOverConnector && 'mouseover-connector-circle' || 'connector-circle'}}"
ng-attr-r="{{connectorSize}}"
ng-attr-cx="{{connector.x()}}"
ng-attr-cy="{{connector.y()}}"
/>
</g>
<g
ng-repeat="connector in node.outputConnectors"
ng-mousedown="connectorMouseDown($event, node, connector, $index, false)"
class="connector output-connector"
>
<text
ng-attr-x="{{connector.x() - 20}}"
ng-attr-y="{{connector.y()}}"
text-anchor="end"
alignment-baseline="middle"
>
{{connector.name()}}
</text>
<circle
ng-attr-class="{{connector == mouseOverConnector && 'mouseover-connector-circle' || 'connector-circle'}}"
ng-attr-r="{{connectorSize}}"
ng-attr-cx="{{connector.x()}}"
ng-attr-cy="{{connector.y()}}"
/>
</g>
</g>
<g>
<g
ng-repeat="connection in chart.connections"
class="connection"
ng-mousedown="connectionMouseDown($event, connection)"
>
<path
ng-attr-class="{{connection.selected() && 'selected-connection-line' || (connection == mouseOverConnection && 'mouseover-connection-line' || 'connection-line')}}"
ng-attr-d="M {{connection.sourceCoordX()}}, {{connection.sourceCoordY()}}
C {{connection.sourceTangentX()}}, {{connection.sourceTangentY()}}
{{connection.destTangentX()}}, {{connection.destTangentY()}}
{{connection.destCoordX()}}, {{connection.destCoordY()}}"
>
</path>
<text
ng-attr-class="{{connection.selected() && 'selected-connection-name' || (connection == mouseOverConnection && 'mouseover-connection-name' || 'connection-name')}}"
ng-attr-x="{{connection.middleX()}}"
ng-attr-y="{{connection.middleY()}}"
text-anchor="middle"
alignment-baseline="middle"
>{{connection.name()}}</text>
<circle
ng-attr-class="{{connection.selected() && 'selected-connection-endpoint' || (connection == mouseOverConnection && 'mouseover-connection-endpoint' || 'connection-endpoint')}}"
r="5"
ng-attr-cx="{{connection.sourceCoordX()}}"
ng-attr-cy="{{connection.sourceCoordY()}}"
>
</circle>
<circle
ng-attr-class="{{connection.selected() && 'selected-connection-endpoint' || (connection == mouseOverConnection && 'mouseover-connection-endpoint' || 'connection-endpoint')}}"
r="5"
ng-attr-cx="{{connection.destCoordX()}}"
ng-attr-cy="{{connection.destCoordY()}}"
>
</circle>
</g>
</g>
<g
ng-if="draggingConnection"
>
<path
class="dragging-connection dragging-connection-line"
ng-attr-d="M {{dragPoint1.x}}, {{dragPoint1.y}}
C {{dragTangent1.x}}, {{dragTangent1.y}}
{{dragTangent2.x}}, {{dragTangent2.y}}
{{dragPoint2.x}}, {{dragPoint2.y}}"
>
</path>
<circle
class="dragging-connection dragging-connection-endpoint"
r="4"
ng-attr-cx="{{dragPoint1.x}}"
ng-attr-cy="{{dragPoint1.y}}"
>
</circle>
<circle
class="dragging-connection dragging-connection-endpoint"
r="4"
ng-attr-cx="{{dragPoint2.x}}"
ng-attr-cy="{{dragPoint2.y}}"
>
</circle>
</g>
<rect
ng-if="dragSelecting"
class="drag-selection-rect"
ng-attr-x="{{dragSelectionRect.x}}"
ng-attr-y="{{dragSelectionRect.y}}"
ng-attr-width="{{dragSelectionRect.width}}"
ng-attr-height="{{dragSelectionRect.height}}"
>
</rect>
</svg>
================================================
FILE: flowchart/flowchart_viewmodel.js
================================================
//
// Global accessor.
//
var flowchart = {
};
// Module.
(function () {
//
// Width of a node.
//
flowchart.defaultNodeWidth = 250;
//
// Amount of space reserved for displaying the node's name.
//
flowchart.nodeNameHeight = 40;
//
// Height of a connector in a node.
//
flowchart.connectorHeight = 35;
//
// Compute the Y coordinate of a connector, given its index.
//
flowchart.computeConnectorY = function (connectorIndex) {
return flowchart.nodeNameHeight + (connectorIndex * flowchart.connectorHeight);
}
//
// Compute the position of a connector in the graph.
//
flowchart.computeConnectorPos = function (node, connectorIndex, inputConnector) {
return {
x: node.x() + (inputConnector ? 0 : node.width ? node.width() : flowchart.defaultNodeWidth),
y: node.y() + flowchart.computeConnectorY(connectorIndex),
};
};
//
// View model for a connector.
//
flowchart.ConnectorViewModel = function (connectorDataModel, x, y, parentNode) {
this.data = connectorDataModel;
this._parentNode = parentNode;
this._x = x;
this._y = y;
//
// The name of the connector.
//
this.name = function () {
return this.data.name;
}
//
// X coordinate of the connector.
//
this.x = function () {
return this._x;
};
//
// Y coordinate of the connector.
//
this.y = function () {
return this._y;
};
//
// The parent node that the connector is attached to.
//
this.parentNode = function () {
return this._parentNode;
};
};
//
// Create view model for a list of data models.
//
var createConnectorsViewModel = function (connectorDataModels, x, parentNode) {
var viewModels = [];
if (connectorDataModels) {
for (var i = 0; i < connectorDataModels.length; ++i) {
var connectorViewModel =
new flowchart.ConnectorViewModel(connectorDataModels[i], x, flowchart.computeConnectorY(i), parentNode);
viewModels.push(connectorViewModel);
}
}
return viewModels;
};
//
// View model for a node.
//
flowchart.NodeViewModel = function (nodeDataModel) {
this.data = nodeDataModel;
// set the default width value of the node
if (!this.data.width || this.data.width < 0) {
this.data.width = flowchart.defaultNodeWidth;
}
this.inputConnectors = createConnectorsViewModel(this.data.inputConnectors, 0, this);
this.outputConnectors = createConnectorsViewModel(this.data.outputConnectors, this.data.width, this);
// Set to true when the node is selected.
this._selected = false;
//
// Name of the node.
//
this.name = function () {
return this.data.name || "";
};
//
// X coordinate of the node.
//
this.x = function () {
return this.data.x;
};
//
// Y coordinate of the node.
//
this.y = function () {
return this.data.y;
};
//
// Width of the node.
//
this.width = function () {
return this.data.width;
}
//
// Height of the node.
//
this.height = function () {
var numConnectors =
Math.max(
this.inputConnectors.length,
this.outputConnectors.length);
return flowchart.computeConnectorY(numConnectors);
}
//
// Select the node.
//
this.select = function () {
this._selected = true;
};
//
// Deselect the node.
//
this.deselect = function () {
this._selected = false;
};
//
// Toggle the selection state of the node.
//
this.toggleSelected = function () {
this._selected = !this._selected;
};
//
// Returns true if the node is selected.
//
this.selected = function () {
return this._selected;
};
//
// Internal function to add a connector.
this._addConnector = function (connectorDataModel, x, connectorsDataModel, connectorsViewModel) {
var connectorViewModel =
new flowchart.ConnectorViewModel(connectorDataModel, x,
flowchart.computeConnectorY(connectorsViewModel.length), this);
connectorsDataModel.push(connectorDataModel);
// Add to node's view model.
connectorsViewModel.push(connectorViewModel);
}
//
// Add an input connector to the node.
//
this.addInputConnector = function (connectorDataModel) {
if (!this.data.inputConnectors) {
this.data.inputConnectors = [];
}
this._addConnector(connectorDataModel, 0, this.data.inputConnectors, this.inputConnectors);
};
//
// Add an ouput connector to the node.
//
this.addOutputConnector = function (connectorDataModel) {
if (!this.data.outputConnectors) {
this.data.outputConnectors = [];
}
this._addConnector(connectorDataModel, this.data.width, this.data.outputConnectors, this.outputConnectors);
};
};
//
// Wrap the nodes data-model in a view-model.
//
var createNodesViewModel = function (nodesDataModel) {
var nodesViewModel = [];
if (nodesDataModel) {
for (var i = 0; i < nodesDataModel.length; ++i) {
nodesViewModel.push(new flowchart.NodeViewModel(nodesDataModel[i]));
}
}
return nodesViewModel;
};
//
// View model for a connection.
//
flowchart.ConnectionViewModel = function (connectionDataModel, sourceConnector, destConnector) {
this.data = connectionDataModel;
this.source = sourceConnector;
this.dest = destConnector;
// Set to true when the connection is selected.
this._selected = false;
this.name = function() {
return this.data.name || "";
}
this.sourceCoordX = function () {
return this.source.parentNode().x() + this.source.x();
};
this.sourceCoordY = function () {
return this.source.parentNode().y() + this.source.y();
};
this.sourceCoord = function () {
return {
x: this.sourceCoordX(),
y: this.sourceCoordY()
};
}
this.sourceTangentX = function () {
return flowchart.computeConnectionSourceTangentX(this.sourceCoord(), this.destCoord());
};
this.sourceTangentY = function () {
return flowchart.computeConnectionSourceTangentY(this.sourceCoord(), this.destCoord());
};
this.destCoordX = function () {
return this.dest.parentNode().x() + this.dest.x();
};
this.destCoordY = function () {
return this.dest.parentNode().y() + this.dest.y();
};
this.destCoord = function () {
return {
x: this.destCoordX(),
y: this.destCoordY()
};
}
this.destTangentX = function () {
return flowchart.computeConnectionDestTangentX(this.sourceCoord(), this.destCoord());
};
this.destTangentY = function () {
return flowchart.computeConnectionDestTangentY(this.sourceCoord(), this.destCoord());
};
this.middleX = function(scale) {
if(typeof(scale)=="undefined")
scale = 0.5;
return this.sourceCoordX()*(1-scale)+this.destCoordX()*scale;
};
this.middleY = function(scale) {
if(typeof(scale)=="undefined")
scale = 0.5;
return this.sourceCoordY()*(1-scale)+this.destCoordY()*scale;
};
//
// Select the connection.
//
this.select = function () {
this._selected = true;
};
//
// Deselect the connection.
//
this.deselect = function () {
this._selected = false;
};
//
// Toggle the selection state of the connection.
//
this.toggleSelected = function () {
this._selected = !this._selected;
};
//
// Returns true if the connection is selected.
//
this.selected = function () {
return this._selected;
};
};
//
// Helper function.
//
var computeConnectionTangentOffset = function (pt1, pt2) {
return (pt2.x - pt1.x) / 2;
}
//
// Compute the tangent for the bezier curve.
//
flowchart.computeConnectionSourceTangentX = function (pt1, pt2) {
return pt1.x + computeConnectionTangentOffset(pt1, pt2);
};
//
// Compute the tangent for the bezier curve.
//
flowchart.computeConnectionSourceTangentY = function (pt1, pt2) {
return pt1.y;
};
//
// Compute the tangent for the bezier curve.
//
flowchart.computeConnectionSourceTangent = function(pt1, pt2) {
return {
x: flowchart.computeConnectionSourceTangentX(pt1, pt2),
y: flowchart.computeConnectionSourceTangentY(pt1, pt2),
};
};
//
// Compute the tangent for the bezier curve.
//
flowchart.computeConnectionDestTangentX = function (pt1, pt2) {
return pt2.x - computeConnectionTangentOffset(pt1, pt2);
};
//
// Compute the tangent for the bezier curve.
//
flowchart.computeConnectionDestTangentY = function (pt1, pt2) {
return pt2.y;
};
//
// Compute the tangent for the bezier curve.
//
flowchart.computeConnectionDestTangent = function(pt1, pt2) {
return {
x: flowchart.computeConnectionDestTangentX(pt1, pt2),
y: flowchart.computeConnectionDestTangentY(pt1, pt2),
};
};
//
// View model for the chart.
//
flowchart.ChartViewModel = function (chartDataModel) {
//
// Find a specific node within the chart.
//
this.findNode = function (nodeID) {
for (var i = 0; i < this.nodes.length; ++i) {
var node = this.nodes[i];
if (node.data.id == nodeID) {
return node;
}
}
throw new Error("Failed to find node " + nodeID);
};
//
// Find a specific input connector within the chart.
//
this.findInputConnector = function (nodeID, connectorIndex) {
var node = this.findNode(nodeID);
if (!node.inputConnectors || node.inputConnectors.length <= connectorIndex) {
throw new Error("Node " + nodeID + " has invalid input connectors.");
}
return node.inputConnectors[connectorIndex];
};
//
// Find a specific output connector within the chart.
//
this.findOutputConnector = function (nodeID, connectorIndex) {
var node = this.findNode(nodeID);
if (!node.outputConnectors || node.outputConnectors.length <= connectorIndex) {
throw new Error("Node " + nodeID + " has invalid output connectors.");
}
return node.outputConnectors[connectorIndex];
};
//
// Create a view model for connection from the data model.
//
this._createConnectionViewModel = function(connectionDataModel) {
var sourceConnector = this.findOutputConnector(connectionDataModel.source.nodeID, connectionDataModel.source.connectorIndex);
var destConnector = this.findInputConnector(connectionDataModel.dest.nodeID, connectionDataModel.dest.connectorIndex);
return new flowchart.ConnectionViewModel(connectionDataModel, sourceConnector, destConnector);
}
//
// Wrap the connections data-model in a view-model.
//
this._createConnectionsViewModel = function (connectionsDataModel) {
var connectionsViewModel = [];
if (connectionsDataModel) {
for (var i = 0; i < connectionsDataModel.length; ++i) {
connectionsViewModel.push(this._createConnectionViewModel(connectionsDataModel[i]));
}
}
return connectionsViewModel;
};
// Reference to the underlying data.
this.data = chartDataModel;
// Create a view-model for nodes.
this.nodes = createNodesViewModel(this.data.nodes);
// Create a view-model for connections.
this.connections = this._createConnectionsViewModel(this.data.connections);
//
// Create a view model for a new connection.
//
this.createNewConnection = function (startConnector, endConnector) {
var connectionsDataModel = this.data.connections;
if (!connectionsDataModel) {
connectionsDataModel = this.data.connections = [];
}
var connectionsViewModel = this.connections;
if (!connectionsViewModel) {
connectionsViewModel = this.connections = [];
}
var startNode = startConnector.parentNode();
var startConnectorIndex = startNode.outputConnectors.indexOf(startConnector);
var startConnectorType = 'output';
if (startConnectorIndex == -1) {
startConnectorIndex = startNode.inputConnectors.indexOf(startConnector);
startConnectorType = 'input';
if (startConnectorIndex == -1) {
throw new Error("Failed to find source connector within either inputConnectors or outputConnectors of source node.");
}
}
var endNode = endConnector.parentNode();
var endConnectorIndex = endNode.inputConnectors.indexOf(endConnector);
var endConnectorType = 'input';
if (endConnectorIndex == -1) {
endConnectorIndex = endNode.outputConnectors.indexOf(endConnector);
endConnectorType = 'output';
if (endConnectorIndex == -1) {
throw new Error("Failed to find dest connector within inputConnectors or outputConnectors of dest node.");
}
}
if (startConnectorType == endConnectorType) {
throw new Error("Failed to create connection. Only output to input connections are allowed.")
}
if (startNode == endNode) {
throw new Error("Failed to create connection. Cannot link a node with itself.")
}
var startNode = {
nodeID: startNode.data.id,
connectorIndex: startConnectorIndex,
}
var endNode = {
nodeID: endNode.data.id,
connectorIndex: endConnectorIndex,
}
var connectionDataModel = {
source: startConnectorType == 'output' ? startNode : endNode,
dest: startConnectorType == 'output' ? endNode : startNode,
};
connectionsDataModel.push(connectionDataModel);
var outputConnector = startConnectorType == 'output' ? startConnector : endConnector;
var inputConnector = startConnectorType == 'output' ? endConnector : startConnector;
var connectionViewModel = new flowchart.ConnectionViewModel(connectionDataModel, outputConnector, inputConnector);
connectionsViewModel.push(connectionViewModel);
};
//
// Add a node to the view model.
//
this.addNode = function (nodeDataModel) {
if (!this.data.nodes) {
this.data.nodes = [];
}
//
// Update the data model.
//
this.data.nodes.push(nodeDataModel);
//
// Update the view model.
//
this.nodes.push(new flowchart.NodeViewModel(nodeDataModel));
}
//
// Select all nodes and connections in the chart.
//
this.selectAll = function () {
var nodes = this.nodes;
for (var i = 0; i < nodes.length; ++i) {
var node = nodes[i];
node.select();
}
var connections = this.connections;
for (var i = 0; i < connections.length; ++i) {
var connection = connections[i];
connection.select();
}
}
//
// Deselect all nodes and connections in the chart.
//
this.deselectAll = function () {
var nodes = this.nodes;
for (var i = 0; i < nodes.length; ++i) {
var node = nodes[i];
node.deselect();
}
var connections = this.connections;
for (var i = 0; i < connections.length; ++i) {
var connection = connections[i];
connection.deselect();
}
};
//
// Update the location of the node and its connectors.
//
this.updateSelectedNodesLocation = function (deltaX, deltaY) {
var selectedNodes = this.getSelectedNodes();
for (var i = 0; i < selectedNodes.length; ++i) {
var node = selectedNodes[i];
node.data.x += deltaX;
node.data.y += deltaY;
}
};
//
// Handle mouse click on a particular node.
//
this.handleNodeClicked = function (node, ctrlKey) {
if (ctrlKey) {
node.toggleSelected();
}
else {
this.deselectAll();
node.select();
}
// Move node to the end of the list so it is rendered after all the other.
// This is the way Z-order is done in SVG.
var nodeIndex = this.nodes.indexOf(node);
if (nodeIndex == -1) {
throw new Error("Failed to find node in view model!");
}
this.nodes.splice(nodeIndex, 1);
this.nodes.push(node);
};
//
// Handle mouse down on a connection.
//
this.handleConnectionMouseDown = function (connection, ctrlKey) {
if (ctrlKey) {
connection.toggleSelected();
}
else {
this.deselectAll();
connection.select();
}
};
//
// Delete all nodes and connections that are selected.
//
this.deleteSelected = function () {
var newNodeViewModels = [];
var newNodeDataModels = [];
var deletedNodeIds = [];
//
// Sort nodes into:
// nodes to keep and
// nodes to delete.
//
for (var nodeIndex = 0; nodeIndex < this.nodes.length; ++nodeIndex) {
var node = this.nodes[nodeIndex];
if (!node.selected()) {
// Only retain non-selected nodes.
newNodeViewModels.push(node);
newNodeDataModels.push(node.data);
}
else {
// Keep track of nodes that were deleted, so their connections can also
// be deleted.
deletedNodeIds.push(node.data.id);
}
}
var newConnectionViewModels = [];
var newConnectionDataModels = [];
//
// Remove connections that are selected.
// Also remove connections for nodes that have been deleted.
//
for (var connectionIndex = 0; connectionIndex < this.connections.length; ++connectionIndex) {
var connection = this.connections[connectionIndex];
if (!connection.selected() &&
deletedNodeIds.indexOf(connection.data.source.nodeID) === -1 &&
deletedNodeIds.indexOf(connection.data.dest.nodeID) === -1)
{
//
// The nodes this connection is attached to, where not deleted,
// so keep the connection.
//
newConnectionViewModels.push(connection);
newConnectionDataModels.push(connection.data);
}
}
//
// Update nodes and connections.
//
this.nodes = newNodeViewModels;
this.data.nodes = newNodeDataModels;
this.connections = newConnectionViewModels;
this.data.connections = newConnectionDataModels;
};
//
// Select nodes and connections that fall within the selection rect.
//
this.applySelectionRect = function (selectionRect) {
this.deselectAll();
for (var i = 0; i < this.nodes.length; ++i) {
var node = this.nodes[i];
if (node.x() >= selectionRect.x &&
node.y() >= selectionRect.y &&
node.x() + node.width() <= selectionRect.x + selectionRect.width &&
node.y() + node.height() <= selectionRect.y + selectionRect.height)
{
// Select nodes that are within the selection rect.
node.select();
}
}
for (var i = 0; i < this.connections.length; ++i) {
var connection = this.connections[i];
if (connection.source.parentNode().selected() &&
connection.dest.parentNode().selected())
{
// Select the connection if both its parent nodes are selected.
connection.select();
}
}
};
//
// Get the array of nodes that are currently selected.
//
this.getSelectedNodes = function () {
var selectedNodes = [];
for (var i = 0; i < this.nodes.length; ++i) {
var node = this.nodes[i];
if (node.selected()) {
selectedNodes.push(node);
}
}
return selectedNodes;
};
//
// Get the array of connections that are currently selected.
//
this.getSelectedConnections = function () {
var selectedConnections = [];
for (var i = 0; i < this.connections.length; ++i) {
var connection = this.connections[i];
if (connection.selected()) {
selectedConnections.push(connection);
}
}
return selectedConnections;
};
};
})();
================================================
FILE: flowchart/flowchart_viewmodel.spec.js
================================================
describe('flowchart-viewmodel', function () {
//
// Create a mock data model from a simple definition.
//
var createMockDataModel = function (nodeIds, connections) {
var nodeDataModels = null;
if (nodeIds) {
nodeDataModels = [];
for (var i = 0; i < nodeIds.length; ++i) {
nodeDataModels.push({
id: nodeIds[i],
x: 0,
y: 0,
inputConnectors: [ {}, {}, {} ],
outputConnectors: [ {}, {}, {} ],
});
}
}
var connectionDataModels = null;
if (connections) {
connectionDataModels = [];
for (var i = 0; i < connections.length; ++i) {
connectionDataModels.push({
source: {
nodeID: connections[i][0][0],
connectorIndex: connections[i][0][1],
},
dest: {
nodeID: connections[i][1][0],
connectorIndex: connections[i][1][1],
},
});
}
}
var dataModel = {};
if (nodeDataModels) {
dataModel.nodes = nodeDataModels;
}
if (connectionDataModels) {
dataModel.connections = connectionDataModels;
}
return dataModel;
};
it('compute input connector pos', function () {
var mockNode = {
x: function () { return 10 },
y: function () { return 15 },
};
flowchart.computeConnectorPos(mockNode, 0, true);
flowchart.computeConnectorPos(mockNode, 1, true);
flowchart.computeConnectorPos(mockNode, 2, true);
});
it('compute output connector pos', function () {
var mockNode = {
x: function () { return 10 },
y: function () { return 15 },
};
flowchart.computeConnectorPos(mockNode, 0, false);
flowchart.computeConnectorPos(mockNode, 1, false);
flowchart.computeConnectorPos(mockNode, 2, false);
});
it('construct ConnectorViewModel', function () {
var mockDataModel = {
name: "Fooey",
};
new flowchart.ConnectorViewModel(mockDataModel, 0, 10, 0);
new flowchart.ConnectorViewModel(mockDataModel, 0, 10, 1);
new flowchart.ConnectorViewModel(mockDataModel, 0, 10, 2);
});
it('ConnectorViewModel has reference to parent node', function () {
var mockDataModel = {
name: "Fooey",
};
var mockParentNodeViewModel = {};
var testObject = new flowchart.ConnectorViewModel(mockDataModel, 0, 10, mockParentNodeViewModel);
expect(testObject.parentNode()).toBe(mockParentNodeViewModel);
});
it('construct NodeViewModel with no connectors', function () {
var mockDataModel = {
x: 10,
y: 12,
name: "Woot",
};
new flowchart.NodeViewModel(mockDataModel);
});
it('construct NodeViewModel with empty connectors', function () {
var mockDataModel = {
x: 10,
y: 12,
name: "Woot",
inputConnectors: [],
outputConnectors: [],
};
new flowchart.NodeViewModel(mockDataModel);
});
it('construct NodeViewModel with connectors', function () {
var mockInputConnector = {
name: "Input",
};
var mockOutputConnector = {
name: "Output",
};
var mockDataModel = {
x: 10,
y: 12,
name: "Woot",
inputConnectors: [
mockInputConnector
],
outputConnectors: [
mockOutputConnector
],
};
new flowchart.NodeViewModel(mockDataModel);
});
it('test name of NodeViewModel', function () {
var mockDataModel = {
name: "Woot",
};
var testObject = new flowchart.NodeViewModel(mockDataModel);
expect(testObject.name()).toBe(mockDataModel.name);
});
it('test name of NodeViewModel defaults to empty string', function () {
var mockDataModel = {};
var testObject = new flowchart.NodeViewModel(mockDataModel);
expect(testObject.name()).toBe("");
});
it('test node is deselected by default', function () {
var mockDataModel = {};
var testObject = new flowchart.NodeViewModel(mockDataModel);
expect(testObject.selected()).toBe(false);
});
it('test node width is set by default', function () {
var mockDataModel = {};
var testObject = new flowchart.NodeViewModel(mockDataModel);
expect(testObject.width() === flowchart.defaultNodeWidth).toBe(true);
});
it('test node width is used', function () {
var mockDataModel = {"width": 900 };
var testObject = new flowchart.NodeViewModel(mockDataModel);
expect(testObject.width()).toBe(900);
});
it('test computeConnectorPos uses node width', function () {
var mockDataModel = {
x: function () {
return 10;
},
y: function () {
return 15;
},
width: function () {
return 900;
},
};
var testObject = flowchart.computeConnectorPos(mockDataModel, 1, false);
expect(testObject.x).toBe(910);
});
it('test computeConnectorPos uses default node width', function () {
var mockDataModel = {
x: function () {
return 10
},
y: function () {
return 15
},
};
var testObject = flowchart.computeConnectorPos(mockDataModel, 1, false);
expect(testObject.x).toBe(flowchart.defaultNodeWidth + 10);
});
it('test node can be selected', function () {
var mockDataModel = {};
var testObject = new flowchart.NodeViewModel(mockDataModel);
testObject.select();
expect(testObject.selected()).toBe(true);
});
it('test node can be deselected', function () {
var mockDataModel = {};
var testObject = new flowchart.NodeViewModel(mockDataModel);
testObject.select();
testObject.deselect();
expect(testObject.selected()).toBe(false);
});
it('test node can be selection can be toggled', function () {
var mockDataModel = {};
var testObject = new flowchart.NodeViewModel(mockDataModel);
testObject.toggleSelected();
expect(testObject.selected()).toBe(true);
testObject.toggleSelected();
expect(testObject.selected()).toBe(false);
});
it('test can add input connector to node', function () {
var mockDataModel = {};
var testObject = new flowchart.NodeViewModel(mockDataModel);
var name1 = "Connector1";
var name2 = "Connector2";
var data1 = {
name: name1
};
var data2 = {
name: name2
}
testObject.addInputConnector(data1);
testObject.addInputConnector(data2);
expect(testObject.inputConnectors.length).toBe(2);
expect(testObject.inputConnectors[0].data).toBe(data1);
expect(testObject.inputConnectors[1].data).toBe(data2);
expect(testObject.data.inputConnectors.length).toBe(2);
expect(testObject.data.inputConnectors[0]).toBe(data1);
expect(testObject.data.inputConnectors[1]).toBe(data2);
});
it('test can add output connector to node', function () {
var mockDataModel = {};
var testObject = new flowchart.NodeViewModel(mockDataModel);
var name1 = "Connector1";
var name2 = "Connector2";
var data1 = {
name: name1
};
var data2 = {
name: name2
}
testObject.addOutputConnector(data1);
testObject.addOutputConnector(data2);
expect(testObject.outputConnectors.length).toBe(2);
expect(testObject.outputConnectors[0].data).toBe(data1);
expect(testObject.outputConnectors[1].data).toBe(data2);
expect(testObject.data.outputConnectors.length).toBe(2);
expect(testObject.data.outputConnectors[0]).toBe(data1);
expect(testObject.data.outputConnectors[1]).toBe(data2);
});
it('construct ChartViewModel with no nodes or connections', function () {
var mockDataModel = {};
new flowchart.ChartViewModel(mockDataModel);
});
it('construct ChartViewModel with empty nodes and connections', function () {
var mockDataModel = {
nodes: [],
connections: [],
};
new flowchart.ChartViewModel(mockDataModel);
});
it('construct ConnectionViewModel', function () {
var mockDataModel = {};
var mockSourceConnector = {};
var mockDestConnector = {};
new flowchart.ConnectionViewModel(mockDataModel, mockSourceConnector, mockDestConnector);
});
it('retreive source and dest coordinates', function () {
var mockDataModel = {
};
var mockSourceParentNode = {
x: function () { return 5 },
y: function () { return 10 },
};
var mockSourceConnector = {
parentNode: function () {
return mockSourceParentNode;
},
x: function() {
return 5;
},
y: function() {
return 15;
},
};
var mockDestParentNode = {
x: function () { return 50 },
y: function () { return 30 },
};
var mockDestConnector = {
parentNode: function () {
return mockDestParentNode;
},
x: function() {
return 25;
},
y: function() {
return 35;
},
};
var testObject = new flowchart.ConnectionViewModel(mockDataModel, mockSourceConnector, mockDestConnector);
testObject.sourceCoord();
expect(testObject.sourceCoordX()).toBe(10);
expect(testObject.sourceCoordY()).toBe(25);
testObject.sourceTangentX();
testObject.sourceTangentY();
testObject.destCoord();
expect(testObject.destCoordX()).toBe(75);
expect(testObject.destCoordY()).toBe(65);
testObject.destTangentX();
testObject.destTangentY();
});
it('test connection is deselected by default', function () {
var mockDataModel = {};
var testObject = new flowchart.ConnectionViewModel(mockDataModel);
expect(testObject.selected()).toBe(false);
});
it('test connection can be selected', function () {
var mockDataModel = {};
var testObject = new flowchart.ConnectionViewModel(mockDataModel);
testObject.select();
expect(testObject.selected()).toBe(true);
});
it('test connection can be deselected', function () {
var mockDataModel = {};
var testObject = new flowchart.ConnectionViewModel(mockDataModel);
testObject.select();
testObject.deselect();
expect(testObject.selected()).toBe(false);
});
it('test connection can be selection can be toggled', function () {
var mockDataModel = {};
var testObject = new flowchart.ConnectionViewModel(mockDataModel);
testObject.toggleSelected();
expect(testObject.selected()).toBe(true);
testObject.toggleSelected();
expect(testObject.selected()).toBe(false);
});
it('construct ChartViewModel with a node', function () {
var mockDataModel = createMockDataModel([1]);
var testObject = new flowchart.ChartViewModel(mockDataModel);
expect(testObject.nodes.length).toBe(1);
expect(testObject.nodes[0].data).toBe(mockDataModel.nodes[0]);
});
it('data model with existing connection creates a connection view model', function () {
var mockDataModel = createMockDataModel(
[ 5, 12 ],
[
[[ 5, 0 ], [ 12, 1 ]],
]
);
var testObject = new flowchart.ChartViewModel(mockDataModel);
expect(testObject.connections.length).toBe(1);
expect(testObject.connections[0].data).toBe(mockDataModel.connections[0]);
expect(testObject.connections[0].source.data).toBe(mockDataModel.nodes[0].outputConnectors[0]);
expect(testObject.connections[0].dest.data).toBe(mockDataModel.nodes[1].inputConnectors[1]);
});
it('test can add new node', function () {
var mockDataModel = createMockDataModel();
var testObject = new flowchart.ChartViewModel(mockDataModel);
var nodeDataModel = {};
testObject.addNode(nodeDataModel);
expect(testObject.nodes.length).toBe(1);
expect(testObject.nodes[0].data).toBe(nodeDataModel);
expect(testObject.data.nodes.length).toBe(1);
expect(testObject.data.nodes[0]).toBe(nodeDataModel);
});
it('test can select all', function () {
var mockDataModel = createMockDataModel([1, 2], [[[1, 0], [2, 1]]]);
var testObject = new flowchart.ChartViewModel(mockDataModel);
var node1 = testObject.nodes[0];
var node2 = testObject.nodes[1];
var connection = testObject.connections[0];
testObject.selectAll();
expect(node1.selected()).toBe(true);
expect(node2.selected()).toBe(true);
expect(connection.selected()).toBe(true);
});
it('test can deselect all nodes', function () {
var mockDataModel = createMockDataModel([1, 2]);
var testObject = new flowchart.ChartViewModel(mockDataModel);
var node1 = testObject.nodes[0];
var node2 = testObject.nodes[1];
node1.select();
node2.select();
testObject.deselectAll();
expect(node1.selected()).toBe(false);
expect(node2.selected()).toBe(false);
});
it('test can deselect all connections', function () {
var mockDataModel = createMockDataModel(
[ 5, 12 ],
[
[[ 5, 0 ], [ 12, 1 ]],
[[ 5, 0 ], [ 12, 1 ]],
]
);
var testObject = new flowchart.ChartViewModel(mockDataModel);
var connection1 = testObject.connections[0];
var connection2 = testObject.connections[1];
connection1.select();
connection2.select();
testObject.deselectAll();
expect(connection1.selected()).toBe(false);
expect(connection2.selected()).toBe(false);
});
it('test mouse down deselects nodes other than the one clicked', function () {
var mockDataModel = createMockDataModel([ 1, 2, 3 ]);
var testObject = new flowchart.ChartViewModel(mockDataModel);
var node1 = testObject.nodes[0];
var node2 = testObject.nodes[1];
var node3 = testObject.nodes[2];
// Fake out the nodes as selected.
node1.select();
node2.select();
node3.select();
testObject.handleNodeClicked(node2); // Doesn't matter which node is actually clicked.
expect(node1.selected()).toBe(false);
expect(node2.selected()).toBe(true);
expect(node3.selected()).toBe(false);
});
it('test mouse down selects the clicked node', function () {
var mockDataModel = createMockDataModel([ 1, 2, 3 ]);
var testObject = new flowchart.ChartViewModel(mockDataModel);
var node1 = testObject.nodes[0];
var node2 = testObject.nodes[1];
var node3 = testObject.nodes[2];
testObject.handleNodeClicked(node3); // Doesn't matter which node is actually clicked.
expect(node1.selected()).toBe(false);
expect(node2.selected()).toBe(false);
expect(node3.selected()).toBe(true);
});
it('test mouse down brings node to front', function () {
var mockDataModel = createMockDataModel([ 1, 2 ]);
var testObject = new flowchart.ChartViewModel(mockDataModel);
var node1 = testObject.nodes[0];
var node2 = testObject.nodes[1];
testObject.handleNodeClicked(node1);
expect(testObject.nodes[0]).toBe(node2); // Mock node 2 should be bought to front.
expect(testObject.nodes[1]).toBe(node1);
});
it('test control + mouse down toggles node selection', function () {
var mockDataModel = createMockDataModel([ 1, 2, 3 ]);
var testObject = new flowchart.ChartViewModel(mockDataModel);
var node1 = testObject.nodes[0];
var node2 = testObject.nodes[1];
var node3 = testObject.nodes[2];
node1.select(); // Mark node 1 as already selected.
testObject.handleNodeClicked(node2, true);
expect(node1.selected()).toBe(true); // This node remains selected.
expect(node2.selected()).toBe(true); // This node is being toggled.
expect(node3.selected()).toBe(false); // This node remains unselected.
testObject.handleNodeClicked(node2, true);
expect(node1.selected()).toBe(true); // This node remains selected.
expect(node2.selected()).toBe(false); // This node is being toggled.
expect(node3.selected()).toBe(false); // This node remains unselected.
testObject.handleNodeClicked(node2, true);
expect(node1.selected()).toBe(true); // This node remains selected.
expect(node2.selected()).toBe(true); // This node is being toggled.
expect(node3.selected()).toBe(false); // This node remains unselected.
});
it('test mouse down deselects connections other than the one clicked', function () {
var mockDataModel = createMockDataModel(
[ 1, 2, 3 ],
[
[[ 1, 0 ], [ 3, 0 ]],
[[ 2, 1 ], [ 3, 2 ]],
[[ 1, 2 ], [ 3, 0 ]]
]
);
var testObject = new flowchart.ChartViewModel(mockDataModel);
var connection1 = testObject.connections[0];
var connection2 = testObject.connections[1];
var connection3 = testObject.connections[2];
// Fake out the connections as selected.
connection1.select();
connection2.select();
connection3.select();
testObject.handleConnectionMouseDown(connection2);
expect(connection1.selected()).toBe(false);
expect(connection2.selected()).toBe(true);
expect(connection3.selected()).toBe(false);
});
it('test node mouse down selects the clicked connection', function () {
var mockDataModel = createMockDataModel(
[ 1, 2, 3 ],
[
[[ 1, 0 ], [ 3, 0 ]],
[[ 2, 1 ], [ 3, 2 ]],
[[ 1, 2 ], [ 3, 0 ]]
]
);
var testObject = new flowchart.ChartViewModel(mockDataModel);
var connection1 = testObject.connections[0];
var connection2 = testObject.connections[1];
var connection3 = testObject.connections[2];
testObject.handleConnectionMouseDown(connection3);
expect(connection1.selected()).toBe(false);
expect(connection2.selected()).toBe(false);
expect(connection3.selected()).toBe(true);
});
it('test control + mouse down toggles connection selection', function () {
var mockDataModel = createMockDataModel(
[ 1, 2, 3 ],
[
[[ 1, 0 ], [ 3, 0 ]],
[[ 2, 1 ], [ 3, 2 ]],
[[ 1, 2 ], [ 3, 0 ]]
]
);
var testObject = new flowchart.ChartViewModel(mockDataModel);
var connection1 = testObject.connections[0];
var connection2 = testObject.connections[1];
var connection3 = testObject.connections[2];
connection1.select(); // Mark connection 1 as already selected.
testObject.handleConnectionMouseDown(connection2, true);
expect(connection1.selected()).toBe(true); // This connection remains selected.
expect(connection2.selected()).toBe(true); // This connection is being toggle.
expect(connection3.selected()).toBe(false); // This connection remains unselected.
testObject.handleConnectionMouseDown(connection2, true);
expect(connection1.selected()).toBe(true); // This connection remains selected.
expect(connection2.selected()).toBe(false); // This connection is being toggle.
expect(connection3.selected()).toBe(false); // This connection remains unselected.
testObject.handleConnectionMouseDown(connection2, true);
expect(connection1.selected()).toBe(true); // This connection remains selected.
expect(connection2.selected()).toBe(true); // This connection is being toggle.
expect(connection3.selected()).toBe(false); // This connection remains unselected.
});
it('test data-model is wrapped in view-model', function () {
var mockDataModel = createMockDataModel([ 1, 2 ], [[[1, 0], [2, 0]]]);
var mockNode = mockDataModel.nodes[0];
var mockInputConnector = mockNode.inputConnectors[0];
var mockOutputConnector = mockNode.outputConnectors[0];
var testObject = new flowchart.ChartViewModel(mockDataModel);
// Chart
expect(testObject).toBeDefined();
expect(testObject).toNotBe(mockDataModel);
expect(testObject.data).toBe(mockDataModel);
expect(testObject.nodes).toBeDefined();
expect(testObject.nodes.length).toBe(2);
// Node
var node = testObject.nodes[0];
expect(node).toNotBe(mockNode);
expect(node.data).toBe(mockNode);
// Connectors
expect(node.inputConnectors.length).toBe(3);
expect(node.inputConnectors[0].data).toBe(mockInputConnector);
expect(node.outputConnectors.length).toBe(3);
expect(node.outputConnectors[0].data).toBe(mockOutputConnector);
// Connection
expect(testObject.connections.length).toBe(1);
expect(testObject.connections[0].source).toBe(testObject.nodes[0].outputConnectors[0]);
expect(testObject.connections[0].dest).toBe(testObject.nodes[1].inputConnectors[0]);
});
it('test can delete 1st selected node', function () {
var mockDataModel = createMockDataModel([ 1, 2 ]);
var testObject = new flowchart.ChartViewModel(mockDataModel);
expect(testObject.nodes.length).toBe(2);
testObject.nodes[0].select();
var mockNode2 = mockDataModel.nodes[1];
testObject.deleteSelected();
expect(testObject.nodes.length).toBe(1);
expect(mockDataModel.nodes.length).toBe(1);
expect(testObject.nodes[0].data).toBe(mockNode2);
});
it('test can delete 2nd selected nodes', function () {
var mockDataModel = createMockDataModel([ 1, 2 ]);
var testObject = new flowchart.ChartViewModel(mockDataModel);
expect(testObject.nodes.length).toBe(2);
testObject.nodes[1].select();
var mockNode1 = mockDataModel.nodes[0];
testObject.deleteSelected();
expect(testObject.nodes.length).toBe(1);
expect(mockDataModel.nodes.length).toBe(1);
expect(testObject.nodes[0].data).toBe(mockNode1);
});
it('test can delete multiple selected nodes', function () {
var mockDataModel = createMockDataModel([ 1, 2, 3, 4 ]);
var testObject = new flowchart.ChartViewModel(mockDataModel);
expect(testObject.nodes.length).toBe(4);
testObject.nodes[1].select();
testObject.nodes[2].select();
var mockNode1 = mockDataModel.nodes[0];
var mockNode4 = mockDataModel.nodes[3];
testObject.deleteSelected();
expect(testObject.nodes.length).toBe(2);
expect(mockDataModel.nodes.length).toBe(2);
expect(testObject.nodes[0].data).toBe(mockNode1);
expect(testObject.nodes[1].data).toBe(mockNode4);
});
it('deleting a node also deletes its connections', function () {
var mockDataModel = createMockDataModel(
[ 1, 2, 3 ],
[
[[ 1, 0 ], [ 2, 0 ]],
[[ 2, 0 ], [ 3, 0 ]],
]
);
var testObject = new flowchart.ChartViewModel(mockDataModel);
expect(testObject.connections.length).toBe(2);
// Select the middle node.
testObject.nodes[1].select();
testObject.deleteSelected();
expect(testObject.connections.length).toBe(0);
});
it('deleting a node doesnt delete other connections', function () {
var mockDataModel = createMockDataModel(
[ 1, 2, 3 ],
[
[[ 1, 0 ], [ 3, 0 ],]
]
);
var testObject = new flowchart.ChartViewModel(mockDataModel);
expect(testObject.connections.length).toBe(1);
// Select the middle node.
testObject.nodes[1].select();
testObject.deleteSelected();
expect(testObject.connections.length).toBe(1);
});
it('test can delete 1st selected connection', function () {
var mockDataModel = createMockDataModel(
[ 1, 2 ],
[
[[ 1, 0 ], [ 2, 0 ]],
[[ 2, 1 ], [ 1, 2 ]]
]
);
var mockRemainingConnectionDataModel = mockDataModel.connections[1];
var testObject = new flowchart.ChartViewModel(mockDataModel);
expect(testObject.connections.length).toBe(2);
testObject.connections[0].select();
testObject.deleteSelected();
expect(testObject.connections.length).toBe(1);
expect(mockDataModel.connections.length).toBe(1);
expect(testObject.connections[0].data).toBe(mockRemainingConnectionDataModel);
});
it('test can delete 2nd selected connection', function () {
var mockDataModel = createMockDataModel(
[ 1, 2 ],
[
[[ 1, 0 ], [ 2, 0 ]],
[[ 2, 1 ], [ 1, 2 ]]
]
);
var mockRemainingConnectionDataModel = mockDataModel.connections[0];
var testObject = new flowchart.ChartViewModel(mockDataModel);
expect(testObject.connections.length).toBe(2);
testObject.connections[1].select();
testObject.deleteSelected();
expect(testObject.connections.length).toBe(1);
expect(mockDataModel.connections.length).toBe(1);
expect(testObject.connections[0].data).toBe(mockRemainingConnectionDataModel);
});
it('test can delete multiple selected connections', function () {
var mockDataModel = createMockDataModel(
[ 1, 2, 3 ],
[
[[ 1, 0 ], [ 2, 0 ]],
[[ 2, 1 ], [ 1, 2 ]],
[[ 1, 1 ], [ 3, 0 ]],
[[ 3, 2 ], [ 2, 1 ]]
]
);
var mockRemainingConnectionDataModel1 = mockDataModel.connections[0];
var mockRemainingConnectionDataModel2 = mockDataModel.connections[3];
var testObject = new flowchart.ChartViewModel(mockDataModel);
expect(testObject.connections.length).toBe(4);
testObject.connections[1].select();
testObject.connections[2].select();
testObject.deleteSelected();
expect(testObject.connections.length).toBe(2);
expect(mockDataModel.connections.length).toBe(2);
expect(testObject.connections[0].data).toBe(mockRemainingConnectionDataModel1);
expect(testObject.connections[1].data).toBe(mockRemainingConnectionDataModel2);
});
it('can select nodes via selection rect', function () {
var mockDataModel = createMockDataModel([ 1, 2, 3 ]);
mockDataModel.nodes[0].x = 0;
mockDataModel.nodes[0].y = 0;
mockDataModel.nodes[1].x = 1020;
mockDataModel.nodes[1].y = 1020;
mockDataModel.nodes[2].x = 3000;
mockDataModel.nodes[2].y = 3000;
var testObject = new flowchart.ChartViewModel(mockDataModel);
testObject.nodes[0].select(); // Select a nodes, to ensure it is correctly deselected.
testObject.applySelectionRect({ x: 1000, y: 1000, width: 1000, height: 1000 });
expect(testObject.nodes[0].selected()).toBe(false);
expect(testObject.nodes[1].selected()).toBe(true);
expect(testObject.nodes[2].selected()).toBe(false);
});
it('can select connections via selection rect', function () {
var mockDataModel = createMockDataModel(
[ 1, 2, 3, 4 ],
[
[[ 1, 0 ], [ 2, 0 ]],
[[ 2, 1 ], [ 3, 2 ]],
[[ 3, 2 ], [ 4, 1 ]]
]
);
mockDataModel.nodes[0].x = 0;
mockDataModel.nodes[0].y = 0;
mockDataModel.nodes[1].x = 1020;
mockDataModel.nodes[1].y = 1020;
mockDataModel.nodes[2].x = 1500;
mockDataModel.nodes[2].y = 1500;
mockDataModel.nodes[3].x = 3000;
mockDataModel.nodes[3].y = 3000;
var testObject = new flowchart.ChartViewModel(mockDataModel);
testObject.connections[0].select(); // Select a connection, to ensure it is correctly deselected.
testObject.applySelectionRect({ x: 1000, y: 1000, width: 1000, height: 1000 });
expect(testObject.connections[0].selected()).toBe(false);
expect(testObject.connections[1].selected()).toBe(true);
expect(testObject.connections[2].selected()).toBe(false);
});
it('test update selected nodes location', function () {
var mockDataModel = createMockDataModel([1]);
var testObject = new flowchart.ChartViewModel(mockDataModel);
var node = testObject.nodes[0];
node.select();
var xInc = 5;
var yInc = 15;
testObject.updateSelectedNodesLocation(xInc, yInc);
expect(node.x()).toBe(xInc);
expect(node.y()).toBe(yInc);
});
it('test update selected nodes location, ignores unselected nodes', function () {
var mockDataModel = createMockDataModel([1]);
var testObject = new flowchart.ChartViewModel(mockDataModel);
var node = testObject.nodes[0];
var xInc = 5;
var yInc = 15;
testObject.updateSelectedNodesLocation(xInc, yInc);
expect(node.x()).toBe(0);
expect(node.y()).toBe(0);
});
it('test find node throws when there are no nodes', function () {
var mockDataModel = createMockDataModel();
var testObject = new flowchart.ChartViewModel(mockDataModel);
expect(function () { testObject.findNode(150); }).toThrow();
});
it('test find node throws when node is not found', function () {
var mockDataModel = createMockDataModel([5, 25, 15, 30]);
var testObject = new flowchart.ChartViewModel(mockDataModel);
expect(function () { testObject.findNode(150); }).toThrow();
});
it('test find node retreives correct node', function () {
var mockDataModel = createMockDataModel([5, 25, 15, 30]);
var testObject = new flowchart.ChartViewModel(mockDataModel);
expect(testObject.findNode(15)).toBe(testObject.nodes[2]);
});
it('test find input connector throws when there are no nodes', function () {
var mockDataModel = createMockDataModel();
var testObject = new flowchart.ChartViewModel(mockDataModel);
expect(function () { testObject.findInputConnector(150, 1); }).toThrow();
});
it('test find input connector throws when the node is not found', function () {
var mockDataModel = createMockDataModel([ 1, 2, 3]);
var testObject = new flowchart.ChartViewModel(mockDataModel);
expect(function () { testObject.findInputConnector(150, 1); }).toThrow();
});
it('test find input connector throws when there are no connectors', function () {
var mockDataModel = createMockDataModel([ 1 ]);
mockDataModel.nodes[0].inputConnectors = [];
var testObject = new flowchart.ChartViewModel(mockDataModel);
expect(function () { testObject.findInputConnector(1, 1); }).toThrow();
});
it('test find input connector throws when connector is not found', function () {
var mockDataModel = createMockDataModel([5]);
mockDataModel.nodes[0].inputConnectors = [
{} // Only 1 input connector.
];
var testObject = new flowchart.ChartViewModel(mockDataModel);
expect(function () { testObject.findInputConnector(5, 1); }).toThrow();
});
it('test find input connector retreives correct connector', function () {
var mockDataModel = createMockDataModel([5, 25, 15, 30]);
var testObject = new flowchart.ChartViewModel(mockDataModel);
expect(testObject.findInputConnector(15, 1)).toBe(testObject.nodes[2].inputConnectors[1]);
});
it('test find output connector throws when there are no nodes', function () {
var mockDataModel = createMockDataModel();
var testObject = new flowchart.ChartViewModel(mockDataModel);
expect(function () { testObject.findOutputConnector(150, 1); }).toThrow();
});
it('test find output connector throws when the node is not found', function () {
var mockDataModel = createMockDataModel([ 1, 2, 3]);
var testObject = new flowchart.ChartViewModel(mockDataModel);
expect(function () { testObject.findOutputConnector(150, 1); }).toThrow();
});
it('test find output connector throws when there are no connectors', function () {
var mockDataModel = createMockDataModel([ 1 ]);
mockDataModel.nodes[0].outputConnectors = [];
var testObject = new flowchart.ChartViewModel(mockDataModel);
expect(function () { testObject.findOutputConnector(1, 1); }).toThrow();
});
it('test find output connector throws when connector is not found', function () {
var mockDataModel = createMockDataModel([5]);
mockDataModel.nodes[0].outputConnectors = [
{} // Only 1 input connector.
];
var testObject = new flowchart.ChartViewModel(mockDataModel);
expect(function () { testObject.findOutputConnector(5, 1); }).toThrow();
});
it('test find output connector retreives correct connector', function () {
var mockDataModel = createMockDataModel([5, 25, 15, 30]);
var testObject = new flowchart.ChartViewModel(mockDataModel);
expect(testObject.findOutputConnector(15, 1)).toBe(testObject.nodes[2].outputConnectors[1]);
});
it('test create new connection', function () {
var mockDataModel = createMockDataModel([5, 25]);
var testObject = new flowchart.ChartViewModel(mockDataModel);
var startConnector = testObject.nodes[0].outputConnectors[0];
var endConnector = testObject.nodes[1].inputConnectors[1];
testObject.createNewConnection(startConnector, endConnector);
expect(testObject.connections.length).toBe(1);
var connection = testObject.connections[0];
expect(connection.source).toBe(startConnector);
expect(connection.dest).toBe(endConnector);
expect(testObject.data.connections.length).toBe(1);
var connectionData = testObject.data.connections[0];
expect(connection.data).toBe(connectionData);
expect(connectionData.source.nodeID).toBe(5);
expect(connectionData.source.connectorIndex).toBe(0);
expect(connectionData.dest.nodeID).toBe(25);
expect(connectionData.dest.connectorIndex).toBe(1);
});
it('test create new connection from input to output', function () {
var mockDataModel = createMockDataModel([5, 25]);
var testObject = new flowchart.ChartViewModel(mockDataModel);
var startConnector = testObject.nodes[1].inputConnectors[1];
var endConnector = testObject.nodes[0].outputConnectors[0];
testObject.createNewConnection(startConnector, endConnector);
expect(testObject.connections.length).toBe(1);
var connection = testObject.connections[0];
expect(connection.source).toBe(endConnector);
expect(connection.dest).toBe(startConnector);
expect(testObject.data.connections.length).toBe(1);
var connectionData = testObject.data.connections[0];
expect(connection.data).toBe(connectionData);
expect(connectionData.source.nodeID).toBe(5);
expect(connectionData.source.connectorIndex).toBe(0);
expect(connectionData.dest.nodeID).toBe(25);
expect(connectionData.dest.connectorIndex).toBe(1);
});
it('test get selected nodes results in empty array when there are no nodes', function () {
var mockDataModel = createMockDataModel();
var testObject = new flowchart.ChartViewModel(mockDataModel);
var selectedNodes = testObject.getSelectedNodes();
expect(selectedNodes.length).toBe(0);
});
it('test get selected nodes results in empty array when none selected', function () {
var mockDataModel = createMockDataModel([1, 2, 3, 4]);
var testObject = new flowchart.ChartViewModel(mockDataModel);
var selectedNodes = testObject.getSelectedNodes();
expect(selectedNodes.length).toBe(0);
});
it('test can get selected nodes', function () {
var mockDataModel = createMockDataModel([1, 2, 3, 4]);
var testObject = new flowchart.ChartViewModel(mockDataModel);
var node1 = testObject.nodes[0];
var node2 = testObject.nodes[1];
var node3 = testObject.nodes[2];
var node4 = testObject.nodes[3];
node2.select();
node3.select();
var selectedNodes = testObject.getSelectedNodes();
expect(selectedNodes.length).toBe(2);
expect(selectedNodes[0]).toBe(node2);
expect(selectedNodes[1]).toBe(node3);
});
it('test can get selected connections', function () {
var mockDataModel = createMockDataModel(
[ 1, 2, 3 ],
[
[[ 1, 0 ], [ 2, 0 ]],
[[ 2, 1 ], [ 1, 2 ]],
[[ 1, 1 ], [ 3, 0 ]],
[[ 3, 2 ], [ 2, 1 ]]
]
);
var testObject = new flowchart.ChartViewModel(mockDataModel);
var connection1 = testObject.connections[0];
var connection2 = testObject.connections[1];
var connection3 = testObject.connections[2];
var connection4 = testObject.connections[3];
connection2.select();
connection3.select();
var selectedConnections = testObject.getSelectedConnections();
expect(selectedConnections.length).toBe(2);
expect(selectedConnections[0]).toBe(connection2);
expect(selectedConnections[1]).toBe(connection3);
});
});
================================================
FILE: flowchart/mouse_capture_service.js
================================================
angular.module('mouseCapture', [])
//
// Service used to acquire 'mouse capture' then receive dragging events while the mouse is captured.
//
.factory('mouseCapture', [ '$rootScope', function ($rootScope) {
//
// Element that the mouse capture applies to, defaults to 'document'
// unless the 'mouse-capture' directive is used.
//
var $element = $(document);
//
// Set when mouse capture is acquired to an object that contains
// handlers for 'mousemove' and 'mouseup' events.
//
var mouseCaptureConfig = null;
//
// Handler for mousemove events while the mouse is 'captured'.
//
var mouseMove = function (evt) {
if (mouseCaptureConfig && mouseCaptureConfig.mouseMove) {
mouseCaptureConfig.mouseMove(evt);
$rootScope.$digest();
}
};
//
// Handler for mouseup event while the mouse is 'captured'.
//
var mouseUp = function (evt) {
if (mouseCaptureConfig && mouseCaptureConfig.mouseUp) {
mouseCaptureConfig.mouseUp(evt);
$rootScope.$digest();
}
};
return {
//
// Register an element to use as the mouse capture element instead of
// the default which is the document.
//
registerElement: function(element) {
$element = element;
},
//
// Acquire the 'mouse capture'.
// After acquiring the mouse capture mousemove and mouseup events will be
// forwarded to callbacks in 'config'.
//
acquire: function (evt, config) {
//
// Release any prior mouse capture.
//
this.release();
mouseCaptureConfig = config;
//
// In response to the mousedown event register handlers for mousemove and mouseup
// during 'mouse capture'.
//
$element.mousemove(mouseMove);
$element.mouseup(mouseUp);
},
//
// Release the 'mouse capture'.
//
release: function () {
if (mouseCaptureConfig) {
if (mouseCaptureConfig.released) {
//
// Let the client know that their 'mouse capture' has been released.
//
mouseCaptureConfig.released();
}
mouseCaptureConfig = null;
}
$element.unbind("mousemove", mouseMove);
$element.unbind("mouseup", mouseUp);
},
};
}]
)
//
// Directive that marks the mouse capture element.
//
.directive('mouseCapture', function () {
return {
restrict: 'A',
controller: ['$scope', '$element', '$attrs', 'mouseCapture',
function($scope, $element, $attrs, mouseCapture) {
//
// Register the directives element as the mouse capture element.
//
mouseCapture.registerElement($element);
}],
};
})
;
================================================
FILE: flowchart/svg_class.js
================================================
//
// http://www.justinmccandless.com/blog/Patching+jQuery's+Lack+of+SVG+Support
//
// Functions to add and remove SVG classes because jQuery doesn't support this.
//
// jQuery's removeClass doesn't work for SVG, but this does!
// takes the object obj to remove from, and removes class remove
// returns true if successful, false if remove does not exist in obj
var removeClassSVG = function(obj, remove) {
var classes = obj.attr('class');
if (!classes) {
return false;
}
var index = classes.search(remove);
// if the class already doesn't exist, return false now
if (index == -1) {
return false;
}
else {
// string manipulation to remove the class
classes = classes.substring(0, index) + classes.substring((index + remove.length), classes.length);
// set the new string as the object's class
obj.attr('class', classes);
return true;
}
};
// jQuery's hasClass doesn't work for SVG, but this does!
// takes an object obj and checks for class has
// returns true if the class exits in obj, false otherwise
var hasClassSVG = function(obj, has) {
var classes = obj.attr('class');
if (!classes) {
return false;
}
var index = classes.search(has);
if (index == -1) {
return false;
}
else {
return true;
}
};
================================================
FILE: flowchart/svg_class.spec.js
================================================
describe('svg_class', function () {
it('removeClassSVG returns false when there is no classes attr', function () {
var mockElement = {
attr: function () {
return null;
},
};
var testClass = 'foo';
expect(removeClassSVG(mockElement, testClass)).toBe(false);
});
it('removeClassSVG returns false when the element doesnt already have the class', function () {
var mockElement = {
attr: function () {
return 'smeg';
},
};
var testClass = 'foo';
expect(removeClassSVG(mockElement, testClass)).toBe(false);
});
it('removeClassSVG returns true and removes the class when the element does have the class', function () {
var testClass = 'foo';
var mockElement = {
attr: function () {
return testClass;
},
};
spyOn(mockElement, 'attr').andCallThrough();
expect(removeClassSVG(mockElement, testClass)).toBe(true);
expect(mockElement.attr).toHaveBeenCalledWith('class', '');
});
it('hasClassSVG returns false when attr returns null', function () {
var mockElement = {
attr: function () {
return null;
},
};
var testClass = 'foo';
expect(hasClassSVG(mockElement, testClass)).toBe(false);
});
it('hasClassSVG returns false when element has no class', function () {
var mockElement = {
attr: function () {
return '';
},
};
var testClass = 'foo';
expect(hasClassSVG(mockElement, testClass)).toBe(false);
});
it('hasClassSVG returns false when element has wrong class', function () {
var mockElement = {
attr: function () {
return 'smeg';
},
};
var testClass = 'foo';
expect(hasClassSVG(mockElement, testClass)).toBe(false);
});
it('hasClassSVG returns true when element has correct class', function () {
var testClass = 'foo';
var mockElement = {
attr: function () {
return testClass;
},
};
expect(hasClassSVG(mockElement, testClass)).toBe(true);
});
it('hasClassSVG returns true when element 1 correct class of many ', function () {
var testClass = 'foo';
var mockElement = {
attr: function () {
return "whar " + testClass + " smeg";
},
};
expect(hasClassSVG(mockElement, testClass)).toBe(true);
});
});
================================================
FILE: index.html
================================================
<html>
<head>
<title>AngularJS-FlowChart</title>
<!--
LiveReload support.
http://livereload.com/
-->
<script src="http://localhost:35729/livereload.js?snipver=1"></script>
</head>
<body
ng-app="app"
ng-controller="AppCtrl"
mouse-capture
ng-keydown="keyDown($event)"
ng-keyup="keyUp($event)"
>
<div style="width: 100%; overflow: hidden;">
<div style="width: 600px; float: left;">
<textarea
style="width: 100%; height: 100%;"
chart-json-edit
view-model="chartViewModel"
>
</textarea>
</div>
<div style="margin-left: 600px;">
<button
ng-click="addNewNode()"
title="Add a new node to the chart"
>
Add Node
</button>
<button
ng-click="addNewInputConnector()"
ng-disabled="chartViewModel.getSelectedNodes().length == 0"
title="Add a new input connector to the selected node"
>
Add Input Connector
</button>
<button
ng-click="addNewOutputConnector()"
ng-disabled="chartViewModel.getSelectedNodes().length == 0"
title="Add a new output connector to the selected node"
>
Add Output Connector
</button>
<button
ng-click="deleteSelected()"
ng-disabled="chartViewModel.getSelectedNodes().length == 0 && chartViewModel.getSelectedConnections().length == 0"
title="Delete selected nodes and connections"
>
Delete Selected
</button>
<!--
This custom element defines the flowchart.
-->
<flow-chart
style="margin: 5px; width: 100%; height: 100%;"
chart="chartViewModel"
>
</flow-chart>
</div>
</div>
<link rel="stylesheet" type="text/css" href="app.css">
<!-- Library code. -->
<script src="lib/jquery-2.0.2.js" type="text/javascript"></script>
<script src="lib/angular.js" type="text/javascript"></script>
<!-- Flowchart code. -->
<script src="debug.js" type="text/javascript"></script>
<script src="flowchart/svg_class.js" type="text/javascript"></script>
<script src="flowchart/mouse_capture_service.js" type="text/javascript"></script>
<script src="flowchart/dragging_service.js" type="text/javascript"></script>
<script src="flowchart/flowchart_viewmodel.js" type="text/javascript"></script>
<script src="flowchart/flowchart_directive.js" type="text/javascript"></script>
<!-- App code. -->
<script src="app.js" type="text/javascript"></script>
</body>
</html>
================================================
FILE: jasmine/SpecRunner.html
================================================
<!DOCTYPE html>
<html>
<head>
<title>Jasmine Spec Runner</title>
<!--
LiveReload support.
http://livereload.com/
-->
<script src="http://localhost:35729/livereload.js?snipver=1"></script>
<link rel="shortcut icon" type="image/png" href="lib/jasmine-1.3.1/jasmine_favicon.png">
<link rel="stylesheet" type="text/css" href="lib/jasmine-1.3.1/jasmine.css">
<script type="text/javascript" src="lib/jasmine-1.3.1/jasmine.js"></script>
<script type="text/javascript" src="lib/jasmine-1.3.1/jasmine-html.js"></script>
<script src="../lib/jquery-2.0.2.js" type="text/javascript"></script>
<script type="text/javascript" src="../lib/angular.js"></script>
<script type="text/javascript" src="../lib/angular-mocks.js"></script>
<!-- include source files here... -->
<script type="text/javascript" src="../debug.js"></script>
<script type="text/javascript" src="../flowchart/svg_class.js"></script>
<script type="text/javascript" src="../flowchart/mouse_capture_service.js"></script>
<script type="text/javascript" src="../flowchart/dragging_service.js"></script>
<script type="text/javascript" src="../flowchart/flowchart_viewmodel.js"></script>
<script type="text/javascript" src="../flowchart/flowchart_directive.js"></script>
<!-- include spec files here... -->
<script type="text/javascript" src="../flowchart/svg_class.spec.js"></script>
<script type="text/javascript" src="../flowchart/flowchart_viewmodel.spec.js"></script>
<script type="text/javascript" src="../flowchart/flowchart_directive.spec.js"></script>
<script type="text/javascript">
(function() {
var jasmineEnv = jasmine.getEnv();
jasmineEnv.updateInterval = 1000;
var htmlReporter = new jasmine.HtmlReporter();
jasmineEnv.addReporter(htmlReporter);
jasmineEnv.specFilter = function(spec) {
return htmlReporter.specFilter(spec);
};
var currentWindowOnload = window.onload;
window.onload = function() {
if (currentWindowOnload) {
currentWindowOnload();
}
execJasmine();
};
function execJasmine() {
jasmineEnv.execute();
}
})();
</script>
</head>
<body>
</body>
</html>
================================================
FILE: jasmine/lib/jasmine-1.3.1/MIT.LICENSE
================================================
Copyright (c) 2008-2011 Pivotal Labs
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: jasmine/lib/jasmine-1.3.1/jasmine-html.js
================================================
jasmine.HtmlReporterHelpers = {};
jasmine.HtmlReporterHelpers.createDom = function(type, attrs, childrenVarArgs) {
var el = document.createElement(type);
for (var i = 2; i < arguments.length; i++) {
var child = arguments[i];
if (typeof child === 'string') {
el.appendChild(document.createTextNode(child));
} else {
if (child) {
el.appendChild(child);
}
}
}
for (var attr in attrs) {
if (attr == "className") {
el[attr] = attrs[attr];
} else {
el.setAttribute(attr, attrs[attr]);
}
}
return el;
};
jasmine.HtmlReporterHelpers.getSpecStatus = function(child) {
var results = child.results();
var status = results.passed() ? 'passed' : 'failed';
if (results.skipped) {
status = 'skipped';
}
return status;
};
jasmine.HtmlReporterHelpers.appendToSummary = function(child, childElement) {
var parentDiv = this.dom.summary;
var parentSuite = (typeof child.parentSuite == 'undefined') ? 'suite' : 'parentSuite';
var parent = child[parentSuite];
if (parent) {
if (typeof this.views.suites[parent.id] == 'undefined') {
this.views.suites[parent.id] = new jasmine.HtmlReporter.SuiteView(parent, this.dom, this.views);
}
parentDiv = this.views.suites[parent.id].element;
}
parentDiv.appendChild(childElement);
};
jasmine.HtmlReporterHelpers.addHelpers = function(ctor) {
for(var fn in jasmine.HtmlReporterHelpers) {
ctor.prototype[fn] = jasmine.HtmlReporterHelpers[fn];
}
};
jasmine.HtmlReporter = function(_doc) {
var self = this;
var doc = _doc || window.document;
var reporterView;
var dom = {};
// Jasmine Reporter Public Interface
self.logRunningSpecs = false;
self.reportRunnerStarting = function(runner) {
var specs = runner.specs() || [];
if (specs.length == 0) {
return;
}
createReporterDom(runner.env.versionString());
doc.body.appendChild(dom.reporter);
setExceptionHandling();
reporterView = new jasmine.HtmlReporter.ReporterView(dom);
reporterView.addSpecs(specs, self.specFilter);
};
self.reportRunnerResults = function(runner) {
reporterView && reporterView.complete();
};
self.reportSuiteResults = function(suite) {
reporterView.suiteComplete(suite);
};
self.reportSpecStarting = function(spec) {
if (self.logRunningSpecs) {
self.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
}
};
self.reportSpecResults = function(spec) {
reporterView.specComplete(spec);
};
self.log = function() {
var console = jasmine.getGlobal().console;
if (console && console.log) {
if (console.log.apply) {
console.log.apply(console, arguments);
} else {
console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
}
}
};
self.specFilter = function(spec) {
if (!focusedSpecName()) {
return true;
}
return spec.getFullName().indexOf(focusedSpecName()) === 0;
};
return self;
function focusedSpecName() {
var specName;
(function memoizeFocusedSpec() {
if (specName) {
return;
}
var paramMap = [];
var params = jasmine.HtmlReporter.parameters(doc);
for (var i = 0; i < params.length; i++) {
var p = params[i].split('=');
paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
}
specName = paramMap.spec;
})();
return specName;
}
function createReporterDom(version) {
dom.reporter = self.createDom('div', { id: 'HTMLReporter', className: 'jasmine_reporter' },
dom.banner = self.createDom('div', { className: 'banner' },
self.createDom('span', { className: 'title' }, "Jasmine "),
self.createDom('span', { className: 'version' }, version)),
dom.symbolSummary = self.createDom('ul', {className: 'symbolSummary'}),
dom.alert = self.createDom('div', {className: 'alert'},
self.createDom('span', { className: 'exceptions' },
self.createDom('label', { className: 'label', 'for': 'no_try_catch' }, 'No try/catch'),
self.createDom('input', { id: 'no_try_catch', type: 'checkbox' }))),
dom.results = self.createDom('div', {className: 'results'},
dom.summary = self.createDom('div', { className: 'summary' }),
dom.details = self.createDom('div', { id: 'details' }))
);
}
function noTryCatch() {
return window.location.search.match(/catch=false/);
}
function searchWithCatch() {
var params = jasmine.HtmlReporter.parameters(window.document);
var removed = false;
var i = 0;
while (!removed && i < params.length) {
if (params[i].match(/catch=/)) {
params.splice(i, 1);
removed = true;
}
i++;
}
if (jasmine.CATCH_EXCEPTIONS) {
params.push("catch=false");
}
return params.join("&");
}
function setExceptionHandling() {
var chxCatch = document.getElementById('no_try_catch');
if (noTryCatch()) {
chxCatch.setAttribute('checked', true);
jasmine.CATCH_EXCEPTIONS = false;
}
chxCatch.onclick = function() {
window.location.search = searchWithCatch();
};
}
};
jasmine.HtmlReporter.parameters = function(doc) {
var paramStr = doc.location.search.substring(1);
var params = [];
if (paramStr.length > 0) {
params = paramStr.split('&');
}
return params;
}
jasmine.HtmlReporter.sectionLink = function(sectionName) {
var link = '?';
var params = [];
if (sectionName) {
params.push('spec=' + encodeURIComponent(sectionName));
}
if (!jasmine.CATCH_EXCEPTIONS) {
params.push("catch=false");
}
if (params.length > 0) {
link += params.join("&");
}
return link;
};
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter);
jasmine.HtmlReporter.ReporterView = function(dom) {
this.startedAt = new Date();
this.runningSpecCount = 0;
this.completeSpecCount = 0;
this.passedCount = 0;
this.failedCount = 0;
this.skippedCount = 0;
this.createResultsMenu = function() {
this.resultsMenu = this.createDom('span', {className: 'resultsMenu bar'},
this.summaryMenuItem = this.createDom('a', {className: 'summaryMenuItem', href: "#"}, '0 specs'),
' | ',
this.detailsMenuItem = this.createDom('a', {className: 'detailsMenuItem', href: "#"}, '0 failing'));
this.summaryMenuItem.onclick = function() {
dom.reporter.className = dom.reporter.className.replace(/ showDetails/g, '');
};
this.detailsMenuItem.onclick = function() {
showDetails();
};
};
this.addSpecs = function(specs, specFilter) {
this.totalSpecCount = specs.length;
this.views = {
specs: {},
suites: {}
};
for (var i = 0; i < specs.length; i++) {
var spec = specs[i];
this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom, this.views);
if (specFilter(spec)) {
this.runningSpecCount++;
}
}
};
this.specComplete = function(spec) {
this.completeSpecCount++;
if (isUndefined(this.views.specs[spec.id])) {
this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom);
}
var specView = this.views.specs[spec.id];
switch (specView.status()) {
case 'passed':
this.passedCount++;
break;
case 'failed':
this.failedCount++;
break;
case 'skipped':
this.skippedCount++;
break;
}
specView.refresh();
this.refresh();
};
this.suiteComplete = function(suite) {
var suiteView = this.views.suites[suite.id];
if (isUndefined(suiteView)) {
return;
}
suiteView.refresh();
};
this.refresh = function() {
if (isUndefined(this.resultsMenu)) {
this.createResultsMenu();
}
// currently running UI
if (isUndefined(this.runningAlert)) {
this.runningAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "runningAlert bar" });
dom.alert.appendChild(this.runningAlert);
}
this.runningAlert.innerHTML = "Running " + this.completeSpecCount + " of " + specPluralizedFor(this.totalSpecCount);
// skipped specs UI
if (isUndefined(this.skippedAlert)) {
this.skippedAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "skippedAlert bar" });
}
this.skippedAlert.innerHTML = "Skipping " + this.skippedCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all";
if (this.skippedCount === 1 && isDefined(dom.alert)) {
dom.alert.appendChild(this.skippedAlert);
}
// passing specs UI
if (isUndefined(this.passedAlert)) {
this.passedAlert = this.createDom('span', { href: jasmine.HtmlReporter.sectionLink(), className: "passingAlert bar" });
}
this.passedAlert.innerHTML = "Passing " + specPluralizedFor(this.passedCount);
// failing specs UI
if (isUndefined(this.failedAlert)) {
this.failedAlert = this.createDom('span', {href: "?", className: "failingAlert bar"});
}
this.failedAlert.innerHTML = "Failing " + specPluralizedFor(this.failedCount);
if (this.failedCount === 1 && isDefined(dom.alert)) {
dom.alert.appendChild(this.failedAlert);
dom.alert.appendChild(this.resultsMenu);
}
// summary info
this.summaryMenuItem.innerHTML = "" + specPluralizedFor(this.runningSpecCount);
this.detailsMenuItem.innerHTML = "" + this.failedCount + " failing";
};
this.complete = function() {
dom.alert.removeChild(this.runningAlert);
this.skippedAlert.innerHTML = "Ran " + this.runningSpecCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all";
if (this.failedCount === 0) {
dom.alert.appendChild(this.createDom('span', {className: 'passingAlert bar'}, "Passing " + specPluralizedFor(this.passedCount)));
} else {
showDetails();
}
dom.banner.appendChild(this.createDom('span', {className: 'duration'}, "finished in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"));
};
return this;
function showDetails() {
if (dom.reporter.className.search(/showDetails/) === -1) {
dom.reporter.className += " showDetails";
}
}
function isUndefined(obj) {
return typeof obj === 'undefined';
}
function isDefined(obj) {
return !isUndefined(obj);
}
function specPluralizedFor(count) {
var str = count + " spec";
if (count > 1) {
str += "s"
}
return str;
}
};
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.ReporterView);
jasmine.HtmlReporter.SpecView = function(spec, dom, views) {
this.spec = spec;
this.dom = dom;
this.views = views;
this.symbol = this.createDom('li', { className: 'pending' });
this.dom.symbolSummary.appendChild(this.symbol);
this.summary = this.createDom('div', { className: 'specSummary' },
this.createDom('a', {
className: 'description',
href: jasmine.HtmlReporter.sectionLink(this.spec.getFullName()),
title: this.spec.getFullName()
}, this.spec.description)
);
this.detail = this.createDom('div', { className: 'specDetail' },
this.createDom('a', {
className: 'description',
href: '?spec=' + encodeURIComponent(this.spec.getFullName()),
title: this.spec.getFullName()
}, this.spec.getFullName())
);
};
jasmine.HtmlReporter.SpecView.prototype.status = function() {
return this.getSpecStatus(this.spec);
};
jasmine.HtmlReporter.SpecView.prototype.refresh = function() {
this.symbol.className = this.status();
switch (this.status()) {
case 'skipped':
break;
case 'passed':
this.appendSummaryToSuiteDiv();
break;
case 'failed':
this.appendSummaryToSuiteDiv();
this.appendFailureDetail();
break;
}
};
jasmine.HtmlReporter.SpecView.prototype.appendSummaryToSuiteDiv = function() {
this.summary.className += ' ' + this.status();
this.appendToSummary(this.spec, this.summary);
};
jasmine.HtmlReporter.SpecView.prototype.appendFailureDetail = function() {
this.detail.className += ' ' + this.status();
var resultItems = this.spec.results().getItems();
var messagesDiv = this.createDom('div', { className: 'messages' });
for (var i = 0; i < resultItems.length; i++) {
var result = resultItems[i];
if (result.type == 'log') {
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
} else if (result.type == 'expect' && result.passed && !result.passed()) {
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
if (result.trace.stack) {
messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
}
}
}
if (messagesDiv.childNodes.length > 0) {
this.detail.appendChild(messagesDiv);
this.dom.details.appendChild(this.detail);
}
};
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SpecView);jasmine.HtmlReporter.SuiteView = function(suite, dom, views) {
this.suite = suite;
this.dom = dom;
this.views = views;
this.element = this.createDom('div', { className: 'suite' },
this.createDom('a', { className: 'description', href: jasmine.HtmlReporter.sectionLink(this.suite.getFullName()) }, this.suite.description)
);
this.appendToSummary(this.suite, this.element);
};
jasmine.HtmlReporter.SuiteView.prototype.status = function() {
return this.getSpecStatus(this.suite);
};
jasmine.HtmlReporter.SuiteView.prototype.refresh = function() {
this.element.className += " " + this.status();
};
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SuiteView);
/* @deprecated Use jasmine.HtmlReporter instead
*/
jasmine.TrivialReporter = function(doc) {
this.document = doc || document;
this.suiteDivs = {};
this.logRunningSpecs = false;
};
jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) {
var el = document.createElement(type);
for (var i = 2; i < arguments.length; i++) {
var child = arguments[i];
if (typeof child === 'string') {
el.appendChild(document.createTextNode(child));
} else {
if (child) { el.appendChild(child); }
}
}
for (var attr in attrs) {
if (attr == "className") {
el[attr] = attrs[attr];
} else {
el.setAttribute(attr, attrs[attr]);
}
}
return el;
};
jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) {
var showPassed, showSkipped;
this.outerDiv = this.createDom('div', { id: 'TrivialReporter', className: 'jasmine_reporter' },
this.createDom('div', { className: 'banner' },
this.createDom('div', { className: 'logo' },
this.createDom('span', { className: 'title' }, "Jasmine"),
this.createDom('span', { className: 'version' }, runner.env.versionString())),
this.createDom('div', { className: 'options' },
"Show ",
showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }),
this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "),
showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }),
this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped")
)
),
this.runnerDiv = this.createDom('div', { className: 'runner running' },
this.createDom('a', { className: 'run_spec', href: '?' }, "run all"),
this.runnerMessageSpan = this.createDom('span', {}, "Running..."),
this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, ""))
);
this.document.body.appendChild(this.outerDiv);
var suites = runner.suites();
for (var i = 0; i < suites.length; i++) {
var suite = suites[i];
var suiteDiv = this.createDom('div', { className: 'suite' },
this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"),
this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description));
this.suiteDivs[suite.id] = suiteDiv;
var parentDiv = this.outerDiv;
if (suite.parentSuite) {
parentDiv = this.suiteDivs[suite.parentSuite.id];
}
parentDiv.appendChild(suiteDiv);
}
this.startedAt = new Date();
var self = this;
showPassed.onclick = function(evt) {
if (showPassed.checked) {
self.outerDiv.className += ' show-passed';
} else {
self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, '');
}
};
showSkipped.onclick = function(evt) {
if (showSkipped.checked) {
self.outerDiv.className += ' show-skipped';
} else {
self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, '');
}
};
};
jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) {
var results = runner.results();
var className = (results.failedCount > 0) ? "runner failed" : "runner passed";
this.runnerDiv.setAttribute("class", className);
//do it twice for IE
this.runnerDiv.setAttribute("className", className);
var specs = runner.specs();
var specCount = 0;
for (var i = 0; i < specs.length; i++) {
if (this.specFilter(specs[i])) {
specCount++;
}
}
var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s");
message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s";
this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild);
this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString()));
};
jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) {
var results = suite.results();
var status = results.passed() ? 'passed' : 'failed';
if (results.totalCount === 0) { // todo: change this to check results.skipped
status = 'skipped';
}
this.suiteDivs[suite.id].className += " " + status;
};
jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) {
if (this.logRunningSpecs) {
this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
}
};
jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) {
var results = spec.results();
var status = results.passed() ? 'passed' : 'failed';
if (results.skipped) {
status = 'skipped';
}
var specDiv = this.createDom('div', { className: 'spec ' + status },
this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"),
this.createDom('a', {
className: 'description',
href: '?spec=' + encodeURIComponent(spec.getFullName()),
title: spec.getFullName()
}, spec.description));
var resultItems = results.getItems();
var messagesDiv = this.createDom('div', { className: 'messages' });
for (var i = 0; i < resultItems.length; i++) {
var result = resultItems[i];
if (result.type == 'log') {
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
} else if (result.type == 'expect' && result.passed && !result.passed()) {
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
if (result.trace.stack) {
messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
}
}
}
if (messagesDiv.childNodes.length > 0) {
specDiv.appendChild(messagesDiv);
}
this.suiteDivs[spec.suite.id].appendChild(specDiv);
};
jasmine.TrivialReporter.prototype.log = function() {
var console = jasmine.getGlobal().console;
if (console && console.log) {
if (console.log.apply) {
console.log.apply(console, arguments);
} else {
console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
}
}
};
jasmine.TrivialReporter.prototype.getLocation = function() {
return this.document.location;
};
jasmine.TrivialReporter.prototype.specFilter = function(spec) {
var paramMap = {};
var params = this.getLocation().search.substring(1).split('&');
for (var i = 0; i < params.length; i++) {
var p = params[i].split('=');
paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
}
if (!paramMap.spec) {
return true;
}
return spec.getFullName().indexOf(paramMap.spec) === 0;
};
================================================
FILE: jasmine/lib/jasmine-1.3.1/jasmine.css
================================================
body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; }
#HTMLReporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; }
#HTMLReporter a { text-decoration: none; }
#HTMLReporter a:hover { text-decoration: underline; }
#HTMLReporter p, #HTMLReporter h1, #HTMLReporter h2, #HTMLReporter h3, #HTMLReporter h4, #HTMLReporter h5, #HTMLReporter h6 { margin: 0; line-height: 14px; }
#HTMLReporter .banner, #HTMLReporter .symbolSummary, #HTMLReporter .summary, #HTMLReporter .resultMessage, #HTMLReporter .specDetail .description, #HTMLReporter .alert .bar, #HTMLReporter .stackTrace { padding-left: 9px; padding-right: 9px; }
#HTMLReporter #jasmine_content { position: fixed; right: 100%; }
#HTMLReporter .version { color: #aaaaaa; }
#HTMLReporter .banner { margin-top: 14px; }
#HTMLReporter .duration { color: #aaaaaa; float: right; }
#HTMLReporter .symbolSummary { overflow: hidden; *zoom: 1; margin: 14px 0; }
#HTMLReporter .symbolSummary li { display: block; float: left; height: 7px; width: 14px; margin-bottom: 7px; font-size: 16px; }
#HTMLReporter .symbolSummary li.passed { font-size: 14px; }
#HTMLReporter .symbolSummary li.passed:before { color: #5e7d00; content: "\02022"; }
#HTMLReporter .symbolSummary li.failed { line-height: 9px; }
#HTMLReporter .symbolSummary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; }
#HTMLReporter .symbolSummary li.skipped { font-size: 14px; }
#HTMLReporter .symbolSummary li.skipped:before { color: #bababa; content: "\02022"; }
#HTMLReporter .symbolSummary li.pending { line-height: 11px; }
#HTMLReporter .symbolSummary li.pending:before { color: #aaaaaa; content: "-"; }
#HTMLReporter .exceptions { color: #fff; float: right; margin-top: 5px; margin-right: 5px; }
#HTMLReporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; }
#HTMLReporter .runningAlert { background-color: #666666; }
#HTMLReporter .skippedAlert { background-color: #aaaaaa; }
#HTMLReporter .skippedAlert:first-child { background-color: #333333; }
#HTMLReporter .skippedAlert:hover { text-decoration: none; color: white; text-decoration: underline; }
#HTMLReporter .passingAlert { background-color: #a6b779; }
#HTMLReporter .passingAlert:first-child { background-color: #5e7d00; }
#HTMLReporter .failingAlert { background-color: #cf867e; }
#HTMLReporter .failingAlert:first-child { background-color: #b03911; }
#HTMLReporter .results { margin-top: 14px; }
#HTMLReporter #details { display: none; }
#HTMLReporter .resultsMenu, #HTMLReporter .resultsMenu a { background-color: #fff; color: #333333; }
#HTMLReporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; }
#HTMLReporter.showDetails .summaryMenuItem:hover { text-decoration: underline; }
#HTMLReporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; }
#HTMLReporter.showDetails .summary { display: none; }
#HTMLReporter.showDetails #details { display: block; }
#HTMLReporter .summaryMenuItem { font-weight: bold; text-decoration: underline; }
#HTMLReporter .summary { margin-top: 14px; }
#HTMLReporter .summary .suite .suite, #HTMLReporter .summary .specSummary { margin-left: 14px; }
#HTMLReporter .summary .specSummary.passed a { color: #5e7d00; }
#HTMLReporter .summary .specSummary.failed a { color: #b03911; }
#HTMLReporter .description + .suite { margin-top: 0; }
#HTMLReporter .suite { margin-top: 14px; }
#HTMLReporter .suite a { color: #333333; }
#HTMLReporter #details .specDetail { margin-bottom: 28px; }
#HTMLReporter #details .specDetail .description { display: block; color: white; background-color: #b03911; }
#HTMLReporter .resultMessage { padding-top: 14px; color: #333333; }
#HTMLReporter .resultMessage span.result { display: block; }
#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; }
#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;*/ /*}*/ }
#TrivialReporter a:visited, #TrivialReporter a { color: #303; }
#TrivialReporter a:hover, #TrivialReporter a:active { color: blue; }
#TrivialReporter .run_spec { float: right; padding-right: 5px; font-size: .8em; text-decoration: none; }
#TrivialReporter .banner { color: #303; background-color: #fef; padding: 5px; }
#TrivialReporter .logo { float: left; font-size: 1.1em; padding-left: 5px; }
#TrivialReporter .logo .version { font-size: .6em; padding-left: 1em; }
#TrivialReporter .runner.running { background-color: yellow; }
#TrivialReporter .options { text-align: right; font-size: .8em; }
#TrivialReporter .suite { border: 1px outset gray; margin: 5px 0; padding-left: 1em; }
#TrivialReporter .suite .suite { margin: 5px; }
#TrivialReporter .suite.passed { background-color: #dfd; }
#TrivialReporter .suite.failed { background-color: #fdd; }
#TrivialReporter .spec { margin: 5px; padding-left: 1em; clear: both; }
#TrivialReporter .spec.failed, #TrivialReporter .spec.passed, #TrivialReporter .spec.skipped { padding-bottom: 5px; border: 1px solid gray; }
#TrivialReporter .spec.failed { background-color: #fbb; border-color: red; }
#TrivialReporter .spec.passed { background-color: #bfb; border-color: green; }
#TrivialReporter .spec.skipped { background-color: #bbb; }
#TrivialReporter .messages { border-left: 1px dashed gray; padding-left: 1em; padding-right: 1em; }
#TrivialReporter .passed { background-color: #cfc; display: none; }
#TrivialReporter .failed { background-color: #fbb; }
#TrivialReporter .skipped { color: #777; background-color: #eee; display: none; }
#TrivialReporter .resultMessage span.result { display: block; line-height: 2em; color: black; }
#TrivialReporter .resultMessage .mismatch { color: black; }
#TrivialReporter .stackTrace { white-space: pre; font-size: .8em; margin-left: 10px; max-height: 5em; overflow: auto; border: 1px inset red; padding: 1em; background: #eef; }
#TrivialReporter .finished-at { padding-left: 1em; font-size: .6em; }
#TrivialReporter.show-passed .passed, #TrivialReporter.show-skipped .skipped { display: block; }
#TrivialReporter #jasmine_content { position: fixed; right: 100%; }
#TrivialReporter .runner { border: 1px solid gray; display: block; margin: 5px 0; padding: 2px 0 2px 10px; }
================================================
FILE: jasmine/lib/jasmine-1.3.1/jasmine.js
================================================
var isCommonJS = typeof window == "undefined" && typeof exports == "object";
/**
* Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework.
*
* @namespace
*/
var jasmine = {};
if (isCommonJS) exports.jasmine = jasmine;
/**
* @private
*/
jasmine.unimplementedMethod_ = function() {
throw new Error("unimplemented method");
};
/**
* Use <code>jasmine.undefined</code> instead of <code>undefined</code>, since <code>undefined</code> is just
* a plain old variable and may be redefined by somebody else.
*
* @private
*/
jasmine.undefined = jasmine.___undefined___;
/**
* Show diagnostic messages in the console if set to true
*
*/
jasmine.VERBOSE = false;
/**
* Default interval in milliseconds for event loop yields (e.g. to allow network activity or to refresh the screen with the HTML-based runner). Small values here may result in slow test running. Zero means no updates until all tests have completed.
*
*/
jasmine.DEFAULT_UPDATE_INTERVAL = 250;
/**
* Maximum levels of nesting that will be included when an object is pretty-printed
*/
jasmine.MAX_PRETTY_PRINT_DEPTH = 40;
/**
* Default timeout interval in milliseconds for waitsFor() blocks.
*/
jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000;
/**
* By default exceptions thrown in the context of a test are caught by jasmine so that it can run the remaining tests in the suite.
* Set to false to let the exception bubble up in the browser.
*
*/
jasmine.CATCH_EXCEPTIONS = true;
jasmine.getGlobal = function() {
function getGlobal() {
return this;
}
return getGlobal();
};
/**
* Allows for bound functions to be compared. Internal use only.
*
* @ignore
* @private
* @param base {Object} bound 'this' for the function
* @param name {Function} function to find
*/
jasmine.bindOriginal_ = function(base, name) {
var original = base[name];
if (original.apply) {
return function() {
return original.apply(base, arguments);
};
} else {
// IE support
return jasmine.getGlobal()[name];
}
};
jasmine.setTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'setTimeout');
jasmine.clearTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearTimeout');
jasmine.setInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'setInterval');
jasmine.clearInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearInterval');
jasmine.MessageResult = function(values) {
this.type = 'log';
this.values = values;
this.trace = new Error(); // todo: test better
};
jasmine.MessageResult.prototype.toString = function() {
var text = "";
for (var i = 0; i < this.values.length; i++) {
if (i > 0) text += " ";
if (jasmine.isString_(this.values[i])) {
text += this.values[i];
} else {
text += jasmine.pp(this.values[i]);
}
}
return text;
};
jasmine.ExpectationResult = function(params) {
this.type = 'expect';
this.matcherName = params.matcherName;
this.passed_ = params.passed;
this.expected = params.expected;
this.actual = params.actual;
this.message = this.passed_ ? 'Passed.' : params.message;
var trace = (params.trace || new Error(this.message));
this.trace = this.passed_ ? '' : trace;
};
jasmine.ExpectationResult.prototype.toString = function () {
return this.message;
};
jasmine.ExpectationResult.prototype.passed = function () {
return this.passed_;
};
/**
* Getter for the Jasmine environment. Ensures one gets created
*/
jasmine.getEnv = function() {
var env = jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env();
return env;
};
/**
* @ignore
* @private
* @param value
* @returns {Boolean}
*/
jasmine.isArray_ = function(value) {
return jasmine.isA_("Array", value);
};
/**
* @ignore
* @private
* @param value
* @returns {Boolean}
*/
jasmine.isString_ = function(value) {
return jasmine.isA_("String", value);
};
/**
* @ignore
* @private
* @param value
* @returns {Boolean}
*/
jasmine.isNumber_ = function(value) {
return jasmine.isA_("Number", value);
};
/**
* @ignore
* @private
* @param {String} typeName
* @param value
* @returns {Boolean}
*/
jasmine.isA_ = function(typeName, value) {
return Object.prototype.toString.apply(value) === '[object ' + typeName + ']';
};
/**
* Pretty printer for expecations. Takes any object and turns it into a human-readable string.
*
* @param value {Object} an object to be outputted
* @returns {String}
*/
jasmine.pp = function(value) {
var stringPrettyPrinter = new jasmine.StringPrettyPrinter();
stringPrettyPrinter.format(value);
return stringPrettyPrinter.string;
};
/**
* Returns true if the object is a DOM Node.
*
* @param {Object} obj object to check
* @returns {Boolean}
*/
jasmine.isDomNode = function(obj) {
return obj.nodeType > 0;
};
/**
* Returns a matchable 'generic' object of the class type. For use in expecations of type when values don't matter.
*
* @example
* // don't care about which function is passed in, as long as it's a function
* expect(mySpy).toHaveBeenCalledWith(jasmine.any(Function));
*
* @param {Class} clazz
* @returns matchable object of the type clazz
*/
jasmine.any = function(clazz) {
return new jasmine.Matchers.Any(clazz);
};
/**
* Returns a matchable subset of a JSON object. For use in expectations when you don't care about all of the
* attributes on the object.
*
* @example
* // don't care about any other attributes than foo.
* expect(mySpy).toHaveBeenCalledWith(jasmine.objectContaining({foo: "bar"});
*
* @param sample {Object} sample
* @returns matchable object for the sample
*/
jasmine.objectContaining = function (sample) {
return new jasmine.Matchers.ObjectContaining(sample);
};
/**
* Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks.
*
* Spies should be created in test setup, before expectations. They can then be checked, using the standard Jasmine
* expectation syntax. Spies can be checked if they were called or not and what the calling params were.
*
* A Spy has the following fields: wasCalled, callCount, mostRecentCall, and argsForCall (see docs).
*
* Spies are torn down at the end of every spec.
*
* Note: Do <b>not</b> call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj.
*
* @example
* // a stub
* var myStub = jasmine.createSpy('myStub'); // can be used anywhere
*
* // spy example
* var foo = {
* not: function(bool) { return !bool; }
* }
*
* // actual foo.not will not be called, execution stops
* spyOn(foo, 'not');
// foo.not spied upon, execution will continue to implementation
* spyOn(foo, 'not').andCallThrough();
*
* // fake example
* var foo = {
* not: function(bool) { return !bool; }
* }
*
* // foo.not(val) will return val
* spyOn(foo, 'not').andCallFake(function(value) {return value;});
*
* // mock example
* foo.not(7 == 7);
* expect(foo.not).toHaveBeenCalled();
* expect(foo.not).toHaveBeenCalledWith(true);
*
* @constructor
* @see spyOn, jasmine.createSpy, jasmine.createSpyObj
* @param {String} name
*/
jasmine.Spy = function(name) {
/**
* The name of the spy, if provided.
*/
this.identity = name || 'unknown';
/**
* Is this Object a spy?
*/
this.isSpy = true;
/**
* The actual function this spy stubs.
*/
this.plan = function() {
};
/**
* Tracking of the most recent call to the spy.
* @example
* var mySpy = jasmine.createSpy('foo');
* mySpy(1, 2);
* mySpy.mostRecentCall.args = [1, 2];
*/
this.mostRecentCall = {};
/**
* Holds arguments for each call to the spy, indexed by call count
* @example
* var mySpy = jasmine.createSpy('foo');
* mySpy(1, 2);
* mySpy(7, 8);
* mySpy.mostRecentCall.args = [7, 8];
* mySpy.argsForCall[0] = [1, 2];
* mySpy.argsForCall[1] = [7, 8];
*/
this.argsForCall = [];
this.calls = [];
};
/**
* Tells a spy to call through to the actual implemenatation.
*
* @example
* var foo = {
* bar: function() { // do some stuff }
* }
*
* // defining a spy on an existing property: foo.bar
* spyOn(foo, 'bar').andCallThrough();
*/
jasmine.Spy.prototype.andCallThrough = function() {
this.plan = this.originalValue;
return this;
};
/**
* For setting the return value of a spy.
*
* @example
* // defining a spy from scratch: foo() returns 'baz'
* var foo = jasmine.createSpy('spy on foo').andReturn('baz');
*
* // defining a spy on an existing property: foo.bar() returns 'baz'
* spyOn(foo, 'bar').andReturn('baz');
*
* @param {Object} value
*/
jasmine.Spy.prototype.andReturn = function(value) {
this.plan = function() {
return value;
};
return this;
};
/**
* For throwing an exception when a spy is called.
*
* @example
* // defining a spy from scratch: foo() throws an exception w/ message 'ouch'
* var foo = jasmine.createSpy('spy on foo').andThrow('baz');
*
* // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch'
* spyOn(foo, 'bar').andThrow('baz');
*
* @param {String} exceptionMsg
*/
jasmine.Spy.prototype.andThrow = function(exceptionMsg) {
this.plan = function() {
throw exceptionMsg;
};
return this;
};
/**
* Calls an alternate implementation when a spy is called.
*
* @example
* var baz = function() {
* // do some stuff, return something
* }
* // defining a spy from scratch: foo() calls the function baz
* var foo = jasmine.createSpy('spy on foo').andCall(baz);
*
* // defining a spy on an existing property: foo.bar() calls an anonymnous function
* spyOn(foo, 'bar').andCall(function() { return 'baz';} );
*
* @param {Function} fakeFunc
*/
jasmine.Spy.prototype.andCallFake = function(fakeFunc) {
this.plan = fakeFunc;
return this;
};
/**
* Resets all of a spy's the tracking variables so that it can be used again.
*
* @example
* spyOn(foo, 'bar');
*
* foo.bar();
*
* expect(foo.bar.callCount).toEqual(1);
*
* foo.bar.reset();
*
* expect(foo.bar.callCount).toEqual(0);
*/
jasmine.Spy.prototype.reset = function() {
this.wasCalled = false;
this.callCount = 0;
this.argsForCall = [];
this.calls = [];
this.mostRecentCall = {};
};
jasmine.createSpy = function(name) {
var spyObj = function() {
spyObj.wasCalled = true;
spyObj.callCount++;
var args = jasmine.util.argsToArray(arguments);
spyObj.mostRecentCall.object = this;
spyObj.mostRecentCall.args = args;
spyObj.argsForCall.push(args);
spyObj.calls.push({object: this, args: args});
return spyObj.plan.apply(this, arguments);
};
var spy = new jasmine.Spy(name);
for (var prop in spy) {
spyObj[prop] = spy[prop];
}
spyObj.reset();
return spyObj;
};
/**
* Determines whether an object is a spy.
*
* @param {jasmine.Spy|Object} putativeSpy
* @returns {Boolean}
*/
jasmine.isSpy = function(putativeSpy) {
return putativeSpy && putativeSpy.isSpy;
};
/**
* Creates a more complicated spy: an Object that has every property a function that is a spy. Used for stubbing something
* large in one call.
*
* @param {String} baseName name of spy class
* @param {Array} methodNames array of names of methods to make spies
*/
jasmine.createSpyObj = function(baseName, methodNames) {
if (!jasmine.isArray_(methodNames) || methodNames.length === 0) {
throw new Error('createSpyObj requires a non-empty array of method names to create spies for');
}
var obj = {};
for (var i = 0; i < methodNames.length; i++) {
obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]);
}
return obj;
};
/**
* All parameters are pretty-printed and concatenated together, then written to the current spec's output.
*
* Be careful not to leave calls to <code>jasmine.log</code> in production code.
*/
jasmine.log = function() {
var spec = jasmine.getEnv().currentSpec;
spec.log.apply(spec, arguments);
};
/**
* Function that installs a spy on an existing object's method name. Used within a Spec to create a spy.
*
* @example
* // spy example
* var foo = {
* not: function(bool) { return !bool; }
* }
* spyOn(foo, 'not'); // actual foo.not will not be called, execution stops
*
* @see jasmine.createSpy
* @param obj
* @param methodName
* @return {jasmine.Spy} a Jasmine spy that can be chained with all spy methods
*/
var spyOn = function(obj, methodName) {
return jasmine.getEnv().currentSpec.spyOn(obj, methodName);
};
if (isCommonJS) exports.spyOn = spyOn;
/**
* Creates a Jasmine spec that will be added to the current suite.
*
* // TODO: pending tests
*
* @example
* it('should be true', function() {
* expect(true).toEqual(true);
* });
*
* @param {String} desc description of this specification
* @param {Function} func defines the preconditions and expectations of the spec
*/
var it = function(desc, func) {
return jasmine.getEnv().it(desc, func);
};
if (isCommonJS) exports.it = it;
/**
* Creates a <em>disabled</em> Jasmine spec.
*
* A convenience method that allows existing specs to be disabled temporarily during development.
*
* @param {String} desc description of this specification
* @param {Function} func defines the preconditions and expectations of the spec
*/
var xit = function(desc, func) {
return jasmine.getEnv().xit(desc, func);
};
if (isCommonJS) exports.xit = xit;
/**
* Starts a chain for a Jasmine expectation.
*
* It is passed an Object that is the actual value and should chain to one of the many
* jasmine.Matchers functions.
*
* @param {Object} actual Actual value to test against and expected value
* @return {jasmine.Matchers}
*/
var expect = function(actual) {
return jasmine.getEnv().currentSpec.expect(actual);
};
if (isCommonJS) exports.expect = expect;
/**
* Defines part of a jasmine spec. Used in cominbination with waits or waitsFor in asynchrnous specs.
*
* @param {Function} func Function that defines part of a jasmine spec.
*/
var runs = function(func) {
jasmine.getEnv().currentSpec.runs(func);
};
if (isCommonJS) exports.runs = runs;
/**
* Waits a fixed time period before moving to the next block.
*
* @deprecated Use waitsFor() instead
* @param {Number} timeout milliseconds to wait
*/
var waits = function(timeout) {
jasmine.getEnv().currentSpec.waits(timeout);
};
if (isCommonJS) exports.waits = waits;
/**
* Waits for the latchFunction to return true before proceeding to the next block.
*
* @param {Function} latchFunction
* @param {String} optional_timeoutMessage
* @param {Number} optional_timeout
*/
var waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) {
jasmine.getEnv().currentSpec.waitsFor.apply(jasmine.getEnv().currentSpec, arguments);
};
if (isCommonJS) exports.waitsFor = waitsFor;
/**
* A function that is called before each spec in a suite.
*
* Used for spec setup, including validating assumptions.
*
* @param {Function} beforeEachFunction
*/
var beforeEach = function(beforeEachFunction) {
jasmine.getEnv().beforeEach(beforeEachFunction);
};
if (isCommonJS) exports.beforeEach = beforeEach;
/**
* A function that is called after each spec in a suite.
*
* Used for restoring any state that is hijacked during spec execution.
*
* @param {Function} afterEachFunction
*/
var afterEach = function(afterEachFunction) {
jasmine.getEnv().afterEach(afterEachFunction);
};
if (isCommonJS) exports.afterEach = afterEach;
/**
* Defines a suite of specifications.
*
* Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared
* are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization
* of setup in some tests.
*
* @example
* // TODO: a simple suite
*
* // TODO: a simple suite with a nested describe block
*
* @param {String} description A string, usually the class under test.
* @param {Function} specDefinitions function that defines several specs.
*/
var describe = function(description, specDefinitions) {
return jasmine.getEnv().describe(description, specDefinitions);
};
if (isCommonJS) exports.describe = describe;
/**
* Disables a suite of specifications. Used to disable some suites in a file, or files, temporarily during development.
*
* @param {String} description A string, usually the class under test.
* @param {Function} specDefinitions function that defines several specs.
*/
var xdescribe = function(description, specDefinitions) {
return jasmine.getEnv().xdescribe(description, specDefinitions);
};
if (isCommonJS) exports.xdescribe = xdescribe;
// Provide the XMLHttpRequest class for IE 5.x-6.x:
jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() {
function tryIt(f) {
try {
return f();
} catch(e) {
}
return null;
}
var xhr = tryIt(function() {
return new ActiveXObject("Msxml2.XMLHTTP.6.0");
}) ||
tryIt(function() {
return new ActiveXObject("Msxml2.XMLHTTP.3.0");
}) ||
tryIt(function() {
return new ActiveXObject("Msxml2.XMLHTTP");
}) ||
tryIt(function() {
return new ActiveXObject("Microsoft.XMLHTTP");
});
if (!xhr) throw new Error("This browser does not support XMLHttpRequest.");
return xhr;
} : XMLHttpRequest;
/**
* @namespace
*/
jasmine.util = {};
/**
* Declare that a child class inherit it's prototype from the parent class.
*
* @private
* @param {Function} childClass
* @param {Function} parentClass
*/
jasmine.util.inherit = function(childClass, parentClass) {
/**
* @private
*/
var subclass = function() {
};
subclass.prototype = parentClass.prototype;
childClass.prototype = new subclass();
};
jasmine.util.formatException = function(e) {
var lineNumber;
if (e.line) {
lineNumber = e.line;
}
else if (e.lineNumber) {
lineNumber = e.lineNumber;
}
var file;
if (e.sourceURL) {
file = e.sourceURL;
}
else if (e.fileName) {
file = e.fileName;
}
var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString();
if (file && lineNumber) {
message += ' in ' + file + ' (line ' + lineNumber + ')';
}
return message;
};
jasmine.util.htmlEscape = function(str) {
if (!str) return str;
return str.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>');
};
jasmine.util.argsToArray = function(args) {
var arrayOfArgs = [];
for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]);
return arrayOfArgs;
};
jasmine.util.extend = function(destination, source) {
for (var property in source) destination[property] = source[property];
return destination;
};
/**
* Environment for Jasmine
*
* @constructor
*/
jasmine.Env = function() {
this.currentSpec = null;
this.currentSuite = null;
this.currentRunner_ = new jasmine.Runner(this);
this.reporter = new jasmine.MultiReporter();
this.updateInterval = jasmine.DEFAULT_UPDATE_INTERVAL;
this.defaultTimeoutInterval = jasmine.DEFAULT_TIMEOUT_INTERVAL;
this.lastUpdate = 0;
this.specFilter = function() {
return true;
};
this.nextSpecId_ = 0;
this.nextSuiteId_ = 0;
this.equalityTesters_ = [];
// wrap matchers
this.matchersClass = function() {
jasmine.Matchers.apply(this, arguments);
};
jasmine.util.inherit(this.matchersClass, jasmine.Matchers);
jasmine.Matchers.wrapInto_(jasmine.Matchers.prototype, this.matchersClass);
};
jasmine.Env.prototype.setTimeout = jasmine.setTimeout;
jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout;
jasmine.Env.prototype.setInterval = jasmine.setInterval;
jasmine.Env.prototype.clearInterval = jasmine.clearInterval;
/**
* @returns an object containing jasmine version build info, if set.
*/
jasmine.Env.prototype.version = function () {
if (jasmine.version_) {
return jasmine.version_;
} else {
throw new Error('Version not set');
}
};
/**
* @returns string containing jasmine version build info, if set.
*/
jasmine.Env.prototype.versionString = function() {
if (!jasmine.version_) {
return "version unknown";
}
var version = this.version();
var versionString = version.major + "." + version.minor + "." + version.build;
if (version.release_candidate) {
versionString += ".rc" + version.release_candidate;
}
versionString += " revision " + version.revision;
return versionString;
};
/**
* @returns a sequential integer starting at 0
*/
jasmine.Env.prototype.nextSpecId = function () {
return this.nextSpecId_++;
};
/**
* @returns a sequential integer starting at 0
*/
jasmine.Env.prototype.nextSuiteId = function () {
return this.nextSuiteId_++;
};
/**
* Register a reporter to receive status updates from Jasmine.
* @param {jasmine.Reporter} reporter An object which will receive status updates.
*/
jasmine.Env.prototype.addReporter = function(reporter) {
this.reporter.addReporter(reporter);
};
jasmine.Env.prototype.execute = function() {
this.currentRunner_.execute();
};
jasmine.Env.prototype.describe = function(description, specDefinitions) {
var suite = new jasmine.Suite(this, description, specDefinitions, this.currentSuite);
var parentSuite = this.currentSuite;
if (parentSuite) {
parentSuite.add(suite);
} else {
this.currentRunner_.add(suite);
}
this.currentSuite = suite;
var declarationError = null;
try {
specDefinitions.call(suite);
} catch(e) {
declarationError = e;
}
if (declarationError) {
this.it("encountered a declaration exception", function() {
throw declarationError;
});
}
this.currentSuite = parentSuite;
return suite;
};
jasmine.Env.prototype.beforeEach = function(beforeEachFunction) {
if (this.currentSuite) {
this.currentSuite.beforeEach(beforeEachFunction);
} else {
this.currentRunner_.beforeEach(beforeEachFunction);
}
};
jasmine.Env.prototype.currentRunner = function () {
return this.currentRunner_;
};
jasmine.Env.prototype.afterEach = function(afterEachFunction) {
if (this.currentSuite) {
this.currentSuite.afterEach(afterEachFunction);
} else {
this.currentRunner_.afterEach(afterEachFunction);
}
};
jasmine.Env.prototype.xdescribe = function(desc, specDefinitions) {
return {
execute: function() {
}
};
};
jasmine.Env.prototype.it = function(description, func) {
var spec = new jasmine.Spec(this, this.currentSuite, description);
this.currentSuite.add(spec);
this.currentSpec = spec;
if (func) {
spec.runs(func);
}
return spec;
};
jasmine.Env.prototype.xit = function(desc, func) {
return {
id: this.nextSpecId(),
runs: function() {
}
};
};
jasmine.Env.prototype.compareRegExps_ = function(a, b, mismatchKeys, mismatchValues) {
if (a.source != b.source)
mismatchValues.push("expected pattern /" + b.source + "/ is not equal to the pattern /" + a.source + "/");
if (a.ignoreCase != b.ignoreCase)
mismatchValues.push("expected modifier i was" + (b.ignoreCase ? " " : " not ") + "set and does not equal the origin modifier");
if (a.global != b.global)
mismatchValues.push("expected modifier g was" + (b.global ? " " : " not ") + "set and does not equal the origin modifier");
if (a.multiline != b.multiline)
mismatchValues.push("expected modifier m was" + (b.multiline ? " " : " not ") + "set and does not equal the origin modifier");
if (a.sticky != b.sticky)
mismatchValues.push("expected modifier y was" + (b.sticky ? " " : " not ") + "set and does not equal the origin modifier");
return (mismatchValues.length === 0);
};
jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) {
if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) {
return true;
}
a.__Jasmine_been_here_before__ = b;
b.__Jasmine_been_here_before__ = a;
var hasKey = function(obj, keyName) {
return obj !== null && obj[keyName] !== jasmine.undefined;
};
for (var property in b) {
if (!hasKey(a, property) && hasKey(b, property)) {
mismatchKeys.push("expected has key '" + property + "', but missing from actual.");
}
}
for (property in a) {
if (!hasKey(b, property) && hasKey(a, property)) {
mismatchKeys.push("expected missing key '" + property + "', but present in actual.");
}
}
for (property in b) {
if (property == '__Jasmine_been_here_before__') continue;
if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) {
mismatchValues.push("'" + property + "' was '" + (b[property] ? jasmine.util.htmlEscape(b[property].toString()) : b[property]) + "' in expected, but was '" + (a[property] ? jasmine.util.htmlEscape(a[property].toString()) : a[property]) + "' in actual.");
}
}
if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) {
mismatchValues.push("arrays were not the same length");
}
delete a.__Jasmine_been_here_before__;
delete b.__Jasmine_been_here_before__;
return (mismatchKeys.length === 0 && mismatchValues.length === 0);
};
jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) {
mismatchKeys = mismatchKeys || [];
mismatchValues = mismatchValues || [];
for (var i = 0; i < this.equalityTesters_.length; i++) {
var equalityTester = this.equalityTesters_[i];
var result = equalityTester(a, b, this, mismatchKeys, mismatchValues);
if (result !== jasmine.undefined) return result;
}
if (a === b) return true;
if (a === jasmine.undefined || a === null || b === jasmine.undefined || b === null) {
return (a == jasmine.undefined && b == jasmine.undefined);
}
if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) {
return a === b;
}
if (a instanceof Date && b instanceof Date) {
return a.getTime() == b.getTime();
}
if (a.jasmineMatches) {
return a.jasmineMatches(b);
}
if (b.jasmineMatches) {
return b.jasmineMatches(a);
}
if (a instanceof jasmine.Matchers.ObjectContaining) {
return a.matches(b);
}
if (b instanceof jasmine.Matchers.ObjectContaining) {
return b.matches(a);
}
if (jasmine.isString_(a) && jasmine.isString_(b)) {
return (a == b);
}
if (jasmine.isNumber_(a) && jasmine.isNumber_(b)) {
return (a == b);
}
if (a instanceof RegExp && b instanceof RegExp) {
return this.compareRegExps_(a, b, mismatchKeys, mismatchValues);
}
if (typeof a === "object" && typeof b === "object") {
return this.compareObjects_(a, b, mismatchKeys, mismatchValues);
}
//Straight check
return (a === b);
};
jasmine.Env.prototype.contains_ = function(haystack, needle) {
if (jasmine.isArray_(haystack)) {
for (var i = 0; i < haystack.length; i++) {
if (this.equals_(haystack[i], needle)) return true;
}
return false;
}
return haystack.indexOf(needle) >= 0;
};
jasmine.Env.prototype.addEqualityTester = function(equalityTester) {
this.equalityTesters_.push(equalityTester);
};
/** No-op base class for Jasmine reporters.
*
* @constructor
*/
jasmine.Reporter = function() {
};
//noinspection JSUnusedLocalSymbols
jasmine.Reporter.prototype.reportRunnerStarting = function(runner) {
};
//noinspection JSUnusedLocalSymbols
jasmine.Reporter.prototype.reportRunnerResults = function(runner) {
};
//noinspection JSUnusedLocalSymbols
jasmine.Reporter.prototype.reportSuiteResults = function(suite) {
};
//noinspection JSUnusedLocalSymbols
jasmine.Reporter.prototype.reportSpecStarting = function(spec) {
};
//noinspection JSUnusedLocalSymbols
jasmine.Reporter.prototype.reportSpecResults = function(spec) {
};
//noinspection JSUnusedLocalSymbols
jasmine.Reporter.prototype.log = function(str) {
};
/**
* Blocks are functions with executable code that make up a spec.
*
* @constructor
* @param {jasmine.Env} env
* @param {Function} func
* @param {jasmine.Spec} spec
*/
jasmine.Block = function(env, func, spec) {
this.env = env;
this.func = func;
this.spec = spec;
};
jasmine.Block.prototype.execute = function(onComplete) {
if (!jasmine.CATCH_EXCEPTIONS) {
this.func.apply(this.spec);
}
else {
try {
this.func.apply(this.spec);
} catch (e) {
this.spec.fail(e);
}
}
onComplete();
};
/** JavaScript API reporter.
*
* @constructor
*/
jasmine.JsApiReporter = function() {
this.started = false;
this.finished = false;
this.suites_ = [];
this.results_ = {};
};
jasmine.JsApiReporter.prototype.reportRunnerStarting = function(runner) {
this.started = true;
var suites = runner.topLevelSuites();
for (var i = 0; i < suites.length; i++) {
var suite = suites[i];
this.suites_.push(this.summarize_(suite));
}
};
jasmine.JsApiReporter.prototype.suites = function() {
return this.suites_;
};
jasmine.JsApiReporter.prototype.summarize_ = function(suiteOrSpec) {
var isSuite = suiteOrSpec instanceof jasmine.Suite;
var summary = {
id: suiteOrSpec.id,
name: suiteOrSpec.description,
type: isSuite ? 'suite' : 'spec',
children: []
};
if (isSuite) {
var children = suiteOrSpec.children();
for (var i = 0; i < children.length; i++) {
summary.children.push(this.summarize_(children[i]));
}
}
return summary;
};
jasmine.JsApiReporter.prototype.results = function() {
return this.results_;
};
jasmine.JsApiReporter.prototype.resultsForSpec = function(specId) {
return this.results_[specId];
};
//noinspection JSUnusedLocalSymbols
jasmine.JsApiReporter.prototype.reportRunnerResults = function(runner) {
this.finished = true;
};
//noinspection JSUnusedLocalSymbols
jasmine.JsApiReporter.prototype.reportSuiteResults = function(suite) {
};
//noinspection JSUnusedLocalSymbols
jasmine.JsApiReporter.prototype.reportSpecResults = function(spec) {
this.results_[spec.id] = {
messages: spec.results().getItems(),
result: spec.results().failedCount > 0 ? "failed" : "passed"
};
};
//noinspection JSUnusedLocalSymbols
jasmine.JsApiReporter.prototype.log = function(str) {
};
jasmine.JsApiReporter.prototype.resultsForSpecs = function(specIds){
var results = {};
for (var i = 0; i < specIds.length; i++) {
var specId = specIds[i];
results[specId] = this.summarizeResult_(this.results_[specId]);
}
return results;
};
jasmine.JsApiReporter.prototype.summarizeResult_ = function(result){
var summaryMessages = [];
var messagesLength = result.messages.length;
for (var messageIndex = 0; messageIndex < messagesLength; messageIndex++) {
var resultMessage = result.messages[messageIndex];
summaryMessages.push({
text: resultMessage.type == 'log' ? resultMessage.toString() : jasmine.undefined,
passed: resultMessage.passed ? resultMessage.passed() : true,
type: resultMessage.type,
message: resultMessage.message,
trace: {
stack: resultMessage.passed && !resultMessage.passed() ? resultMessage.trace.stack : jasmine.undefined
}
});
}
return {
result : result.result,
messages : summaryMessages
};
};
/**
* @constructor
* @param {jasmine.Env} env
* @param actual
* @param {jasmine.Spec} spec
*/
jasmine.Matchers = function(env, actual, spec, opt_isNot) {
this.env = env;
this.actual = actual;
this.spec = spec;
this.isNot = opt_isNot || false;
this.reportWasCalled_ = false;
};
// todo: @deprecated as of Jasmine 0.11, remove soon [xw]
jasmine.Matchers.pp = function(str) {
throw new Error("jasmine.Matchers.pp() is no longer supported, please use jasmine.pp() instead!");
};
// todo: @deprecated Deprecated as of Jasmine 0.10. Rewrite your custom matchers to return true or false. [xw]
jasmine.Matchers.prototype.report = function(result, failing_message, details) {
throw new Error("As of jasmine 0.11, custom matchers must be implemented differently -- please see jasmine docs");
};
jasmine.Matchers.wrapInto_ = function(prototype, matchersClass) {
for (var methodName in prototype) {
if (methodName == 'report') continue;
var orig = prototype[methodName];
matchersClass.prototype[methodName] = jasmine.Matchers.matcherFn_(methodName, orig);
}
};
jasmine.Matchers.matcherFn_ = function(matcherName, matcherFunction) {
return function() {
var matcherArgs = jasmine.util.argsToArray(arguments);
var result = matcherFunction.apply(this, arguments);
if (this.isNot) {
result = !result;
}
if (this.reportWasCalled_) return result;
var message;
if (!result) {
if (this.message) {
message = this.message.apply(this, arguments);
if (jasmine.isArray_(message)) {
message = message[this.isNot ? 1 : 0];
}
} else {
var englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); });
message = "Expected " + jasmine.pp(this.actual) + (this.isNot ? " not " : " ") + englishyPredicate;
if (matcherArgs.length > 0) {
for (var i = 0; i < matcherArgs.length; i++) {
if (i > 0) message += ",";
message += " " + jasmine.pp(matcherArgs[i]);
}
}
message += ".";
}
}
var expectationResult = new jasmine.ExpectationResult({
matcherName: matcherName,
passed: result,
expected: matcherArgs.length > 1 ? matcherArgs : matcherArgs[0],
actual: this.actual,
message: message
});
this.spec.addMatcherResult(expectationResult);
return jasmine.undefined;
};
};
/**
* toBe: compares the actual to the expected using ===
* @param expected
*/
jasmine.Matchers.prototype.toBe = function(expected) {
return this.actual === expected;
};
/**
* toNotBe: compares the actual to the expected using !==
* @param expected
* @deprecated as of 1.0. Use not.toBe() instead.
*/
jasmine.Matchers.prototype.toNotBe = function(expected) {
return this.actual !== expected;
};
/**
* toEqual: compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc.
*
* @param expected
*/
jasmine.Matchers.prototype.toEqual = function(expected) {
return this.env.equals_(this.actual, expected);
};
/**
* toNotEqual: compares the actual to the expected using the ! of jasmine.Matchers.toEqual
* @param expected
* @deprecated as of 1.0. Use not.toEqual() instead.
*/
jasmine.Matchers.prototype.toNotEqual = function(expected) {
return !this.env.equals_(this.actual, expected);
};
/**
* Matcher that compares the actual to the expected using a regular expression. Constructs a RegExp, so takes
* a pattern or a String.
*
* @param expected
*/
jasmine.Matchers.prototype.toMatch = function(expected) {
return new RegExp(expected).test(this.actual);
};
/**
* Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch
* @param expected
* @deprecated as of 1.0. Use not.toMatch() instead.
*/
jasmine.Matchers.prototype.toNotMatch = function(expected) {
return !(new RegExp(expected).test(this.actual));
};
/**
* Matcher that compares the actual to jasmine.undefined.
*/
jasmine.Matchers.prototype.toBeDefined = function() {
return (this.actual !== jasmine.undefined);
};
/**
* Matcher that compares the actual to jasmine.undefined.
*/
jasmine.Matchers.prototype.toBeUndefined = function() {
return (this.actual === jasmine.undefined);
};
/**
* Matcher that compares the actual to null.
*/
jasmine.Matchers.prototype.toBeNull = function() {
return (this.actual === null);
};
/**
* Matcher that compares the actual to NaN.
*/
jasmine.Matchers.prototype.toBeNaN = function() {
this.message = function() {
return [ "Expected " + jasmine.pp(this.actual) + " to be NaN." ];
};
return (this.actual !== this.actual);
};
/**
* Matcher that boolean not-nots the actual.
*/
jasmine.Matchers.prototype.toBeTruthy = function() {
return !!this.actual;
};
/**
* Matcher that boolean nots the actual.
*/
jasmine.Matchers.prototype.toBeFalsy = function() {
return !this.actual;
};
/**
* Matcher that checks to see if the actual, a Jasmine spy, was called.
*/
jasmine.Matchers.prototype.toHaveBeenCalled = function() {
if (arguments.length > 0) {
throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith');
}
if (!jasmine.isSpy(this.actual)) {
throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
}
this.message = function() {
return [
"Expected spy " + this.actual.identity + " to have been called.",
"Expected spy " + this.actual.identity + " not to have been called."
];
};
return this.actual.wasCalled;
};
/** @deprecated Use expect(xxx).toHaveBeenCalled() instead */
jasmine.Matchers.prototype.wasCalled = jasmine.Matchers.prototype.toHaveBeenCalled;
/**
* Matcher that checks to see if the actual, a Jasmine spy, was not called.
*
* @deprecated Use expect(xxx).not.toHaveBeenCalled() instead
*/
jasmine.Matchers.prototype.wasNotCalled = function() {
if (arguments.length > 0) {
throw new Error('wasNotCalled does not take arguments');
}
if (!jasmine.isSpy(this.actual)) {
throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
}
this.message = function() {
return [
"Expected spy " + this.actual.identity + " to not have been called.",
"Expected spy " + this.actual.identity + " to have been called."
];
};
return !this.actual.wasCalled;
};
/**
* Matcher that checks to see if the actual, a Jasmine spy, was called with a set of parameters.
*
* @example
*
*/
jasmine.Matchers.prototype.toHaveBeenCalledWith = function() {
var expectedArgs = jasmine.util.argsToArray(arguments);
if (!jasmine.isSpy(this.actual)) {
throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
}
this.message = function() {
var invertedMessage = "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but it was.";
var positiveMessage = "";
if (this.actual.callCount === 0) {
positiveMessage = "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but it was never called.";
} else {
positiveMessage = "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but actual calls were " + jasmine.pp(this.actual.argsForCall).replace(/^\[ | \]$/g, '')
}
return [positiveMessage, invertedMessage];
};
return this.env.contains_(this.actual.argsForCall, expectedArgs);
};
/** @deprecated Use expect(xxx).toHaveBeenCalledWith() instead */
jasmine.Matchers.prototype.wasCalledWith = jasmine.Matchers.prototype.toHaveBeenCalledWith;
/** @deprecated Use expect(xxx).not.toHaveBeenCalledWith() instead */
jasmine.Matchers.prototype.wasNotCalledWith = function() {
var expectedArgs = jasmine.util.argsToArray(arguments);
if (!jasmine.isSpy(this.actual)) {
throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
}
this.message = function() {
return [
"Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was",
"Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was"
];
};
return !this.env.contains_(this.actual.argsForCall, expectedArgs);
};
/**
* Matcher that checks that the expected item is an element in the actual Array.
*
* @param {Object} expected
*/
jasmine.Matchers.prototype.toContain = function(expected) {
return this.env.contains_(this.actual, expected);
};
/**
* Matcher that checks that the expected item is NOT an element in the actual Array.
*
* @param {Object} expected
* @deprecated as of 1.0. Use not.toContain() instead.
*/
jasmine.Matchers.prototype.toNotContain = function(expected) {
return !this.env.contains_(this.actual, expected);
};
jasmine.Matchers.prototype.toBeLessThan = function(expected) {
return this.actual < expected;
};
jasmine.Matchers.prototype.toBeGreaterThan = function(expected) {
return this.actual > expected;
};
/**
* Matcher that checks that the expected item is equal to the actual item
* up to a given level of decimal precision (default 2).
*
* @param {Number} expected
* @param {Number} precision, as number of decimal places
*/
jasmine.Matchers.prototype.toBeCloseTo = function(expected, precision) {
if (!(precision === 0)) {
precision = precision || 2;
}
return Math.abs(expected - this.actual) < (Math.pow(10, -precision) / 2);
};
/**
* Matcher that checks that the expected exception was thrown by the actual.
*
* @param {String} [expected]
*/
jasmine.Matchers.prototype.toThrow = function(expected) {
var result = false;
var exception;
if (typeof this.actual != 'function') {
throw new Error('Actual is not a function');
}
try {
this.actual();
} catch (e) {
exception = e;
}
if (exception) {
result = (expected === jasmine.undefined || this.env.equals_(exception.message || exception, expected.message || expected));
}
var not = this.isNot ? "not " : "";
this.message = function() {
if (exception && (expected === jasmine.undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) {
return ["Expected function " + not + "to throw", expected ? expected.message || expected : "an exception", ", but it threw", exception.message || exception].join(' ');
} else {
return "Expected function to throw an exception.";
}
};
return result;
};
jasmine.Matchers.Any = function(expectedClass) {
this.expectedClass = expectedClass;
};
jasmine.Matchers.Any.prototype.jasmineMatches = function(other) {
if (this.expectedClass == String) {
return typeof other == 'string' || other instanceof String;
}
if (this.expectedClass == Number) {
return typeof other == 'number' || other instanceof Number;
}
if (this.expectedClass == Function) {
return typeof other == 'function' || other instanceof Function;
}
if (this.expectedClass == Object) {
return typeof other == 'object';
}
return other instanceof this.expectedClass;
};
jasmine.Matchers.Any.prototype.jasmineToString = function() {
return '<jasmine.any(' + this.expectedClass + ')>';
};
jasmine.Matchers.ObjectContaining = function (sample) {
this.sample = sample;
};
jasmine.Matchers.ObjectContaining.prototype.jasmineMatches = function(other, mismatchKeys, mismatchValues) {
mismatchKeys = mismatchKeys || [];
mismatchValues = mismatchValues || [];
var env = jasmine.getEnv();
var hasKey = function(obj, keyName) {
return obj != null && obj[keyName] !== jasmine.undefined;
};
for (var property in this.sample) {
if (!hasKey(other, property) && hasKey(this.sample, property)) {
mismatchKeys.push("expected has key '" + property + "', but missing from actual.");
}
else if (!env.equals_(this.sample[property], other[property], mismatchKeys, mismatchValues)) {
mismatchValues.push("'" + property + "' was '" + (other[property] ? jasmine.util.htmlEscape(other[property].toString()) : other[property]) + "' in expected, but was '" + (this.sample[property] ? jasmine.util.htmlEscape(this.sample[property].toString()) : this.sample[property]) + "' in actual.");
}
}
return (mismatchKeys.length === 0 && mismatchValues.length === 0);
};
jasmine.Matchers.ObjectContaining.prototype.jasmineToString = function () {
return "<jasmine.objectContaining(" + jasmine.pp(this.sample) + ")>";
};
// Mock setTimeout, clearTimeout
// Contributed by Pivotal Computer Systems, www.pivotalsf.com
jasmine.FakeTimer = function() {
this.reset();
var self = this;
self.setTimeout = function(funcToCall, millis) {
self.timeoutsMade++;
self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false);
return self.timeoutsMade;
};
self.setInterval = function(funcToCall, millis) {
self.timeoutsMade++;
self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true);
return self.timeoutsMade;
};
self.clearTimeout = function(timeoutKey) {
self.scheduledFunctions[timeoutKey] = jasmine.undefined;
};
self.clearInterval = function(timeoutKey) {
self.scheduledFunctions[timeoutKey] = jasmine.undefined;
};
};
jasmine.FakeTimer.prototype.reset = function() {
this.timeoutsMade = 0;
this.scheduledFunctions = {};
this.nowMillis = 0;
};
jasmine.FakeTimer.prototype.tick = function(millis) {
var oldMillis = this.nowMillis;
var newMillis = oldMillis + millis;
this.runFunctionsWithinRange(oldMillis, newMillis);
this.nowMillis = newMillis;
};
jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) {
var scheduledFunc;
var funcsToRun = [];
for (var timeoutKey in this.scheduledFunctions) {
scheduledFunc = this.scheduledFunctions[timeoutKey];
if (scheduledFunc != jasmine.undefined &&
scheduledFunc.runAtMillis >= oldMillis &&
scheduledFunc.runAtMillis <= nowMillis) {
funcsToRun.push(scheduledFunc);
this.scheduledFunctions[timeoutKey] = jasmine.undefined;
}
}
if (funcsToRun.length > 0) {
funcsToRun.sort(function(a, b) {
return a.runAtMillis - b.runAtMillis;
});
for (var i = 0; i < funcsToRun.length; ++i) {
try {
var funcToRun = funcsToRun[i];
this.nowMillis = funcToRun.runAtMillis;
funcToRun.funcToCall();
if (funcToRun.recurring) {
this.scheduleFunction(funcToRun.timeoutKey,
funcToRun.funcToCall,
funcToRun.millis,
true);
}
} catch(e) {
}
}
this.runFunctionsWithinRange(oldMillis, nowMillis);
}
};
jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) {
this.scheduledFunctions[timeoutKey] = {
runAtMillis: this.nowMillis + millis,
funcToCall: funcToCall,
recurring: recurring,
timeoutKey: timeoutKey,
millis: millis
};
};
/**
* @namespace
*/
jasmine.Clock = {
defaultFakeTimer: new jasmine.FakeTimer(),
reset: function() {
jasmine.Clock.assertInstalled();
jasmine.Clock.defaultFakeTimer.reset();
},
tick: function(millis) {
jasmine.Clock.assertInstalled();
jasmine.Clock.defaultFakeTimer.tick(millis);
},
runFunctionsWithinRange: function(oldMillis, nowMillis) {
jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis);
},
scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) {
jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring);
},
useMock: function() {
if (!jasmine.Clock.isInstalled()) {
var spec = jasmine.getEnv().currentSpec;
spec.after(jasmine.Clock.uninstallMock);
jasmine.Clock.installMock();
}
},
installMock: function() {
jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer;
},
uninstallMock: function() {
jasmine.Clock.assertInstalled();
jasmine.Clock.installed = jasmine.Clock.real;
},
real: {
setTimeout: jasmine.getGlobal().setTimeout,
clearTimeout: jasmine.getGlobal().clearTimeout,
setInterval: jasmine.getGlobal().setInterval,
clearInterval: jasmine.getGlobal().clearInterval
},
assertInstalled: function() {
if (!jasmine.Clock.isInstalled()) {
throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()");
}
},
isInstalled: function() {
return jasmine.Clock.installed == jasmine.Clock.defaultFakeTimer;
},
installed: null
};
jasmine.Clock.installed = jasmine.Clock.real;
//else for IE support
jasmine.getGlobal().setTimeout = function(funcToCall, millis) {
if (jasmine.Clock.installed.setTimeout.apply) {
return jasmine.Clock.installed.setTimeout.apply(this, arguments);
} else {
return jasmine.Clock.installed.setTimeout(funcToCall, millis);
}
};
jasmine.getGlobal().setInterval = function(funcToCall, millis) {
if (jasmine.Clock.installed.setInterval.apply) {
return jasmine.Clock.installed.setInterval.apply(this, arguments);
} else {
return jasmine.Clock.installed.setInterval(funcToCall, millis);
}
};
jasmine.getGlobal().clearTimeout = function(timeoutKey) {
if (jasmine.Clock.installed.clearTimeout.apply) {
return jasmine.Clock.installed.clearTimeout.apply(this, arguments);
} else {
return jasmine.Clock.installed.clearTimeout(timeoutKey);
}
};
jasmine.getGlobal().clearInterval = function(timeoutKey) {
if (jasmine.Clock.installed.clearTimeout.apply) {
return jasmine.Clock.installed.clearInterval.apply(this, arguments);
} else {
return jasmine.Clock.installed.clearInterval(timeoutKey);
}
};
/**
* @constructor
*/
jasmine.MultiReporter = function() {
this.subReporters_ = [];
};
jasmine.util.inherit(jasmine.MultiReporter, jasmine.Reporter);
jasmine.MultiReporter.prototype.addReporter = function(reporter) {
this.subReporters_.push(reporter);
};
(function() {
var functionNames = [
"reportRunnerStarting",
"reportRunnerResults",
"reportSuiteResults",
"reportSpecStarting",
"reportSpecResults",
"log"
];
for (var i = 0; i < functionNames.length; i++) {
var functionName = functionNames[i];
jasmine.MultiReporter.prototype[functionName] = (function(functionName) {
return function() {
for (var j = 0; j < this.subReporters_.length; j++) {
var subReporter = this.subReporters_[j];
if (subReporter[functionName]) {
subReporter[functionName].apply(subReporter, arguments);
}
}
};
})(functionName);
}
})();
/**
* Holds results for a set of Jasmine spec. Allows for the results array to hold another jasmine.NestedResults
*
* @constructor
*/
jasmine.NestedResults = function() {
/**
* The total count of results
*/
this.totalCount = 0;
/**
* Number of passed results
*/
this.passedCount = 0;
/**
* Number of failed results
*/
this.failedCount = 0;
/**
* Was this suite/spec skipped?
*/
this.skipped = false;
/**
* @ignore
*/
this.items_ = [];
};
/**
* Roll up the result counts.
*
* @param result
*/
jasmine.NestedResults.prototype.rollupCounts = function(result) {
this.totalCount += result.totalCount;
this.passedCount += result.passedCount;
this.failedCount += result.failedCount;
};
/**
* Adds a log message.
* @param values Array of message parts which will be concatenated later.
*/
jasmine.NestedResults.prototype.log = function(values) {
this.items_.push(new jasmine.MessageResult(values));
};
/**
* Getter for the results: message & results.
*/
jasmine.NestedResults.prototype.getItems = function() {
return this.items_;
};
/**
* Adds a result, tracking counts (total, passed, & failed)
* @param {jasmine.ExpectationResult|jasmine.NestedResults} result
*/
jasmine.NestedResults.prototype.addResult = function(result) {
if (result.type != 'log') {
if (result.items_) {
this.rollupCounts(result);
} else {
this.totalCount++;
if (result.passed()) {
this.passedCount++;
} else {
this.failedCount++;
}
}
}
this.items_.push(result);
};
/**
* @returns {Boolean} True if <b>everything</b> below passed
*/
jasmine.NestedResults.prototype.passed = function() {
return this.passedCount === this.totalCount;
};
/**
* Base class for pretty printing for expectation results.
*/
jasmine.PrettyPrinter = function() {
this.ppNestLevel_ = 0;
};
/**
* Formats a value in a nice, human-readable string.
*
* @param value
*/
jasmine.PrettyPrinter.prototype.format = function(value) {
this.ppNestLevel_++;
try {
if (value === jasmine.undefined) {
this.emitScalar('undefined');
} else if (value === null) {
this.emitScalar('null');
} else if (value === jasmine.getGlobal()) {
this.emitScalar('<global>');
} else if (value.jasmineToString) {
this.emitScalar(value.jasmineToString());
} else if (typeof value === 'string') {
this.emitString(value);
} else if (jasmine.isSpy(value)) {
this.emitScalar("spy on " + value.identity);
} else if (value instanceof RegExp) {
this.emitScalar(value.toString());
} else if (typeof value === 'function') {
this.emitScalar('Function');
} else if (typeof value.nodeType === 'number') {
this.emitScalar('HTMLNode');
} else if (value instanceof Date) {
this.emitScalar('Date(' + value + ')');
} else if (value.__Jasmine_been_here_before__) {
this.emitScalar('<circular reference: ' + (jasmine.isArray_(value) ? 'Array' : 'Object') + '>');
} else if (jasmine.isArray_(value) || typeof value == 'object') {
value.__Jasmine_been_here_before__ = true;
if (jasmine.isArray_(value)) {
this.emitArray(value);
} else {
this.emitObject(value);
}
delete value.__Jasmine_been_here_before__;
} else {
this.emitScalar(value.toString());
}
} finally {
this.ppNestLevel_--;
}
};
jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) {
for (var property in obj) {
if (!obj.hasOwnProperty(property)) continue;
if (property == '__Jasmine_been_here_before__') continue;
fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) !== jasmine.undefined &&
obj.__lookupGetter__(property) !== null) : false);
}
};
jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_;
jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_;
jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_;
jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_;
jasmine.StringPrettyPrinter = function() {
jasmine.PrettyPrinter.call(this);
this.string = '';
};
jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter);
jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) {
this.append(value);
};
jasmine.StringPrettyPrinter.prototype.emitString = function(value) {
this.append("'" + value + "'");
};
jasmine.StringPrettyPrinter.prototype.emitArray = function(array) {
if (this.ppNestLevel_ > jasmine.MAX_PRETTY_PRINT_DEPTH) {
this.append("Array");
return;
}
this.append('[ ');
for (var i = 0; i < array.length; i++) {
if (i > 0) {
this.append(', ');
}
this.format(array[i]);
}
this.append(' ]');
};
jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) {
if (this.ppNestLevel_ > jasmine.MAX_PRETTY_PRINT_DEPTH) {
this.append("Object");
return;
}
var self = this;
this.append('{ ');
var first = true;
this.iterateObject(obj, function(property, isGetter) {
if (first) {
first = false;
} else {
self.append(', ');
}
self.append(property);
self.append(' : ');
if (isGetter) {
self.append('<getter>');
} else {
self.format(obj[property]);
}
});
this.append(' }');
};
jasmine.StringPrettyPrinter.prototype.append = function(value) {
this.string += value;
};
jasmine.Queue = function(env) {
this.env = env;
// parallel to blocks. each true value in this array means the block will
// get executed even if we abort
this.ensured = [];
this.blocks = [];
this.running = false;
this.index = 0;
this.offset = 0;
this.abort = false;
};
jasmine.Queue.prototype.addBefore = function(block, ensure) {
if (ensure === jasmine.undefined) {
ensure = false;
}
this.blocks.unshift(block);
this.ensured.unshift(ensure);
};
jasmine.Queue.prototype.add = function(block, ensure) {
if (ensure === jasmine.undefined) {
ensure = false;
}
this.blocks.push(block);
this.ensured.push(ensure);
};
jasmine.Queue.prototype.insertNext = function(block, ensure) {
if (ensure === jasmine.undefined) {
ensure = false;
}
this.ensured.splice((this.index + this.offset + 1), 0, ensure);
this.blocks.splice((this.index + this.offset + 1), 0, block);
this.offset++;
};
jasmine.Queue.prototype.start = function(onComplete) {
this.running = true;
this.onComplete = onComplete;
this.next_();
};
jasmine.Queue.prototype.isRunning = function() {
return this.running;
};
jasmine.Queue.LOOP_DONT_RECURSE = true;
jasmine.Queue.prototype.next_ = function() {
var self = this;
var goAgain = true;
while (goAgain) {
goAgain = false;
if (self.index < self.blocks.length && !(this.abort && !this.ensured[self.index])) {
var calledSynchronously = true;
var completedSynchronously = false;
var onComplete = function () {
if (jasmine.Queue.LOOP_DONT_RECURSE && calledSynchronously) {
completedSynchronously = true;
return;
}
if (self.blocks[self.index].abort) {
self.abort = true;
}
self.offset = 0;
self.index++;
var now = new Date().getTime();
if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) {
self.env.lastUpdate = now;
self.env.setTimeout(function() {
self.next_();
}, 0);
} else {
if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) {
goAgain = true;
} else {
self.next_();
}
}
};
self.blocks[self.index].execute(onComplete);
calledSynchronously = false;
if (completedSynchronously) {
onComplete();
}
} else {
self.running = false;
if (self.onComplete) {
self.onComplete();
}
}
}
};
jasmine.Queue.prototype.results = function() {
var results = new jasmine.NestedResults();
for (var i = 0; i < this.blocks.length; i++) {
if (this.blocks[i].results) {
results.addResult(this.blocks[i].results());
}
}
return results;
};
/**
* Runner
*
* @constructor
* @param {jasmine.Env} env
*/
jasmine.Runner = function(env) {
var self = this;
self.env = env;
self.queue = new jasmine.Queue(env);
self.before_ = [];
self.after_ = [];
self.suites_ = [];
};
jasmine.Runner.prototype.execute = function() {
var self = this;
if (self.env.reporter.reportRunnerStarting) {
self.env.reporter.reportRunnerStarting(this);
}
self.queue.start(function () {
self.finishCallback();
});
};
jasmine.Runner.prototype.beforeEach = function(beforeEachFunction) {
beforeEachFunction.typeName = 'beforeEach';
this.before_.splice(0,0,beforeEachFunction);
};
jasmine.Runner.prototype.afterEach = function(afterEachFunction) {
afterEachFunction.typeName = 'afterEach';
this.after_.splice(0,0,afterEachFunction);
};
jasmine.Runner.prototype.finishCallback = function() {
this.env.reporter.reportRunnerResults(this);
};
jasmine.Runner.prototype.addSuite = function(suite) {
this.suites_.push(suite);
};
jasmine.Runner.prototype.add = function(block) {
if (block instanceof jasmine.Suite) {
this.addSuite(block);
}
this.queue.add(block);
};
jasmine.Runner.prototype.specs = function () {
var suites = this.suites();
var specs = [];
for (var i = 0; i < suites.length; i++) {
specs = specs.concat(suites[i].specs());
}
return specs;
};
jasmine.Runner.prototype.suites = function() {
return this.suites_;
};
jasmine.Runner.prototype.topLevelSuites = function() {
var topLevelSuites = [];
for (var i = 0; i < this.suites_.length; i++) {
if (!this.suites_[i].parentSuite) {
topLevelSuites.push(this.suites_[i]);
}
}
return topLevelSuites;
};
jasmine.Runner.prototype.results = function() {
return this.queue.results();
};
/**
* Internal representation of a Jasmine specification, or test.
*
* @constructor
* @param {jasmine.Env} env
* @param {jasmine.Suite} suite
* @param {String} description
*/
jasmine.Spec = function(env, suite, description) {
if (!env) {
throw new Error('jasmine.Env() required');
}
if (!suite) {
throw new Error('jasmine.Suite() required');
}
var spec = this;
spec.id = env.nextSpecId ? env.nextSpecId() : null;
spec.env = env;
spec.suite = suite;
spec.description = description;
spec.queue = new jasmine.Queue(env);
spec.afterCallbacks = [];
spec.spies_ = [];
spec.results_ = new jasmine.NestedResults();
spec.results_.description = description;
spec.matchersClass = null;
};
jasmine.Spec.prototype.getFullName = function() {
return this.suite.getFullName() + ' ' + this.description + '.';
};
jasmine.Spec.prototype.results = function() {
return this.results_;
};
/**
* All parameters are pretty-printed and concatenated together, then written to the spec's output.
*
* Be careful not to leave calls to <code>jasmine.log</code> in production code.
*/
jasmine.Spec.prototype.log = function() {
return this.results_.log(arguments);
};
jasmine.Spec.prototype.runs = function (func) {
var block = new jasmine.Block(this.env, func, this);
this.addToQueue(block);
return this;
};
jasmine.Spec.prototype.addToQueue = function (block) {
if (this.queue.isRunning()) {
this.queue.insertNext(block);
} else {
this.queue.add(block);
}
};
/**
* @param {jasmine.ExpectationResult} result
*/
jasmine.Spec.prototype.addMatcherResult = function(result) {
this.results_.addResult(result);
};
jasmine.Spec.prototype.expect = func
gitextract_jh45o0se/ ├── .github/ │ └── FUNDING.yml ├── LICENSE ├── README.md ├── app.css ├── app.js ├── debug.js ├── flowchart/ │ ├── dragging_service.js │ ├── flowchart_directive.js │ ├── flowchart_directive.spec.js │ ├── flowchart_template.html │ ├── flowchart_viewmodel.js │ ├── flowchart_viewmodel.spec.js │ ├── mouse_capture_service.js │ ├── svg_class.js │ └── svg_class.spec.js ├── index.html ├── jasmine/ │ ├── SpecRunner.html │ └── lib/ │ └── jasmine-1.3.1/ │ ├── MIT.LICENSE │ ├── jasmine-html.js │ ├── jasmine.css │ └── jasmine.js ├── lib/ │ ├── angular-mocks.js │ ├── angular.js │ └── jquery-2.0.2.js └── server.js
SYMBOL INDEX (350 symbols across 5 files)
FILE: jasmine/lib/jasmine-1.3.1/jasmine-html.js
function focusedSpecName (line 126) | function focusedSpecName() {
function createReporterDom (line 148) | function createReporterDom(version) {
function noTryCatch (line 165) | function noTryCatch() {
function searchWithCatch (line 169) | function searchWithCatch() {
function setExceptionHandling (line 188) | function setExceptionHandling() {
function showDetails (line 363) | function showDetails() {
function isUndefined (line 369) | function isUndefined(obj) {
function isDefined (line 373) | function isDefined(obj) {
function specPluralizedFor (line 377) | function specPluralizedFor(count) {
FILE: jasmine/lib/jasmine-1.3.1/jasmine.js
function getGlobal (line 55) | function getGlobal() {
function tryIt (line 621) | function tryIt(f) {
FILE: lib/angular-mocks.js
function concat (line 299) | function concat(array1, array2, index) {
function tick (line 481) | function tick() {
function jsonStringToDate (line 564) | function jsonStringToDate(string) {
function int (line 584) | function int(str) {
function padNumber (line 588) | function padNumber(num, digits, trim) {
function serialize (line 828) | function serialize(object) {
function serializeScope (line 861) | function serializeScope(scope, offset) {
function createHttpBackendMock (line 1093) | function createHttpBackendMock($rootScope, $delegate, $browser) {
function MockHttpExpectation (line 1544) | function MockHttpExpectation(method, url, data, headers) {
function MockXhr (line 1582) | function MockXhr() {
function formatPendingTasksAsString (line 1673) | function formatPendingTasksAsString(tasks) {
function workFn (line 1981) | function workFn() {
function workFn (line 2090) | function workFn() {
FILE: lib/angular.js
function minErr (line 38) | function minErr(module, ErrorConstructor) {
function isArrayLike (line 269) | function isArrayLike(obj) {
function forEach (line 321) | function forEach(obj, iterator, context) {
function forEachSorted (line 365) | function forEachSorted(obj, iterator, context) {
function reverseParams (line 379) | function reverseParams(iteratorFn) {
function nextUid (line 393) | function nextUid() {
function setHashKey (line 403) | function setHashKey(obj, h) {
function baseExtend (line 412) | function baseExtend(dst, objs, deep) {
function extend (line 460) | function extend(dst) {
function merge (line 483) | function merge(dst) {
function toInt (line 489) | function toInt(str) {
function inherit (line 494) | function inherit(parent, extra) {
function noop (line 514) | function noop() {}
function identity (line 536) | function identity($) {return $;}
function valueFn (line 540) | function valueFn(value) {return function() {return value;};}
function hasCustomToString (line 542) | function hasCustomToString(obj) {
function isUndefined (line 559) | function isUndefined(value) {return typeof value === 'undefined';}
function isDefined (line 574) | function isDefined(value) {return typeof value !== 'undefined';}
function isObject (line 590) | function isObject(value) {
function isBlankObject (line 601) | function isBlankObject(value) {
function isString (line 618) | function isString(value) {return typeof value === 'string';}
function isNumber (line 639) | function isNumber(value) {return typeof value === 'number';}
function isDate (line 654) | function isDate(value) {
function isFunction (line 685) | function isFunction(value) {return typeof value === 'function';}
function isRegExp (line 695) | function isRegExp(value) {
function isWindow (line 707) | function isWindow(obj) {
function isScope (line 712) | function isScope(obj) {
function isFile (line 717) | function isFile(obj) {
function isFormData (line 722) | function isFormData(obj) {
function isBlob (line 727) | function isBlob(obj) {
function isBoolean (line 732) | function isBoolean(value) {
function isPromiseLike (line 737) | function isPromiseLike(obj) {
function isTypedArray (line 743) | function isTypedArray(value) {
function isElement (line 773) | function isElement(node) {
function makeMap (line 783) | function makeMap(str) {
function nodeName_ (line 792) | function nodeName_(element) {
function includes (line 796) | function includes(array, obj) {
function arrayRemove (line 800) | function arrayRemove(array, value) {
function copy (line 866) | function copy(source, destination, stackSource, stackDest) {
function shallowCopy (line 967) | function shallowCopy(src, dst) {
function equals (line 1017) | function equals(o1, o2) {
function noUnsafeEval (line 1083) | function noUnsafeEval() {
function concat (line 1148) | function concat(array1, array2, index) {
function sliceArgs (line 1152) | function sliceArgs(args, startIndex) {
function bind (line 1176) | function bind(self, fn) {
function toJsonReplacer (line 1197) | function toJsonReplacer(key, value) {
function toJson (line 1229) | function toJson(obj, pretty) {
function fromJson (line 1250) | function fromJson(json) {
function timezoneToOffset (line 1257) | function timezoneToOffset(timezone, fallback) {
function addDateMinutes (line 1263) | function addDateMinutes(date, minutes) {
function convertTimezoneToLocal (line 1270) | function convertTimezoneToLocal(date, timezone, reverse) {
function startingTag (line 1280) | function startingTag(element) {
function tryDecodeURIComponent (line 1310) | function tryDecodeURIComponent(value) {
function parseKeyValue (line 1323) | function parseKeyValue(/**string*/keyValue) {
function toKeyValue (line 1350) | function toKeyValue(obj) {
function encodeUriSegment (line 1378) | function encodeUriSegment(val) {
function encodeUriQuery (line 1397) | function encodeUriQuery(val, pctEncodeSpaces) {
function getNgAttribute (line 1409) | function getNgAttribute(element, ngAttr) {
function angularInit (line 1547) | function angularInit(element, bootstrap) {
function bootstrap (line 1626) | function bootstrap(element, modules, config) {
function reloadWithDebugInfo (line 1704) | function reloadWithDebugInfo() {
function getTestability (line 1717) | function getTestability(rootElement) {
function snake_case (line 1727) | function snake_case(name, separator) {
function bindJQuery (line 1736) | function bindJQuery() {
function assertArg (line 1794) | function assertArg(arg, name, reason) {
function assertArgFn (line 1801) | function assertArgFn(arg, name, acceptArrayAnnotation) {
function assertNotHasOwnProperty (line 1816) | function assertNotHasOwnProperty(name, context) {
function getter (line 1830) | function getter(obj, path, bindFnToScope) {
function getBlockNodes (line 1854) | function getBlockNodes(nodes) {
function createMap (line 1884) | function createMap() {
function setupModuleLoader (line 1904) | function setupModuleLoader(window) {
function serializeObject (line 2246) | function serializeObject(obj) {
function toDebugString (line 2261) | function toDebugString(obj) {
function publishExternalAPI (line 2390) | function publishExternalAPI(angular) {
function jqNextId (line 2657) | function jqNextId() { return ++jqId; }
function camelCase (line 2670) | function camelCase(name) {
function jqLiteIsTextNode (line 2698) | function jqLiteIsTextNode(html) {
function jqLiteAcceptsData (line 2702) | function jqLiteAcceptsData(node) {
function jqLiteHasData (line 2709) | function jqLiteHasData(node) {
function jqLiteBuildFragment (line 2716) | function jqLiteBuildFragment(html, context) {
function jqLiteParseHTML (line 2753) | function jqLiteParseHTML(html, context) {
function JQLite (line 2769) | function JQLite(element) {
function jqLiteClone (line 2794) | function jqLiteClone(element) {
function jqLiteDealoc (line 2798) | function jqLiteDealoc(element, onlyDescendants) {
function jqLiteOff (line 2809) | function jqLiteOff(element, type, fn, unsupported) {
function jqLiteRemoveData (line 2841) | function jqLiteRemoveData(element, name) {
function jqLiteExpandoStore (line 2863) | function jqLiteExpandoStore(element, createIfNecessary) {
function jqLiteData (line 2876) | function jqLiteData(element, key, value) {
function jqLiteHasClass (line 2902) | function jqLiteHasClass(element, selector) {
function jqLiteRemoveClass (line 2908) | function jqLiteRemoveClass(element, cssClasses) {
function jqLiteAddClass (line 2920) | function jqLiteAddClass(element, cssClasses) {
function jqLiteAddNodes (line 2937) | function jqLiteAddNodes(root, elements) {
function jqLiteController (line 2963) | function jqLiteController(element, name) {
function jqLiteInheritedData (line 2967) | function jqLiteInheritedData(element, name, value) {
function jqLiteEmpty (line 2987) | function jqLiteEmpty(element) {
function jqLiteRemove (line 2994) | function jqLiteRemove(element, keepData) {
function jqLiteDocumentLoaded (line 3001) | function jqLiteDocumentLoaded(action, win) {
function trigger (line 3021) | function trigger() {
function getBooleanAttrName (line 3075) | function getBooleanAttrName(element, name) {
function getAliasedAttrName (line 3083) | function getAliasedAttrName(name) {
function getText (line 3175) | function getText(element, value) {
function createEventHandler (line 3260) | function createEventHandler(element, events) {
function $$jqLiteProvider (line 3556) | function $$jqLiteProvider() {
function hashKey (line 3587) | function hashKey(obj, nextUidFn) {
function HashMap (line 3610) | function HashMap(array, isolatedUid) {
function anonFn (line 3722) | function anonFn(fn) {
function annotate (line 3733) | function annotate(fn, strictDi, name) {
function createInjector (line 4268) | function createInjector(modulesToLoad, strictDi) {
function $AnchorScrollProvider (line 4513) | function $AnchorScrollProvider() {
function mergeClasses (line 4780) | function mergeClasses(a,b) {
function extractElementNode (line 4789) | function extractElementNode(element) {
function splitClasses (line 4798) | function splitClasses(classes) {
function prepareAnimateOptions (line 4823) | function prepareAnimateOptions(options) {
function AnimateRunner (line 4831) | function AnimateRunner() {}
function updateData (line 4882) | function updateData(data, classes, value) {
function handleCSSClassChanges (line 4897) | function handleCSSClassChanges() {
function addRemoveClassesPostDigest (line 4926) | function addRemoveClassesPostDigest(element, add, remove) {
function domInsert (line 5040) | function domInsert(element, parentElement, afterElement) {
function run (line 5431) | function run() {
function close (line 5442) | function close() {
function Browser (line 5483) | function Browser(window, document, $log, $sniffer) {
function $BrowserProvider (line 5813) | function $BrowserProvider() {
function $CacheFactoryProvider (line 5901) | function $CacheFactoryProvider() {
function $TemplateCacheProvider (line 6214) | function $TemplateCacheProvider() {
function $CompileProvider (line 6963) | function $CompileProvider($provide, $$sanitizeUriProvider) {
function directiveNormalize (line 8904) | function directiveNormalize(name) {
function nodesetLinkingFn (line 8953) | function nodesetLinkingFn(
function directiveLinkingFn (line 8960) | function directiveLinkingFn(
function tokenDifference (line 8968) | function tokenDifference(str1, str2) {
function removeComments (line 8984) | function removeComments(jqNodes) {
function identifierForController (line 9005) | function identifierForController(controller, ident) {
function $ControllerProvider (line 9024) | function $ControllerProvider() {
function $DocumentProvider (line 9197) | function $DocumentProvider() {
function $ExceptionHandlerProvider (line 9243) | function $ExceptionHandlerProvider() {
function serializeValue (line 9289) | function serializeValue(v) {
function $HttpParamSerializerProvider (line 9297) | function $HttpParamSerializerProvider() {
function $HttpParamSerializerJQLikeProvider (line 9334) | function $HttpParamSerializerJQLikeProvider() {
function defaultHttpResponseTransform (line 9406) | function defaultHttpResponseTransform(data, headers) {
function isJsonLike (line 9422) | function isJsonLike(str) {
function parseHeaders (line 9433) | function parseHeaders(headers) {
function headersGetter (line 9469) | function headersGetter(headers) {
function transformData (line 9499) | function transformData(data, headers, status, fns) {
function isSuccess (line 9512) | function isSuccess(status) {
function $HttpProvider (line 9523) | function $HttpProvider() {
function $xhrFactoryProvider (line 10608) | function $xhrFactoryProvider() {
function $HttpBackendProvider (line 10633) | function $HttpBackendProvider() {
function createHttpBackend (line 10639) | function createHttpBackend($browser, createXhr, $browserDefer, callbacks...
function $InterpolateProvider (line 10833) | function $InterpolateProvider() {
function $IntervalProvider (line 11136) | function $IntervalProvider() {
function encodePath (line 11348) | function encodePath(path) {
function parseAbsoluteUrl (line 11359) | function parseAbsoluteUrl(absoluteUrl, locationObj) {
function parseAppUrl (line 11368) | function parseAppUrl(relativeUrl, locationObj) {
function beginsWith (line 11393) | function beginsWith(begin, whole) {
function stripHash (line 11400) | function stripHash(url) {
function trimEmptyHash (line 11405) | function trimEmptyHash(url) {
function stripFile (line 11410) | function stripFile(url) {
function serverBase (line 11415) | function serverBase(url) {
function LocationHtml5Url (line 11429) | function LocationHtml5Url(appBase, appBaseNoFile, basePrefix) {
function LocationHashbangUrl (line 11508) | function LocationHashbangUrl(appBase, appBaseNoFile, hashPrefix) {
function LocationHashbangInHtml5Url (line 11620) | function LocationHashbangInHtml5Url(appBase, appBaseNoFile, hashPrefix) {
function locationGetter (line 11984) | function locationGetter(property) {
function locationGetterSetter (line 11991) | function locationGetterSetter(property, preprocess) {
function $LocationProvider (line 12037) | function $LocationProvider() {
function $LogProvider (line 12371) | function $LogProvider() {
function ensureSafeMemberName (line 12527) | function ensureSafeMemberName(name, fullExpression) {
function getStringValue (line 12538) | function getStringValue(name, fullExpression) {
function ensureSafeObject (line 12557) | function ensureSafeObject(obj, fullExpression) {
function ensureSafeFunction (line 12588) | function ensureSafeFunction(obj, fullExpression) {
function ensureSafeAssignContext (line 12602) | function ensureSafeAssignContext(obj, fullExpression) {
function ifDefined (line 13119) | function ifDefined(v, d) {
function plusFn (line 13123) | function plusFn(l, r) {
function isStateless (line 13129) | function isStateless($filter, filterName) {
function findConstantAndWatchExpressions (line 13134) | function findConstantAndWatchExpressions(ast, $filter) {
function getInputs (line 13238) | function getInputs(body) {
function isAssignable (line 13246) | function isAssignable(ast) {
function assignableAST (line 13250) | function assignableAST(ast) {
function isLiteral (line 13256) | function isLiteral(ast) {
function isConstant (line 13264) | function isConstant(ast) {
function ASTCompiler (line 13268) | function ASTCompiler(astBuilder, $filter) {
function ASTInterpreter (line 13760) | function ASTInterpreter(astBuilder, $filter) {
function isPossiblyDangerousMemberName (line 14153) | function isPossiblyDangerousMemberName(name) {
function getValueOf (line 14159) | function getValueOf(value) {
function $ParseProvider (line 14214) | function $ParseProvider() {
function $QProvider (line 14642) | function $QProvider() {
function $$QProvider (line 14651) | function $$QProvider() {
function qFactory (line 14667) | function qFactory(nextTick, exceptionHandler) {
function $$RAFProvider (line 15021) | function $$RAFProvider() { //rAF
function $RootScopeProvider (line 15118) | function $RootScopeProvider() {
function $$SanitizeUriProvider (line 16403) | function $$SanitizeUriProvider() {
function adjustMatcher (line 16494) | function adjustMatcher(matcher) {
function adjustMatchers (line 16522) | function adjustMatchers(matchers) {
function $SceDelegateProvider (line 16600) | function $SceDelegateProvider() {
function $SceProvider (line 17130) | function $SceProvider() {
function $SnifferProvider (line 17542) | function $SnifferProvider() {
function $TemplateRequestProvider (line 17636) | function $TemplateRequestProvider() {
function $$TestabilityProvider (line 17689) | function $$TestabilityProvider() {
function $TimeoutProvider (line 17804) | function $TimeoutProvider() {
function urlResolve (line 17955) | function urlResolve(url) {
function urlIsSameOrigin (line 17989) | function urlIsSameOrigin(requestUrl) {
function $WindowProvider (line 18036) | function $WindowProvider() {
function $$CookieReader (line 18049) | function $$CookieReader($document) {
function $$CookieReaderProvider (line 18091) | function $$CookieReaderProvider() {
function $FilterProvider (line 18195) | function $FilterProvider($provide) {
function filterFilter (line 18385) | function filterFilter() {
function createPredicateFn (line 18422) | function createPredicateFn(expression, comparator, matchAgainstAnyProp) {
function deepCompare (line 18459) | function deepCompare(actual, expected, comparator, matchAgainstAnyProp, ...
function getTypeForFilter (line 18509) | function getTypeForFilter(val) {
function currencyFilter (line 18566) | function currencyFilter($locale) {
function numberFilter (line 18640) | function numberFilter($locale) {
function formatNumber (line 18653) | function formatNumber(number, pattern, groupSep, decimalSep, fractionSiz...
function padNumber (line 18741) | function padNumber(num, digits, trim) {
function dateGetter (line 18756) | function dateGetter(name, size, offset, trim) {
function dateStrGetter (line 18768) | function dateStrGetter(name, shortForm) {
function timeZoneGetter (line 18777) | function timeZoneGetter(date, formats, offset) {
function getFirstThursdayOfYear (line 18787) | function getFirstThursdayOfYear(year) {
function getThursdayThisWeek (line 18795) | function getThursdayThisWeek(datetime) {
function weekGetter (line 18801) | function weekGetter(size) {
function ampmGetter (line 18813) | function ampmGetter(date, formats) {
function eraGetter (line 18817) | function eraGetter(date, formats) {
function longEraGetter (line 18821) | function longEraGetter(date, formats) {
function dateFilter (line 18955) | function dateFilter($locale) {
function jsonFilter (line 19062) | function jsonFilter() {
function limitToFilter (line 19191) | function limitToFilter() {
function orderByFilter (line 19392) | function orderByFilter($parse) {
function ngDirective (line 19510) | function ngDirective(directive) {
function defaultLinkFn (line 19912) | function defaultLinkFn(scope, element, attr) {
function nullFormRenameControl (line 20014) | function nullFormRenameControl(control, name) {
function FormController (line 20062) | function FormController(element, attrs, $scope, $animate, $interpolate) {
function getSetter (line 20540) | function getSetter(expression) {
function stringBasedInputType (line 21629) | function stringBasedInputType(ctrl) {
function textInputType (line 21635) | function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
function baseInputType (line 21640) | function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) {
function weekParser (line 21730) | function weekParser(isoWeek, existingDate) {
function createDateParser (line 21762) | function createDateParser(regexp, mapping) {
function createDateInputType (line 21812) | function createDateInputType(type, regexp, parseDate, format) {
function badInputChecker (line 21884) | function badInputChecker(scope, element, attr, ctrl) {
function numberInputType (line 21899) | function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
function urlInputType (line 21953) | function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) {
function emailInputType (line 21966) | function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) {
function radioInputType (line 21979) | function radioInputType(scope, element, attr, ctrl) {
function parseConstantExpr (line 22001) | function parseConstantExpr($parse, context, name, expression, fallback) {
function checkboxInputType (line 22014) | function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browse...
function classDirective (line 22597) | function classDirective(name, selector) {
function processParseErrors (line 25142) | function processParseErrors() {
function processSyncValidators (line 25162) | function processSyncValidators() {
function processAsyncValidators (line 25178) | function processAsyncValidators() {
function setValidity (line 25204) | function setValidity(name, isValid) {
function validationDone (line 25210) | function validationDone(allValid) {
function writeToModelIfNeeded (line 25289) | function writeToModelIfNeeded() {
function addSetValidityMethod (line 25843) | function addSetValidityMethod(context) {
function isObjectEmpty (line 25937) | function isObjectEmpty(obj) {
function parseOptionsExpression (line 26212) | function parseOptionsExpression(optionsExp, selectElement, scope) {
function updateOptionElement (line 26558) | function updateOptionElement(option, element) {
function addOrReuseElement (line 26573) | function addOrReuseElement(parent, current, type, templateElement) {
function removeExcessElements (line 26594) | function removeExcessElements(current) {
function skipEmptyAndUnknownOptions (line 26604) | function skipEmptyAndUnknownOptions(current) {
function updateOptions (line 26622) | function updateOptions() {
function updateElementText (line 26948) | function updateElementText(newText) {
function chromeHack (line 28583) | function chromeHack(optionElement) {
function addOption (line 28618) | function addOption(optionValue) {
function getDecimals (line 28772) | function getDecimals(n) {
function getVF (line 28778) | function getVF(n, opt_precision) {
FILE: lib/jquery-2.0.2.js
function isArraylike (line 848) | function isArraylike( obj ) {
function Sizzle (line 1042) | function Sizzle( selector, context, results, seed ) {
function isNative (line 1155) | function isNative( fn ) {
function createCache (line 1165) | function createCache() {
function markFunction (line 1183) | function markFunction( fn ) {
function assert (line 1192) | function assert( fn ) {
function addHandle (line 1215) | function addHandle( attrs, handler, test ) {
function boolHandler (line 1234) | function boolHandler( elem, name ) {
function interpolationHandler (line 1248) | function interpolationHandler( elem, name ) {
function valueHandler (line 1258) | function valueHandler( elem ) {
function siblingCheck (line 1273) | function siblingCheck( a, b ) {
function createInputPseudo (line 1300) | function createInputPseudo( type ) {
function createButtonPseudo (line 1311) | function createButtonPseudo( type ) {
function createPositionalPseudo (line 1322) | function createPositionalPseudo( fn ) {
function tokenize (line 2330) | function tokenize( selector, parseOnly ) {
function toSelector (line 2397) | function toSelector( tokens ) {
function addCombinator (line 2407) | function addCombinator( matcher, combinator, base ) {
function elementMatcher (line 2457) | function elementMatcher( matchers ) {
function condense (line 2471) | function condense( unmatched, map, filter, context, xml ) {
function setMatcher (line 2492) | function setMatcher( preFilter, selector, matcher, postFilter, postFinde...
function matcherFromTokens (line 2585) | function matcherFromTokens( tokens ) {
function matcherFromGroupMatchers (line 2640) | function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
function multipleContexts (line 2768) | function multipleContexts( selector, contexts, results ) {
function select (line 2777) | function select( selector, context, results, seed ) {
function setFilters (line 2845) | function setFilters() {}
function createOptions (line 2876) | function createOptions( options ) {
function Data (line 3338) | function Data() {
function dataAttr (line 3646) | function dataAttr( elem, key, data ) {
function returnTrue (line 4321) | function returnTrue() {
function returnFalse (line 4325) | function returnFalse() {
function safeActiveElement (line 4329) | function safeActiveElement() {
function sibling (line 5284) | function sibling( cur, dir ) {
function winnow (line 5400) | function winnow( elements, qualifier, not ) {
function manipulationTarget (line 5909) | function manipulationTarget( elem, content ) {
function disableScript (line 5919) | function disableScript( elem ) {
function restoreScript (line 5923) | function restoreScript( elem ) {
function setGlobalEval (line 5936) | function setGlobalEval( elems, refElements ) {
function cloneCopyEvent (line 5947) | function cloneCopyEvent( src, dest ) {
function getAll (line 5982) | function getAll( context, tag ) {
function fixInput (line 5993) | function fixInput( src, dest ) {
function vendorPropName (line 6094) | function vendorPropName( style, name ) {
function isHidden (line 6116) | function isHidden( elem, el ) {
function getStyles (line 6125) | function getStyles( elem ) {
function showHide (line 6129) | function showHide( elements, show ) {
function setPositiveNumber (line 6397) | function setPositiveNumber( elem, value, subtract ) {
function augmentWidthOrHeight (line 6405) | function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) {
function getWidthOrHeight (line 6444) | function getWidthOrHeight( elem, name, extra ) {
function css_defaultDisplay (line 6488) | function css_defaultDisplay( nodeName ) {
function actualDisplay (line 6520) | function actualDisplay( name, doc ) {
function buildParams (line 6706) | function buildParams( prefix, obj, traditional, add ) {
function addToPrefiltersOrTransports (line 6822) | function addToPrefiltersOrTransports( structure ) {
function inspectPrefiltersOrTransports (line 6854) | function inspectPrefiltersOrTransports( structure, options, originalOpti...
function ajaxExtend (line 6881) | function ajaxExtend( target, src ) {
function done (line 7327) | function done( status, nativeStatusText, responses, headers ) {
function ajaxHandleResponses (line 7474) | function ajaxHandleResponses( s, jqXHR, responses ) {
function ajaxConvert (line 7530) | function ajaxConvert( s, response, jqXHR, isSuccess ) {
function createFxNow (line 7925) | function createFxNow() {
function createTween (line 7932) | function createTween( value, prop, animation ) {
function Animation (line 7946) | function Animation( elem, properties, options ) {
function propFilter (line 8050) | function propFilter( props, specialEasing ) {
function defaultPrefilter (line 8117) | function defaultPrefilter( elem, props, opts ) {
function Tween (line 8240) | function Tween( elem, options, prop, end, easing ) {
function genFx (line 8464) | function genFx( type, includeWidth ) {
function getWindow (line 8762) | function getWindow( elem ) {
Condensed preview — 25 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,667K chars).
[
{
"path": ".github/FUNDING.yml",
"chars": 67,
"preview": "# These are supported funding model platforms\n\ngithub: ashleydavis\n"
},
{
"path": "LICENSE",
"chars": 1079,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2014 Ashley Davis\n\nPermission is hereby granted, free of charge, to any person obta"
},
{
"path": "README.md",
"chars": 2739,
"preview": "AngularJS-FlowChart\n===================\n\nA WebUI control for visualizing and editing flow charts.\n\nThis isn't designed t"
},
{
"path": "app.css",
"chars": 1578,
"preview": "/*\nGeneric reset.\n*/\n\n* {\n\tpadding: 0;\n\tmargin: 0;\n}\n\n.test {\n\tborder: 5px red solid;\n\tpadding: 10;\n\tmargin: 10;\n\tfont-f"
},
{
"path": "app.js",
"chars": 4394,
"preview": "\n//\n// Define the 'app' module.\n//\nangular.module('app', ['flowChart', ])\n\n//\n// Simple service to create a prompt.\n//\n."
},
{
"path": "debug.js",
"chars": 415,
"preview": "//\n// Debug utilities.\n//\n\n(function () {\n\n\tif (typeof debug !== \"undefined\") {\n\t\tthrow new Error(\"debug object already "
},
{
"path": "flowchart/dragging_service.js",
"chars": 2146,
"preview": "\nangular.module('dragging', ['mouseCapture', ] )\n\n//\n// Service used to help with dragging and clicking on elements.\n//\n"
},
{
"path": "flowchart/flowchart_directive.js",
"chars": 10649,
"preview": "//\n// Flowchart module.\n//\nangular.module('flowChart', ['dragging'] )\n\n//\n// Directive that generates the rendered chart"
},
{
"path": "flowchart/flowchart_directive.spec.js",
"chars": 15034,
"preview": "\ndescribe('flowchart-directive', function () {\n\n\tvar testObject;\n\tvar mockScope;\n\tvar mockDragging;\n\tvar mockSvgElement;"
},
{
"path": "flowchart/flowchart_template.html",
"chars": 5450,
"preview": "<svg \n class=\"draggable-container\"\n xmlns=\"http://www.w3.org/2000/svg\"\n ng-mousedown=\"mouseDown($event)\"\n ng-mousemo"
},
{
"path": "flowchart/flowchart_viewmodel.js",
"chars": 18793,
"preview": "\n//\n// Global accessor.\n//\nvar flowchart = {\n\n};\n\n// Module.\n(function () {\n\n\t//\n\t// Width of a node.\n\t//\n\tflowchart.def"
},
{
"path": "flowchart/flowchart_viewmodel.spec.js",
"chars": 33826,
"preview": "describe('flowchart-viewmodel', function () {\n\n\t//\n\t// Create a mock data model from a simple definition.\n\t//\n\tvar creat"
},
{
"path": "flowchart/mouse_capture_service.js",
"chars": 2540,
"preview": "\nangular.module('mouseCapture', [])\n\n//\n// Service used to acquire 'mouse capture' then receive dragging events while th"
},
{
"path": "flowchart/svg_class.js",
"chars": 1367,
"preview": "//\n// http://www.justinmccandless.com/blog/Patching+jQuery's+Lack+of+SVG+Support\n//\n// Functions to add and remove SVG c"
},
{
"path": "flowchart/svg_class.spec.js",
"chars": 2695,
"preview": "\ndescribe('svg_class', function () {\n\n it('removeClassSVG returns false when there is no classes attr', function () {"
},
{
"path": "index.html",
"chars": 2519,
"preview": "<html>\n\t<head>\n\t\t<title>AngularJS-FlowChart</title>\n\n\t\t<!-- \n\t\tLiveReload support.\n\t\thttp://livereload.com/\n\t\t-->\n\t\t<scr"
},
{
"path": "jasmine/SpecRunner.html",
"chars": 2219,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <title>Jasmine Spec Runner</title>\n\n <!-- \n LiveReload support.\n http://livereload.co"
},
{
"path": "jasmine/lib/jasmine-1.3.1/MIT.LICENSE",
"chars": 1061,
"preview": "Copyright (c) 2008-2011 Pivotal Labs\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of th"
},
{
"path": "jasmine/lib/jasmine-1.3.1/jasmine-html.js",
"chars": 20765,
"preview": "jasmine.HtmlReporterHelpers = {};\n\njasmine.HtmlReporterHelpers.createDom = function(type, attrs, childrenVarArgs) {\n va"
},
{
"path": "jasmine/lib/jasmine-1.3.1/jasmine.css",
"chars": 6537,
"preview": "body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; }\n\n#HTMLReporter { font-size: 11px; font-"
},
{
"path": "jasmine/lib/jasmine-1.3.1/jasmine.js",
"chars": 70892,
"preview": "var isCommonJS = typeof window == \"undefined\" && typeof exports == \"object\";\n\n/**\n * Top level namespace for Jasmine, a "
},
{
"path": "lib/angular-mocks.js",
"chars": 67304,
"preview": "/**\n * @license AngularJS v1.2.6\n * (c) 2010-2014 Google, Inc. http://angularjs.org\n * License: MIT\n */\n(function(window"
},
{
"path": "lib/angular.js",
"chars": 1064919,
"preview": "/**\n * @license AngularJS v1.4.7\n * (c) 2010-2015 Google, Inc. http://angularjs.org\n * License: MIT\n */\n(function(window"
},
{
"path": "lib/jquery-2.0.2.js",
"chars": 242915,
"preview": "/*!\n * jQuery JavaScript Library v2.0.2\n * http://jquery.com/\n *\n * Includes Sizzle.js\n * http://sizzlejs.com/\n *\n * Cop"
},
{
"path": "server.js",
"chars": 1637,
"preview": "//\n// Simple nodejs server for running the sample.\n//\n// http://stackoverflow.com/questions/6084360/node-js-as-a-simple-"
}
]
About this extraction
This page contains the full source code of the codecapers/AngularJS-FlowChart GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 25 files (1.5 MB), approximately 391.3k tokens, and a symbol index with 350 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.