describe('$ionicScroll Controller', function() { beforeEach(module('ionic')); var scope, ctrl, timeout; function setup(options) { options = options || {}; options.el = options.el || //scrollView requires an outer container element and a child //content element angular.element('
')[0]; angular.element('
').append(options.el); inject(function($controller, $rootScope, $timeout) { scope = $rootScope.$new(); ctrl = $controller('$ionicScroll', { $scope: scope, scrollViewOptions: options }); spyOn(ctrl.scrollView, 'run'); // don't actually call run, this is a dumb test timeout = $timeout; }); } it('should set $scope.$ionicScrollController by default', function() { setup() expect(scope.$ionicScrollController).toBe(ctrl); }); it('should set options.controllerBind to ctrl', function() { setup({ controllerBind: 'something' }); expect(scope.something).toBe(ctrl); }); it('should set bounce to !isAndroid after platformReady, if not options.boucing', function() { var isAndroid; spyOn(ionic.Platform, 'isAndroid').andCallFake(function() { return isAndroid; }); setup({ bouncing: true }); expect(ionic.Platform.isAndroid).not.toHaveBeenCalled(); isAndroid = true; setup(); expect(ctrl.scrollView.options.bouncing).toBe(false); isAndroid = false; setup(); expect(ctrl.scrollView.options.bouncing).toBe(true); }); it('should set this.element and this.$element', function() { setup(); expect(ctrl.element.tagName).toMatch(/div/i); expect(ctrl.$element[0]).toBe(ctrl.element); }); it('should register scrollView as this.scrollView', function() { setup(); expect(ctrl.scrollView instanceof ionic.views.Scroll).toBe(true); }); it('should register controller on element.data', function() { setup(); expect(ctrl.$element.parent().data('$$ionicScrollController')).toBe(ctrl); }); it('should run after a timeout', function() { setup(); timeout.flush(); expect(ctrl.scrollView.run).toHaveBeenCalled(); }); it('should resize the scrollview on window resize', function() { setup(); timeout.flush(); spyOn(ctrl.scrollView, 'resize'); ionic.trigger('resize', { target: window }); expect(ctrl.scrollView.resize).toHaveBeenCalled(); }); it('should remember scroll position on $viewContentLoaded event', function() { var historyData = { viewId: '1' }; setup(); spyOn(ctrl, 'rememberScrollPosition'); spyOn(ctrl, 'scrollToRememberedPosition'); scope.$broadcast('$viewContentLoaded', historyData); timeout.flush(); expect(ctrl.rememberScrollPosition).toHaveBeenCalledWith('1'); expect(ctrl.scrollToRememberedPosition).toHaveBeenCalled(); }); it('should forget on $viewHistory.viewBack after $viewContentLoaded', inject(function($rootScope) { var historyData = { viewId: 'foo' }; setup(); spyOn($rootScope, '$on').andCallThrough();; scope.$broadcast('$viewContentLoaded', historyData); expect(scope.$on).toHaveBeenCalledWith('$viewHistory.viewBack', jasmine.any(Function)); //Should not forget unless backViewId is the same spyOn(ctrl, 'forgetScrollPosition'); $rootScope.$broadcast('$viewHistory.viewBack', 'bar'); expect(ctrl.forgetScrollPosition).not.toHaveBeenCalled(); $rootScope.$broadcast('$viewHistory.viewBack', 'foo'); expect(ctrl.forgetScrollPosition).toHaveBeenCalled(); })); it('should not remember scrollValues on $destroy without id', inject(function($$scrollValueCache) { setup(); spyOn(ctrl.scrollView, 'getValues'); scope.$destroy(); expect(ctrl.scrollView.getValues).not.toHaveBeenCalled(); expect($$scrollValueCache).toEqual({}); })); it('should remember scrollValues on $destroy with id', inject(function($$scrollValueCache) { setup(); ctrl.rememberScrollPosition('super'); spyOn(ctrl.scrollView, 'getValues').andCallFake(function() { return 'scrollValues'; }); scope.$destroy(); expect(ctrl.scrollView.getValues).toHaveBeenCalled(); expect($$scrollValueCache).toEqual({ 'super': 'scrollValues' }); })); it('rememberScrollPosition should throw without id', function() { setup(); expect(function() { ctrl.rememberScrollPosition(); }).toThrow(); }); it('should unbind window event listener on scope destroy', function() { spyOn(window, 'removeEventListener'); spyOn(window, 'addEventListener'); setup(); expect(window.addEventListener).toHaveBeenCalled(); expect(window.addEventListener.mostRecentCall.args[0]).toBe('resize'); scope.$destroy(); expect(window.removeEventListener).toHaveBeenCalled(); expect(window.removeEventListener.mostRecentCall.args[0]).toBe('resize'); }); it('rememberScrollPosition should set id', function() { setup(); expect(ctrl._rememberScrollId).toBeFalsy(); ctrl.rememberScrollPosition('banana'); expect(ctrl._rememberScrollId).toBe('banana'); }); it('forgetScrollPosition should remove from cache and unset id', inject(function($$scrollValueCache) { setup(); ctrl._rememberScrollId = 'elephant'; $$scrollValueCache.elephant = 'stampede'; ctrl.forgetScrollPosition(); expect(ctrl._rememberScrollId).toBeFalsy(); expect($$scrollValueCache.elephant).toBeFalsy(); })); it('should listen to scroll event and call $onScroll', function() { setup(); scope.$onScroll = jasmine.createSpy(); expect(scope.$onScroll).not.toHaveBeenCalled(); ionic.trigger('scroll', {target: ctrl.element}); expect(scope.$onScroll).toHaveBeenCalled(); expect(scope.$onScroll.mostRecentCall.args[0].scrollLeft).toBe(0); expect(scope.$onScroll.mostRecentCall.args[0].scrollTop).toBe(0); scope.$onScroll.reset(); ionic.trigger('scroll', {target: ctrl.element, scrollTop: 3, scrollLeft: 4}); expect(scope.$onScroll.mostRecentCall.args[0].scrollLeft).toBe(4); expect(scope.$onScroll.mostRecentCall.args[0].scrollTop).toBe(3); }); it('.resize() should resize after timeout', inject(function($timeout) { setup(); $timeout.flush(); spyOn(ctrl.scrollView, 'resize'); ctrl.resize(); expect(ctrl.scrollView.resize).not.toHaveBeenCalled(); $timeout.flush(); expect(ctrl.scrollView.resize).toHaveBeenCalled(); })); [false, true].forEach(function(shouldAnimate) { describe('with animate='+shouldAnimate, function() { it('scrollToRememberedPosition should work', inject(function($$scrollValueCache) { setup(); spyOn(ctrl.scrollView, 'scrollTo'); $$scrollValueCache.foo = { left: 3, top: 4 }; ctrl._rememberScrollId = 'foo'; expect(ctrl.scrollView.scrollTo).not.toHaveBeenCalled(); ctrl.scrollToRememberedPosition(shouldAnimate); expect(ctrl.scrollView.scrollTo).toHaveBeenCalledWith(3, 4, shouldAnimate); })); describe('scroll action', function() { beforeEach(function() { setup(); //Mock resize to insta-call through for easier tests ctrl.resize = function() { return { then: function(cb) { cb(); } }; }; }); it('.scrollTop', function() { spyOn(ctrl.scrollView, 'scrollTo'); ctrl.scrollTop(shouldAnimate); expect(ctrl.scrollView.scrollTo).toHaveBeenCalledWith(0, 0, shouldAnimate); }); it('.scrollBottom', function() { spyOn(ctrl.scrollView, 'scrollTo'); spyOn(ctrl.scrollView, 'getScrollMax').andCallFake(function() { return { left: 33, top: 44 }; }); ctrl.scrollBottom(shouldAnimate); expect(ctrl.scrollView.scrollTo).toHaveBeenCalledWith(33, 44, shouldAnimate); }); it('.scrollTo', function() { spyOn(ctrl.scrollView, 'scrollTo'); ctrl.scrollTo(1, 2, shouldAnimate); expect(ctrl.scrollView.scrollTo).toHaveBeenCalledWith(1, 2, shouldAnimate); }); it('.anchorScroll without hash should scrollTop', inject(function($location, $document) { $document[0].getElementById = jasmine.createSpy(); $location.hash = function() { return null; }; spyOn(ctrl.scrollView, 'scrollTo'); ctrl.anchorScroll(shouldAnimate); expect($document[0].getElementById).not.toHaveBeenCalled(); expect(ctrl.scrollView.scrollTo).toHaveBeenCalledWith(0, 0, shouldAnimate); })); it('.anchorScroll with hash but no element should scrollTop', inject(function($location, $document) { $document[0].getElementById = jasmine.createSpy(); $location.hash = function() { return 'foo'; }; spyOn(ctrl.scrollView, 'scrollTo'); ctrl.anchorScroll(shouldAnimate); expect($document[0].getElementById).toHaveBeenCalledWith('foo'); expect(ctrl.scrollView.scrollTo).toHaveBeenCalledWith(0, 0, shouldAnimate); })); it('.anchorScroll with el matching hash should scroll to it', inject(function($location, $document) { $document[0].getElementById = jasmine.createSpy('byId').andCallFake(function() { return { offsetLeft: 8, offsetTop: 9 }; }); spyOn($location, 'hash').andCallFake(function() { return 'foo'; }); spyOn(ctrl.scrollView, 'scrollTo'); ctrl.anchorScroll(shouldAnimate); expect(ctrl.scrollView.scrollTo).toHaveBeenCalledWith(8, 9, shouldAnimate); })); }); }); }); it('should not activatePullToRefresh if setRefresher is not called', function() { setup(); timeout.flush(); expect(ctrl.refresher).toBeFalsy(); spyOn(ctrl.scrollView, 'activatePullToRefresh'); expect(ctrl.scrollView.activatePullToRefresh).not.toHaveBeenCalled(); }); it('should activatePullToRefresh and work when setRefresher', function() { var startCb, refreshingCb, doneCb, refresherEl; setup({ el: angular.element('
')[0] }); spyOn(ctrl.scrollView, 'activatePullToRefresh').andCallFake(function(height, start, refreshing, done) { startCb = start; refreshingCb = refreshing; doneCb = done; }); ctrl._setRefresher(scope, ctrl.element); var scrollOnRefreshSpy = jasmine.createSpy('scroll.onRefresh'); scope.$onRefresh = jasmine.createSpy('onRefresh'); scope.$onPulling = jasmine.createSpy('onPulling'); timeout.flush(); var refresher = ctrl.refresher; expect(refresher.classList.contains('active')).toBe(false); expect(refresher.classList.contains('refreshing')).toBe(false); startCb(); expect(refresher.classList.contains('active')).toBe(true); expect(refresher.classList.contains('refreshing')).toBe(false); expect(scope.$onPulling).toHaveBeenCalled(); refreshingCb(); expect(refresher.classList.contains('active')).toBe(false); expect(refresher.classList.contains('refreshing')).toBe(false); expect(scope.$onRefresh).not.toHaveBeenCalled(); doneCb(); expect(refresher.classList.contains('active')).toBe(false); expect(refresher.classList.contains('refreshing')).toBe(true); expect(scope.$onRefresh).toHaveBeenCalled(); }); });