Added instructions and tests

Change-Id: I18e491c4a0c188866dcad1f9db52c4f781054e62
diff --git a/views/ngXosViews/UITutorial/spec/errorHandler.test.js b/views/ngXosViews/UITutorial/spec/errorHandler.test.js
new file mode 100644
index 0000000..3f46330
--- /dev/null
+++ b/views/ngXosViews/UITutorial/spec/errorHandler.test.js
@@ -0,0 +1,22 @@
+'use strict';
+
+describe('The ErrorHandler service', () => {
+  
+  var ErrorHandler, done;
+
+  beforeEach(module('xos.UITutorial'));
+  beforeEach(module('templates'));
+
+  beforeEach(inject(function (_ErrorHandler_) {
+    // The injector unwraps the underscores (_) from around the parameter names when matching
+    ErrorHandler = _ErrorHandler_;
+    done = jasmine.createSpy('done');
+  }));
+
+  describe('the print method', () => {
+    it('should return an html template', () => {
+      ErrorHandler.print('myError', done);
+      expect(done).toHaveBeenCalledWith(`<span class="error">[ERROR] myError</span>`)
+    });
+  });
+});
\ No newline at end of file
diff --git a/views/ngXosViews/UITutorial/spec/exploreCmd.test.js b/views/ngXosViews/UITutorial/spec/exploreCmd.test.js
new file mode 100644
index 0000000..a5d7cfe
--- /dev/null
+++ b/views/ngXosViews/UITutorial/spec/exploreCmd.test.js
@@ -0,0 +1,197 @@
+'use strict';
+
+describe('The ExploreCmd service', () => {
+  
+  var ExploreCmd, ErrorHandler, ResponseHandler, done, shell, ResourceMock, rootScope;
+
+  beforeEach(module('xos.UITutorial'));
+  beforeEach(module('templates'));
+
+  beforeEach(() => {
+    module(function ($provide) {
+      $provide.value('Resource', ResourceMock);
+    })
+  });
+
+  beforeEach(inject(function (_ExploreCmd_, _ErrorHandler_, _ResponseHandler_, $rootScope) {
+    // The injector unwraps the underscores (_) from around the parameter names when matching
+    ExploreCmd = _ExploreCmd_;
+    ErrorHandler = _ErrorHandler_;
+    ResponseHandler = _ResponseHandler_;
+    rootScope = $rootScope;
+    done = jasmine.createSpy('done');
+    shell = {
+      setCommandHandler: jasmine.createSpy('setCommandHandler'),
+      bestMatch: jasmine.createSpy('bestMatch')
+    };
+    // binding the mock shell to the service (for easy testing)
+    ExploreCmd.shell = shell;
+    spyOn(console, 'error');
+
+    ResourceMock = {
+      query: jasmine.createSpy('query').and.callFake(() => {
+        var deferred = $q.defer();
+        deferred.resolve('Remote call result');
+        return {$promise: deferred.promise};
+      })
+    };
+  }));
+
+  it('should set the resouce command handler', () => {
+    ExploreCmd.setup(shell);
+    expect(shell.setCommandHandler).toHaveBeenCalledWith('resource', { exec: ExploreCmd.resourceExec, completion: ExploreCmd.resourceCompletion })
+  });
+
+  describe('the resourceCompletion function', () => {
+
+    beforeEach(() => {
+      spyOn(ExploreCmd, 'getAvailableResources').and.returnValue(['Sites', 'Slices']);
+    });
+
+    it('should suggest a resource list', () => {
+      ExploreCmd.resourceCompletion('resource', '', {text: 'resource'}, done)
+      expect(shell.bestMatch).toHaveBeenCalledWith('', [ 'list', 'Sites', 'Slices' ]);
+
+      ExploreCmd.resourceCompletion('resource', 'S', {text: 'resource S'}, done)
+      expect(shell.bestMatch).toHaveBeenCalledWith('S', [ 'list', 'Sites', 'Slices' ]);
+    });
+
+    it('should suggest a method list', () => {
+      ExploreCmd.resourceCompletion('resource', '', {text: 'resource Sites '}, done)
+      expect(shell.bestMatch).toHaveBeenCalledWith('', [ 'query', 'get', 'save', '$save', 'delete' ]);
+
+      ExploreCmd.resourceCompletion('resource', 'q', {text: 'resource Sites q'}, done)
+      expect(shell.bestMatch).toHaveBeenCalledWith('q', [ 'query', 'get', 'save', '$save', 'delete' ]);
+    });
+  });
+
+  describe('the resourceExec function', () => {
+
+    beforeEach(() => {
+      spyOn(ExploreCmd, 'listAvailableResources');
+      spyOn(ExploreCmd, 'consumeResource');
+    });
+
+    it('should list available resources', () => {
+      ExploreCmd.resourceExec('explore', ['list'], done);
+      expect(ExploreCmd.listAvailableResources).toHaveBeenCalledWith(done);
+    });
+
+    it('should use a resource', () => {
+      ExploreCmd.resourceExec('explore', ['Resource', 'query'], done);
+      expect(ExploreCmd.consumeResource).toHaveBeenCalledWith('Resource', 'query', [], done);
+    });
+  });
+
+  describe('the getAvailableResources function', () => {
+
+    beforeEach(() => {
+      spyOn(angular, 'module').and.returnValue({
+        _invokeQueue: [
+          ['$provide', 'service', ['Sites', ['$resource']]],
+          ['$provide', 'service', ['Slices', ['$q', '$resource']]],
+          ['$provide', 'factory', ['_', []]],
+          ['$provide', 'service', ['helper', ['Slices']]]
+        ]
+      });
+    });
+
+    it('should return a list of resources in the angular app', () => {
+      const resources = ExploreCmd.getAvailableResources();
+      expect(resources).toEqual(['Sites', 'Slices']);
+    });
+  });
+
+  describe('the listAvailableResources function', () => {
+    beforeEach(() => {
+      spyOn(ExploreCmd, 'getAvailableResources').and.returnValue(['Sites', 'Slices']);
+    });
+
+    it('should format resource in an html template', () => {
+      ExploreCmd.listAvailableResources(done);
+      expect(ExploreCmd.getAvailableResources).toHaveBeenCalled();
+      expect(done).toHaveBeenCalledWith(`Sites<br/>Slices<br/>`);
+    });
+  });
+
+  describe('the consumeResource function', () => {
+    beforeEach(() => {
+      spyOn(ExploreCmd, 'getAvailableResources').and.returnValue(['Resource', 'Fake']);
+      spyOn(ErrorHandler, 'print');
+    });
+
+    it('should notify that a resource does not exists', () => {
+      ExploreCmd.consumeResource('Test', null, null, done);
+      expect(ErrorHandler.print).toHaveBeenCalledWith(`Resource "Test" does not exists`, done)
+    });
+
+    it('should notify that a method does not exists', () => {
+      ExploreCmd.consumeResource('Resource', 'test', null, done);
+      expect(ErrorHandler.print).toHaveBeenCalledWith(`Method "test" not allowed`, done)
+    });
+
+    const methodsWithParams = ['get', '$save', 'delete'];
+    methodsWithParams.forEach(method => {
+      it(`should notify that the ${method} method require parameters`, () => {
+        ExploreCmd.consumeResource('Resource', method, [], done);
+        expect(ErrorHandler.print).toHaveBeenCalledWith(`Method "${method}" require parameters`, done)
+      });
+    });
+
+    it('should not accept id as parameter for the query method', () => {
+      ExploreCmd.consumeResource('Resource', 'query', ['{id:1}'], done);
+      expect(ErrorHandler.print).toHaveBeenCalledWith(`Is not possible to use "id" as filter in method "query", use "get" instead!`, done)
+    });
+
+    it('should notify the user in case of malformed parameters', () => {
+      ExploreCmd.consumeResource('Resource', 'query', ['{child: 3}'], done);
+      expect(ErrorHandler.print).toHaveBeenCalledWith(`Parameter is not valid, it shoudl be in the form of: <code>{id:1}</code>, with no spaces`, done)
+      pending();
+    });
+
+    describe('when called with the correct parameters', () => {
+
+      let deferred;
+      beforeEach(inject(($q) => {
+        spyOn(ResponseHandler, 'parse');
+
+        deferred = $q.defer();
+        ResourceMock = {
+          query: jasmine.createSpy('query').and.callFake(function(){
+            return {$promise: deferred.promise};
+          })
+        };
+      }));
+
+      it('should notify the user if an error occurred while loading the resource', () => {
+        ExploreCmd.consumeResource('Fake', 'query', [], done);
+        expect(console.error).toHaveBeenCalled();
+        expect(ErrorHandler.print).toHaveBeenCalledWith('Failed to inject resource "Fake"', done);
+      });
+
+      it('should call a resource and return the results', () => {
+        ExploreCmd.consumeResource('Resource', 'query', [], done);
+        deferred.resolve([]);
+        rootScope.$apply();
+        expect(ErrorHandler.print).not.toHaveBeenCalled()
+        expect(ResponseHandler.parse).toHaveBeenCalledWith([], 'Resource.query()', done);
+      });
+
+      it('should call a resource with parameters and return the results', () => {
+        ExploreCmd.consumeResource('Resource', 'query', ['{child:3}'], done);
+        deferred.resolve([]);
+        rootScope.$apply();
+        expect(ErrorHandler.print).not.toHaveBeenCalled()
+        expect(ResponseHandler.parse).toHaveBeenCalledWith([], 'Resource.query({"child":3})', done);
+      });
+
+      it('should call a resource and display a not found message', () => {
+        ExploreCmd.consumeResource('Resource', 'query', [], done);
+        deferred.reject({status: 404, data: {detail: 'Not Found.'}});
+        rootScope.$apply();
+        expect(ErrorHandler.print).toHaveBeenCalledWith('Resource with method "query" and parameters {} Not Found.', done)
+        expect(ResponseHandler.parse).not.toHaveBeenCalled();
+      });
+    });
+  });
+});
\ No newline at end of file
diff --git a/views/ngXosViews/UITutorial/spec/responseHandler.test.js b/views/ngXosViews/UITutorial/spec/responseHandler.test.js
new file mode 100644
index 0000000..4ce26be
--- /dev/null
+++ b/views/ngXosViews/UITutorial/spec/responseHandler.test.js
@@ -0,0 +1,46 @@
+'use strict';
+
+describe('The ResponseHandler service', () => {
+  
+  var ResponseHandler, done;
+
+  beforeEach(module('xos.UITutorial'));
+  beforeEach(module('templates'));
+
+  beforeEach(inject(function (_ResponseHandler_) {
+    // The injector unwraps the underscores (_) from around the parameter names when matching
+    ResponseHandler = _ResponseHandler_;
+    done = jasmine.createSpy('done');
+  }));
+
+  describe('the parse method', () => {
+    it('should return an html template for a collection', () => {
+      const collection = [
+        {id: 1, deleted: true, name: 'one'},
+        {id: 2, deleted: true, name: 'two'}
+      ];
+
+      const collectionHtml = `
+      <div>
+        <p>Corresponding js code: <code>jsCode</code></p>
+        <div class="json"><div class="jsonCollection">[<div class="jsonObject">{"id":1,"name":"one"},</code></div><div class="jsonObject">{"id":2,"name":"two"}</code></div>]</div></div>
+      </div>
+    `;
+      ResponseHandler.parse(collection, 'jsCode', done);
+      expect(done).toHaveBeenCalledWith(collectionHtml)
+    });
+
+    it('should return an html template for an object', () => {
+      const object = {id: 1, deleted: true, name: 'one'};
+
+      const objectHtml = `
+      <div>
+        <p>Corresponding js code: <code></code></p>
+        <div class="json"><div class="jsonObject">{"id":1,"name":"one"}</code></div></div>
+      </div>
+    `;
+      ResponseHandler.parse(object, '', done);
+      expect(done).toHaveBeenCalledWith(objectHtml)
+    });
+  });
+});
\ No newline at end of file
diff --git a/views/ngXosViews/UITutorial/spec/sample.test.js b/views/ngXosViews/UITutorial/spec/sample.test.js
deleted file mode 100644
index 9e16127..0000000
--- a/views/ngXosViews/UITutorial/spec/sample.test.js
+++ /dev/null
@@ -1,37 +0,0 @@
-'use strict';
-
-describe('The User List', () => {
-  
-  var scope, element, isolatedScope, httpBackend;
-
-  beforeEach(module('xos.UITutorial'));
-  beforeEach(module('templates'));
-
-  beforeEach(inject(function($httpBackend, $compile, $rootScope){
-    
-    httpBackend = $httpBackend;
-    // Setting up mock request
-    $httpBackend.expectGET('/api/core/users/?no_hyperlinks=1').respond([
-      {
-        email: 'matteo.scandolo@gmail.com',
-        firstname: 'Matteo',
-        lastname: 'Scandolo' 
-      }
-    ]);
-  
-    scope = $rootScope.$new();
-    element = angular.element('<users-list></users-list>');
-    $compile(element)(scope);
-    scope.$digest();
-    isolatedScope = element.isolateScope().vm;
-  }));
-
-  xit('should load 1 users', () => {
-    httpBackend.flush();
-    expect(isolatedScope.users.length).toBe(1);
-    expect(isolatedScope.users[0].email).toEqual('matteo.scandolo@gmail.com');
-    expect(isolatedScope.users[0].firstname).toEqual('Matteo');
-    expect(isolatedScope.users[0].lastname).toEqual('Scandolo');
-  });
-
-});
\ No newline at end of file
diff --git a/views/ngXosViews/UITutorial/spec/shell.test.js b/views/ngXosViews/UITutorial/spec/shell.test.js
new file mode 100644
index 0000000..19ea63e
--- /dev/null
+++ b/views/ngXosViews/UITutorial/spec/shell.test.js
@@ -0,0 +1,29 @@
+'use strict';
+
+describe('The Js Shell directive', () => {
+  
+  var scope, element, isolatedScope, shellSpy;
+
+  beforeEach(module('xos.UITutorial'));
+  beforeEach(module('templates'));
+
+  beforeEach(inject(function($compile, $rootScope){
+    scope = $rootScope.$new();
+    element = angular.element('<js-shell></js-shell>');
+    $compile(element)(scope);
+    scope.$digest();
+    isolatedScope = element.isolateScope().vm;
+    spyOn(isolatedScope.shell, 'setCommandHandler');
+    spyOn(isolatedScope.shell, 'activate');
+  }));
+
+  // NOTE see http://stackoverflow.com/questions/38906605/angular-jasmine-testing-immediatly-invoked-functions-inside-a-directive-contr
+
+  xit('should register the explore command', () => {
+    expect(isolatedScope.shell.setCommandHandler).toHaveBeenCalled();
+  });
+
+  xit('should activate the shell', () => {
+    expect(isolatedScope.shell.activate).toHaveBeenCalled();
+  });
+});
\ No newline at end of file
diff --git a/views/ngXosViews/UITutorial/spec/templateHandler.test.js b/views/ngXosViews/UITutorial/spec/templateHandler.test.js
new file mode 100644
index 0000000..0af68b9
--- /dev/null
+++ b/views/ngXosViews/UITutorial/spec/templateHandler.test.js
@@ -0,0 +1,22 @@
+'use strict';
+
+describe('The TemplateHandler service', () => {
+  
+  var TemplateHandler;
+
+  beforeEach(module('xos.UITutorial'));
+  beforeEach(module('templates'));
+
+  beforeEach(inject(function (_TemplateHandler_) {
+    TemplateHandler = _TemplateHandler_;
+  }));
+
+  const templates = ['error', 'instructions', 'resourcesResponse'];
+
+  templates.forEach(t => {
+    it(`should have a ${t} template`, () => {
+      expect(TemplateHandler[t]).toBeDefined();
+      expect(angular.isFunction(TemplateHandler[t])).toBeTruthy();
+    });
+  });
+});
\ No newline at end of file