var app = angular.module('yeomanContactsApp', ['ngResource']);

// Set up our mappings between URLs, templates, and controllers
app.config(function($routeProvider, $locationProvider) {
  $routeProvider.
    when('/', {
      controller: 'MainCtrl',
      templateUrl: 'main.html'
    }).
    when('/view/:id', {
      controller: 'DetailCtrl',
      templateUrl: 'detail.html'
    }).
    when('/contacts.json', {
      controller: 'ContactsCtrl',
      templateUrl: 'contacts.html',
      resolve: {
        contacts: function(ContactsLoader){
         return ContactsLoader(); 
        }
      }
    }).
    otherwise({
      redirectTo: '/'
    });
  
  $locationProvider.html5Mode(false);
});




//Contact Resource
app.factory('Contact', ['$resource', function($resource){
  return $resource('/contacts/:id', {id: '@id'});
}]);

//Contacts Loader
app.factory('ContactsLoader', ['Contact', '$q', '$http', function(Contact, $q, $http){
  return function(){
    var delay = $q.defer();
    var contacts = [];
    
    $http.get('/contacts.json').success(function(results){
      console.log(results);
      contacts = results.results;
      delay.resolve(contacts);
    });

    /*
    
    Contact.query(function(contacts){
      delay.resolve(contacts);
    }, function(){
      delay.reject('Unable to fetch contacts.');
    });
    */
    return delay.promise;
  }
}]);

//Contact Loader
app.factory('ContactLoader',['Contact', '$route', '$q', function(Contact, $route, $q){
  return function(){
    var delay = $q.defer();
    var id = $route.current.params.id;
    Contact.get({id: id}, function(contact){
      delay.resolve(contact);
    }, function(){
      delay.reject('Unable to fetch contact.' + id);
    });
    return delay.promise;
  }
}]);

//Contact Directive - Custom html for our contact.
app.directive('contact', function () {
  return {
    templateUrl: 'contact_directive.html',
    replace: true,
    restrict: 'E',
    link: function postLink(scope, element, attrs) {
      
    }
  };
});
  
//Contacts Service - Connects to Parse.com and fetches the data.
app.factory('ContactsService', ['$q', '$http', function($q, $http){
  return function(successCb, errorCb){
    Parse.initialize("mNf6N9zKOa0iqoyhXjlnNWQiJATFWkcb5ukvuOkX", "tjgFaPiYlrTPbNkI0qu62RSl4tE8VH0y0W7yF687");
    var ContactModel = Parse.Object.extend('Contact');
    var ContactsCollection = Parse.Collection.extend(ContactModel);
    var delay = $q.defer();
    var data = null;
    var query = new Parse.Query('Contact');
        query.find({
          success: function(results) {
             data = results.map(function(obj){
              return {  
                id: obj.get('id'),
                name: obj.get('name'),
                phone: obj.get('phone'),
                website: obj.get('website'),
                email: obj.get('email'),
                avatar: 'http://www.gravatar.com/avatar/' + calcMD5(obj.get('email'))
              }
            });
            if(successCb){
              successCb(data);
            } else {
              delay.resolve(data);
            }
          },
          error: function(error) {
            if(errorCb){
              errorCb(error);
            }
            delay.reject(error);
          }
        });
      return delay.promise;
  }
}]);
  
//Contact Tel filter -
app.filter('tel', function () {
  return function (input) {
    var pattern = /\b[1]?[(-|\(|\.|\s)]?([\d]{3})(-|\)|\.|\s)[\s]?([\d]{3})(-|\.|\s)([\d]{4})\b/g,
        replace = '($1)$3-$5';
    return input.replace(pattern, replace);
  };
});

//Main Controller
app.controller('MainCtrl', function($scope, $rootScope, ContactsService) {
  
  $scope.query = '';
  $scope.limit = 10;
  $scope.order = 'name';
  $scope.query = '';
  
  
  $scope.App = {
    icon: 'book',
    name: 'AngularJS Contacts',
    loading: true,
    model: null,
    nav: [
      { title: 'View Contacts', href:'/contacts' },
      { title: 'Add Contact', href:'/add' }
      
    ],
    filter:{
      limit: 10,
      order: 'name',
      reverse: false,
      query: null
    },
    init: function(){
      this.getContacts();
      return this;
    },
    getContacts: function(){
      new ContactsService(function(data){
        $scope.$apply(function(){
          $scope.App.loading = false;
          $scope.App.model = data;
        })
      });  
    }
  };
  window.App = $scope.App.init();
});

