Explorar el Código

tests for all things eventLimit related

Adam Shaw hace 11 años
padre
commit
4f0737fe7d

+ 43 - 0
tests/automated/dayPopoverFormat.js

@@ -0,0 +1,43 @@
+
+describe('dayPopoverFormat', function() {
+
+	var options;
+
+	beforeEach(function() {
+		affix('#cal');
+		options = {
+			defaultDate: '2014-08-01',
+			eventLimit: 3,
+			events: [
+				{ title: 'event1', start: '2014-07-28', end: '2014-07-30', className: 'event1' },
+				{ title: 'event2', start: '2014-07-29', end: '2014-07-31', className: 'event2' },
+				{ title: 'event3', start: '2014-07-29', className: 'event3' },
+				{ title: 'event4', start: '2014-07-29', className: 'event4' }
+			]
+		};
+	});
+
+	function init() {
+		$('#cal').fullCalendar(options);
+		$('.fc-more').simulate('click');
+	}
+
+	it('can be set to a custom value', function() {
+		options.dayPopoverFormat = 'ddd, MMMM';
+		init();
+		expect($('.fc-more-popover > .fc-header .fc-title')).toHaveText('Tue, July');
+	});
+
+	it('is affected by the current locale when the value is default', function() {
+		options.lang = 'fr';
+		init();
+		expect($('.fc-more-popover > .fc-header .fc-title')).toHaveText('29 juillet 2014');
+	});
+
+	it('still maintains the same format when explicitly set, and there is a lang', function() {
+		options.lang = 'fr';
+		options.dayPopoverFormat = 'YYYY';
+		init();
+		expect($('.fc-more-popover > .fc-header .fc-title')).toHaveText('2014');
+	});
+});

+ 212 - 0
tests/automated/eventLimit-popover.js

