feat(refresher): Allow refrsher to work with native scrolling

This update allows `<ion-refresher>` to work with native scrolling. Native scrolling can be enabled in the state deffinition, through the `$ionicConfigProvider` like `$ionicConfig.scrolling.jsScrolling(false);` or in the controller directly. It should function exactly the same as with JS scrolling enabled.

This is a merge of the wip-scrolling branch.
This commit is contained in:
perry
2015-02-05 11:41:53 -06:00
parent e90477c10e
commit 7134114bd5
9 changed files with 565 additions and 120 deletions

View File

@@ -0,0 +1,313 @@
IonicModule
.controller('$ionicRefresher', [
'$scope',
'$attrs',
'$element',
'$ionicBind',
'$timeout',
function($scope, $attrs, $element, $ionicBind, $timeout) {
var self = this,
isDragging = false,
isOverscrolling = false,
dragOffset = 0,
lastOverscroll = 0,
ptrThreshold = 60,
activated = false,
scrollTime = 500,
startY = null,
deltaY = null,
canOverscroll = true,
scrollParent,
scrollChild;
if (!isDefined($attrs.pullingIcon)) {
$attrs.$set('pullingIcon', 'ion-android-arrow-down');
}
$scope.showSpinner = !isDefined($attrs.refreshingIcon);
$ionicBind($scope, $attrs, {
pullingIcon: '@',
pullingText: '@',
refreshingIcon: '@',
refreshingText: '@',
spinner: '@',
disablePullingRotation: '@',
$onRefresh: '&onRefresh',
$onPulling: '&onPulling'
});
function handleTouchend() {
// if this wasn't an overscroll, get out immediately
if (!canOverscroll && !isDragging) {
return;
}
// reset Y
startY = null;
// the user has overscrolled but went back to native scrolling
if (!isDragging) {
dragOffset = 0;
isOverscrolling = false;
setScrollLock(false);
return true;
}
isDragging = false;
dragOffset = 0;
// the user has scroll far enough to trigger a refresh
if (lastOverscroll > ptrThreshold) {
start();
scrollTo(ptrThreshold, scrollTime);
// the user has overscrolled but not far enough to trigger a refresh
} else {
scrollTo(0, scrollTime, deactivate);
isOverscrolling = false;
}
return true;
}
function handleTouchmove(e) {
// if multitouch or regular scroll event, get out immediately
if (!canOverscroll || e.touches.length > 1) {
return;
}
//if this is a new drag, keep track of where we start
if (startY === null) {
startY = parseInt(e.touches[0].screenY, 10);
}
// how far have we dragged so far?
deltaY = parseInt(e.touches[0].screenY, 10) - startY;
// if we've dragged up and back down in to native scroll territory
if (deltaY - dragOffset <= 0 || scrollParent.scrollTop !== 0) {
if (isOverscrolling) {
isOverscrolling = false;
setScrollLock(false);
}
if (isDragging) {
nativescroll(scrollParent,parseInt(deltaY - dragOffset, 10) * -1);
}
// if we're not at overscroll 0 yet, 0 out
if (lastOverscroll !== 0) {
overscroll(0);
}
return true;
} else if (deltaY > 0 && scrollParent.scrollTop === 0 && !isOverscrolling) {
// starting overscroll, but drag started below scrollTop 0, so we need to offset the position
dragOffset = deltaY;
}
// prevent native scroll events while overscrolling
e.preventDefault();
// if not overscrolling yet, initiate overscrolling
if (!isOverscrolling) {
isOverscrolling = true;
setScrollLock(true);
}
isDragging = true;
// overscroll according to the user's drag so far
overscroll(parseInt(deltaY - dragOffset, 10));
// update the icon accordingly
if (!activated && lastOverscroll > ptrThreshold) {
activated = true;
ionic.requestAnimationFrame(activate);
} else if (activated && lastOverscroll < ptrThreshold) {
activated = false;
ionic.requestAnimationFrame(deactivate);
}
}
function handleScroll(e) {
// canOverscrol is used to greatly simplify the drag handler during normal scrolling
canOverscroll = (e.target.scrollTop === 0) || isDragging;
}
function overscroll(val) {
scrollChild.style[ionic.CSS.TRANSFORM] = 'translateY(' + val + 'px)';
lastOverscroll = val;
}
function nativescroll(target, newScrollTop) {
// creates a scroll event that bubbles, can be cancelled, and with its view
// and detail property initialized to window and 1, respectively
target.scrollTop = newScrollTop;
var e = document.createEvent("UIEvents");
e.initUIEvent("scroll", true, true, window, 1);
target.dispatchEvent(e);
}
function setScrollLock(enabled) {
// set the scrollbar to be position:fixed in preparation to overscroll
// or remove it so the app can be natively scrolled
if (enabled) {
ionic.requestAnimationFrame(function() {
scrollChild.classList.add('overscroll');
show();
});
} else {
ionic.requestAnimationFrame(function() {
scrollChild.classList.remove('overscroll');
hide();
deactivate();
});
}
}
$scope.$on('scroll.refreshComplete', function() {
// prevent the complete from firing before the scroll has started
$timeout(function() {
ionic.requestAnimationFrame(tail);
// scroll back to home during tail animation
scrollTo(0, scrollTime, deactivate);
// return to native scrolling after tail animation has time to finish
$timeout(function() {
if (isOverscrolling) {
isOverscrolling = false;
setScrollLock(false);
}
}, scrollTime);
}, scrollTime);
});
function scrollTo(Y, duration, callback) {
// scroll animation loop w/ easing
// credit https://gist.github.com/dezinezync/5487119
var start = Date.now(),
from = lastOverscroll;
if (from === Y) {
callback();
return; /* Prevent scrolling to the Y point if already there */
}
// decelerating to zero velocity
function easeOutCubic(t) {
return (--t) * t * t + 1;
}
// scroll loop
function scroll() {
var currentTime = Date.now(),
time = Math.min(1, ((currentTime - start) / duration)),
// where .5 would be 50% of time on a linear scale easedT gives a
// fraction based on the easing method
easedT = easeOutCubic(time);
overscroll(parseInt((easedT * (Y - from)) + from, 10));
if (time < 1) {
ionic.requestAnimationFrame(scroll);
} else {
if (Y < 5 && Y > -5) {
isOverscrolling = false;
setScrollLock(false);
}
callback && callback();
}
}
// start scroll loop
ionic.requestAnimationFrame(scroll);
}
self.init = function() {
scrollParent = $element.parent().parent()[0];
scrollChild = $element.parent()[0];
if (!scrollParent.classList.contains('ionic-scroll') ||
!scrollChild.classList.contains('scroll')) {
throw new Error('Refresher must be immediate child of ion-content or ion-scroll');
}
ionic.on('touchmove', handleTouchmove, scrollChild);
ionic.on('touchend', handleTouchend, scrollChild);
ionic.on('scroll', handleScroll, scrollParent);
};
$scope.$on('$destroy', destroy);
function destroy() {
ionic.off('dragdown', handleTouchmove, scrollChild);
ionic.off('dragend', handleTouchend, scrollChild);
ionic.off('scroll', handleScroll, scrollParent);
scrollParent = null;
scrollChild = null;
}
// DOM manipulation and broadcast methods shared by JS and Native Scrolling
// getter used by JS Scrolling
self.getRefresherDomMethods = function() {
return {
activate: activate,
deactivate: deactivate,
start: start,
show: show,
hide: hide,
tail: tail
};
};
function activate() {
$element[0].classList.add('active');
$scope.$onPulling();
}
function deactivate() {
// give tail 150ms to finish
$timeout(function() {
// deactivateCallback
$element.removeClass('active refreshing refreshing-tail');
if (activated) activated = false;
}, 150);
}
function start() {
// startCallback
$element[0].classList.add('refreshing');
$scope.$onRefresh();
}
function show() {
// showCallback
$element[0].classList.remove('invisible');
}
function hide() {
// showCallback
$element[0].classList.add('invisible');
}
function tail() {
// tailCallback
$element[0].classList.add('refreshing-tail');
}
// for testing
self.__handleTouchmove = handleTouchmove;
self.__getScrollChild = function() { return scrollChild; };
self.__getScrollParent= function() { return scrollParent; };
}
]);

