scroll: add $ionicScrollDelegate with new pattern

Addresses #877
This commit is contained in:
Andy Joslin
2014-03-24 15:56:23 -06:00
parent b1a7c1990a
commit c1d2571d5e
12 changed files with 437 additions and 152 deletions

View File

@@ -16,13 +16,15 @@ module.exports = {
' *\n' +
' * Licensed under the MIT license. Please see LICENSE for more information.\n'+
' *\n' +
' */\n\n',
' */\n\n' +
'(function() {\n',
bundleBanner:
'/*!\n' +
' * ionic.bundle.js is a concatenation of:\n' +
' * ionic.js, angular.js, angular-animate.js,\n'+
' * angular-ui-router.js, and ionic-angular.js\n'+
' */\n\n',
footer: '\n})();',
ionicFiles: [
// Base

View File

@@ -1,8 +1,173 @@
(function() {
'use strict';
angular.module('ionic.ui.scroll')
/**
* @ngdoc service
* @name $ionicScrollDelegate
* @module ionic
* @description
* Delegate for controlling scrollViews (created by
* {@link ionic.directive:ionContent} and
* {@link ionic.directive:ionScroll} directives).
*
* Each method on $ionicScrollDelegate can be called on the service itself to control all scrollViews. Alternatively, one can control one specific scrollView using `withHandle` and `delegate-handle`. See the example below.
*
* @usage
*
* Basic Usage:
*
* ```html
* <body ng-controller="MainCtrl">
* <ion-content>
* <button ng-click="scrollTop()">Scroll to Top!</button>
* </ion-content>
* </body>
* ```
* ```js
* function MainCtrl($scope, $ionicScrollDelegate) {
* $scope.scrollTop = function() {
* $ionicScrollDelegate.scrollTop();
* };
* }
* ```
*
* Example of advanced usage, with two scroll areas using `delegate-handle`
* for fine control.
*
* ```html
* <body ng-controller="MainCtrl">
* <ion-content delegate-handle="mainScroll">
* <button ng-click="scrollMainToTop()">
* Scroll content to top!
* </button>
* <ion-scroll delegate-handle="small" style="height: 100px;">
* <button ng-click="scrollSmallToTop()">
* Scroll small area to top!
* </button>
* </ion-scroll>
* </ion-content>
* </body>
* ```
* ```js
* function MainCtrl($scope, $ionicScrollDelegate) {
* $scope.scrollMainToTop = function() {
* $ionicScrollDelegate.withHandle('mainScroll').scrollTop();
* };
* $scope.scrollSmallToTop = function() {
* $ionicScrollDelegate.withHandle('small').scrollTop();
* };
* }
* ```
*/
/**
* @ngdoc method
* @name $ionicScrollDelegate#withHandle
* @param {string} handle
* @returns `delegateInstance` A delegate instance that controls only the
* scrollView with delegate-handle matching the given handle.
*/
.service('$ionicScrollDelegate', delegateService([
/**
* @ngdoc method
* @name $ionicScrollDelegate#resize
* @description Tell the scrollView to recalculate the size of its container.
*/
'resize',
/**
* @ngdoc method
* @name $ionicScrollDelegate#scrollTop
* @param {boolean=} shouldAnimate Whether the scroll should animate.
*/
'scrollTop',
/**
* @ngdoc method
* @name $ionicScrollDelegate#scrollBottom
* @param {boolean=} shouldAnimate Whether the scroll should animate.
*/
'scrollBottom',
/**
* @ngdoc method
* @name $ionicScrollDelegate#scroll
* @param {number} left The x-value to scroll to.
* @param {number} top The y-value to scroll to.
* @param {boolean=} shouldAnimate Whether the scroll should animate.
*/
'scrollTo',
/**
* @ngdoc method
* @name $ionicScrollDelegate#anchorScroll
* @description Tell the scrollView to scroll to the element with an id
* matching window.location.hash.
*
* If no matching element is found, it will scroll to top.
*
* @param {boolean=} shouldAnimate Whether the scroll should animate.
*/
'anchorScroll',
/**
* @ngdoc method
* @name $ionicScrollDelegate#rememberScrollPosition
* @description
* Will make it so, when this scrollView is destroyed (user leaves the page),
* the last scroll position the page was on will be saved, indexed by the
* given id.
*
* Note: for pages associated with a view under an ion-nav-view,
* rememberScrollPosition automatically saves their scroll.
*
* Related methods: scrollToRememberedPosition, forgetScrollPosition (below).
*
* In the following example, the scroll position of the ion-scroll element
* will persist, even when the user changes the toggle switch.
*
* ```html
* <ion-toggle ng-model="shouldShowScrollView"></ion-toggle>
* <ion-scroll delegate-handle="myScroll" ng-if="shouldShowScrollView">
* <div ng-controller="ScrollCtrl">
* <ion-list>
* <ion-item ng-repeat="i in items">{{i}}</ion-item>
* </ion-list>
* </div>
* </ion-scroll>
* ```
* ```js
* function ScrollCtrl($scope, $ionicScrollDelegate) {
* var delegate = $ionicScrollDelegate.withHandle('myScroll');
*
* // Put any unique ID here. The point of this is: every time the controller is recreated
* // we want to load the correct remembered scroll values.
* delegate.rememberScrollPosition('my-scroll-id');
* delegate.scrollToRememberedPosition();
* $scope.items = [];
* for (var i=0; i<100; i++) {
* $scope.items.push(i);
* }
* }
* ```
*
* @param {string} id The id to remember the scroll position of this
* scrollView by.
*/
'rememberScrollPosition',
/**
* @ngdoc method
* @name $ionicScrollDelegate#forgetScrollPosition
* @description
* Stop remembering the scroll position for this scrollView.
*/
'forgetScrollPosition',
/**
* @ngdoc method
* @name $ionicScrollDelegate#scrollToRememberedPosition
* @description
* If this scrollView has an id associated with its scroll position,
* (through calling rememberScrollPosition), and that position is remembered,
* load the position and scroll to it.
* @param {boolean=} shouldAnimate Whether to animate the scroll.
*/
'scrollToRememberedPosition'
]))
/**
* @private
*/
@@ -10,14 +175,6 @@ angular.module('ionic.ui.scroll')
return {};
})
/**
* @ngdoc controller
* @name ionicScroll
* @module ionic
* @description
* Controller for the {@link ionic.directive:ionContent} and
* {@link ionic.directive:ionScroll} directives.
*/
.controller('$ionicScroll', [
'$scope',
'scrollViewOptions',
@@ -25,13 +182,18 @@ angular.module('ionic.ui.scroll')
'$window',
'$$scrollValueCache',
'$location',
'$parse',
'$rootScope',
'$document',
function($scope, scrollViewOptions, $timeout, $window, $$scrollValueCache, $location, $parse, $rootScope, $document) {
'$ionicScrollDelegate',
'$parse', //DEPRECATED
function($scope, scrollViewOptions, $timeout, $window, $$scrollValueCache, $location, $rootScope, $document, $ionicScrollDelegate, $parse) {
var self = this;
this._scrollViewOptions = scrollViewOptions; //for testing
$parse('$ionicScrollController').assign($scope.$parent || $scope, this);
var element = this.element = scrollViewOptions.el;
var $element = this.$element = angular.element(element);
var scrollView = this.scrollView = new ionic.views.Scroll(scrollViewOptions);
@@ -42,8 +204,9 @@ function($scope, scrollViewOptions, $timeout, $window, $$scrollValueCache, $loca
($element.parent().length ? $element.parent() : $element)
.data('$$ionicScrollController', this);
$parse(scrollViewOptions.controllerBind || '$ionicScrollController')
.assign($scope.$parent || $scope, this);
var deregisterInstance = $ionicScrollDelegate._registerInstance(
this, scrollViewOptions.delegateHandle
);
if (!angular.isDefined(scrollViewOptions.bouncing)) {
ionic.Platform.ready(function() {
@@ -58,6 +221,7 @@ function($scope, scrollViewOptions, $timeout, $window, $$scrollValueCache, $loca
var backListenDone = angular.noop;
$scope.$on('$destroy', function() {
deregisterInstance();
ionic.off('resize', resize, $window);
$window.removeEventListener('resize', resize);
backListenDone();
@@ -101,31 +265,16 @@ function($scope, scrollViewOptions, $timeout, $window, $$scrollValueCache, $loca
this._rememberScrollId = null;
/**
* @ngdoc method
* @name ionicScroll#resize
* @description Tell the scrollView to recalculate the size of its container.
*/
this.resize = function() {
return $timeout(resize);
};
/**
* @ngdoc method
* @name ionicScroll#scrollTop
* @param {boolean=} shouldAnimate Whether the scroll should animate.
*/
this.scrollTop = function(shouldAnimate) {
this.resize().then(function() {
scrollView.scrollTo(0, 0, !!shouldAnimate);
});
};
/**
* @ngdoc method
* @name ionicScroll#scrollBottom
* @param {boolean=} shouldAnimate Whether the scroll should animate.
*/
this.scrollBottom = function(shouldAnimate) {
this.resize().then(function() {
var max = scrollView.getScrollMax();
@@ -133,29 +282,12 @@ function($scope, scrollViewOptions, $timeout, $window, $$scrollValueCache, $loca
});
};
/**
* @ngdoc method
* @name ionicScroll#scroll
* @param {number} left The x-value to scroll to.
* @param {number} top The y-value to scroll to.
* @param {boolean=} shouldAnimate Whether the scroll should animate.
*/
this.scrollTo = function(left, top, shouldAnimate) {
this.resize().then(function() {
scrollView.scrollTo(left, top, !!shouldAnimate);
});
};
/**
* @ngdoc method
* @name ionicScroll#anchorScroll
* @description Tell the scrollView to scroll to the element with an id
* matching window.location.hash.
*
* If no matching element is found, it will scroll to top.
*
* @param {boolean=} shouldAnimate Whether the scroll should animate.
*/
this.anchorScroll = function(shouldAnimate) {
this.resize().then(function() {
var hash = $location.hash();
@@ -169,75 +301,16 @@ function($scope, scrollViewOptions, $timeout, $window, $$scrollValueCache, $loca
});
};
/**
* @ngdoc method
* @name ionicScroll#rememberScrollPosition
* @description
* Will make it so, when this scrollView is destroyed (user leaves the page),
* the last scroll position the page was on will be saved, indexed by the
* given id.
*
* Note: for pages associated with a view under an ion-nav-view,
* rememberScrollPosition automatically saves their scroll.
*
* Related methods: scrollToRememberedPosition, forgetScrollPosition (below).
*
* In the following example, the scroll position of the ion-scroll element
* will persist, even when the user changes the toggle switch.
*
* ```html
* <ion-toggle ng-model="shouldShowScrollView"></ion-toggle>
* <ion-scroll ng-if="shouldShowScrollView">
* <div ng-controller="ScrollCtrl">
* <ion-list>
* <ion-item ng-repeat="i in items">{{i}}</ion-item>
* </ion-list>
* </div>
* </ion-scroll>
* ```
* ```js
* function ScrollCtrl($scope) {
* // Put any unique ID here. The point of this is: every time the controller is recreated
* // we want to load the correct remembered scroll values.
* $scope.$ionicScrollController.rememberScrollPosition('my-scroll-id');
*
* $scope.$ionicScrollController.scrollToRememberedPosition();
* $scope.items = [];
* for (var i=0; i<100; i++) {
* $scope.items.push(i);
* }
* }
* ```
*
* @param {string} id The id to remember the scroll position of this
* scrollView by.
*/
this.rememberScrollPosition = function(id) {
if (!id) {
throw new Error("Must supply an id to remember the scroll by!");
}
this._rememberScrollId = id;
};
/**
* @ngdoc method
* @name ionicScroll#forgetScrollPosition
* @description
* Stop remembering the scroll position for this scrollView.
*/
this.forgetScrollPosition = function() {
delete $$scrollValueCache[this._rememberScrollId];
this._rememberScrollId = null;
};
/**
* @ngdoc method
* @name ionicScroll#scrollToRememberedPosition
* @description
* If this scrollView has an id associated with its scroll position,
* (through calling rememberScrollPosition), and that position is remembered,
* load the position and scroll to it.
* @param {boolean=} shouldAnimate Whether to animate the scroll.
*/
this.scrollToRememberedPosition = function(shouldAnimate) {
var values = $$scrollValueCache[this._rememberScrollId];
if (values) {
@@ -268,4 +341,3 @@ function($scope, scrollViewOptions, $timeout, $window, $$scrollValueCache, $loca
};
}]);
})();

View File

@@ -28,7 +28,6 @@ angular.module('ionic.ui.content', ['ionic.ui.scroll'])
* @ngdoc directive
* @name ionContent
* @module ionic
* @controller ionicScroll as $scope.$ionicScrollController
* @restrict E
*
* @description
@@ -44,9 +43,8 @@ angular.module('ionic.ui.content', ['ionic.ui.scroll'])
* directive, and infinite scrolling with the {@link ionic.directive:ionInfiniteScroll}
* directive.
*
* @param {string=} controller-bind The scope variable to bind this element's scrollView's
* {@link ionic.controller:ionicScroll ionicScroll controller} to.
* Default: $scope.$ionicScrollController.
* @param {string=} delegate-handle The handle used to identify this scrollView
* with {@link ionic.service:$ionicScrollDelegate}.
* @param {boolean=} padding Whether to add padding to the content.
* of the content. Defaults to true on iOS, false on Android.
* @param {boolean=} scroll Whether to allow scrolling of content. Defaults to true.
@@ -122,7 +120,7 @@ function($parse, $timeout, $controller, $ionicBind) {
$scope: $scope,
scrollViewOptions: {
el: $element[0],
controllerBind: $attr.controllerBind,
delegateHandle: attr.delegateHandle,
bouncing: $scope.$eval($scope.hasBouncing),
startX: $scope.$eval($scope.startX) || 0,
startY: $scope.$eval($scope.startY) || 0,

View File

@@ -7,15 +7,13 @@ angular.module('ionic.ui.scroll', [])
* @ngdoc directive
* @name ionScroll
* @module ionic
* @controller ionicScroll as $scope.$ionicScrollController
* @restrict E
*
* @description
* Creates a scrollable container for all content inside.
*
* @param {string=} controller-bind The scope variable to bind this element's scrollView's
* {@link ionic.controller:ionicScroll ionicScroll controller} to.
* Default: $scope.$ionicScrollController.
* @param {string=} delegate-handle The handle used to identify this scrollView
* with {@link ionic.service:$ionicScrollDelegate}.
* @param {string=} direction Which way to scroll. 'x' or 'y'. Default 'y'.
* @param {boolean=} paging Whether to scroll with paging.
* @param {expression=} on-refresh Called on pull-to-refresh, triggered by an {@link ionic.directive:ionRefresher}.
@@ -63,7 +61,7 @@ angular.module('ionic.ui.scroll', [])
var scrollViewOptions= {
el: $element[0],
controllerBind: $attr.controllerBind,
delegateHandle: $attr.delegateHandle,
paging: isPaging,
scrollbarX: $scope.$eval($scope.scrollbarX) !== false,
scrollbarY: $scope.$eval($scope.scrollbarY) !== false,

View File

@@ -0,0 +1,66 @@
function delegateService(methodNames) {
return ['$log', function($log) {
var delegate = this;
this._instances = {};
this._registerInstance = function(instance, handle) {
handle || (handle = ionic.Utils.nextUid());
delegate._instances[handle] = instance;
return function deregister() {
delete delegate._instances[handle];
};
};
this.withHandle = function(handle) {
if (!handle) {
return delegate;
}
return new InstanceWithHandle(handle);
};
/*
* Creates a new object that will have all the methodNames given,
* and call them on the given the controller instance matching given
* handle.
* The reason we don't just let withHandle return the controller instance
* itself is that the controller instance might not exist yet.
*
* We want people to be able to do
* `var instance = $ionicScrollDelegate.withHandle('foo')` on controller
* instantiation, but on controller instantiation a child directive
* may not have been compiled yet!
*
* So this is our way of solving this problem: we create an object
* that will only try to fetch the controller with given handle
* once the methods are actually called.
*/
function InstanceWithHandle(handle) {
this.handle = handle;
}
methodNames.forEach(function(methodName) {
InstanceWithHandle.prototype[methodName] = function() {
var instance = delegate._instances[this.handle];
if (!instance) {
return $log.error(
'Delegate with handle "'+this.handle+'" could not find a',
'corresponding element with delegate-handle="'+this.handle+'"!',
methodName, 'was not called!');
}
return instance[methodName].apply(instance, arguments);
};
delegate[methodName] = function() {
var args = arguments;
var returnValue;
angular.forEach(delegate._instances, function(instance) {
var result = instance[methodName].apply(instance, args);
if (!angular.isDefined(returnValue)) {
returnValue = result;
}
});
return returnValue;
};
});
}];
}

View File

@@ -35,7 +35,7 @@
</div>
<script>
function MyCtrl($scope, $location) {
function MyCtrl($scope, $location, $ionicScrollDelegate) {
$scope.items = [];
for (var i=0; i < 100; i++) {
$scope.items.push({id:i});
@@ -43,7 +43,7 @@ function MyCtrl($scope, $location) {
$scope.doScroll = function() {
$location.hash('foo-50');
$scope.$ionicScrollController.anchorScroll(true);
$ionicScrollDelegate.anchorScroll(true);
}
}
</script>

View File

@@ -27,17 +27,28 @@ describe('$ionicScroll Controller', function() {
});
}
it('should set $scope.$ionicScrollController by default', function() {
setup()
expect(scope.$ionicScrollController).toBe(ctrl);
});
it('should register with no handle', inject(function($ionicScrollDelegate) {
spyOn($ionicScrollDelegate, '_registerInstance');
var el = setup();
expect($ionicScrollDelegate._registerInstance)
.toHaveBeenCalledWith(ctrl, undefined);
}));
it('should set options.controllerBind to ctrl', function() {
setup({
controllerBind: 'something'
it('should register with given handle and deregister on destroy', inject(function($ionicScrollDelegate) {
var deregisterSpy = jasmine.createSpy('deregister');
spyOn($ionicScrollDelegate, '_registerInstance').andCallFake(function() {
return deregisterSpy;
});
expect(scope.something).toBe(ctrl);
});
setup({
delegateHandle: 'something'
});
expect($ionicScrollDelegate._registerInstance)
.toHaveBeenCalledWith(ctrl, 'something');
expect(deregisterSpy).not.toHaveBeenCalled();
scope.$destroy();
expect(deregisterSpy).toHaveBeenCalled();
}));
it('should set bouncing to option if given', function() {
spyOn(ionic.Platform, 'isAndroid');

View File

@@ -16,6 +16,12 @@ describe('Ionic Content directive', function() {
expect(element.controller('$ionicScroll').element).toBe(element[0]);
});
it('passes delegateHandle attribute', function() {
var element = compile('<ion-content delegate-handle="handleMe">')(scope);
expect(element.controller('$ionicScroll')._scrollViewOptions.delegateHandle)
.toBe('handleMe');
});
it('Has content class', function() {
var element = compile('<ion-content></ion-content>')(scope);
expect(element.hasClass('scroll-content')).toBe(true);
@@ -46,18 +52,12 @@ describe('Ionic Content directive', function() {
it('Should set start x and y', function() {
var element = compile('<ion-content start-x="100" start-y="300"></ion-content>')(scope);
scope.$apply();
var scrollView = scope.$ionicScrollController.scrollView;
var scrollView = element.controller('$ionicScroll').scrollView;
var vals = scrollView.getValues();
expect(vals.left).toBe(100);
expect(vals.top).toBe(300);
});
it('should pass attr.controllerBind ionicScrollController', function() {
var element = compile('<ion-content controller-bind="scrolly">')(scope);
scope.$apply();
expect(scope.scrolly).toBe(element.controller('$ionicScroll'));
});
});
/* Tests #555 */
describe('Ionic Content Directive scoping', function() {

View File

@@ -14,9 +14,10 @@ describe('Ionic Scroll Directive', function() {
});
}));
it('Has $ionicScroll controller', function() {
element = compile('<ion-scroll></ion-scroll>')(scope);
expect(element.controller('$ionicScroll').element).toBe(element[0]);
it('passes delegateHandle attribute', function() {
var element = compile('<ion-scroll delegate-handle="handleThis">')(scope);
expect(element.controller('$ionicScroll')._scrollViewOptions.delegateHandle)
.toBe('handleThis');
});
it('has $onScroll (used by $ionicScrollController)', function() {
@@ -44,16 +45,10 @@ describe('Ionic Scroll Directive', function() {
expect(scrollElement.hasClass('padding')).toEqual(true);
});
it('should pass attr.controllerBind ionicScrollController', function() {
var element = compile('<ion-scroll controller-bind="scrolly">')(scope);
scope.$apply();
expect(scope.scrolly).toBe(element.controller('$ionicScroll'));
});
it('Should set start x and y', function() {
element = compile('<ion-content start-x="100" start-y="300" has-header="true"></ion-scroll>')(scope);
scope.$apply();
var scrollView = scope.$ionicScrollController.scrollView;
var scrollView = element.controller('$ionicScroll').scrollView;
var vals = scrollView.getValues();
expect(vals.left).toBe(100);
expect(vals.top).toBe(300);

View File

@@ -10,7 +10,7 @@
</head>
<body>
<ion-toggle ng-model="shouldShowScrollView"></ion-toggle>
<ion-scroll ng-if="shouldShowScrollView">
<ion-scroll delegate-handle="myScroll" ng-if="shouldShowScrollView">
<div ng-controller="ScrollCtrl">
<ion-list>
<ion-item ng-repeat="i in items">{{i}}</ion-item>
@@ -18,9 +18,11 @@
</div>
</ion-scroll>
<script>
function ScrollCtrl($scope) {
$scope.$ionicScrollController.rememberScrollPosition('my-scroll-id');
$scope.$ionicScrollController.scrollToRememberedPosition();
function ScrollCtrl($scope, $ionicScrollDelegate) {
var delegate = $ionicScrollDelegate.withHandle('myScroll');
delegate.rememberScrollPosition('my-scroll-id');
delegate.scrollToRememberedPosition();
$scope.items = [];
for (var i=0; i<100; i++) {

View File

@@ -87,7 +87,7 @@
}
})
.controller('ThisCtrl', function($scope) {
.controller('ThisCtrl', function($scope, $ionicScrollDelegate) {
var header = document.getElementById('header');
var content = document.getElementById('container');
@@ -103,7 +103,7 @@
}
$scope.scrollTo = function() {
console.log('scrollTo');
$scope.$ionicScrollController.scrollTo(0, 100, true);
$ionicScrollDelegate.scrollTo(0, 100, true);
};
$scope.onScroll = function(event, scrollTop, scrollLeft) {
/*

View File

@@ -0,0 +1,141 @@
describe('DelegateFactory', function() {
function setup(methods) {
var delegate;
inject(function($log, $injector) {
$log.error = jasmine.createSpy('error');
delegate = $injector.instantiate(delegateService(methods || []));
});
return delegate;
}
it('should have properties', function() {
expect(setup()._instances).toEqual({});
expect(setup()._registerInstance).toEqual(jasmine.any(Function));
expect(setup().withHandle).toEqual(jasmine.any(Function));
});
it('should allow reg & dereg of instance with handle', function() {
var delegate = setup();
var instance = {};
var deregister = delegate._registerInstance(instance, 'handle');
expect(delegate._instances['handle']).toBe(instance);
deregister();
expect(delegate._instances['handle']).toBeUndefined();
});
it('should allow reg & dereg of instance, and make its own handle', function() {
spyOn(ionic.Utils, 'nextUid').andCallFake(function() {
return 'uid';
});
var delegate = setup();
var instance = {};
var deregister = delegate._registerInstance(instance);
expect(ionic.Utils.nextUid).toHaveBeenCalled();
expect(delegate._instances['uid']).toBe(instance);
deregister();
expect(delegate._instances['uid']).toBeUndefined();
});
it('should have given methodNames on root', function() {
var delegate = setup(['foo', 'bar']);
expect(delegate.foo).toEqual(jasmine.any(Function));
expect(delegate.bar).toEqual(jasmine.any(Function));
});
it('should call given methodNames on all instances with args', function() {
var delegate = setup(['foo', 'bar']);
var instance1 = {
foo: jasmine.createSpy('foo1'),
bar: jasmine.createSpy('bar1')
};
var instance2 = {
foo: jasmine.createSpy('foo1'),
bar: jasmine.createSpy('bar1')
};
delegate._registerInstance(instance1);
delegate._registerInstance(instance2);
expect(instance1.foo).not.toHaveBeenCalled();
expect(instance2.foo).not.toHaveBeenCalled();
delegate.foo('a', 'b', 'c');
expect(instance1.foo).toHaveBeenCalledWith('a', 'b', 'c');
expect(instance2.foo).toHaveBeenCalledWith('a', 'b', 'c');
instance1.foo.reset();
instance2.foo.reset();
delegate.foo(1, 2, 3);
expect(instance1.foo).toHaveBeenCalledWith(1, 2, 3);
expect(instance2.foo).toHaveBeenCalledWith(1, 2, 3);
expect(instance1.bar).not.toHaveBeenCalled();
expect(instance2.bar).not.toHaveBeenCalled();
delegate.bar('something');
expect(instance1.bar).toHaveBeenCalledWith('something');
expect(instance2.bar).toHaveBeenCalledWith('something');
});
it('should return the first return value from multiple instances', function() {
var delegate = setup(['fn']);
var instance1 = {
fn: jasmine.createSpy('fn').andCallFake(function() {
return 'ret1';
})
};
var instance2 = {
fn: jasmine.createSpy('fn').andCallFake(function() {
return 'ret2';
})
};
var deregister = delegate._registerInstance(instance1);
delegate._registerInstance(instance2);
expect(delegate.fn()).toBe('ret1');
deregister();
expect(delegate.fn()).toBe('ret2');
});
it('withHandle should return this for blank handle', function() {
var delegate = setup();
expect(delegate.withHandle()).toBe(delegate);
});
describe('withHandle', function() {
var delegate, instance1, instance2;
beforeEach(function() {
delegate = setup(['a']);
instance1 = {
a: jasmine.createSpy('a1').andCallFake(function() {
return 'a1';
}),
};
instance2 = {
a: jasmine.createSpy('a2').andCallFake(function() {
return 'a2';
}),
};
});
it('should return an InstanceWithHandle object with fields', function() {
expect(delegate.withHandle('one').a).toEqual(jasmine.any(Function));
expect(delegate.withHandle('two').a).toEqual(jasmine.any(Function));
expect(delegate.withHandle('invalid').a).toEqual(jasmine.any(Function));
});
it('should do nothing if calling for a non-added instance', function() {
expect(delegate.withHandle('one').a()).toBeUndefined();
});
it('should call an added instance\'s method', function() {
delegate._registerInstance(instance1, '1');
delegate._registerInstance(instance2, '2');
var result = delegate.withHandle('1').a(1,2,3);
expect(instance1.a).toHaveBeenCalledWith(1,2,3);
expect(instance2.a).not.toHaveBeenCalled();
expect(result).toBe('a1');
instance1.a.reset();
var result = delegate.withHandle('2').a(2,3,4);
expect(instance2.a).toHaveBeenCalledWith(2,3,4);
expect(instance1.a).not.toHaveBeenCalled();
expect(result).toBe('a2');
});
});
});