@@ -0,0 +1,212 @@
+
+describe('eventLimit popover', function() {
+
+	var options;
+
+	beforeEach(function() {
+		affix('#cal');
+		options = {
+			defaultView: 'month',
+			defaultDate: '2014-08-01',
+			eventLimit: 3,
+			events: [
+				{ title: 'event1', start: '2014-07-28', end: '2014-07-30', className: 'event1' },
+				{ title: 'event2', start: '2014-07-29', end: '2014-07-31', className: 'event2' },
+				{ title: 'event3', start: '2014-07-29', className: 'event3' },
+				{ title: 'event4', start: '2014-07-29', className: 'event4' }
+			]
+		}
+	});
+
+	function init() {
+		$('#cal').fullCalendar(options);
+		$('.fc-more').simulate('click');
+	}
+
+	[ 'month', 'basicWeek', 'agendaWeek' ].forEach(function(viewName) {
+
+		describe('when in ' + viewName + ' view', function() {
+
+			beforeEach(function() {
+				options.defaultView = viewName;
+			});
+
+			it('aligns horizontally with left edge of cell if LTR', function() {
+				options.isRTL = false;
+				init();
+				var cellLeft = $('.fc-day-grid .fc-row:eq(0) .fc-bg td:not(.fc-axis):eq(2)').offset().left;
+				var popoverLeft = $('.fc-more-popover').offset().left;
+				var diff = Math.abs(cellLeft - popoverLeft);
+				expect(diff).toBeLessThan(2);
+			});
+
+			it('aligns horizontally with left edge of cell if RTL', function() {
+				options.isRTL = true;
+				init();
+				var cell = $('.fc-day-grid .fc-row:eq(0) .fc-bg td:not(.fc-axis):eq(4)');
+				var cellRight = cell.offset().left + cell.outerWidth();
+				var popover = $('.fc-more-popover');
+				var popoverRight = popover.offset().left + popover.outerWidth();
+				var diff = Math.abs(cellRight - popoverRight);
+				expect(diff).toBeLessThan(2);
+			});
+		});
+	});
+
+	describe('when in month view', function() {
+
+		beforeEach(function() {
+			options.defaultView = 'month';
+		});
+
+		it('aligns with top of cell', function() {
+			init();
+			var popoverTop = $('.fc-more-popover').offset().top;
+			var rowTop = $('.fc-day-grid .fc-row:eq(0)').offset().top;
+			var diff = Math.abs(popoverTop - rowTop);
+			expect(diff).toBeLessThan(2);
+		});
+	});
+
+	[ 'basicWeek', 'agendaWeek' ].forEach(function(viewName) {
+
+		describe('when in ' + viewName + ' view', function() {
+
+			beforeEach(function() {
+				options.defaultView = viewName;
+			});
+
+			it('aligns with top of header', function() {
+				init();
+				var popoverTop = $('.fc-more-popover').offset().top;
+				var headTop = $('.fc-view > table > thead .fc-row').offset().top;
+				var diff = Math.abs(popoverTop - headTop);
+				expect(diff).toBeLessThan(2);
+			});
+		});
+	});
+
+	// TODO: somehow test how the popover does to the edge of any scroll container
+
+	it('closes when user clicks the X', function() {
+		init();
+		expect($('.fc-more-popover')).toBeVisible();
+		$('.fc-more-popover .fc-close').simulate('click');
+		expect($('.fc-more-popover')).not.toBeVisible();
+	});
+
+	it('doesn\'t close when user clicks somewhere inside of the popover', function() {
+		init();
+		expect($('.fc-more-popover')).toBeVisible();
+		expect($('.fc-more-popover .fc-header')).toBeInDOM();
+		$('.fc-more-popover .fc-header').simulate('mousedown').simulate('click');
+		expect($('.fc-more-popover')).toBeVisible();
+	});
+
+	it('closes when user clicks outside of the popover', function() {
+		init();
+		expect($('.fc-more-popover')).toBeVisible();
+		$('body').simulate('mousedown').simulate('click');
+		expect($('.fc-more-popover')).not.toBeVisible();
+	});
+
+	it('has the correct event contents', function() {
+		init();
+		expect($('.fc-more-popover .event1')).toBeMatchedBy('.fc-not-start.fc-end');
+		expect($('.fc-more-popover .event2')).toBeMatchedBy('.fc-start.fc-not-end');
+		expect($('.fc-more-popover .event3')).toBeMatchedBy('.fc-start.fc-end');
+		expect($('.fc-more-popover .event4')).toBeMatchedBy('.fc-start.fc-end');
+	});
+
+
+	describe('when dragging events out', function() {
+
+		beforeEach(function() {
+			options.editable = true;
+		});
+
+		describe('when dragging an all-day event to a different day', function() {
+
+			it('should have the new day and remain all-day', function(done) {
+
+				options.eventDrop = function(event) {
+					expect(event.start).toEqualMoment('2014-07-28');
+					expect(event.allDay).toBe(true);
+					done();
+				};
+				init();
+
+				setTimeout(function() { // simulate was getting confused about which thing was being clicked :(
+					$('.fc-more-popover .event4').simulate('drag-n-drop', {
+						dragTarget: $('.fc-day-grid .fc-row:eq(0) .fc-bg td:not(.fc-axis):eq(1)') // one day before
+					});
+				}, 0);
+			});
+		});
+
+		describe('when dragging a timed event to a whole day', function() {
+
+			it('should move to new day but maintain its time', function(done) {
+
+				options.events.push({ // add timed event
+					title: 'event5',
+					start: '2014-07-29T13:00:00',
+					className: 'event5'
+				});
+				options.eventDrop = function(event) {
+					expect(event.start).toEqualMoment('2014-07-28T13:00:00');
+					expect(event.allDay).toBe(false);
+					done();
+				};
+				init();
+
+				setTimeout(function() { // simulate was getting confused about which thing was being clicked :(
+					$('.fc-more-popover .event5').simulate('drag-n-drop', {
+						dragTarget: $('.fc-day-grid .fc-row:eq(0) .fc-bg td:not(.fc-axis):eq(1)') // one day before
+					});
+				}, 0);
+			});
+		});
+
+		describe('when dragging a whole day event to a timed slot', function() {
+
+			it('should assume the new time, with a cleared end', function(done) {
+
+				options.defaultView = 'agendaWeek';
+				options.scrollTime = '00:00:00';
+				options.eventDrop = function(event) {
+					expect(event.start).toEqualMoment('2014-07-30T03:00:00');
+					expect(event.allDay).toBe(false);
+					done();
+				};
+				init();
+
+				setTimeout(function() { // simulate was getting confused about which thing was being clicked :(
+					$('.fc-more-popover .event4').simulate('drag-n-drop', {
+						dragTarget: $('.fc-slats tr:eq(6)') // the middle will be 7/30, 3:00am
+					});
+				}, 0);
+			});
+		});
+
+		describe('when a single-day event isn\'t dragged out all the way', function() {
+
+			it('shouldn\'t do anything', function(done) {
+
+				options.eventDragStop = function() {
+					setTimeout(function() { // try to wait until drag is over. eventDrop won't fire BTW
+						expect($('.fc-more-popover')).toBeInDOM();
+						done();
+					},0);
+				};
+				init();
+
+				$('.fc-more-popover .event4').simulate('drag-n-drop', {
+					dx: 20
+				});
+			});
+		});
+
+	});
+
+});