//Home Controller
app.controller('HomeCtrl', function ($scope) {
  $scope.awesomeThings = ['HTML5 Boilerplate','AngularJS','Karma'];
});


//Contacts Controller
app.controller('ContactsCtrl', function($scope, $http) {
  $scope.name = 'Contacts';
  $scope.contacts = [];
  $http.get('/contacts.json').then(function(results){
    console.log(results);
    $scope.contacts = results.results;
  });
  console.log($scope.contacts);
});

//Detail Controller
app.controller('DetailCtrl', function($scope, $rootScope, $routeParams) {
  $scope.name = 'Detail';
  $scope.contact = $rootScope.App.model[$routeParams.id];
});
describe('App:', function() {
  var $scope = null, ctrl = null

  //you need to indicate your module in a test
  beforeEach(module('yeomanContactsApp'));
  
  //Create a new scope for each 
  beforeEach(inject(function($rootScope, $controller) {
    $scope = $rootScope.$new();

    ctrl = $controller('MainCtrl', {
      $scope: $scope
    });
  }));
  
  //Add custom matchers to jasmine
  beforeEach(function(){
    this.addMatchers({
      toEqualData: function(expected){
        return angular.equals(this.actual, expected);
      }
    })
  });
  
  //Tests on the Main controller
  it('should say AngularJS Contacts', function() {
    expect($scope.App.name).toEqual('AngularJS Contacts');
  });
  

  //Home Controller Tests
  describe('Controller: MainCtrl', function () {
  var MainCtrl, scope, mockBackend;

  // Initialize the controller and a mock scope
  beforeEach(inject(function ( _$httpBackend_, $controller, $rootScope) {
    //mockBackend = _$httpBackend_;
    
    // because this call will get triggered when the controller is created
    //mockBackend.expectGET('http://server/names?filter=none').
    //respond(['Brad', 'Shyam']);
    
    scope = $rootScope.$new();
    MainCtrl = $controller('MainCtrl', {
      $scope: scope
    });
  }));
  
  it("should try to call the service, but we intercept it", inject(function(ContactsService) {
    //spyOn($scope.App, 'getContacts').andReturn([1, 2, 3]);
    //expect($scope.App.model).toBe(3);
  }));

  it('should fetch names from server on load', function() {
    // Initially, the request has not returned a response
    //expect(scope.App.model).toBeNull();
    // Tell the fake backend to return responses to all current requests that are in flight.
    //mockBackend.flush();
    // Now App.model should be set on the scope
    //expect(scope.App.model).toBe(3);
  });
  
});


  //Directive Tests
  describe('Directive: contact', function () {
  var element;
  it('should make element a contact', inject(function ($rootScope, $compile) {
    element = angular.element('<contact></contact>');
    element = $compile(element)($rootScope);
    expect(true).toBe(true);
  }));
});


  //Filter Tests
  describe('Filter: tel', function () {
  // load the filter's module
  //beforeEach(module('yeomanContactsApp'));
  // initialize a new instance of the filter before each test
  var tel;
  beforeEach(inject(function ($filter) {
    tel = $filter('tel');
  }));

  it('should return the string formated as (555)555-5555', function () {
    var text = '555-555-5555';
    expect(tel(text)).toBe('(555)555-5555');
  });
});


  //Service Tests
  describe('Service: ContactsService', function () {

  // load the service's module
  //beforeEach(module('yeomanContactsApp'));

  // instantiate service
  var ContactsService;
  beforeEach(inject(function (_ContactsService_) {
    ContactsService = _ContactsService_;
  }));

  it('should do something', function () {
    expect(!!ContactsService).toBe(true);
  });

});


  describe("ContactsController", function () {
 
      var scope, httpBackend, http, controller;
      beforeEach(inject(function ($rootScope, $controller, $httpBackend, $http) {
          scope = $rootScope.$new();
          httpBackend = $httpBackend;
          controller = $controller;
          http = $http;
          httpBackend.when("GET", "/contacts.json").respond([{id: 1}, {id: 2}, {id: 3}]);
          $controller('ContactsCtrl', {
              $scope: scope,
              $http: $http
          });
      }));

      it('should make a http GET request for contacts', function () {
          httpBackend.expectGET('/contacts.json');
          controller('ContactsCtrl', {
            $scope: scope,
            $http: http
          });
          httpBackend.flush();
      });
  });

});
<!DOCTYPE html>
<html ng-app="yeomanContactsApp">

  <head>
    <meta charset="utf-8" />
    <title>AngularJS Contacts</title>
    <link data-require="jasmine" data-semver="1.3.1" rel="stylesheet" href="//cdn.jsdelivr.net/jasmine/1.3.1/jasmine.css" />
    <link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/css/bootstrap-combined.no-icons.min.css" rel="stylesheet" />
    <link href="//netdna.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome.css" rel="stylesheet" />
    <script src="http://www.parsecdn.com/js/parse-1.2.8.min.js"></script>
    <script data-require="json2" data-semver="0.0.2012100-8" src="//cdnjs.cloudflare.com/ajax/libs/json2/20121008/json2.js"></script>
    <script data-require="jasmine" data-semver="1.3.1" src="//cdn.jsdelivr.net/jasmine/1.3.1/jasmine.js"></script>
    <script data-require="jasmine" data-semver="1.3.1" src="//cdn.jsdelivr.net/jasmine/1.3.1/jasmine-html.js"></script>
    <script data-require="angular.js" data-semver="1.1.5" src="http://code.angularjs.org/1.1.5/angular.min.js"></script>
    <script data-require="angular-mocks" data-semver="1.1.5" src="http://code.angularjs.org/1.1.5/angular-mocks.js"></script>
    <script data-require="jquery@*" data-semver="2.0.1" src="http://code.jquery.com/jquery-2.0.1.min.js"></script>
    <script data-require="bootstrap@2.3.2" data-semver="2.3.2" src="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/js/bootstrap.min.js"></script>
    <script data-require="angular-resource@1.1.5" data-semver="1.1.5" src="http://code.angularjs.org/1.1.5/angular-resource.min.js"></script>
    <link rel="stylesheet" href="style.css" />
    <script src="md5.js"></script>
    <script src="app.js"></script>
    <script src="appSpec.js"></script>
    <script src="jasmineBootstrap.js"></script>
  </head>

  <body ng-controller="MainCtrl">
    <!-- #app - This is the application view html, this is the static view layout. -->
    <div id="app">
      <div class="navbar navbar-invers navbar-stati-top">
        <div class="navbar-inner">
          <div class="container-fluid">
            <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
              <span class="icon-bar"></span>
              <span class="icon-bar"></span>
              <span class="icon-bar"></span>
            </a>
            <a class="brand" href="#">
              <i class="icon-{{App.icon}} icon-1x"></i> {{App.name}}</a>
            <div class="nav-collapse collapse">
              <ul class="nav">
                <li ng-repeat="item in App.nav">
                  <a ng-href="#{{item.href}}">{{item.title}}</a>
                </li>
              </ul>
            </div>
          </div>
        </div>
      </div>
      <div id="container" class="container-fluid">
        <article class="row-fluid">
          <section id="view" class="span12" ng-view=""></section>
        </article>
        <footer class="row-fluid">
          <div class="span12">
            <p>Copywrite © 2013 by Jonnie Spratley</p>
          </div>
        </footer>
      </div>
    </div>
    <!-- main.html - This is the contact main view html for the view.  -->
    <script id="main.html" type="text/ng-template">
      <section id="contacts-list">
        <p ng-show="App.loading">
           <i class="icon-spinner icon-spin icon-large"></i> Please wait while loading content..
         </p>
        <ul class="contacts unstyled">
          <li class="">
            <form class="form-search">
              <input type="text" ng-model="$.query" class="span12 search-query" placeholder="Filter by name"/>
            </form>
          </li>
          <li ng-repeat="item in App.model | filter:query | limitTo:limit | orderBy:order:reverse">
            <contact ng-model="item"></contact>
          </li>
        </ul>
       </section>
    </script>
    
    <!-- detail.html - This is the contact details html for the view -->
    <script id="detail.html" type="text/ng-template">
      <h2>Details</h2>
    </script>
    
    <!-- contacts.html - This is contacts view -->
    <script id="contacts.html" type="text/ng-template">
      <pre>{{contacts | json}}</pre>
    </script>
  
  
    
    <!-- contact_direct.html - This is the contact directive custom html for the view. -->
    <script id="contact_directive.html" type="text/ng-template">
      <div id="contact-{{$index}}" class="contact well well-small clearfix">
          <a class="pull-left thumb" ng-href="/view/{{$index}}">
            <img class="media-object" ng-src="{{item.avatar}}" src="http://placehold.it/100x100&text=Image" alt="Contact {{item.name}} Image"/>
          </a>
          <div class="media-body">
            <h4 class="media-heading">
              {{item.name}}
            </h4>
            <p class="meta">
              <ul class="unstyled">
                <li class="phone"><a ng-href="tel:{{item.phone}}"><i class="icon-phone"></i> {{item.phone | tel}} </a></li>
                <li class="email"><a ng-href="mailto:{{item.email}}"><i class="icon-envelope"></i> {{item.email}} </a></li>
              </ul>
            </p>
          </div>
      </div>
    </script>
    <div id="HTMLReporter" class="jasmine_reporter"></div>
  </body>

