Procházet zdrojové kódy

Merge remote-tracking branch 'upstream/master'

Axel Duch před 9 roky
rodič
revize
d2fda41df0

+ 11 - 0
CHANGELOG.md

@@ -1,4 +1,15 @@
 
+v3.0.1 (2016-09-26)
+-------------------
+
+Bugfixes:
+- list view rendering event times incorrectly (#3334)
+- list view rendering events/days out of order (#3347)
+- events with no title rendering as "undefined"
+- add .fc scope to table print styles (#3343)
+- "display no events" text fix for German (#3354)
+
+
 v3.0.0 (2016-09-04)
 -------------------
 

+ 20 - 13
bin/build-release.sh

@@ -29,33 +29,30 @@ then
 fi
 
 success=0
-if ! {
+if {
 	# make sure deps are as new as possible for bundle
-	npm install && \
+	npm install &&
 
 	# ensures stray files stay out of the release
-	gulp clean && \
+	gulp clean &&
 
 	# update package manager json files with version number and release date
-	gulp bump --version=$version && \
+	gulp bump --version=$version &&
 
 	# build all dist files, lint, and run tests
 	gulp release
 }
 then
-	# failure. discard changes from version bump
-	git checkout -- *.json
-else
 	# save reference to current branch
 	current_branch=$(git symbolic-ref --quiet --short HEAD)
 
 	# make a tagged detached commit of the dist files.
-	# no-verify (-n) avoids commit hooks.
+	# no-verify avoids commit hooks.
 	if {
-		git checkout --detach --quiet && \
-		git add *.json && \
-		git add -f dist/*.js dist/*.css dist/locale/*.js && \
-		git commit -n -e -m "version $version" && \
+		git checkout --quiet --detach &&
+		git add *.json &&
+		git add -f dist/*.js dist/*.css dist/locale/*.js &&
+		git commit --quiet --no-verify -e -m "version $version" &&
 		git tag -a "v$version" -m "version $version"
 	}
 	then
@@ -66,10 +63,20 @@ else
 	git checkout --quiet "$current_branch"
 fi
 
-if [[ "$success" = "1" ]]
+if [[ "$success" == "1" ]]
 then
+	# keep newly generated dist files around
+	git checkout --quiet "v$version" -- dist
+	git reset --quiet -- dist
+
 	echo "Success."
 else
+	# unstage all dist/ or *.json changes
+	git reset --quiet
+
+	# discard changes from version bump
+	git checkout --quiet -- *.json
+
 	echo "Failure."
 	exit 1
 fi

+ 5 - 1
bin/publish-release.sh

@@ -34,7 +34,11 @@ fi
 # return to branch
 git checkout --quiet "$current_branch"
 
-if [[ "$success" = "1" ]]
+# restore generated dist files
+git checkout --quiet "v$version" -- dist
+git reset --quiet -- dist
+
+if [[ "$success" == "1" ]]
 then
 	echo "Success."
 else

+ 2 - 2
bin/update-demo-dates.sh

@@ -25,6 +25,6 @@ find demos -type f \( -name '*.html' -o -name '*.json' \) -print0 \
 
 # build the commit
 git add demos
-git commit -m "updated demo dates"
+git commit --quiet -m "updated demo dates"
 
-echo "DONE"
+echo "Success."

+ 1 - 1
demos/gcal.html

@@ -20,7 +20,7 @@
 				right: 'month,listYear'
 			},
 
-			listTime: false, // don't show the time column in list view
+			displayEventTime: false, // don't show the time column in list view
 
 			// THIS KEY WON'T WORK IN PRODUCTION!!!
 			// To make your own Google API key, follow the directions here:

+ 6 - 6
karma.conf.js

@@ -37,12 +37,12 @@ module.exports = function(config) {
 			//'../fullcalendar-scheduler/dist/scheduler.js',
 			//'../fullcalendar-scheduler/dist/scheduler.css',
 
-			// serve everything in the dist directory, like sourcemaps and the files they reference (in dist/src).
-			// above files take precedence of over this, and will be watched. never cache (always serve from disk).
-			{ pattern: 'dist/**/*', included: false, watched: false, nocache: true },
-
-			// serve assets for 3rd-party libs, like jquery-ui theme images.
-			{ pattern: 'node_modules/**/*', included: false, watched: false, nocache: true },
+			// we want everything in these directories to be served, but not included as script tags:
+			//   dist - for sourcemap files
+			//   src - for source files the sourcemap references
+			//   node_modules - 3rd party lib dependencies, like jquery-ui theme images
+			// (don't let the webserver cache the results)
+			{ pattern: '{dist,src,node_modules}/**/*', included: false, watched: false, nocache: true },
 
 			'tests/automated/*.js'
 		],

+ 1 - 1
locale/de.js

@@ -10,5 +10,5 @@ $.fullCalendar.locale("de", {
 	eventLimitText: function(n) {
 		return "+ weitere " + n;
 	},
-	noEventsMessage: "Keine Ereignisse anzeigen"
+	noEventsMessage: "Keine Ereignisse anzuzeigen"
 });

+ 5 - 5
src/common/print.css

@@ -32,11 +32,11 @@
 /* Table & Day-Row Restyling
 --------------------------------------------------------------------------------------------------*/
 
-th,
-td,
-hr,
-thead,
-tbody,
+.fc th,
+.fc td,
+.fc hr,
+.fc thead,
+.fc tbody,
 .fc-row {
 	border-color: #ccc !important;
 	background: #fff !important;

+ 43 - 0
src/date-formatting.js

@@ -230,3 +230,46 @@ function chunkFormatString(formatStr) {
 
 	return chunks;
 }
+
+
+// Misc Utils
+// -------------------------------------------------------------------------------------------------
+
+
+// granularity only goes up until day
+// TODO: unify with similarUnitMap
+var tokenGranularities = {
+	Y: { value: 1, unit: 'year' },
+	M: { value: 2, unit: 'month' },
+	W: { value: 3, unit: 'week' },
+	w: { value: 3, unit: 'week' },
+	D: { value: 4, unit: 'day' }, // day of month
+	d: { value: 4, unit: 'day' } // day of week
+};
+
+// returns a unit string, either 'year', 'month', 'day', or null
+// for the most granular formatting token in the string.
+FC.queryMostGranularFormatUnit = function(formatStr) {
+	var chunks = getFormatStringChunks(formatStr);
+	var i, chunk;
+	var candidate;
+	var best;
+
+	for (i = 0; i < chunks.length; i++) {
+		chunk = chunks[i];
+		if (chunk.token) {
+			candidate = tokenGranularities[chunk.token.charAt(0)];
+			if (candidate) {
+				if (!best || candidate.value > best.value) {
+					best = candidate;
+				}
+			}
+		}
+	}
+
+	if (best) {
+		return best.unit;
+	}
+
+	return null;
+};

+ 67 - 27
src/list/ListView.js

@@ -76,21 +76,36 @@ var ListViewGrid = Grid.extend({
 	// slices by day
 	spanToSegs: function(span) {
 		var view = this.view;
-		var dayStart = view.start.clone();
-		var dayEnd;
+		var dayStart = view.start.clone().time(0); // timed, so segs get times!
+		var dayIndex = 0;
 		var seg;
 		var segs = [];
 
 		while (dayStart < view.end) {
-			dayEnd = dayStart.clone().add(1, 'day');
+
 			seg = intersectRanges(span, {
 				start: dayStart,
-				end: dayEnd
+				end: dayStart.clone().add(1, 'day')
 			});
+
 			if (seg) {
+				seg.dayIndex = dayIndex;
 				segs.push(seg);
 			}
-			dayStart = dayEnd;
+
+			dayStart.add(1, 'day');
+			dayIndex++;
+
+			// detect when span won't go fully into the next day,
+			// and mutate the latest seg to the be the end.
+			if (
+				seg && !seg.isEnd && span.end.hasTime() &&
+				span.end < dayStart.clone().add(this.view.nextDayThreshold)
+			) {
+				seg.end = span.end.clone();
+				seg.isEnd = true;
+				break;
+			}
 		}
 
 		return segs;
@@ -123,11 +138,12 @@ var ListViewGrid = Grid.extend({
 
 		if (!segs.length) {
 			this.renderEmptyMessage();
-			return segs;
 		}
 		else {
-			return this.renderSegList(segs);
+			this.renderSegList(segs);
 		}
+
+		return segs;
 	},
 
 	renderEmptyMessage: function() {
@@ -142,30 +158,47 @@ var ListViewGrid = Grid.extend({
 		);
 	},
 
-	// render the event segments in the view. returns the mutated array.
-	renderSegList: function(segs) {
+	// render the event segments in the view
+	renderSegList: function(allSegs) {
+		var segsByDay = this.groupSegsByDay(allSegs); // sparse array
+		var dayIndex;
+		var daySegs;
+		var i;
 		var tableEl = $('<table class="fc-list-table"><tbody/></table>');
 		var tbodyEl = tableEl.find('tbody');
-		var i, seg;
-		var dayDate;
 
-		this.sortEventSegs(segs);
+		for (dayIndex = 0; dayIndex < segsByDay.length; dayIndex++) {
+			daySegs = segsByDay[dayIndex];
+			if (daySegs) { // sparse array, so might be undefined
 
-		for (i = 0; i < segs.length; i++) {
-			seg = segs[i];
+				// append a day header
+				tbodyEl.append(this.dayHeaderHtml(
+					this.view.start.clone().add(dayIndex, 'days')
+				));
 
-			// append a day header
-			if (!dayDate || !seg.start.isSame(dayDate, 'day')) {
-				dayDate = seg.start.clone().stripTime();
-				tbodyEl.append(this.dayHeaderHtml(dayDate));
-			}
+				this.sortEventSegs(daySegs);
 
-			tbodyEl.append(seg.el); // append event row
+				for (i = 0; i < daySegs.length; i++) {
+					tbodyEl.append(daySegs[i].el); // append event row
+				}
+			}
 		}
 
 		this.el.empty().append(tableEl);
+	},
+
+	// Returns a sparse array of arrays, segs grouped by their dayIndex
+	groupSegsByDay: function(segs) {
+		var segsByDay = []; // sparse array
+		var i, seg;
+
+		for (i = 0; i < segs.length; i++) {
+			seg = segs[i];
+			(segsByDay[seg.dayIndex] || (segsByDay[seg.dayIndex] = []))
+				.push(seg);
+		}
 
-		return segs; // return the sorted list
+		return segsByDay;
 	},
 
 	// generates the HTML for the day headers that live amongst the event rows
@@ -203,13 +236,20 @@ var ListViewGrid = Grid.extend({
 		var url = event.url;
 		var timeHtml;
 
-		if (!seg.start.hasTime()) {
-			if (this.displayEventTime) {
+		if (event.allDay) {
+			timeHtml = view.getAllDayHtml();
+		}
+		else if (view.isMultiDayEvent(event)) { // if the event appears to span more than one day
+			if (seg.isStart || seg.isEnd) { // outer segment that probably lasts part of the day
+				timeHtml = htmlEscape(this.getEventTimeText(seg));
+			}
+			else { // inner segment that lasts the whole day
 				timeHtml = view.getAllDayHtml();
 			}
 		}
 		else {
-			timeHtml = htmlEscape(this.getEventTimeText(event)); // might return empty
+			// Display the normal time text for the *event's* times
+			timeHtml = htmlEscape(this.getEventTimeText(event));
 		}
 
 		if (url) {
@@ -217,9 +257,9 @@ var ListViewGrid = Grid.extend({
 		}
 
 		return '<tr class="' + classes.join(' ') + '">' +
-			(timeHtml ?
+			(this.displayEventTime ?
 				'<td class="fc-list-item-time ' + view.widgetContentClass + '">' +
-					timeHtml +
+					(timeHtml || '') +
 				'</td>' :
 				'') +
 			'<td class="fc-list-item-marker ' + view.widgetContentClass + '">' +
@@ -231,7 +271,7 @@ var ListViewGrid = Grid.extend({
 			'</td>' +
 			'<td class="fc-list-item-title ' + view.widgetContentClass + '">' +
 				'<a' + (url ? ' href="' + htmlEscape(url) + '"' : '') + '>' +
-					htmlEscape(seg.event.title) +
+					htmlEscape(seg.event.title || '') +
 				'</a>' +
 			'</td>' +
 		'</tr>';

+ 0 - 1
src/list/config.js

@@ -4,7 +4,6 @@ fcViews.list = {
 	buttonTextKey: 'list', // what to lookup in locale files
 	defaults: {
 		buttonText: 'list', // text to display for English
-		listTime: true, // show the time column?
 		listDayFormat: 'LL', // like "January 1, 2016"
 		noEventsMessage: 'No events to display'
 	}

+ 3 - 2
tasks/test.js

@@ -4,8 +4,9 @@ var KarmaServer = require('karma').Server;
 
 var karmaConf = path.join(__dirname, '../karma.conf.js'); // was getting confused with relative URLs
 
-// runs a server, outputs a URL to visit
-gulp.task('test', [ 'modules', 'locale' ], function(done) {
+// runs a server, outputs a URL to visit.
+// we want sourcemaps (modules:dev).
+gulp.task('test', [ 'modules:dev', 'locale' ], function(done) {
 	new KarmaServer({
 		configFile: karmaConf,
 		singleRun: false,

+ 285 - 79
tests/automated/ListView.js

@@ -10,121 +10,241 @@ describe('ListView rendering', function() {
 	});
 
 	describe('with all-day events', function() {
-		beforeEach(function() {
-			options.events = [
-				{
-					title: 'event 1',
-					start: '2016-08-15'
-				},
-				{
-					title: 'event 2',
-					start: '2016-08-17'
-				}
-			];
-		});
 
-		it('renders only days with events', function() {
-			$('#cal').fullCalendar(options);
+		describe('when single-day', function() {
+			beforeEach(function() {
+				options.events = [
+					{
+						title: 'event 1',
+						start: '2016-08-15'
+					},
+					{
+						title: 'event 2',
+						start: '2016-08-17'
+					}
+				];
+			});
 
-			var days = getDayInfo();
-			var events = getEventInfo();
+			it('renders only days with events', function() {
+				$('#cal').fullCalendar(options);
 
-			expect(days.length).toBe(2);
-			expect(days[0].date.format()).toEqual('2016-08-15');
-			expect(days[1].date.format()).toEqual('2016-08-17');
+				var days = getDayInfo();
+				var events = getEventInfo();
 
-			expect(events.length).toBe(2);
-			expect(events[0].title).toBe('event 1');
-			expect(events[0].timeText).toBe('all-day');
-			expect(events[1].title).toBe('event 2');
-			expect(events[1].timeText).toBe('all-day');
+				expect(days.length).toBe(2);
+				expect(days[0].date.format()).toEqual('2016-08-15');
+				expect(days[1].date.format()).toEqual('2016-08-17');
+
+				expect(events.length).toBe(2);
+				expect(events[0].title).toBe('event 1');
+				expect(events[0].timeText).toBe('all-day');
+				expect(events[1].title).toBe('event 2');
+				expect(events[1].timeText).toBe('all-day');
+			});
+
+			it('filters events through eventRender', function() {
+				options.eventRender = function(event, el) {
+					el.find('.fc-event-dot').replaceWith('<span class="custom-icon" />');
+				};
+
+				$('#cal').fullCalendar(options);
+
+				expect($('.custom-icon').length).toBe(2);
+			});
 		});
 
-		it('filters events through eventRender', function() {
-			options.eventRender = function(event, el) {
-				el.find('.fc-event-dot').replaceWith('<span class="custom-icon" />');
-			};
+		describe('when multi-day', function() {
+			beforeEach(function() {
+				options.events = [
+					{
+						title: 'event 1',
+						start: '2016-08-15',
+						end: '2016-08-18' // 3 days
+					}
+				];
+			});
 
-			$('#cal').fullCalendar(options);
+			it('renders all-day for every day', function() {
+				$('#cal').fullCalendar(options);
+
+				var events = getEventInfo();
 
-			expect($('.custom-icon').length).toBe(2);
+				expect(events.length).toBe(3);
+				expect(events[0].title).toBe('event 1');
+				expect(events[0].timeText).toBe('all-day');
+				expect(events[1].title).toBe('event 1');
+				expect(events[1].timeText).toBe('all-day');
+				expect(events[2].title).toBe('event 1');
+				expect(events[2].timeText).toBe('all-day');
+			});
 		});
 	});
 
 	describe('with timed events', function() {
-		beforeEach(function() {
+
+		describe('when single-day', function() {
+			beforeEach(function() {
+				options.events = [
+					{
+						title: 'event 1',
+						start: '2016-08-15T07:00'
+					},
+					{
+						title: 'event 2',
+						start: '2016-08-17T09:00',
+						end: '2016-08-17T11:00'
+					}
+				];
+			});
+
+			it('renders times', function() {
+				$('#cal').fullCalendar(options);
+
+				var events = getEventInfo();
+
+				expect(events.length).toBe(2);
+				expect(events[0].title).toBe('event 1');
+				expect(events[0].timeText).toBe('7:00am');
+				expect(events[1].title).toBe('event 2');
+				expect(events[1].timeText).toBe('9:00am - 11:00am');
+			});
+
+			it('doesn\'t render times when displayEventTime is false', function() {
+				options.displayEventTime = false;
+				$('#cal').fullCalendar(options);
+
+				var events = getEventInfo();
+
+				expect(events.length).toBe(2);
+				expect(events[0].title).toBe('event 1');
+				expect(events[0].timeText).toBe('');
+				expect(events[1].title).toBe('event 2');
+				expect(events[1].timeText).toBe('');
+			});
+
+			it('doesn\'t render end times when displayEventEnd is false', function() {
+				options.displayEventEnd = false;
+				$('#cal').fullCalendar(options);
+
+				var events = getEventInfo();
+
+				expect(events.length).toBe(2);
+				expect(events[0].title).toBe('event 1');
+				expect(events[0].timeText).toBe('7:00am');
+				expect(events[1].title).toBe('event 2');
+				expect(events[1].timeText).toBe('9:00am');
+			});
+
+			// regression test for when localized event dates get unlocalized and leak into view rendering
+			it('renders dates and times in locale', function() {
+				options.locale = 'fr';
+				$('#cal').fullCalendar(options);
+
+				var days = getDayInfo();
+				var events = getEventInfo();
+
+				expect(days.length).toBe(2);
+				expect(days[0].date.format()).toEqual('2016-08-15');
+				expect(days[0].mainText).toEqual('lundi');
+				expect(days[0].altText).toEqual('15 août 2016');
+				expect(days[1].date.format()).toEqual('2016-08-17');
+				expect(days[1].mainText).toEqual('mercredi');
+				expect(days[1].altText).toEqual('17 août 2016');
+
+				expect(events.length).toBe(2);
+				expect(events[0].title).toBe('event 1');
+				expect(events[0].timeText).toBe('07:00');
+				expect(events[1].title).toBe('event 2');
+				expect(events[1].timeText).toBe('09:00 - 11:00');
+			});
+		});
+
+		describe('when multi-day', function() {
+			beforeEach(function() {
+				options.nextDayThreshold = '00:00';
+				options.events = [
+					{
+						title: 'event 1',
+						start: '2016-08-15T07:00',
+						end: '2016-08-17T11:00'
+					}
+				];
+			});
+
+			it('renders partial and full days', function() {
+				$('#cal').fullCalendar(options);
+
+				var events = getEventInfo();
+
+				expect(events.length).toBe(3);
+				expect(events[0].title).toBe('event 1');
+				expect(events[0].timeText).toBe('7:00am - 12:00am');
+				expect(events[1].title).toBe('event 1');
+				expect(events[1].timeText).toBe('all-day');
+				expect(events[2].title).toBe('event 1');
+				expect(events[2].timeText).toBe('12:00am - 11:00am');
+			});
+		});
+
+		it('renders same days when equal to nextDayThreshold', function() {
+			options.nextDayThreshold = '09:00';
 			options.events = [
 				{
 					title: 'event 1',
-					start: '2016-08-15T07:00'
-				},
-				{
-					title: 'event 2',
-					start: '2016-08-17T09:00',
-					end: '2016-08-17T11:00',
+					start: '2016-08-15T07:00',
+					end: '2016-08-17T09:00'
 				}
 			];
-		});
 
-		it('renders times', function() {
 			$('#cal').fullCalendar(options);
 
 			var events = getEventInfo();
 
-			expect(events.length).toBe(2);
+			expect(events.length).toBe(3);
 			expect(events[0].title).toBe('event 1');
-			expect(events[0].timeText).toBe('7:00am');
-			expect(events[1].title).toBe('event 2');
-			expect(events[1].timeText).toBe('9:00am - 11:00am');
+			expect(events[0].timeText).toBe('7:00am - 12:00am');
+			expect(events[1].title).toBe('event 1');
+			expect(events[1].timeText).toBe('all-day');
+			expect(events[2].title).toBe('event 1');
+			expect(events[2].timeText).toBe('12:00am - 9:00am');
 		});
 
-		it('doesn\'t render times when displayEventTime is false', function() {
-			options.displayEventTime = false;
-			$('#cal').fullCalendar(options);
-
-			var events = getEventInfo();
-
-			expect(events.length).toBe(2);
-			expect(events[0].title).toBe('event 1');
-			expect(events[0].timeText).toBe('');
-			expect(events[1].title).toBe('event 2');
-			expect(events[1].timeText).toBe('');
-		});
+		it('renders fewer days when before nextDayThreshold', function() {
+			options.nextDayThreshold = '09:00';
+			options.events = [
+				{
+					title: 'event 1',
+					start: '2016-08-15T07:00',
+					end: '2016-08-17T08:00'
+				}
+			];
 
-		it('doesn\'t render end times when displayEventEnd is false', function() {
-			options.displayEventEnd = false;
 			$('#cal').fullCalendar(options);
 
 			var events = getEventInfo();
 
 			expect(events.length).toBe(2);
 			expect(events[0].title).toBe('event 1');
-			expect(events[0].timeText).toBe('7:00am');
-			expect(events[1].title).toBe('event 2');
-			expect(events[1].timeText).toBe('9:00am');
+			expect(events[0].timeText).toBe('7:00am - 12:00am');
+			expect(events[1].title).toBe('event 1');
+			expect(events[1].timeText).toBe('12:00am - 8:00am');
 		});
+	});
 
-		// regression test for when localized event dates get unlocalized and leak into view rendering
-		it('renders dates and times in locale', function() {
-			options.locale = 'fr';
+	describe('when an event has no title', function() {
+		it('renders no text for its title', function() {
+			options.events = [
+				{
+					start: '2016-08-15'
+				}
+			];
 			$('#cal').fullCalendar(options);
 
-			var days = getDayInfo();
 			var events = getEventInfo();
 
-			expect(days.length).toBe(2);
-			expect(days[0].date.format()).toEqual('2016-08-15');
-			expect(days[0].mainText).toEqual('lundi');
-			expect(days[0].altText).toEqual('15 août 2016');
-			expect(days[1].date.format()).toEqual('2016-08-17');
-			expect(days[1].mainText).toEqual('mercredi');
-			expect(days[1].altText).toEqual('17 août 2016');
-
-			expect(events.length).toBe(2);
-			expect(events[0].title).toBe('event 1');
-			expect(events[0].timeText).toBe('07:00');
-			expect(events[1].title).toBe('event 2');
-			expect(events[1].timeText).toBe('09:00 - 11:00');
+			expect(events.length).toBe(1);
+			expect(events[0].title).toBe('');
+			expect(events[0].timeText).toBe('all-day');
 		});
 	});
 
@@ -135,6 +255,92 @@ describe('ListView rendering', function() {
 		});
 	});
 
+	it('sorts events correctly', function() {
+		options.now = '2016-08-29';
+		options.events = [
+			{
+				title: 'All Day Event',
+				start: '2016-08-29'
+			},
+			{
+				title: 'Long Event',
+				start: '2016-08-28',
+				end: '2016-09-04'
+			},
+			{
+				title: 'Meeting',
+				start: '2016-08-29T10:30:00'
+			},
+			{
+				title: 'Lunch',
+				start: '2016-08-30T12:00:00'
+			},
+			{
+				title: 'Meeting',
+				start: '2016-08-30T14:30:00'
+			},
+			{
+				title: 'Happy Hour',
+				start: '2014-11-12T17:30:00'
+			},
+			{
+				title: 'Dinner',
+				start: '2014-11-12T20:00:00'
+			},
+			{
+				title: 'Birthday Party',
+				start: '2016-08-29T07:00:00'
+			},
+			{
+				title: 'Click for Google',
+				url: 'http://google.com/',
+				start: '2016-08-31'
+			}
+		];
+
+		$('#cal').fullCalendar(options);
+
+		var days = getDayInfo();
+		var events = getEventInfo();
+
+		expect(days.length).toBe(7);
+		expect(days[0].date.format()).toEqual('2016-08-28');
+		expect(days[1].date.format()).toEqual('2016-08-29');
+		expect(days[2].date.format()).toEqual('2016-08-30');
+		expect(days[3].date.format()).toEqual('2016-08-31');
+		expect(days[4].date.format()).toEqual('2016-09-01');
+		expect(days[5].date.format()).toEqual('2016-09-02');
+		expect(days[6].date.format()).toEqual('2016-09-03');
+
+		expect(events.length).toBe(13);
+		expect(events[0].title).toBe('Long Event');
+		expect(events[0].timeText).toBe('all-day');
+		expect(events[1].title).toBe('Long Event');
+		expect(events[1].timeText).toBe('all-day');
+		expect(events[2].title).toBe('All Day Event');
+		expect(events[2].timeText).toBe('all-day');
+		expect(events[3].title).toBe('Birthday Party');
+		expect(events[3].timeText).toBe('7:00am');
+		expect(events[4].title).toBe('Meeting');
+		expect(events[4].timeText).toBe('10:30am');
+		expect(events[5].title).toBe('Long Event');
+		expect(events[5].timeText).toBe('all-day');
+		expect(events[6].title).toBe('Lunch');
+		expect(events[6].timeText).toBe('12:00pm');
+		expect(events[7].title).toBe('Meeting');
+		expect(events[7].timeText).toBe('2:30pm');
+		expect(events[8].title).toBe('Long Event');
+		expect(events[8].timeText).toBe('all-day');
+		expect(events[9].title).toBe('Click for Google');
+		expect(events[9].timeText).toBe('all-day');
+		expect(events[10].title).toBe('Long Event');
+		expect(events[10].timeText).toBe('all-day');
+		expect(events[11].title).toBe('Long Event');
+		expect(events[11].timeText).toBe('all-day');
+		expect(events[12].title).toBe('Long Event');
+		expect(events[12].timeText).toBe('all-day');
+	});
+
 	function getDayInfo() {
 		return $('.fc-list-heading').map(function(i, el) {
 			el = $(el);
@@ -146,11 +352,11 @@ describe('ListView rendering', function() {
 		}).get();
 	}
 
-	function getEventInfo() {
+	function getEventInfo() { // gets all *segments*
 		return $('.fc-list-item').map(function(i, el) {
 			el = $(el);
 			return {
-				title: el.find('.fc-list-item-title').text(), // text!
+				title: el.find('.fc-list-item-title').text() || '', // text!
 				timeText: el.find('.fc-list-item-time').text() || '' // text!
 			};
 		}).get();