Calculating rack details
diff --git a/views/ngXosViews/diagnostic/spec/.eslintrc b/views/ngXosViews/diagnostic/spec/.eslintrc
new file mode 100644
index 0000000..c1764a5
--- /dev/null
+++ b/views/ngXosViews/diagnostic/spec/.eslintrc
@@ -0,0 +1,15 @@
+{
+    "globals" :{
+        "describe": true,
+        "xdescribe": true,
+        "beforeEach": true,
+        "it": true,
+        "inject": true,
+        "expect": true,
+        "jasmine": true
+    },
+    "rules": {
+      "max-nested-callbacks": [0, 4],
+      "camelcase": 0
+    }
+}
diff --git a/views/ngXosViews/diagnostic/spec/logicTopologyHelper.test.js b/views/ngXosViews/diagnostic/spec/logicTopologyHelper.test.js
new file mode 100644
index 0000000..0353398
--- /dev/null
+++ b/views/ngXosViews/diagnostic/spec/logicTopologyHelper.test.js
@@ -0,0 +1,48 @@
+(function () {
+  'use strict';
+
+  describe('The Logic Topology Helper Service', () => {
+    
+    var Service;
+
+    beforeEach(module('xos.serviceTopology'));
+
+    // inject the rackHelper service
+    beforeEach(inject(function (_LogicTopologyHelper_) {
+      // The injector unwraps the underscores (_) from around the parameter names when matching
+      Service = _LogicTopologyHelper_;
+    }));
+
+    var customMatchers = {
+      toBeSimilar: () => {
+
+        const tolerance = 0.1;
+
+        return {
+          compare: (actual, expected) => {
+            return {
+              pass: (Math.abs(actual - expected) < tolerance),
+              message: `Expected ${actual} to be ${expected}`
+            }
+          }
+        }
+      }
+    };
+
+    beforeEach(function() {
+      jasmine.addMatchers(customMatchers);
+    });
+
+    it('should calculate horizontal position for each element', () => {
+      let [el0x, el1x, el2x, el3x, el4x, el5x] = Service.computeElementPosition(900);
+
+      expect(el0x).toBeSimilar(900 - 30);
+      expect(el1x).toBeSimilar(900 - 183.4);
+      expect(el2x).toBeSimilar(900 - 337.3);
+      expect(el3x).toBeSimilar(900 - 490.7);
+      expect(el4x).toBeSimilar(900 - 602.1);
+      expect(el5x).toBeSimilar(900 - 713.5);
+    });
+  });
+
+})();
diff --git a/views/ngXosViews/diagnostic/spec/rackHelper.test.js b/views/ngXosViews/diagnostic/spec/rackHelper.test.js
new file mode 100644
index 0000000..932b17f
--- /dev/null
+++ b/views/ngXosViews/diagnostic/spec/rackHelper.test.js
@@ -0,0 +1,117 @@
+(function () {
+  'use strict';
+
+  const computeNodes = [
+    {
+      humanReadableName: 'cp-1.teone.xos-pg0.clemson.cloudlab.us',
+      instances: [
+        {
+          instance_name: 'mysite_clients-3'
+        },
+        {
+          instance_name: 'mysite_clients-4'
+        },
+        {
+          instance_name: 'mysite_clients-5'
+        }
+      ]
+    },
+    {
+      humanReadableName: 'cp-2.teone.xos-pg0.clemson.cloudlab.us',
+      instances: [
+        {
+          instance_name: 'mysite_clients-1'
+        },
+        {
+          instance_name: 'mysite_clients-2'
+        }
+      ]
+    },
+    {
+      humanReadableName: 'cp-2.teone.xos-pg0.clemson.cloudlab.us',
+      instances: [
+        {
+          instance_name: 'mysite_clients-1'
+        },
+        {
+          instance_name: 'mysite_clients-2'
+        }
+      ]
+    }
+  ];
+
+  describe('The Rack Helper Service', () => {
+    
+    var Service;
+
+    beforeEach(module('xos.serviceTopology'));
+
+    // inject the rackHelper service
+    beforeEach(inject(function (_RackHelper_) {
+      // The injector unwraps the underscores (_) from around the parameter names when matching
+      Service = _RackHelper_;
+    }));
+
+    describe('Given a list of instances', () => {
+      it('should calculate the Compute Node Size', () => {
+        const [width, height] = Service.getComputeNodeSize(computeNodes[0].instances);
+        expect(width).toBe(95);
+        expect(height).toBe(67);
+      });
+    });
+
+    describe('Given a list of Compute Nodes', () => {
+      it('should return rack size', () => {
+        const [width, height] = Service.getRackSize(computeNodes);
+        expect(width).toBe(105);
+        expect(height).toBe(179);
+      });
+    });
+
+    describe('Given an instance index', () => {
+      it('should return the position for first instance', () => {
+        const [x, y] = Service.getInstancePosition(0);
+        expect(x).toBe(5);
+        expect(y).toBe(25);
+      });
+
+      it('should return the position for second instance', () => {
+        const [x, y] = Service.getInstancePosition(1);
+        expect(x).toBe(50);
+        expect(y).toBe(25);
+      });
+
+      it('should return the position for third instance', () => {
+        const [x, y] = Service.getInstancePosition(2);
+        expect(x).toBe(5);
+        expect(y).toBe(46);
+      });
+
+      it('should return the position for 4th instance', () => {
+        const [x, y] = Service.getInstancePosition(3);
+        expect(x).toBe(50);
+        expect(y).toBe(46);
+      });
+    });
+
+    describe('Given an ComputeNode index', () => {
+      it('should return the position for 1st node', () => {
+        const [x, y] = Service.getComputeNodePosition(computeNodes, 0);
+        expect(x).toBe(5);
+        expect(y).toBe(5);
+      });
+
+      it('should return the position for 2st node', () => {
+        const [x, y] = Service.getComputeNodePosition(computeNodes, 1);
+        expect(x).toBe(5);
+        expect(y).toBe(77);
+      });
+
+      it('should return the position for 2st node', () => {
+        const [x, y] = Service.getComputeNodePosition(computeNodes, 2);
+        expect(x).toBe(5);
+        expect(y).toBe(128);
+      });
+    });
+  });
+})();
diff --git a/views/ngXosViews/diagnostic/spec/sample.test.js b/views/ngXosViews/diagnostic/spec/serviceChain.test.js
similarity index 100%
rename from views/ngXosViews/diagnostic/spec/sample.test.js
rename to views/ngXosViews/diagnostic/spec/serviceChain.test.js