View File

@@ -12,7 +12,16 @@ IonicModule
'$document',
'$ionicScrollDelegate',
'$ionicHistory',
function($scope, scrollViewOptions, $timeout, $window, $location, $document, $ionicScrollDelegate, $ionicHistory) {
'$controller',
function($scope,
scrollViewOptions,
$timeout,
$window,
$location,
$document,
$ionicScrollDelegate,
$ionicHistory,
$controller) {
var self = this;
// for testing
@@ -171,38 +180,17 @@ function($scope, scrollViewOptions, $timeout, $window, $location, $document, $io
/**
* @private
*/
self._setRefresher = function(refresherScope, refresherElement) {
var refresher = self.refresher = refresherElement;
self._setRefresher = function(
refresherScope,
refresherElement,
refresherMethods
) {
self.refresher = refresherElement;
var refresherHeight = self.refresher.clientHeight || 60;
scrollView.activatePullToRefresh(refresherHeight, function() {
// activateCallback
refresher.classList.add('active');
refresherScope.$onPulling();
onPullProgress(1);
}, function() {
// deactivateCallback
refresher.classList.remove('active');
refresher.classList.remove('refreshing');
refresher.classList.remove('refreshing-tail');
}, function() {
// startCallback
refresher.classList.add('refreshing');
refresherScope.$onRefresh();
}, function() {
// showCallback
refresher.classList.remove('invisible');
}, function() {
// hideCallback
refresher.classList.add('invisible');
}, function() {
// tailCallback
refresher.classList.add('refreshing-tail');
}, onPullProgress);
function onPullProgress(progress) {
$scope.$broadcast('$ionicRefresher.pullProgress', progress);
refresherScope.$onPullProgress && refresherScope.$onPullProgress(progress);
}
scrollView.activatePullToRefresh(
refresherHeight,
refresherMethods
);
};
}]);

View File

@@ -48,10 +48,6 @@
* of the refresher.
* @param {expression=} on-pulling Called when the user starts to pull down
* on the refresher.
* @param {expression=} on-pull-progress Repeatedly called as the user is pulling down
* the refresher. The callback should have a `progress` argument which will be a number
* from `0` and `1`. For example, if the user has pulled the refresher halfway
* down, its progress would be `0.5`.
* @param {string=} pulling-icon The icon to display while the user is pulling down.
* Default: 'ion-android-arrow-down'.
* @param {string=} spinner The {@link ionic.directive:ionSpinner} icon to display
@@ -64,13 +60,14 @@
*
*/
IonicModule
.directive('ionRefresher', ['$ionicBind', '$parse', function($ionicBind, $parse) {
.directive('ionRefresher', [function() {
return {
restrict: 'E',
replace: true,
require: '^$ionicScroll',
require: ['?^$ionicScroll', 'ionRefresher'],
controller: '$ionicRefresher',
template:
'<div class="scroll-refresher" collection-repeat-ignore>' +
'<div class="scroll-refresher invisible" collection-repeat-ignore>' +
'<div class="ionic-refresher-content" ' +
'ng-class="{\'ionic-refresher-with-text\': pullingText || refreshingText}">' +
'<div class="icon-pulling" ng-class="{\'pulling-rotation-disabled\':disablePullingRotation}">' +
@@ -84,38 +81,32 @@ IonicModule
'<div class="text-refreshing" ng-bind-html="refreshingText"></div>' +
'</div>' +
'</div>',
link: function($scope, $element, $attrs, scrollCtrl) {
if (angular.isUndefined($attrs.pullingIcon)) {
$attrs.$set('pullingIcon', 'ion-android-arrow-down');
}
$scope.showSpinner = angular.isUndefined($attrs.refreshingIcon);
link: function($scope, $element, $attrs, ctrls) {
$ionicBind($scope, $attrs, {
pullingIcon: '@',
pullingText: '@',
refreshingIcon: '@',
refreshingText: '@',
spinner: '@',
disablePullingRotation: '@',
$onRefresh: '&onRefresh',
$onPulling: '&onPulling'
});
// JS Scrolling uses the scroll controller
var scrollCtrl = ctrls[0],
refresherCtrl = ctrls[1];
if (isDefined($attrs.onPullProgress)) {
var onPullProgressFn = $parse($attrs.onPullProgress);
$scope.$onPullProgress = function(progress) {
onPullProgressFn($scope, {
progress: progress
if (!!scrollCtrl) {
$element[0].classList.add('js-scrolling');
scrollCtrl._setRefresher(
$scope,
$element[0],
refresherCtrl.getRefresherDomMethods()
);
$scope.$on('scroll.refreshComplete', function() {
$scope.$evalAsync(function() {
scrollCtrl.scrollView.finishPullToRefresh();
});
};
});
} else {
// Kick off native scrolling
refresherCtrl.init();
}
scrollCtrl._setRefresher($scope, $element[0]);
$scope.$on('scroll.refreshComplete', function() {
$scope.$evalAsync(function() {
scrollCtrl.scrollView.finishPullToRefresh();
});
});
}
};
}]);

View File

@@ -538,9 +538,6 @@ ionic.views.Scroll = ionic.views.View.inherit({
/** Callback to execute to start the actual refresh. Call {@link #refreshFinish} when done */
__refreshStart: null,
/** Callback to state the progress while pulling to refresh */
__refreshPullProgress: null,
/** Zoom level */
__zoomLevel: 1,
@@ -1347,17 +1344,16 @@ ionic.views.Scroll = ionic.views.View.inherit({
* @param tailCallback {Function} Callback to execute just before the refresher returns to it's original state. This is for zooming out the refresher.
* @param pullProgressCallback Callback to state the progress while pulling to refresh
*/
activatePullToRefresh: function(height, activateCallback, deactivateCallback, startCallback, showCallback, hideCallback, tailCallback, pullProgressCallback) {
activatePullToRefresh: function(height, refresherMethods) {
var self = this;
self.__refreshHeight = height;
self.__refreshActivate = function() {ionic.requestAnimationFrame(activateCallback);};
self.__refreshDeactivate = function() {ionic.requestAnimationFrame(deactivateCallback);};
self.__refreshStart = function() {ionic.requestAnimationFrame(startCallback);};
self.__refreshShow = function() {ionic.requestAnimationFrame(showCallback);};
self.__refreshHide = function() {ionic.requestAnimationFrame(hideCallback);};
self.__refreshTail = function() {ionic.requestAnimationFrame(tailCallback);};
self.__refreshPullProgress = pullProgressCallback;
self.__refreshActivate = function() {ionic.requestAnimationFrame(refresherMethods.activate);};
self.__refreshDeactivate = function() {ionic.requestAnimationFrame(refresherMethods.deactivate);};
self.__refreshStart = function() {ionic.requestAnimationFrame(refresherMethods.start);};
self.__refreshShow = function() {ionic.requestAnimationFrame(refresherMethods.show);};
self.__refreshHide = function() {ionic.requestAnimationFrame(refresherMethods.hide);};
self.__refreshTail = function() {ionic.requestAnimationFrame(refresherMethods.tail);};
self.__refreshTailTime = 100;
self.__minSpinTime = 600;
},
@@ -1850,9 +1846,6 @@ ionic.views.Scroll = ionic.views.View.inherit({
self.__refreshDeactivate();
}
} else if (!self.__refreshActive && self.__refreshPullProgress) {
self.__refreshPullProgress(scrollTop / -self.__refreshHeight);
}
}

View File

@@ -8,7 +8,6 @@
overflow: hidden;
margin: auto;
height: 60px;
.ionic-refresher-content {
position: absolute;
bottom: 15px;
@@ -80,6 +79,13 @@
}
}
}
.overflow-scroll > .scroll{
&.overscroll{
position:fixed;
}
-webkit-overflow-scrolling:touch;
width:100%;
}
@-webkit-keyframes refresh-spin {
0% { -webkit-transform: translate3d(0,0,0) rotate(0); }

View File

@@ -10,13 +10,11 @@
<body ng-controller="MyCtrl">
<ion-header-bar class="bar-positive">
<h1 class="title">Pull To Refresh</h1>
<h1 class="title" id="test">Pull To Refresh</h1>
</ion-header-bar>
<ion-content>
<ion-refresher on-refresh="doRefresh()">
</ion-refresher>
<ion-refresher on-refresh="doRefresh()"></ion-refresher>
<ion-list>
<ion-item ng-repeat="item in items">{{item}}</ion-item>
</ion-list>
@@ -24,18 +22,20 @@
<script src="../../dist/js/ionic.bundle.js"></script>
<script>
angular.module('ionicApp', ['ionic'])
.controller('MyCtrl', function($scope, $timeout) {
$scope.items = ['Item 1', 'Item 2', 'Item 3'];
$scope.doRefresh = function() {
$timeout( function() {
//simulate async response
$scope.items.push('New Item ' + Math.floor(Math.random() * 1000) + 4);
//Stop the ion-refresher from spinning
$scope.$broadcast('scroll.refreshComplete');
}, 30);
};
});
.config(function($ionicConfigProvider) {
//$ionicConfigProvider.scrolling.jsScrolling(false);
})
.controller('MyCtrl', function($scope, $timeout) {
$scope.items = ['Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5', 'Item 6','Item 7','Item 8','Item 9','Item 10'];
$scope.doRefresh = function() {
$timeout( function() {
//simulate async response
$scope.items.push('New Item ' + Math.floor(Math.random() * 1000) + 4);
//Stop the ion-refresher from spinning
$scope.$broadcast('scroll.refreshComplete');
}, 500);
};
});
</script>
</body>
</html>

View File

@@ -0,0 +1,129 @@
describe('$ionicRefresh Controller', function() {
beforeEach(module('ionic'));
beforeEach(inject(function($ionicConfig) {
ionic.Platform.ready = function(cb) { cb(); };
ionic.requestAnimationFrame = function(cb) { cb(); };
$ionicConfig.scrolling.jsScrolling(false);
}));
function setup(options) {
options = options || {};
options.el = options.el ||
angular.element('<ion-refresher></ion-refresher><div class="list"></div>')[0];
scrollEl = options.parent ||
angular.element('<div class="ionic-scroll"><div class="scroll"></div></div>');
scrollEl.find('.scroll').append(options.el);
inject(function($controller, $rootScope, $timeout, $compile) {
scope = $rootScope.$new();
$compile(scrollEl)(scope);
scope.$apply();
scope.$onRefresh = jasmine.createSpy('onRefresh');
scope.$onPulling = jasmine.createSpy('onPulling');
refresher = scrollEl.find('.scroll-refresher')[0];
ctrl = angular.element(refresher).controller('ionRefresher');
timeout = $timeout;
});
}
function evt(y) {
return {
touches: [
{screenY:y}
],
preventDefault: function() {}
};
}
it('should error if not child of scroll view', function() {
setup({parent: angular.element('<div></div>')});
expect(ctrl).toBe(undefined);
});
it('should oversroll using CSS transforms', function() {
setup();
ctrl.__handleTouchmove(evt(0));
ctrl.__handleTouchmove(evt(10));
ctrl.__handleTouchmove(evt(20));
expect(ctrl.__getScrollChild().style[ionic.CSS.TRANSFORM]).toBe('translateY(10px)');
expect(ctrl.__getScrollChild().classList.contains('overscroll')).toBe(true);
expect(refresher.classList.contains('invisible')).toBe(false);
});
it('should resume native scrolling when overscroll is done', function() {
setup();
var domMethods = ctrl.getRefresherDomMethods();
spyOn(domMethods, 'activate');
spyOn(domMethods, 'deactivate');
ctrl.__handleTouchmove(evt(0));
ctrl.__handleTouchmove(evt(10));
expect(refresher.classList.contains('invisible')).toBe(false);
ctrl.__handleTouchmove(evt(0));
expect(ctrl.__getScrollChild().style[ionic.CSS.TRANSFORM]).toBe('translateY(0px)');
expect(ctrl.__getScrollChild().classList.contains('overscroll')).toBe(false);
expect(refresher.classList.contains('invisible')).toBe(true);
});
it('should activate and deactivate when dragging past activation threshold', function() {
setup();
var domMethods = ctrl.getRefresherDomMethods();
spyOn(domMethods, 'activate');
spyOn(domMethods, 'deactivate');
ctrl.__handleTouchmove(evt(0));
ctrl.__handleTouchmove(evt(10));
ctrl.__handleTouchmove(evt(100));
expect(ctrl.__getScrollChild().style[ionic.CSS.TRANSFORM]).toBe('translateY(90px)');
expect(ctrl.__getScrollChild().classList.contains('overscroll')).toBe(true);
expect(refresher.classList.contains('invisible')).toBe(false);
expect(refresher.classList.contains('active')).toBe(true);
ctrl.__handleTouchmove(evt(0));
timeout.flush();
expect(ctrl.__getScrollChild().style[ionic.CSS.TRANSFORM]).toBe('translateY(0px)');
expect(ctrl.__getScrollChild().classList.contains('overscroll')).toBe(false);
expect(refresher.classList.contains('invisible')).toBe(true);
expect(refresher.classList.contains('active')).toBe(false);
});
it('should update refresher class when shared methods fire', function() {
setup();
scope.$onRefresh = jasmine.createSpy('onRefresh');
scope.$onPulling = jasmine.createSpy('onPulling');
expect(refresher.classList.contains('active')).toBe(false);
expect(refresher.classList.contains('refreshing')).toBe(false);
expect(refresher.classList.contains('invisible')).toBe(true);
expect(scope.$onRefresh).not.toHaveBeenCalled();
expect(scope.$onPulling).not.toHaveBeenCalled();
ctrl.getRefresherDomMethods().show();
expect(refresher.classList.contains('invisible')).toBe(false);
ctrl.getRefresherDomMethods().activate();
expect(refresher.classList.contains('active')).toBe(true);
expect(refresher.classList.contains('refreshing')).toBe(false);
expect(scope.$onPulling).toHaveBeenCalled();
ctrl.getRefresherDomMethods().start();
expect(refresher.classList.contains('refreshing')).toBe(true);
expect(scope.$onRefresh).toHaveBeenCalled();
ctrl.getRefresherDomMethods().tail();
expect(refresher.classList.contains('refreshing-tail')).toBe(true);
ctrl.getRefresherDomMethods().deactivate();
timeout.flush();
expect(refresher.classList.contains('active')).toBe(false);
expect(refresher.classList.contains('refreshing')).toBe(false);
expect(refresher.classList.contains('refreshing-tail')).toBe(false);
ctrl.getRefresherDomMethods().hide();
expect(refresher.classList.contains('invisible')).toBe(true);
});
});

View File

@@ -242,19 +242,41 @@ describe('$ionicScroll Controller', function() {
expect(ctrl.scrollView.activatePullToRefresh).not.toHaveBeenCalled();
});
it('should activatePullToRefresh and work when setRefresher', function() {
var startCb, refreshingCb, doneCb, refresherEl;
it('should activatePullToRefresh and work when setRefresher', inject(function($compile) {
var refresherEl,
activateCB,
deactivateCB,
startCB,
showCB,
hideCB,
tailCB,
onPullProgressCB;
setup({
el: angular.element('<div><div class="scroll-refresher"></div></div>')[0]
el: angular.element('<div><ion-refresher class="refresher"></ion-refresher></div>')[0]
});
spyOn(ctrl.scrollView, 'activatePullToRefresh').andCallFake(function(height, start, refreshing, done, show, hide) {
startCb = start;
refreshingCb = refreshing;
doneCb = done;
showCb = show;
hideCb = hide;
$compile(ctrl.element)(scope);
scope.$apply();
spyOn(ctrl.scrollView, 'activatePullToRefresh').andCallFake(function(height, start, done, refreshing, show, hide, tail, onPull) {
activateCB = start;
deactivateCB = done;
startCB = refreshing;
showCB = show;
hideCB = hide;
tailCB = tail;
onPullProgressCB = onPull;
});
ctrl._setRefresher(scope, ctrl.element);
var refresher = ctrl.refresher;
var refreshCtrl = angular.element(refresher).controller('ionRefresher');
var dm = refreshCtrl.getRefresherDomMethods()
ctrl._setRefresher(
scope,
ctrl.element,
dm
);
var scrollOnRefreshSpy = jasmine.createSpy('scroll.onRefresh');
@@ -262,31 +284,29 @@ describe('$ionicScroll Controller', function() {
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();
dm.activate();
expect(refresher.classList.contains('active')).toBe(true);
expect(refresher.classList.contains('refreshing')).toBe(false);
expect(scope.$onPulling).toHaveBeenCalled();
refreshingCb();
expect(refresher.classList.contains('refreshing')).toBe(false);
expect(scope.$onRefresh).not.toHaveBeenCalled();
doneCb();
dm.start();
expect(refresher.classList.contains('refreshing')).toBe(true);
expect(scope.$onRefresh).toHaveBeenCalled();
dm.deactivate();
timeout.flush();
expect(refresher.classList.contains('active')).toBe(false);
showCb();
dm.show();
expect(refresher.classList.contains('invisible')).toBe(false);
hideCb();
dm.hide();
expect(refresher.classList.contains('invisible')).toBe(true);
});
}));
});

View File

@@ -1,6 +1,9 @@
[true, false].forEach(function(jsScrollingEnabled) {
describe('ionRefresher directive', function() {
beforeEach(module('ionic'));
beforeEach(inject(function($ionicConfig) {
$ionicConfig.scrolling.jsScrolling(jsScrollingEnabled);
}));
function setup(attrs, scopeProps) {
var el;
inject(function($compile, $rootScope) {
@@ -19,12 +22,13 @@ describe('ionRefresher directive', function() {
$compile(el)(scope);
ionic.requestAnimationFrame = function() {};
el.refresherCtrl = el.data('$ionRefresherController');
$rootScope.$apply();
});
return el;
}
it('should error without ionicScroll', inject(function($compile, $rootScope) {
it('should error without ionScroll or ionContent', inject(function($compile, $rootScope) {
expect(function() {
$compile('<ion-refresher>')($rootScope);
}).toThrow();
@@ -54,7 +58,7 @@ describe('ionRefresher directive', function() {
var el = setup();
expect(el.controller('$ionicScroll')._setRefresher.callCount).toBe(1);
expect(el.controller('$ionicScroll')._setRefresher).toHaveBeenCalledWith(
el.scope(), el[0]
el.scope(), el[0], el.refresherCtrl.getRefresherDomMethods()
);
});
@@ -108,3 +112,4 @@ describe('ionRefresher directive', function() {
expect(el[0].querySelector('.pulling-rotation-disabled').innerHTML).toBeTruthy();
});
});
});