+ 223 - 0
tests/automated/eventLimit.js

@@ -0,0 +1,223 @@
+
+describe('eventLimit', function() {
+
+	var options;
+
+	beforeEach(function() {
+		affix('#cal');
+		options = {
+			defaultDate: '2014-08-01' // important that it is the first week, so works w/ month + week views
+		};
+	});
+
+	describe('as a number', function() {
+
+		beforeEach(function() {
+			options.eventLimit = 3;
+		});
+
+		[ 'month', 'basicWeek', 'agendaWeek' ].forEach(function(viewName) {
+
+			describe('when in ' + viewName + ' view', function() {
+
+				beforeEach(function() {
+					options.defaultView = viewName;
+				});
+
+				it('doesn\'t display a more link when limit is more than the # of events', function() {
+					options.events = [
+						{ title: 'event1', start: '2014-07-29' },
+						{ title: 'event2', start: '2014-07-29' }
+					];
+					$('#cal').fullCalendar(options);
+					expect($('.fc-more').length).toBe(0);
+				});
+
+				it('doesn\'t display a more link when limit equal to the # of events', function() {
+					options.events = [
+						{ title: 'event1', start: '2014-07-29' },
+						{ title: 'event2', start: '2014-07-29' },
+						{ title: 'event2', start: '2014-07-29' }
+					];
+					$('#cal').fullCalendar(options);
+					expect($('.fc-more').length).toBe(0);
+				});
+
+				it('displays a more link when limit is less than the # of events', function() {
+					options.events = [
+						{ title: 'event1', start: '2014-07-29' },
+						{ title: 'event2', start: '2014-07-29' },
+						{ title: 'event2', start: '2014-07-29' },
+						{ title: 'event2', start: '2014-07-29' }
+					];
+					$('#cal').fullCalendar(options);
+					expect($('.fc-more').length).toBe(1);
+					expect($('.fc-more')).toHaveText('+2 more');
+				});
+
+				it('displays one more per day, when a multi-day event is above', function() {
+					options.events = [
+						{ title: 'event1', start: '2014-07-29', end: '2014-07-31' },
+						{ title: 'event2', start: '2014-07-29', end: '2014-07-31' },
+						{ title: 'event2', start: '2014-07-29', end: '2014-07-31' },
+						{ title: 'event2', start: '2014-07-29', end: '2014-07-31' }
+					];
+					$('#cal').fullCalendar(options);
+					var cells = $('.fc-day-grid .fc-row:eq(0) .fc-bg td:not(.fc-axis)');
+					expect($('.fc-more').length).toBe(2);
+					expect($('.fc-more').eq(0)).toHaveText('+2 more');
+					expect($('.fc-more').eq(0)).toBeBoundedBy(cells.eq(2));
+					expect($('.fc-more').eq(1)).toHaveText('+2 more');
+					expect($('.fc-more').eq(1)).toBeBoundedBy(cells.eq(3));
+				});
+
+				it('will render a link in a multi-day event\'s second column ' +
+					'if it has already been hidden in the first',
+				function() {
+					options.events = [
+						{ title: 'event1', start: '2014-07-29', end: '2014-07-31' },
+						{ title: 'event2', start: '2014-07-29', end: '2014-07-31' },
+						{ title: 'event2', start: '2014-07-29', end: '2014-07-31' },
+						{ title: 'event2', start: '2014-07-29' }
+					];
+					$('#cal').fullCalendar(options);
+					var cells = $('.fc-day-grid .fc-row:eq(0) .fc-bg td:not(.fc-axis)');
+					expect($('.fc-more').length).toBe(2);
+					expect($('.fc-more').eq(0)).toHaveText('+2 more');
+					expect($('.fc-more').eq(0)).toBeBoundedBy(cells.eq(2));
+					expect($('.fc-more').eq(1)).toHaveText('+1 more');
+					expect($('.fc-more').eq(1)).toBeBoundedBy(cells.eq(3));
+				});
+
+				it('will render a link in a multi-day event\'s second column ' +
+					'if it has already been hidden in the first even if he second column hardly has any events',
+				function() {
+					options.events = [
+						{ title: 'event1', start: '2014-07-28', end: '2014-07-30' },
+						{ title: 'event2', start: '2014-07-28', end: '2014-07-30' },
+						{ title: 'event2', start: '2014-07-28', end: '2014-07-30' },
+						{ title: 'event2', start: '2014-07-29', end: '2014-07-31' }
+					];
+					$('#cal').fullCalendar(options);
+					var cells = $('.fc-day-grid .fc-row:eq(0) .fc-bg td:not(.fc-axis)');
+					var link = $('.fc-more').eq(0); // will appear to be the third link, but will be in first row, so 0dom
+					expect(link.length).toBe(1);
+					expect(link).toHaveText('+1 more');
+					expect(link).toBeBoundedBy(cells.eq(3));
+				});
+
+				it('will render a link in place of a hidden single day event, if covered by a multi-day', function() {
+					options.events = [
+						{ title: 'event1', start: '2014-07-28', end: '2014-07-30' },
+						{ title: 'event2', start: '2014-07-28', end: '2014-07-30' },
+						{ title: 'event2', start: '2014-07-28' },
+						{ title: 'event2', start: '2014-07-28' }
+					];
+					$('#cal').fullCalendar(options);
+					var cells = $('.fc-day-grid .fc-row:eq(0) .fc-bg td:not(.fc-axis)');
+					var link = $('.fc-more').eq(0);
+					expect(link.length).toBe(1);
+					expect(link).toHaveText('+2 more');
+					expect(link).toBeBoundedBy(cells.eq(1));
+				});
+
+				it('will render a link in place of a hidden single day event, if covered by a multi-day ' +
+					'and in its second column',
+				function() {
+					options.events = [
+						{ title: 'event1', start: '2014-07-28', end: '2014-07-30' },
+						{ title: 'event2', start: '2014-07-28', end: '2014-07-30' },
+						{ title: 'event2', start: '2014-07-29' },
+						{ title: 'event2', start: '2014-07-29' }
+					];
+					$('#cal').fullCalendar(options);
+					var cells = $('.fc-day-grid .fc-row:eq(0) .fc-bg td:not(.fc-axis)');
+					var link = $('.fc-more').eq(0);
+					expect(link.length).toBe(1);
+					expect(link).toHaveText('+2 more');
+					expect(link).toBeBoundedBy(cells.eq(2));
+				});
+			});
+		});
+	});
+
+	describe('when auto', function() {
+
+		beforeEach(function() {
+			options.eventLimit = true;
+		});
+
+		describe('in month view', function() {
+
+			beforeEach(function() {
+				options.defaultView = 'month';
+			});
+
+			it('renders the heights of all the rows the same, regardless of # of events', function() {
+				options.events = [
+					{ title: 'event1', start: '2014-07-28', end: '2014-07-30' },
+					{ title: 'event2', start: '2014-07-28', end: '2014-07-30' },
+					{ title: 'event2', start: '2014-07-29' },
+					{ title: 'event2', start: '2014-07-29' },
+					{ title: 'event2', start: '2014-07-29' },
+					{ title: 'event2', start: '2014-07-29' },
+					{ title: 'event2', start: '2014-07-29' },
+					{ title: 'event2', start: '2014-07-29' },
+					{ title: 'event2', start: '2014-07-29' },
+					{ title: 'event2', start: '2014-07-29' },
+					{ title: 'event2', start: '2014-07-29' },
+					{ title: 'event2', start: '2014-07-29' },
+					{ title: 'event2', start: '2014-07-29' }
+				];
+				$('#cal').fullCalendar(options);
+				var rowEls = $('.fc-day-grid .fc-row').slice(0, -1); // remove last b/c it will be a different height
+				expect(rowEls.length).toBeGreaterThan(0);
+				var height = rowEls.height();
+				rowEls.each(function(i, node) {
+					expect($(node).height()).toBe(height);
+				});
+			});
+		});
+
+		[ 'month', 'basicWeek' ].forEach(function(viewName) {
+
+			describe('in ' + viewName + ' view', function() {
+
+				beforeEach(function() {
+					options.defaultView = viewName;
+				});
+
+				it('doesn\'t render a more link where there should obviously not be a limit', function() {
+					options.events = [
+						{ title: 'event1', start: '2014-07-28', end: '2014-07-30' }
+					];
+					$('#cal').fullCalendar(options);
+					expect($('.fc-more').length).toBe(0);
+				});
+			});
+		});
+
+		describe('in agendaWeek view', function() {
+
+			beforeEach(function() {
+				options.defaultView = 'agendaWeek';
+			});
+
+			it('behaves as if limit is 5', function() {
+				options.events = [
+					{ title: 'event1', start: '2014-07-29' },
+					{ title: 'event2', start: '2014-07-29' },
+					{ title: 'event2', start: '2014-07-29' },
+					{ title: 'event2', start: '2014-07-29' },
+					{ title: 'event2', start: '2014-07-29' },
+					{ title: 'event2', start: '2014-07-29' },
+					{ title: 'event2', start: '2014-07-29' }
+				];
+				$('#cal').fullCalendar(options);
+				expect($('.fc-event:visible').length).toBe(4);
+				expect($('.fc-more').length).toBe(1);
+				expect($('.fc-more')).toHaveText('+3 more');
+			});
+		});
+	});
+});