</html>
(function() {
  var jasmineEnv = jasmine.getEnv();
  jasmineEnv.updateInterval = 250;

  /**
   Create the `HTMLReporter`, which Jasmine calls to provide results of each spec and each suite. The Reporter is responsible for presenting results to the user.
   */
  var htmlReporter = new jasmine.HtmlReporter();
  jasmineEnv.addReporter(htmlReporter);

  /**
   Delegate filtering of specs to the reporter. Allows for clicking on single suites or specs in the results to only run a subset of the suite.
   */
  jasmineEnv.specFilter = function(spec) {
    return htmlReporter.specFilter(spec);
  };

  /**
   Run all of the tests when the page finishes loading - and make sure to run any previous `onload` handler

   ### Test Results

   Scroll down to see the results of all of these specs.
   */
  var currentWindowOnload = window.onload;
  window.onload = function() {
    if (currentWindowOnload) {
      currentWindowOnload();
    }

    //document.querySelector('.version').innerHTML = jasmineEnv.versionString();
    execJasmine();
  };

  function execJasmine() {
    jasmineEnv.execute();
  }
})();
body { 
  background-color: #fff; 
  padding: 0; 
  margin: 8px; 
}
footer{
  text-align:center;
  font-size: .8em;
  color:#888;
}
.jasmine_reporter { 
  background-color: #eee; 
  margin: 0; 
}
.contact:hover{
  cursor: pointer;
  border: 1px solid #888;
}
.contact a{
  color: #777;
}
.contact a:hover{
  text-decoration: none;
}
.contact .email, .contact .phone{
  font-size: .9em;
  white-space: nowrap;
  color: #777;
}
.contact .thumb{
  margin-right: 10px;
}
/*
 * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
 * Digest Algorithm, as defined in RFC 1321.
 * Copyright (C) Paul Johnston 1999 - 2000.
 * Updated by Greg Holt 2000 - 2001.
 * See http://pajhome.org.uk/site/legal.html for details.
 */

