diff --git a/demos/collection-repeat/index.html b/demos/collection-repeat/index.html index bfee65c343..ad2d42fc43 100644 --- a/demos/collection-repeat/index.html +++ b/demos/collection-repeat/index.html @@ -35,8 +35,9 @@ {{item.letter || (item.first_name+' '+item.last_name)}} @@ -83,6 +84,9 @@ margin-top: 10px; margin-right: 10px; } +.list { + height: 100%; +} diff --git a/demos/collection-repeat/script.js b/demos/collection-repeat/script.js index 642070d44a..635dd6ce3a 100644 --- a/demos/collection-repeat/script.js +++ b/demos/collection-repeat/script.js @@ -39,7 +39,10 @@ angular.module('contactsApp', ['ionic']) //Letters are shorter, everything else is 52 pixels $scope.getItemHeight = function(item) { - return item.isLetter ? 38 : 80; + return item.isLetter ? 45 : '25%'; + }; + $scope.getItemWidth = function(item) { + return item.isLetter ? '100%' : '50%'; }; var letterHasMatch = {}; diff --git a/js/angular/directive/collectionRepeat.js b/js/angular/directive/collectionRepeat.js index c5c93c76b4..87099873e7 100644 --- a/js/angular/directive/collectionRepeat.js +++ b/js/angular/directive/collectionRepeat.js @@ -22,16 +22,30 @@ function($collectionRepeatManager, $collectionDataSource, $parse) { } else if (!isVertical && !$attr.collectionItemWidth) { throw new Error("collection-repeat expected attribute collection-item-width to be a an expression that returns a number."); } - var heightGetter = $attr.collectionItemHeight ? + $attr.collectionItemHeight = $attr.collectionItemHeight || '100%'; + $attr.collectionItemWidth = $attr.collectionItemWidth || '100%'; + + var heightParsed = $attr.collectionItemHeight ? $parse($attr.collectionItemHeight) : function() { return scrollView.__clientHeight; }; - var widthGetter = $attr.collectionItemWidth ? + var widthParsed = $attr.collectionItemWidth ? $parse($attr.collectionItemWidth) : function() { return scrollView.__clientWidth; }; - console.log(widthGetter()); - setTimeout(function() { - console.log(widthGetter()); - }); + + var heightGetter = function(scope, locals) { + var result = heightParsed(scope, locals); + if (angular.isString(result) && result.indexOf('%') > -1) { + return Math.floor(parseInt(result, 10) / 100 * scrollView.__clientHeight); + } + return result; + }; + var widthGetter = function(scope, locals) { + var result = widthParsed(scope, locals); + if (angular.isString(result) && result.indexOf('%') > -1) { + return Math.floor(parseInt(result, 10) / 100 * scrollView.__clientWidth); + } + return result; + }; var match = $attr.collectionRepeat.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/); if (!match) { diff --git a/js/angular/service/collectionRepeatManager.js b/js/angular/service/collectionRepeatManager.js index ad7691de95..df6484cc68 100644 --- a/js/angular/service/collectionRepeatManager.js +++ b/js/angular/service/collectionRepeatManager.js @@ -9,14 +9,13 @@ function($rootScope, $timeout) { this.dataSource = options.dataSource; this.element = options.element; this.scrollView = options.scrollView; - this.itemSizePrimary = options.itemSizePrimary; - this.itemSizeSecondary = options.itemSizeSecondary; this.isVertical = !!this.scrollView.options.scrollingY; this.renderedItems = {}; this.lastRenderScrollValue = this.bufferTransformOffset = this.hasBufferStartIndex = this.hasBufferEndIndex = this.bufferItemsLength = 0; + this.setCurrentIndex(0); this.scrollView.__$callback = this.scrollView.__callback; this.scrollView.__callback = angular.bind(this, this.renderScroll); @@ -34,12 +33,18 @@ function($rootScope, $timeout) { this.scrollSize = function() { return this.scrollView.__clientHeight; }; - this.getSecondaryScrollSize = function() { + this.secondaryScrollSize = function() { return this.scrollView.__clientWidth; }; this.transformString = function(y, x) { return 'translate3d('+x+'px,'+y+'px,0)'; }; + this.primaryDimension = function(dim) { + return dim.height; + }; + this.secondaryDimension = function(dim) { + return dim.width; + }; } else { this.scrollView.options.getContentWidth = getViewportSize; @@ -52,12 +57,18 @@ function($rootScope, $timeout) { this.scrollSize = function() { return this.scrollView.__clientWidth; }; - this.getSecondaryScrollSize = function() { + this.secondaryScrollSize = function() { return this.scrollView.__clientHeight; }; this.transformString = function(x, y) { return 'translate3d('+x+'px,'+y+'px,0)'; }; + this.primaryDimension = function(dim) { + return dim.width; + }; + this.secondaryDimension = function(dim) { + return dim.height; + }; } } @@ -67,36 +78,41 @@ function($rootScope, $timeout) { this.removeItem(i); } }, - resize: function() { + calculateDimensions: function() { var primaryPos = 0; var secondaryPos = 0; - var itemsPerSpace = 0; var len = this.dataSource.dimensions.length; - this.dimensions = this.dataSource.dimensions.map(function(dimensions, index) { + var secondaryScrollSize = this.secondaryScrollSize(); + var previous; + + return this.dataSource.dimensions.map(function(dim) { var rect = { - primarySize: this.isVertical ? dimensions.height : dimensions.width, - secondarySize: this.isVertical ? dimensions.width : dimensions.height, - primaryPos: primaryPos, - secondaryPos: secondaryPos + primarySize: this.primaryDimension(dim), + secondarySize: Math.min(this.secondaryDimension(dim), secondaryScrollSize) }; - itemsPerSpace++; - secondaryPos += rect.secondarySize; - if (secondaryPos >= this.getSecondaryScrollSize()) { - secondaryPos = 0; - primaryPos += rect.primarySize; - - if (!this.itemsPerSpace) { - this.itemsPerSpace = itemsPerSpace; + if (previous) { + secondaryPos += previous.secondarySize; + if (previous.primaryPos === primaryPos && + secondaryPos + rect.secondarySize > secondaryScrollSize) { + secondaryPos = 0; + primaryPos += previous.primarySize; + } else { } } + rect.primaryPos = primaryPos; + rect.secondaryPos = secondaryPos; + + previous = rect; return rect; }, this); - - this.viewportSize = primaryPos; + }, + resize: function() { + this.dimensions = this.calculateDimensions(); + var last = this.dimensions[this.dimensions.length - 1]; + this.viewportSize = last ? last.primaryPos + last.primarySize : 0; this.setCurrentIndex(0); - this.lastRenderScrollValue = 0; this.render(true); }, setCurrentIndex: function(index, height) { @@ -129,40 +145,45 @@ function($rootScope, $timeout) { }, getIndexForScrollValue: function(i, scrollValue) { var rect; - //Scrolling down + //Scrolling up if (scrollValue <= this.dimensions[i].primaryPos) { while ( (rect = this.dimensions[i - 1]) && rect.primaryPos > scrollValue) { - i -= this.itemsPerSpace; + i--; } - //Scrolling up + //Scrolling down } else { while ( (rect = this.dimensions[i + 1]) && rect.primaryPos < scrollValue) { - i += this.itemsPerSpace; + i++; } } return i; }, render: function(shouldRedrawAll) { - if (this.currentIndex >= this.dataSource.getLength()) { - return; - } - var i; - if (shouldRedrawAll) { + if (this.currentIndex >= this.dataSource.getLength() || shouldRedrawAll) { for (i in this.renderedItems) { this.removeItem(i); } + if (this.currentIndex >= this.dataSource.getLength()) return null; } + + var rect; var scrollValue = this.scrollValue(); var scrollDelta = scrollValue - this.lastRenderScrollValue; var scrollSize = this.scrollSize(); var scrollSizeEnd = scrollSize + scrollValue; var startIndex = this.getIndexForScrollValue(this.currentIndex, scrollValue); - var bufferStartIndex = Math.max(0, startIndex - this.itemsPerSpace); + + //Make buffer start on previous row + var bufferStartIndex = Math.max(startIndex - 1, 0); + while (bufferStartIndex > 0 && + (rect = this.dimensions[bufferStartIndex]) && + rect.primaryPos === this.dimensions[startIndex - 1].primaryPos) { + bufferStartIndex--; + } var startPos = this.dimensions[bufferStartIndex].primaryPos; i = bufferStartIndex; - var rect; while ((rect = this.dimensions[i]) && (rect.primaryPos - rect.primarySize < scrollSizeEnd)) { this.renderItem(i, rect.primaryPos - startPos, rect.secondaryPos); i++; @@ -183,7 +204,6 @@ function($rootScope, $timeout) { } }, renderItem: function(dataIndex, primaryPos, secondaryPos) { - var self = this; var item = this.dataSource.getItem(dataIndex); if (item) { this.dataSource.attachItem(item); diff --git a/test/unit/angular/service/collectionRepeatManager.unit.js b/test/unit/angular/service/collectionRepeatManager.unit.js new file mode 100644 index 0000000000..15ef16d11d --- /dev/null +++ b/test/unit/angular/service/collectionRepeatManager.unit.js @@ -0,0 +1,473 @@ +describe('collectionRepeatManager service', function() { + beforeEach(module('ionic')); + + var afThrottle; + beforeEach(function() { + afThrottle = ionic.animationFrameThrottle; + ionic.animationFrameThrottle = function(cb) { + return function() { + cb.apply(this, arguments); + }; + }; + }); + afterEach(function() { + ionic.animationFrameThrottle = afThrottle; + }); + + function setup(options, dataSourceOptions, scrollViewOptions) { + var manager; + inject(function($collectionDataSource, $rootScope, $compile, $collectionRepeatManager) { + var dataSource = new $collectionDataSource(angular.extend({ + scope: $rootScope.$new(), + transcludeParent: angular.element('
'), + trancsludeFn: function(scope, cb) { + cb($compile('
')(scope)); + }, + keyExpr: 'key', + listExpr: 'list', + heightGetter: function() { + return 1; + }, + widthGetter: function() { + return 1; + } + }, dataSourceOptions || {})); + + var el = angular.element('
') + .append(angular.element('
')); + options = angular.extend({ + dataSource: dataSource, + element: el, + scrollView: new ionic.views.Scroll(angular.extend({ + el: el[0] + }, scrollViewOptions || {})), + }, options || {}); + manager = new $collectionRepeatManager(options); + }); + return manager; + } + + it('should have properties', function() { + var manager = setup(); + expect(manager.dataSource).toBeTruthy(); + expect(manager.scrollView).toBeTruthy(); + expect(manager.element[0]).toBe(manager.scrollView.__container); + }); + + it('should be isVertical if scrollingY', function() { + var manager = setup(); + expect(manager.scrollView.options.scrollingY).toBe(true); + expect(manager.isVertical).toBe(true); + }); + + it('should be !isVertical if scrollingX', function() { + var manager = setup({}, {}, { + scrollingX: true, + scrollingY: false + }); + expect(manager.scrollView.options.scrollingX).toBe(true); + expect(manager.scrollView.options.scrollingY).toBe(false); + expect(manager.isVertical).toBe(false); + }); + + describe('isVertical', function() { + + it('should set getContentHeight', function() { + var manager = setup(); + manager.viewportSize = 55; + expect(manager.scrollView.options.getContentHeight()).toBe(55); + }); + + it('.scrollValue()', function() { + var manager = setup(); + manager.scrollView.__scrollTop = 33; + expect(manager.scrollValue()).toBe(33); + }); + + it('.scrollMaxValue()', function() { + var manager = setup(); + manager.scrollView.__maxScrollTop = 123; + expect(manager.scrollMaxValue()).toBe(123); + }); + + it('.scrollSize()', function() { + var manager = setup(); + manager.scrollView.__clientHeight = 12; + expect(manager.scrollSize()).toBe(12); + }); + + it('.secondaryScrollSize()', function() { + var manager = setup(); + manager.scrollView.__clientWidth = 999; + expect(manager.secondaryScrollSize()).toBe(999); + }); + + it('.transformString()', function() { + var manager = setup(); + expect(manager.transformString(3,6)).toBe('translate3d(6px,3px,0)'); + }); + + it('.primaryDimension()', function() { + var manager = setup(); + expect(manager.primaryDimension({height: 100})).toBe(100); + }); + + it('.secondaryDimension()', function() { + var manager = setup(); + expect(manager.secondaryDimension({width: 99})).toBe(99); + }); + + }); + + describe('!isVertical', function() { + + it('should set getContentWidth', function() { + var manager = setup({}, {}, { scrollingX: true, scrollingY: false }); + manager.viewportSize = 55; + expect(manager.scrollView.options.getContentWidth()).toBe(55); + }); + + it('.scrollValue()', function() { + var manager = setup({}, {}, { scrollingX: true, scrollingY: false }); + manager.scrollView.__scrollLeft = 33; + expect(manager.scrollValue()).toBe(33); + }); + + it('.scrollMaxValue()', function() { + var manager = setup({}, {}, { scrollingX: true, scrollingY: false }); + manager.scrollView.__maxScrollLeft = 123; + expect(manager.scrollMaxValue()).toBe(123); + }); + + it('.scrollSize()', function() { + var manager = setup({}, {}, { scrollingX: true, scrollingY: false }); + manager.scrollView.__clientWidth = 12; + expect(manager.scrollSize()).toBe(12); + }); + + it('.secondaryScrollSize()', function() { + var manager = setup({}, {}, { scrollingX: true, scrollingY: false }); + manager.scrollView.__clientHeight = 999; + expect(manager.secondaryScrollSize()).toBe(999); + }); + + it('.transformString()', function() { + var manager = setup({}, {}, { scrollingX: true, scrollingY: false }); + expect(manager.transformString(3,6)).toBe('translate3d(3px,6px,0)'); + }); + + it('.primaryDimension()', function() { + var manager = setup({}, {}, { scrollingX: true, scrollingY: false }); + expect(manager.primaryDimension({width: 100})).toBe(100); + }); + + it('.secondaryDimension()', function() { + var manager = setup({}, {}, { scrollingX: true, scrollingY: false }); + expect(manager.secondaryDimension({height: 99})).toBe(99); + }); + + }); + + it('.destroy() should remove items', function() { + var manager = setup(); + spyOn(manager, 'removeItem'); + manager.renderedItems = { '1': true, '2': true }; + manager.destroy(); + expect(manager.removeItem).toHaveBeenCalledWith('1'); + expect(manager.removeItem).toHaveBeenCalledWith('2'); + }); + + describe('.calculateDimensions()', function() { + it('should work with 1 item per space', function() { + var manager = setup(); + manager.dataSource.dimensions = [ + { width: 100, height: 20 }, + { width: 100, height: 30 }, + { width: 100, height: 40 }, + { width: 100, height: 50 } + ]; + manager.secondaryScrollSize = function() { + return 100; + }; + var result = manager.calculateDimensions(); + expect(result[0].primarySize).toBe(20); + expect(result[0].secondarySize).toBe(100); + expect(result[0].primaryPos).toBe(0); + expect(result[0].secondaryPos).toBe(0); + + expect(result[1].primarySize).toBe(30); + expect(result[1].secondarySize).toBe(100); + expect(result[1].primaryPos).toBe(20); + expect(result[1].secondaryPos).toBe(0); + + expect(result[2].primarySize).toBe(40); + expect(result[2].secondarySize).toBe(100); + expect(result[2].primaryPos).toBe(50); + expect(result[2].secondaryPos).toBe(0); + + expect(result[3].primarySize).toBe(50); + expect(result[3].secondarySize).toBe(100); + expect(result[3].primaryPos).toBe(90); + expect(result[3].secondaryPos).toBe(0); + }); + it('should work with a grid', function() { + var manager = setup(); + manager.dataSource.dimensions = [ + { width: 30, height: 30 }, + { width: 35, height: 30 }, + { width: 40, height: 30 }, + { width: 30, height: 30 }, + { width: 20, height: 30 }, + { width: 35, height: 30 }, + ]; + manager.secondaryScrollSize = function() { + return 90; + }; + var result = manager.calculateDimensions(); + expect(result[0].primarySize).toBe(30); + expect(result[0].secondarySize).toBe(30); + expect(result[0].primaryPos).toBe(0); + expect(result[0].secondaryPos).toBe(0); + + expect(result[1].primarySize).toBe(30); + expect(result[1].secondarySize).toBe(35); + expect(result[1].primaryPos).toBe(0); + expect(result[1].secondaryPos).toBe(30); + + expect(result[2].primarySize).toBe(30); + expect(result[2].secondarySize).toBe(40); + expect(result[2].primaryPos).toBe(30); + expect(result[2].secondaryPos).toBe(0); + + expect(result[3].primarySize).toBe(30); + expect(result[3].secondarySize).toBe(30); + expect(result[3].primaryPos).toBe(30); + expect(result[3].secondaryPos).toBe(40); + + expect(result[4].primarySize).toBe(30); + expect(result[4].secondarySize).toBe(20); + expect(result[4].primaryPos).toBe(30); + expect(result[4].secondaryPos).toBe(70); + + expect(result[5].primarySize).toBe(30); + expect(result[5].secondarySize).toBe(35); + expect(result[5].primaryPos).toBe(60); + expect(result[5].secondaryPos).toBe(0); + }); + }); + + describe('.resize()', function() { + it('should work without data', function() { + var manager = setup(); + spyOn(manager, 'render'); + spyOn(manager, 'calculateDimensions').andReturn([]); + spyOn(manager, 'setCurrentIndex'); + manager.resize(); + expect(manager.dimensions).toEqual([]); + expect(manager.viewportSize).toBe(0); + expect(manager.setCurrentIndex).toHaveBeenCalledWith(0); + expect(manager.render).toHaveBeenCalledWith(true); + }); + it('should work with data', function() { + var manager = setup(); + spyOn(manager, 'calculateDimensions').andReturn([{ + primaryPos: 100, primarySize: 30 + }]); + manager.resize(); + expect(manager.viewportSize).toBe(130); + }); + }); + + describe('.setCurrentIndex()', function() { + it('without prev or next', function() { + var manager = setup(); + spyOn(manager.dataSource, 'getLength').andReturn(0); + manager.setCurrentIndex(0); + expect(manager.currentIndex).toBe(0); + expect(manager.hasPrevIndex).toBe(false); + expect(manager.previousPos).toBeFalsy(); + expect(manager.hasNextIndex).toBe(false); + expect(manager.nextPos).toBeFalsy(); + }); + + it('with next', function() { + var manager = setup(); + spyOn(manager.dataSource, 'getLength').andReturn(2); + manager.dimensions = [{ primaryPos: 0 }, { primaryPos: 25 }]; + manager.setCurrentIndex(0); + expect(manager.currentIndex).toBe(0); + expect(manager.hasPrevIndex).toBe(false); + expect(manager.previousPos).toBeFalsy(); + expect(manager.hasNextIndex).toBe(true); + expect(manager.nextPos).toBe(25); + }); + + it('with prev', function() { + var manager = setup(); + spyOn(manager.dataSource, 'getLength').andReturn(2); + manager.dimensions = [{ primaryPos: 0 }, { primaryPos: 25 }]; + manager.setCurrentIndex(1); + expect(manager.currentIndex).toBe(1); + expect(manager.hasPrevIndex).toBe(true); + expect(manager.previousPos).toBe(0); + expect(manager.hasNextIndex).toBe(false); + expect(manager.nextPos).toBeFalsy(); + }); + + it('with next and prev', function() { + var manager = setup(); + spyOn(manager.dataSource, 'getLength').andReturn(3); + manager.dimensions = [{ primaryPos: 0 }, { primaryPos: 25 }, { primaryPos: 50 }]; + manager.setCurrentIndex(1); + expect(manager.currentIndex).toBe(1); + expect(manager.hasPrevIndex).toBe(true); + expect(manager.previousPos).toBe(0); + expect(manager.hasNextIndex).toBe(true); + expect(manager.nextPos).toBe(50); + }); + }); + + describe('.renderScroll()', function() { + it('with isVertical', function() { + var manager = setup(); + spyOn(manager, 'getTransformPosition').andReturn('banana'); + spyOn(manager.scrollView, '__$callback'); + manager.renderScroll(1, 2, 3, 4); + expect(manager.getTransformPosition).toHaveBeenCalledWith(2); + expect(manager.scrollView.__$callback).toHaveBeenCalledWith(1,'banana',3,4); + }); + + it('with !isVertical', function() { + var manager = setup(); + manager.isVertical = false; + spyOn(manager, 'getTransformPosition').andReturn('blueberry'); + spyOn(manager.scrollView, '__$callback'); + manager.renderScroll(1, 2, 3, 4); + expect(manager.getTransformPosition).toHaveBeenCalledWith(1); + expect(manager.scrollView.__$callback).toHaveBeenCalledWith('blueberry',2,3,4); + }); + }); + + describe('.getTransformPosition()', function() { + it('should return pos - lastRenderScrollValue', function() { + var manager = setup(); + manager.lastRenderScrollValue = 11; + expect(manager.getTransformPosition(44)).toBe(33); + }); + + it('should render if >= nextPos', function() { + var manager = setup(); + spyOn(manager, 'render'); + manager.hasNextIndex = true; + manager.nextPos = 30; + manager.getTransformPosition(20); + expect(manager.render).not.toHaveBeenCalled(); + manager.getTransformPosition(30); + expect(manager.render).toHaveBeenCalled(); + }); + + it('should render if < previousPos', function() { + var manager = setup(); + spyOn(manager, 'render'); + manager.hasPrevIndex = true; + manager.previousPos = 50; + manager.getTransformPosition(60); + expect(manager.render).not.toHaveBeenCalled(); + manager.getTransformPosition(50); + expect(manager.render).not.toHaveBeenCalled(); + manager.getTransformPosition(49); + expect(manager.render).toHaveBeenCalled(); + }); + + it('should render if abs(val)>100', function() { + var manager = setup(); + spyOn(manager, 'render'); + manager.getTransformPosition(60); + expect(manager.render).not.toHaveBeenCalled(); + manager.getTransformPosition(100); + expect(manager.render).not.toHaveBeenCalled(); + manager.getTransformPosition(101); + expect(manager.render).toHaveBeenCalled(); + + manager.render.reset(); + + manager.getTransformPosition(-60); + expect(manager.render).not.toHaveBeenCalled(); + manager.getTransformPosition(-100); + expect(manager.render).not.toHaveBeenCalled(); + manager.getTransformPosition(-101); + expect(manager.render).toHaveBeenCalled(); + }); + }); + + describe('.getIndexForScrollValue()', function() { + it('should scroll until it finds right position', function() { + var manager = setup(); + manager.dimensions = [ + { primaryPos: 0 }, + { primaryPos: 100 }, + { primaryPos: 200 }, + { primaryPos: 300 }, + { primaryPos: 400 }, + { primaryPos: 500 } + ]; + expect(manager.getIndexForScrollValue(3, 50)).toBe(1); + expect(manager.getIndexForScrollValue(1, 450)).toBe(4); + //bounds + expect(manager.getIndexForScrollValue(0, 1000)).toBe(5); + expect(manager.getIndexForScrollValue(2, -100)).toBe(0); + }); + }); + + describe('.render()', function() { + + it('currentIndex >= length should remove all and return', function() { + var manager = setup(); + manager.renderedItems = {'a':1, 'b':1}; + spyOn(manager, 'removeItem'); + expect(manager.render()).toBe(null); + expect(manager.removeItem).toHaveBeenCalledWith('a'); + expect(manager.removeItem).toHaveBeenCalledWith('b'); + }); + it('shouldRedrawAll should remove all', function() { + var manager = setup(); + manager.renderedItems = {'a':1, 'b':1}; + spyOn(manager, 'removeItem'); + manager.render(true); + expect(manager.removeItem).toHaveBeenCalledWith('a'); + expect(manager.removeItem).toHaveBeenCalledWith('b'); + }); + + //TODO test .render() logic + + }); + + describe('.renderItem()', function() { + it('should attachItem and set the element transform', function() { + var manager = setup(); + var item = { + element: [{ style: {} }] + }; + spyOn(manager.dataSource, 'getItem').andReturn(item); + spyOn(manager.dataSource, 'attachItem'); + manager.renderItem(0, 33, 44); + expect(manager.dataSource.attachItem).toHaveBeenCalledWith(item); + expect(item.element[0].style[ionic.CSS.TRANSFORM]) + .toEqual(manager.transformString(33, 44)); + expect(manager.renderedItems[0]).toBe(item); + }); + }); + + describe('.removeItem()', function() { + it('should detachItem', function() { + var manager = setup(); + var item = {}; + manager.renderedItems[0] = item; + spyOn(manager.dataSource, 'getItem').andReturn(item); + spyOn(manager.dataSource, 'detachItem'); + manager.removeItem(0); + expect(manager.dataSource.detachItem).toHaveBeenCalledWith(item); + expect(manager.renderedItems).toEqual({}); + }); + }); +});