Skip to content

Commit

Permalink
test(collectionRepeat): finish unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
ajoslin committed May 5, 2014
1 parent 74891ac commit e6e1896
Show file tree
Hide file tree
Showing 4 changed files with 204 additions and 26 deletions.
2 changes: 0 additions & 2 deletions demos/collection-repeat/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ <h1 class="title">3000 Contacts</h1>
<input type="search"
placeholder="Filter contacts..."
ng-model="search"
ng-focus="searchFocused = true"
ng-blur="searchFocused = false"
ng-change="scrollTop()">
<button ng-if="search.length"
class="button button-icon ion-android-close input-button"
Expand Down
54 changes: 32 additions & 22 deletions js/angular/directive/collectionRepeat.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,20 @@
* Here are a few things to keep in mind while using collection-repeat:
*
* 1. The data supplied to collection-repeat must be an array.
* 2. You must explicitly tell the directive what size your items will be in the DOM
* (pixel amount or percentage), using directive attributes (see below).
* 3. The elements rendered will be absolutely positioned: be sure to let your CSS work with this (see below).
* 4. Keep the HTML of your repeated elements as simple as possible. As the user scrolls down, elements
* will be lazily compiled. Resultingly, the more complicated your elements, the more likely it is that
* the on-demand compilation will cause jankiness in the user's scrolling.
* 5. The more elements you render on the screen at a time, the slower the scrolling will be.
* It is recommended to keep grids of collection-repeat list elements at 3-wide or less.
* 2. You must explicitly tell the directive what size your items will be in the DOM, using directive attributes. Pixel amounts or percentages are allowed (see below).
* 3. The elements rendered will be absolutely positioned: be sure to let your CSS work with
* this (see below).
* 4. Keep the HTML of your repeated elements as simple as possible. As the user scrolls down,
* elements will be lazily compiled. Resultingly, the more complicated your elements, the more
* likely it is that the on-demand compilation will cause some jerkiness in the user's scrolling.
* 5. The more elements you render on the screen per row, the more likelihood for scrolling to
* slow down. It is recommended to keep grids of collection-repeat list elements at 3-wide or less.
* For example, if you have a gallery of images just set their width to 33%.
* 6. Each collection-repeat list will take up all of its parent scrollView's space.
* If you wish to have multiple lists on one page, put each list within its own
* {@link ionic.directive:ionScroll ionScroll} container.
*
*
*
* @usage
*
* #### Basic Usage (single rows of items)
Expand Down Expand Up @@ -127,6 +127,11 @@
* @param {expression} collection-item-height The height of the repeated element. Can be a number (in pixels), or a percentage.
*
*/
var COLLECTION_REPEAT_SCROLLVIEW_XY_ERROR = "Cannot create a collection-repeat within a scrollView that is scrollable on both x and y axis. Choose either x direction or y direction.";
var COLLECTION_REPEAT_ATTR_HEIGHT_ERROR = "collection-repeat expected attribute collection-item-height to be a an expression that returns a number (in pixels) or percentage.";
var COLLECTION_REPEAT_ATTR_WIDTH_ERROR = "collection-repeat expected attribute collection-item-width to be a an expression that returns a number (in pixels) or percentage.";
var COLLECTION_REPEAT_ATTR_REPEAT_ERROR = "collection-repeat expected expression in form of '_item_ in _collection_[ track by _id_]' but got '%'";