/*
 * Convert a 32-bit number to a hex string with ls-byte first
 */
var hex_chr = "0123456789abcdef";
function rhex(num)
{
  str = "";
  for(j = 0; j <= 3; j++)
    str += hex_chr.charAt((num >> (j * 8 + 4)) & 0x0F) +
           hex_chr.charAt((num >> (j * 8)) & 0x0F);
  return str;
}

/*
 * Convert a string to a sequence of 16-word blocks, stored as an array.
 * Append padding bits and the length, as described in the MD5 standard.
 */
function str2blks_MD5(str)
{
  nblk = ((str.length + 8) >> 6) + 1;
  blks = new Array(nblk * 16);
  for(i = 0; i < nblk * 16; i++) blks[i] = 0;
  for(i = 0; i < str.length; i++)
    blks[i >> 2] |= str.charCodeAt(i) << ((i % 4) * 8);
  blks[i >> 2] |= 0x80 << ((i % 4) * 8);
  blks[nblk * 16 - 2] = str.length * 8;
  return blks;
}

/*
 * Add integers, wrapping at 2^32. This uses 16-bit operations internally 
 * to work around bugs in some JS interpreters.
 */
function add(x, y)
{
  var lsw = (x & 0xFFFF) + (y & 0xFFFF);
  var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
  return (msw << 16) | (lsw & 0xFFFF);
}