+ 135 - 0
tests/automated/eventLimitClick.js

@@ -0,0 +1,135 @@
+
+describe('eventLimitClick', function() { // simulate a click
+
+	var options;
+
+	beforeEach(function() {
+		affix('#cal');
+		options = {
+			defaultDate: '2014-08-01', // important that it is the first week, so works w/ month + week views
+			defaultView: 'month',
+			eventLimit: 3,
+			events: [
+				{ title: 'event1', start: '2014-07-29' },
+				{ title: 'event2', start: '2014-07-29' },
+				{ title: 'event2', start: '2014-07-29' },
+				{ title: 'event2', start: '2014-07-29' }
+			]
+		};
+	});
+
+	describe('when set to "popover"', function() {
+
+		beforeEach(function() {
+			options.eventLimitClick = 'popover';
+		});
+
+		it('renders a popover upon click', function() {
+			$('#cal').fullCalendar(options);
+			$('.fc-more').simulate('click');
+			expect($('.fc-more-popover')).toBeVisible();
+		});
+
+		// more popover tests are done in eventLimit-popover
+	});
+
+	describe('when set to "week"', function() {
+
+		beforeEach(function() {
+			options.eventLimitClick = 'week';
+		});
+
+		it('should go to basicWeek if it is one of the available views', function() {
+			options.header = {
+				left: 'prev,next today',
+				center: 'title',
+				right: 'month,basicWeek,basicDay'
+			};
+			$('#cal').fullCalendar(options);
+			$('.fc-more').simulate('click');
+			var view = $('#cal').fullCalendar('getView');
+			expect(view.name).toBe('basicWeek');
+		});
+
+		it('should go to agendaWeek if it is one of the available views', function() {
+			options.header = {
+				left: 'prev,next today',
+				center: 'title',
+				right: 'month,agendaWeek,agendaDay'
+			};
+			$('#cal').fullCalendar(options);
+			$('.fc-more').simulate('click');
+			var view = $('#cal').fullCalendar('getView');
+			expect(view.name).toBe('agendaWeek');
+		});
+	});
+
+	describe('when set to "day"', function() {
+
+		beforeEach(function() {
+			options.eventLimitClick = 'day';
+		});
+
+		it('should go to basicDay if it is one of the available views', function() {
+			options.header = {
+				left: 'prev,next today',
+				center: 'title',
+				right: 'month,basicWeek,basicDay'
+			};
+			$('#cal').fullCalendar(options);
+			$('.fc-more').simulate('click');
+			var view = $('#cal').fullCalendar('getView');
+			expect(view.name).toBe('basicDay');
+		});
+
+		it('should go to agendaDay if it is one of the available views', function() {
+			options.header = {
+				left: 'prev,next today',
+				center: 'title',
+				right: 'month,agendaWeek,agendaDay'
+			};
+			$('#cal').fullCalendar(options);
+			$('.fc-more').simulate('click');
+			var view = $('#cal').fullCalendar('getView');
+			expect(view.name).toBe('agendaDay');
+		});
+	});
+
+	it('works with an explicit view name', function() {
+		options.eventLimitClick = 'agendaWeek';
+		options.header = {
+			left: 'prev,next today',
+			center: 'title',
+			right: 'month,basicWeek,basicDay'
+		};
+		$('#cal').fullCalendar(options);
+		$('.fc-more').simulate('click');
+		var view = $('#cal').fullCalendar('getView');
+		expect(view.name).toBe('agendaWeek');
+	});
+
+	it('works with custom function and all the arguments are correct', function() {
+		options.eventLimitClick = function(cellInfo, jsEvent) {
+			expect(typeof cellInfo).toBe('object');
+			expect(typeof jsEvent).toBe('object');
+			expect(cellInfo.date).toEqualMoment('2014-07-29');
+			expect(cellInfo.dayEl.data('date')).toBe('2014-07-29');
+			expect(cellInfo.hiddenSegs.length).toBe(2);
+			expect(cellInfo.segs.length).toBe(4);
+			expect(cellInfo.moreEl).toHaveClass('fc-more');
+		};
+		$('#cal').fullCalendar(options);
+		$('.fc-more').simulate('click');
+	});
+
+	it('works with custom function, and can return a view name', function() {
+		options.eventLimitClick = function(cellInfo, jsEvent) {
+			return 'agendaDay';
+		};
+		$('#cal').fullCalendar(options);
+		$('.fc-more').simulate('click');
+		var view = $('#cal').fullCalendar('getView');
+		expect(view.name).toBe('agendaDay');
+	});
+
+});