IonicModule
.directive('collectionRepeat', [
'$collectionRepeatManager',
Expand All @@ -142,14 +147,14 @@ function($collectionRepeatManager, $collectionDataSource, $parse) {
link: function($scope, $element, $attr, scrollCtrl, $transclude) {
var scrollView = scrollCtrl.scrollView;
if (scrollView.options.scrollingX && scrollView.options.scrollingY) {
throw new Error("Cannot create a collection-repeat within a scrollView that is scrollable on both x and y axis. Choose either x direction or y direction.");
throw new Error(COLLECTION_REPEAT_SCROLLVIEW_XY_ERROR);
}

var isVertical = !!scrollView.options.scrollingY;
if (isVertical && !$attr.collectionItemHeight) {
throw new Error("collection-repeat expected attribute collection-item-height to be a an expression that returns a number.");
throw new Error(COLLECTION_REPEAT_ATTR_HEIGHT_ERROR);
} else if (!isVertical && !$attr.collectionItemWidth) {
throw new Error("collection-repeat expected attribute collection-item-width to be a an expression that returns a number.");
throw new Error(COLLECTION_REPEAT_ATTR_WIDTH_ERROR);
}
$attr.collectionItemHeight = $attr.collectionItemHeight || '"100%"';
$attr.collectionItemWidth = $attr.collectionItemWidth || '"100%"';
Expand Down Expand Up @@ -178,16 +183,20 @@ function($collectionRepeatManager, $collectionDataSource, $parse) {

var match = $attr.collectionRepeat.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/);
if (!match) {
throw new Error("collection-repeat expected expression in form of '_item_ in _collection_[ track by _id_]' but got '" + $attr.collectionRepeat + "'.");
throw new Error(COLLECTION_REPEAT_ATTR_REPEAT_ERROR
.replace('%', $attr.collectionRepeat));
}
var keyExpr = match[1];
var listExpr = match[2];
var trackByExpr = match[3];

var dataSource = new $collectionDataSource({
scope: $scope,
transcludeFn: $transclude,
transcludeParent: $element.parent(),
keyExpr: match[1],
listExpr: match[2],
trackByExpr: match[3],
keyExpr: keyExpr,
listExpr: listExpr,
trackByExpr: trackByExpr,
heightGetter: heightGetter,
widthGetter: widthGetter
});
Expand All @@ -197,7 +206,7 @@ function($collectionRepeatManager, $collectionDataSource, $parse) {
scrollView: scrollCtrl.scrollView,
});

$scope.$watchCollection(dataSource.listExpr, function(value) {
$scope.$watchCollection(listExpr, function(value) {
if (value && !angular.isArray(value)) {
throw new Error("collection-repeat expects an array to repeat over, but instead got '" + typeof value + "'.");
}
Expand All @@ -209,15 +218,16 @@ function($collectionRepeatManager, $collectionDataSource, $parse) {
dataSource.setData(value);
collectionRepeatManager.resize();
}
var resize = angular.bind(collectionRepeatManager, collectionRepeatManager.resize);
ionic.on('resize', function() {
rerender($scope.$eval(dataSource.listExpr));
}, window);
function onWindowResize() {
rerender($scope.$eval(listExpr));
}

ionic.on('resize', onWindowResize, window);

$scope.$on('$destroy', function() {
collectionRepeatManager.destroy();
dataSource.destroy();
ionic.off('resize', resize, window);
ionic.off('resize', onWindowResize, window);
});
}
};
Expand Down
8 changes: 6 additions & 2 deletions js/views/scrollView.js
Original file line number Diff line number Diff line change
Expand Up @@ -802,11 +802,15 @@ ionic.views.Scroll = ionic.views.View.inherit({
self.__fadeScrollbars('out');
}, 100, false);

document.addEventListener("mousewheel", function(e) {
//For Firefox
document.addEventListener('DOMMouseScroll', onMouseWheel);
document.addEventListener('mousewheel', onMouseWheel);
function onMouseWheel(e) {
console.log('wheel', e);
wheelShowBarFn();
self.scrollBy(e.wheelDeltaX/self.options.wheelDampen, -e.wheelDeltaY/self.options.wheelDampen);
wheelHideBarFn();
});
}
}
},

Expand Down
166 changes: 166 additions & 0 deletions test/unit/angular/directive/collectionRepeat.unit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
describe('collectionRepeat directive', function() {

var dataSource, repeatManager;
beforeEach(module('ionic', function($provide) {
$provide.value(
'$collectionDataSource',
jasmine.createSpy('$dataSource').andCallFake(function(opts) {
dataSource = {
options: opts,
setData: jasmine.createSpy('setData')
};
return dataSource;
})
);
$provide.value(
'$collectionRepeatManager',
jasmine.createSpy('$repeatManager').andCallFake(function(opts) {
repeatManager = {
options: opts,
resize: jasmine.createSpy('resize')
};
return repeatManager;
})
);
}));

function setup(attrs, scrollViewOpts) {
var el;
inject(function($compile, $rootScope) {
var content = $compile('<ion-content>')($rootScope.$new());
$rootScope.$apply();
angular.extend(
content.controller('$ionicScroll').scrollView.options,
scrollViewOpts || {}
);
el = angular.element('<div ' + (attrs||'') + '></div>');
content.append(el);
$compile(el)(content.scope());
$rootScope.$apply();
});
return el;
}

describe('errors', function() {
it('should error if scrollView is x and y', function() {
expect(function() {
setup('collection-repeat', {scrollingX:true, scrollingY:true});
}).toThrow(COLLECTION_REPEAT_SCROLLVIEW_XY_ERROR);
});
it('should error if scrollView is x and no width', function() {
expect(function() {
setup('collection-repeat collection-item-height', {
scrollingX:true,
scrollingY:false
});
}).toThrow(COLLECTION_REPEAT_ATTR_WIDTH_ERROR);
});
it('should error if scrollView is y and no height', function() {
expect(function() {
setup('collection-repeat collection-item-width', {
scrollingX:false,
scrollingY:true
});
}).toThrow(COLLECTION_REPEAT_ATTR_HEIGHT_ERROR);
});
it('should error if no repeat expression', function() {
expect(function() {
setup('collection-repeat="bad" collection-item-height="1"');
}).toThrow(COLLECTION_REPEAT_ATTR_REPEAT_ERROR.replace('%', 'bad'));
});
});

describe('widthGetter & heightGetter', function() {
it('should work with given amounts', function() {
var el = setup('collection-repeat="a in b" collection-item-height="5" collection-item-width="10"');
expect(dataSource.options.heightGetter()).toBe(5);
expect(dataSource.options.widthGetter()).toBe(10);
});
it('should default width of y-scroller to 100%', function() {
var el = setup('collection-repeat="a in b" collection-item-height="5"');
el.controller('$ionicScroll').scrollView.__clientWidth = 200;
expect(dataSource.options.widthGetter()).toBe(200);
});
it('should default height of x-scroller to 100%', function() {
var el = setup('collection-repeat="a in b" collection-item-width="5"', {
scrollingX: true,
scrollingY: false
});
el.controller('$ionicScroll').scrollView.__clientHeight = 199;
expect(dataSource.options.heightGetter()).toBe(199);
});
it('should work with user-inputted percentage height', function() {
var el = setup('collection-repeat="a in b" collection-item-height="\'23%\'"');
el.controller('$ionicScroll').scrollView.__clientHeight = 300;
expect(dataSource.options.heightGetter()).toEqual(0.23 * 300);
});
it('should work with user-inputted percentage width', function() {
var el = setup('collection-repeat="a in b" collection-item-width="\'23%\'"', {
scrollingX: true,
scrollingY: false
});
el.controller('$ionicScroll').scrollView.__clientWidth = 300;
expect(dataSource.options.widthGetter()).toEqual(0.23 * 300);
});
});

it('should error if list is not an array and is truthy', function() {
var el = setup('collection-repeat="item in items" collection-item-height="50"');
expect(function() {
el.scope().$apply('items = "string"');
}).toThrow();
expect(function() {
el.scope().$apply('items = 123');
}).toThrow();
expect(function() {
el.scope().$apply('items = {}');
}).toThrow();
expect(function() {
el.scope().$apply('items = []');
}).not.toThrow();
});

it('should rerender on list change', function() {
var el = setup('collection-repeat="item in items" collection-item-height="50"');
var scrollView = el.controller('$ionicScroll').scrollView;
spyOn(scrollView, 'resize');
dataSource.setData.reset();
repeatManager.resize.reset();

el.scope().$apply('items = [ 1,2,3 ]');
expect(dataSource.setData).toHaveBeenCalledWith(el.scope().items);
expect(repeatManager.resize.callCount).toBe(1);
expect(scrollView.resize.callCount).toBe(1);
el.scope().$apply('items = null');
expect(dataSource.setData).toHaveBeenCalledWith(null);
expect(repeatManager.resize.callCount).toBe(2);
expect(scrollView.resize.callCount).toBe(2);
});

it('should rerender on window resize', function() {
var el = setup('collection-repeat="item in items" collection-item-height="50"');
var scrollView = el.controller('$ionicScroll').scrollView;
spyOn(scrollView, 'resize');
dataSource.setData.reset();
repeatManager.resize.reset();

el.scope().items = [1,2,3];

ionic.trigger('resize', { target: window });
expect(dataSource.setData).toHaveBeenCalledWith(el.scope().items);
expect(repeatManager.resize.callCount).toBe(1);
expect(scrollView.resize.callCount).toBe(1);
});

it('$destroy', function() {
var el = setup('collection-repeat="item in items" collection-item-height="50"');
dataSource.destroy = jasmine.createSpy('dataSourceDestroy');
repeatManager.destroy = jasmine.createSpy('repeatManagerDestroy');
spyOn(ionic, 'off');

el.scope().$destroy();
expect(dataSource.destroy).toHaveBeenCalled();
expect(repeatManager.destroy).toHaveBeenCalled();
expect(ionic.off).toHaveBeenCalledWith('resize', jasmine.any(Function), window);
});
});

0 comments on commit e6e1896

Please sign in to comment.