<!DOCTYPE html>
<html>

  <head>
    <link data-require="bootstrap@3.3.1" data-semver="3.3.1" rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css" />
    <link data-require="jasmine@2.2.1" data-semver="2.2.1" rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/jasmine/2.2.1/jasmine.css" />
    <script data-require="jquery@2.1.3" data-semver="2.1.3" src="https://code.jquery.com/jquery-2.1.3.min.js"></script>
    <script data-require="jasmine@2.2.1" data-semver="2.2.1" src="http://cdnjs.cloudflare.com/ajax/libs/jasmine/2.2.1/jasmine.js"></script>
    <script data-require="jasmine@2.2.1" data-semver="2.2.1" src="http://cdnjs.cloudflare.com/ajax/libs/jasmine/2.2.1/jasmine-html.js"></script>
    <script data-require="jasmine@2.2.1" data-semver="2.2.1" src="http://cdnjs.cloudflare.com/ajax/libs/jasmine/2.2.1/boot.js"></script>
    <script data-require="angular.js@1.3.12" data-semver="1.3.12" src="https://code.angularjs.org/1.3.12/angular.js"></script>
    <script data-require="lodash.js@2.4.1" data-semver="2.4.1" src="http://cdnjs.cloudflare.com/ajax/libs/lodash.js/2.4.1/lodash.js"></script>
    <script data-require="angular-mocks@1.3.12" data-semver="1.3.12" src="https://code.angularjs.org/1.3.12/angular-mocks.js"></script>
    <link rel="stylesheet" href="style.css" />
    <script src="service.js"></script>
    <script src="script.js"></script>
    <script src="item.test.js"></script>
    <script src="itemsList.test.js"></script>
    <script src="searchBox.test.js"></script>
    <script src="itemsContainer.test.js"></script>
  </head>

  <body ng-app="app">
    <div id="HTMLReporter" class="jasmine_reporter"></div>
    <script>
    (function() {
      var jasmineEnv = jasmine.getEnv();
      jasmineEnv.updateInterval = 250;

      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>
  </body>

</html>
var app = angular.module('app', ['itemsService']);

app.controller('ItemsContainerController', ['ItemsService',
  function(ItemsService) {

    var items = ItemsService.fetchAll(),
      self = this;

    // init
    updateItems();

    function updateItems(filteredItems) {
      var collection = filteredItems || items;
      self.activeItems = _.filter(collection, function(item) {
        return item.active;
      });
      
      self.inactiveItems = _.filter(collection, function(item) {
        return !item.active;
      });
    }

    this.switchStatus = function(item) {
      item.active = !item.active;
      items = ItemsService.update(item);
      updateItems();
    };

    this.updateFilter = function(val, activeOnly) {
      if (!val) {
        updateItems();
        return;
      }

      var filteredItems = items.filter(function(item) {
        return (activeOnly && !item.active) || item.name.indexOf(val) === 0;
      });

      updateItems(filteredItems);
    };

  }
]);


app.directive('itemsContainer', function() {
  return {
    controller: 'ItemsContainerController',
    controllerAs: 'ctrl',
    bindToController: true,
    templateUrl: 'items-container.html'
  };
});


app.directive('searchBox', function() {
  return {
    scope: {
      onChange: '&',
      searchValue: '@'
    },
    controllerAs: 'ctrl',
    controller: function() {},
    bindToController: true,
    templateUrl: 'search-box.html'
  };
});

app.directive('item', function() {
  return {
    scope: {
      item: '=set',
      onClick: '&'
    },
    controller: function() {},
    controllerAs: 'ctrl',
    bindToController: true,
    restrict: 'EA',
    template: '<input type="checkbox" ng-click="ctrl.onClick({item: ctrl.item})" ng-checked="ctrl.item.active" /> {{ ctrl.item.name }}'
  }
});

app.directive('itemsList', function() {
  return {
    scope: {
      title: '@',
      items: '=',
      onClick: '&'
    },
    restrict: 'EA',
    controller: function() {},
    controllerAs: 'ctrl',
    bindToController: true,
    templateUrl: 'items-list.html'
  }
});
ul {
  margin: 0px;
  padding: 0px;
}
ul li {
  list-style: none;
}
.main {
  padding: 10%;
  width: 80%;
}
var app = angular.module('itemsService', []);

app.provider('ItemsService', function() {
    var items = [{
      id: 1,
      name: 'view',
      active: true
    }, {
      id: 2,
      name: 'model',
      active: true
    }, {
      id: 3,
      name: 'scope',
      active: false
    }, {
      id: 4,
      name: 'filter',
      active: true
    }, {
      id: 5,
      name: 'directives',
      active: false
    }];

    function update(searchItem) {
      var found = _.first(items, function(item) {
        return searchItem.id == item.id
      });

      // do nothing
      if (!found) return;
    }

    return {
      $get: function() {
        return {
          
          fetchAll: function() {
            return items;
          },

          update: function(item) {
            var found = find(item);
            if (found) {
              var index = _.indexOf(items, _.find(items, item));
              items.splice(index, 1, item);
            }
            
            return items;
          }
        }
      }
    }
  });
<div class="main">
  <search-box on-change="ctrl.updateFilter(search, active)"></search-box>
  <items-list data-title="Active Items" data-items="ctrl.activeItems" data-on-click="ctrl.switchStatus(item)"></items-list>
  <items-list data-title="Inactive Items" data-items="ctrl.inactiveItems" data-on-click="ctrl.switchStatus(item)"></items-list>  
</div>
<div class="items-list">
  <h3>{{ctrl.title}}</h3>
  <span ng-if="ctrl.items.length == 0">No items available.</span>
  <ul class="items">
    <li ng-repeat="item in ctrl.items">
      <item data-set="item" on-click="ctrl.onClick({item: item})"></item>
    </li>
  </ul>
</div>
<div class="search-box">
    <input class="form-control"
           ng-model="ctrl.searchValue"
           ng-model-options="{ debounce: 100 }"
           id="search-box"
           placeholder="search" 
           ng-change="ctrl.onChange({search: ctrl.searchValue, active: ctrl.onlyActive})"/>
           
           <item data-set="{name: 'Only active items'}" ng-click="ctrl.onlyActive = !!!ctrl.onlyActive"></item>
</div>
describe('ItemsContainer component', function() {
  var element, scope, mockedItemsService, controller, items;

  beforeEach(module('app'));

  beforeEach(function() {
    items = [{
      id: 1,
      name: 'view',
      active: true
    }, {
      id: 2,
      name: 'model',
      active: true
    }, {
      id: 3,
      name: 'scope',
      active: false
    }];

    mockedItemsService = {
      fetchAll: function() {
        return items;
      },
      update: function(item) {
        return items;
      }
    };
  });

  beforeEach(inject(function($controller, _$rootScope_, _$compile_) {
    $compile = _$compile_;
    $rootScope = _$rootScope_;

    scope = $rootScope.$new();
    controller = $controller('ItemsContainerController as ctrl', {
      $scope: scope,
      ItemsService: mockedItemsService
    });
  }));

  it('should keep track of all active and inactive items', function() {
    expect(controller.activeItems.length).toEqual(2);
  });

  it('should update the active and inactive items when calling updateFilter', function() {
    controller.updateFilter('view');
    expect(controller.activeItems.length).toEqual(1);
  });

  it('should only update the active items when calling updateFilter with the activeOnly flag', function() {
    controller.updateFilter('view', true);
    expect(controller.inactiveItems.length).toEqual(1);
  });

  it('should call ItemsService.update() when calling switchStatus', function() {
    spyOn(mockedItemsService, 'update');
    controller.switchStatus(items[0]);
    expect(mockedItemsService.update).toHaveBeenCalledWith(items[0]);
  });
});

describe('Item component', function() {
  var element, scope;

  beforeEach(module('app'));

  beforeEach(inject(function(_$rootScope_, _$compile_) {
    $compile = _$compile_;
    $rootScope = _$rootScope_;

    scope = $rootScope.$new();

    element = angular.element('<item data-set="item" on-click="ctrl.callback({item: item})"></item>');

    $compile(element)(scope);

  }));

  it('should display the controller defined title', function() {
    var expected = 'Some rendered text';
    scope.item = {
      name: expected,
      active: false
    };

    scope.$digest();

    expect(element.text()).toContain(expected);
  });

  it('should call the controller defined callback', function() {
    scope.ctrl = {
      callback: jasmine.createSpy('callback')
    };

    scope.$digest();

    element.find('input[type=checkbox]').eq(0).click();

    expect(scope.ctrl.callback).toHaveBeenCalled();
  });
});
describe('ItemList component', function() {
  var element, scope;

  beforeEach(module('app'));

  beforeEach(inject(function($templateCache) {
    $templateCache.put('items-list.html',
      '<div class="items-list">' +
      '<h3>{{ctrl.title}}</h3>' +
      '<span ng-if="ctrl.items.length == 0">No items available.</span> ' +
      '<ul class="items"> ' +
      '<li ng-repeat="item in ctrl.items"> ' +
      '<item data-set="item" on-click="ctrl.onClick({item: item})"></item> ' +
      '</li> ' +
      '</ul> ' +
      '</div>'
    );
  }));

  beforeEach(inject(function(_$rootScope_, _$compile_) {
    $compile = _$compile_;
    $rootScope = _$rootScope_;

    scope = $rootScope.$new();
    element = angular.element('<items-list data-title="Testing Items" data-items="items" data-on-click="ctrl.switchStatus(item)"></items-list>');
    $compile(element)(scope);
  }));


  it('the h3 title tag should be defined via the data-title attribute', function() {
    scope.$digest();

    var title = element.find('.items-list h3').eq(0);
    expect(title.text()).toBe('Testing Items');
  });

  it('creates item components', function() {
    scope.items = [{
      name: 'foo',
      active: false
    }, {
      name: 'bar',
      active: false
    }];

    scope.$digest();

    var items = element.find('.items-list ul li');
    expect(items.length).toEqual(2);
  });

  it('clicking on an item should call the controller defined callback', function() {
    scope.items = [{
      name: 'foo',
      active: false
    }, {
      name: 'bar',
      active: false
    }];
    scope.ctrl = {
      switchStatus: jasmine.createSpy('switchStatus')
    };

    scope.$digest();

    var item = element.find('.items-list ul li input').eq(0);
    item.click();

    expect(scope.ctrl.switchStatus).toHaveBeenCalledWith(scope.items[0]);
  });
});
describe('SearchBox component', function() {
  var element, scope;

  beforeEach(module('app'));

  beforeEach(inject(function($templateCache) {
    $templateCache.put('search-box.html',
      '<div class="search-box">' +
      '<input class="form-control" ' +
      'ng-model="ctrl.searchValue"' +
      ' id="search-box"' +
      'placeholder="search"' +
      'ng-change="ctrl.onChange({search: ctrl.searchValue, active: ctrl.onlyActive})"/>' +
      '<item data-set="{name: \'Only active items\'}" ng-click="ctrl.onlyActive = !!!ctrl.onlyActive"></item>' +
      '</div>'
    );
  }));

  beforeEach(inject(function(_$rootScope_, _$compile_) {
      $compile = _$compile_;
      $rootScope = _$rootScope_;

      scope = $rootScope.$new();
      element = angular.element('<search-box data-search-value="{{ctrl.searchValue}}" on-change="ctrl.updateFilter(search)"></search-box>');
      $compile(element)(scope);
  }));


  it('input should have the controller defined value', function() {
      var expected = 'Some rendered text';
      scope.ctrl = {
          searchValue : expected
      };

      scope.$digest();

      var input = element.find('#search-box').eq(0);
      expect(input.val()).toBe(expected);
  });
  
  it('changing the search string should trigger the controller defined callback', function() {
      scope.ctrl = {
          updateFilter : jasmine.createSpy('updateFilter')
      };

      scope.$digest();

      var input = element.find('#search-box').eq(0);
      var ngModelCtrl = input.controller('ngModel');
      ngModelCtrl.$setViewValue('view');

      expect(scope.ctrl.updateFilter).toHaveBeenCalledWith('view');
  });

});