+ 48 - 0
tests/automated/eventLimitText.js

@@ -0,0 +1,48 @@
+
+describe('eventLimitText', function() {
+
+	var options;
+
+	beforeEach(function() {
+		affix('#cal');
+		options = {
+			defaultDate: '2014-08-01', // important that it is the first week, so works w/ month + week views
+			defaultView: 'month',
+			eventLimit: 3,
+			events: [
+				{ title: 'event1', start: '2014-07-29' },
+				{ title: 'event2', start: '2014-07-29' },
+				{ title: 'event2', start: '2014-07-29' },
+				{ title: 'event2', start: '2014-07-29' }
+			]
+		};
+	});
+
+	it('allows a string', function() {
+		options.eventLimitText = 'extra';
+		$('#cal').fullCalendar(options);
+		expect($('.fc-more')).toHaveText('+2 extra');
+	});
+
+	it('allows a function', function() {
+		options.eventLimitText = function(n) {
+			expect(typeof n).toBe('number');
+			return 'there are ' + n + ' more events!';
+		};
+		$('#cal').fullCalendar(options);
+		expect($('.fc-more')).toHaveText('there are 2 more events!');
+	});
+
+	it('has a default value that is affected by the custom locale', function() {
+		options.lang = 'fr';
+		$('#cal').fullCalendar(options);
+		expect($('.fc-more')).toHaveText('+2 en plus');
+	});
+
+	it('is not affected by a custom locale when the value is explicitly specified', function() {
+		options.lang = 'fr';
+		options.eventLimitText = 'extra';
+		$('#cal').fullCalendar(options);
+		expect($('.fc-more')).toHaveText('+2 extra');
+	});
+});