/*
 * Bitwise rotate a 32-bit number to the left
 */
function rol(num, cnt)
{
  return (num << cnt) | (num >>> (32 - cnt));
}

/*
 * These functions implement the basic operation for each round of the
 * algorithm.
 */
function cmn(q, a, b, x, s, t)
{
  return add(rol(add(add(a, q), add(x, t)), s), b);
}
function ff(a, b, c, d, x, s, t)
{
  return cmn((b & c) | ((~b) & d), a, b, x, s, t);
}
function gg(a, b, c, d, x, s, t)
{
  return cmn((b & d) | (c & (~d)), a, b, x, s, t);
}
function hh(a, b, c, d, x, s, t)
{
  return cmn(b ^ c ^ d, a, b, x, s, t);
}
function ii(a, b, c, d, x, s, t)
{
  return cmn(c ^ (b | (~d)), a, b, x, s, t);
}

/*
 * Take a string and return the hex representation of its MD5.
 */
function calcMD5(str)
{
  x = str2blks_MD5(str);
  a =  1732584193;
  b = -271733879;
  c = -1732584194;
  d =  271733878;

  for(i = 0; i < x.length; i += 16)
  {
    olda = a;
    oldb = b;
    oldc = c;
    oldd = d;

    a = ff(a, b, c, d, x[i+ 0], 7 , -680876936);
    d = ff(d, a, b, c, x[i+ 1], 12, -389564586);
    c = ff(c, d, a, b, x[i+ 2], 17,  606105819);
    b = ff(b, c, d, a, x[i+ 3], 22, -1044525330);
    a = ff(a, b, c, d, x[i+ 4], 7 , -176418897);
    d = ff(d, a, b, c, x[i+ 5], 12,  1200080426);
    c = ff(c, d, a, b, x[i+ 6], 17, -1473231341);
    b = ff(b, c, d, a, x[i+ 7], 22, -45705983);
    a = ff(a, b, c, d, x[i+ 8], 7 ,  1770035416);
    d = ff(d, a, b, c, x[i+ 9], 12, -1958414417);
    c = ff(c, d, a, b, x[i+10], 17, -42063);
    b = ff(b, c, d, a, x[i+11], 22, -1990404162);
    a = ff(a, b, c, d, x[i+12], 7 ,  1804603682);
    d = ff(d, a, b, c, x[i+13], 12, -40341101);
    c = ff(c, d, a, b, x[i+14], 17, -1502002290);
    b = ff(b, c, d, a, x[i+15], 22,  1236535329);    

    a = gg(a, b, c, d, x[i+ 1], 5 , -165796510);
    d = gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
    c = gg(c, d, a, b, x[i+11], 14,  643717713);
    b = gg(b, c, d, a, x[i+ 0], 20, -373897302);
    a = gg(a, b, c, d, x[i+ 5], 5 , -701558691);
    d = gg(d, a, b, c, x[i+10], 9 ,  38016083);
    c = gg(c, d, a, b, x[i+15], 14, -660478335);
    b = gg(b, c, d, a, x[i+ 4], 20, -405537848);
    a = gg(a, b, c, d, x[i+ 9], 5 ,  568446438);
    d = gg(d, a, b, c, x[i+14], 9 , -1019803690);
    c = gg(c, d, a, b, x[i+ 3], 14, -187363961);
    b = gg(b, c, d, a, x[i+ 8], 20,  1163531501);
    a = gg(a, b, c, d, x[i+13], 5 , -1444681467);
    d = gg(d, a, b, c, x[i+ 2], 9 , -51403784);
    c = gg(c, d, a, b, x[i+ 7], 14,  1735328473);
    b = gg(b, c, d, a, x[i+12], 20, -1926607734);
    
    a = hh(a, b, c, d, x[i+ 5], 4 , -378558);
    d = hh(d, a, b, c, x[i+ 8], 11, -2022574463);
    c = hh(c, d, a, b, x[i+11], 16,  1839030562);
    b = hh(b, c, d, a, x[i+14], 23, -35309556);
    a = hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
    d = hh(d, a, b, c, x[i+ 4], 11,  1272893353);
    c = hh(c, d, a, b, x[i+ 7], 16, -155497632);
    b = hh(b, c, d, a, x[i+10], 23, -1094730640);
    a = hh(a, b, c, d, x[i+13], 4 ,  681279174);
    d = hh(d, a, b, c, x[i+ 0], 11, -358537222);
    c = hh(c, d, a, b, x[i+ 3], 16, -722521979);
    b = hh(b, c, d, a, x[i+ 6], 23,  76029189);
    a = hh(a, b, c, d, x[i+ 9], 4 , -640364487);
    d = hh(d, a, b, c, x[i+12], 11, -421815835);
    c = hh(c, d, a, b, x[i+15], 16,  530742520);
    b = hh(b, c, d, a, x[i+ 2], 23, -995338651);

    a = ii(a, b, c, d, x[i+ 0], 6 , -198630844);
    d = ii(d, a, b, c, x[i+ 7], 10,  1126891415);
    c = ii(c, d, a, b, x[i+14], 15, -1416354905);
    b = ii(b, c, d, a, x[i+ 5], 21, -57434055);
    a = ii(a, b, c, d, x[i+12], 6 ,  1700485571);
    d = ii(d, a, b, c, x[i+ 3], 10, -1894986606);
    c = ii(c, d, a, b, x[i+10], 15, -1051523);
    b = ii(b, c, d, a, x[i+ 1], 21, -2054922799);
    a = ii(a, b, c, d, x[i+ 8], 6 ,  1873313359);
    d = ii(d, a, b, c, x[i+15], 10, -30611744);
    c = ii(c, d, a, b, x[i+ 6], 15, -1560198380);
    b = ii(b, c, d, a, x[i+13], 21,  1309151649);
    a = ii(a, b, c, d, x[i+ 4], 6 , -145523070);
    d = ii(d, a, b, c, x[i+11], 10, -1120210379);
    c = ii(c, d, a, b, x[i+ 2], 15,  718787259);
    b = ii(b, c, d, a, x[i+ 9], 21, -343485551);

    a = add(a, olda);
    b = add(b, oldb);
    c = add(c, oldc);
    d = add(d, oldd);
  }
  return rhex(a) + rhex(b) + rhex(c) + rhex(d);
}
{"results":[{"id":"57","name":"Emi Winters","email":"Nunc@vel.com","phone":"483-246-0559","address":"Ap #580-3097 Auctor Ave","city":"Machynlleth","state":"NE","createdAt":"2013-06-23T02:23:46.537Z","updatedAt":"2013-06-23T02:23:46.537Z","objectId":"ukfqvqw8NM"},{"address":"7824 Spring Valley","city":"Citrus Heights","email":"jonniespratley@gmail.com","name":"Jonnie Spratley","phone":"916-241-3613","state":"CA","website":"http://jonniespratley.me","createdAt":"2013-06-25T02:28:22.220Z","updatedAt":"2013-06-30T00:22:27.533Z","objectId":"FP0NiW098X"},{"address":"735-715 Luctus Rd.","city":"Newquay","email":"Maecenas@Nullainterdum.com","id":"60","name":"Kenneth Stokes","phone":"530-652-8089","state":"NY","website":"http://somesite.com","createdAt":"2013-06-23T02:23:46.576Z","updatedAt":"2013-06-30T00:22:37.555Z","objectId":"y1w1tLSGGR"}]}