+ 38 - 0
tests/lib/jasmine-ext.js

@@ -8,6 +8,9 @@ beforeEach(function() {
 	moment.suppressDeprecationWarnings = true;
 	moment.suppressDeprecationWarnings = true;
 
 
 	jasmine.addMatchers({
 	jasmine.addMatchers({
+
+		// Moment and Duration
+
 		toEqualMoment: function() {
 		toEqualMoment: function() {
 			return {
 			return {
 				compare: function(actual, expected) {
 				compare: function(actual, expected) {
@@ -51,7 +54,30 @@ beforeEach(function() {
 					return result;
 					return result;
 				}
 				}
 			};
 			};
+		},
+
+
+		// Geometry
+
+		toBeBoundedBy: function() {
+			return {
+				compare: function(actual, expected) {
+					var outer = getBounds(expected);
+					var inner = getBounds(actual);
+					var result = {
+						pass: inner.left >= outer.left &&
+							inner.right <= outer.right &&
+							inner.top >= outer.top &&
+							inner.bottom <= outer.bottom
+					};
+					if (!result.pass) {
+						result.message = 'Element does not bound other element';
+					}
+					return result;
+				}
+			};
 		}
 		}
+
 	});
 	});
 
 
 	function serializeDuration(duration) {
 	function serializeDuration(duration) {
@@ -67,4 +93,16 @@ beforeEach(function() {
 		return n.length >= width ? n : new Array(width - n.length + 1).join('0') + n;
 		return n.length >= width ? n : new Array(width - n.length + 1).join('0') + n;
 	}
 	}
 
 
+	function getBounds(node) {
+		var el = $(node);
+		var offset = el.offset();
+
+		return {
+			top: offset.top,
+			left: offset.left,
+			right: offset.left + el.outerWidth(),
+			bottom: offset.top + el.outerHeight()
+		};
+	}
+
 });
 });