Преглед на файлове

beef up automated tests for moment stuff

Adam Shaw преди 11 години
родител
ревизия
1d0c3838ad
променени са 2 файла, в които са добавени 699 реда и са изтрити 230 реда
  1. 53 230
      tests/automated/moment-ambig.js
  2. 646 0
      tests/automated/moment-construct.js

+ 53 - 230
tests/automated/moment-ambig.js

@@ -1,177 +1,4 @@
 
-describe('$.fullCalendar.moment', function() {
-
-	describe('when given an existing moment', function() {
-
-		it('has no side effects', function() {
-			var oldMom = moment();
-			var oldDate = oldMom.toDate();
-			var newMom = $.fullCalendar.moment(oldMom).add('months', 1);
-			var newDate = newMom.toDate();
-			expect(+oldDate).not.toBe(+newDate);
-		});
-
-		it('transfers all ambiguity', function() {
-			var oldMom = $.fullCalendar.moment('2014-06-06');
-			expect(oldMom.hasZone()).toBe(false);
-			expect(oldMom.hasTime()).toBe(false);
-			var newMom = $.fullCalendar.moment(oldMom);
-			expect(newMom.hasZone()).toBe(false);
-			expect(newMom.hasTime()).toBe(false);
-		});
-
-	});
-
-	describe('when given an ISO8601 string', function() {
-
-		it('assumes local when no TZO', function() {
-			var mom = $.fullCalendar.moment('2014-06-08T10:00:00');
-			var dateEquiv = new Date(2014, 5, 8, 10, 0, 0);
-			expect(mom.format()).toContain('2014-06-08T10:00:00');
-			expect(mom.zone()).toBe(dateEquiv.getTimezoneOffset());
-		});
-
-		it('is local regardless of inputted zone', function() {
-			var mom = $.fullCalendar.moment('2014-06-08T10:00:00+0130');
-			var simpleMoment = moment('2014-06-08T10:00:00+0130');
-			expect(mom.zone()).toBe(mom.zone());
-		});
-
-		it('accepts an ambiguous time', function() {
-			var mom = $.fullCalendar.moment('2014-06-08');
-			expect(mom.format()).toBe('2014-06-08');
-			expect(mom.hasTime()).toBe(false);
-		});
-
-		it('assumes first-of-month and ambiguous time when no date-of-month', function() {
-			var mom = $.fullCalendar.moment('2014-06');
-			expect(mom.format()).toBe('2014-06-01');
-			expect(mom.hasTime()).toBe(false);
-		});
-
-	});
-
-	it('is local when given no arguments', function() {
-		var mom = $.fullCalendar.moment();
-		var nowDate = new Date();
-		expect(mom.zone()).toBe(nowDate.getTimezoneOffset());
-	});
-
-	it('is local when given a native Date', function() {
-		var date = new Date();
-		var mom = $.fullCalendar.moment(date);
-		expect(mom.zone()).toBe(date.getTimezoneOffset());
-	});
-
-	it('is local when given an array', function() {
-		var a = [ 2014, 5, 8, 10, 0, 0 ];
-		var date = new Date(2014, 5, 8, 10, 0, 0);
-		var mom = $.fullCalendar.moment(a);
-		expect(mom.format()).toContain('2014-06-08');
-		expect(mom.zone()).toBe(date.getTimezoneOffset());
-	});
-
-});
-
-describe('$.fullCalendar.moment.utc', function() {
-
-	describe('when given an ISO8601 string', function() {
-
-		it('assumes UTC when no TZO', function() {
-			var mom = $.fullCalendar.moment.utc('2014-06-08T10:00:00');
-			expect(mom.format()).toContain('2014-06-08T10:00:00');
-			expect(mom.zone()).toBe(0);
-		});
-
-		it('is UTC regardless of inputted zone', function() {
-			var mom = $.fullCalendar.moment.utc('2014-06-08T10:00:00+0130');
-			expect(mom.zone()).toBe(0);
-		});
-
-		it('accepts an ambiguous time', function() {
-			var mom = $.fullCalendar.moment.utc('2014-06-08');
-			expect(mom.format()).toBe('2014-06-08');
-			expect(mom.hasTime()).toBe(false);
-		});
-
-		it('assumes first-of-month and ambiguous time when no date-of-month', function() {
-			var mom = $.fullCalendar.moment.utc('2014-06');
-			expect(mom.format()).toBe('2014-06-01');
-			expect(mom.hasTime()).toBe(false);
-		});
-
-	});
-
-	it('is UTC when given no arguments', function() {
-		var mom = $.fullCalendar.moment.utc();
-		expect(mom.zone()).toBe(0);
-	});
-
-	it('is UTC when given a native Date', function() {
-		var date = new Date();
-		var mom = $.fullCalendar.moment.utc(date);
-		expect(mom.zone()).toBe(0);
-	});
-
-	it('is UTC when given an array', function() {
-		var a = [ 2014, 5, 8, 10, 0, 0 ];
-		var mom = $.fullCalendar.moment.utc(a);
-		expect(mom.format()).toContain('2014-06-08');
-		expect(mom.zone()).toBe(0);
-	});
-
-});
-
-describe('$.fullCalendar.moment.parseZone', function() {
-
-	describe('when given an ISO8601 string', function() {
-
-		it('accepts the inputted TZO', function() {
-			var mom = $.fullCalendar.moment.parseZone('2014-06-08T11:00:00+0130');
-			expect(mom.zone()).toBe(-90);
-		});
-
-		it('accepts an ambiguous zone', function() {
-			var mom = $.fullCalendar.moment.parseZone('2014-06-08T11:00:00');
-			expect(mom.format()).toContain('2014-06-08T11:00:00');
-			expect(mom.hasZone()).toBe(false);
-		});
-
-		it('accepts an ambiguous time', function() {
-			var mom = $.fullCalendar.moment.parseZone('2014-06-08');
-			expect(mom.format()).toContain('2014-06-08');
-			expect(mom.hasTime()).toBe(false);
-		});
-
-		it('assumes first-of-month and ambiguous time when no date-of-month', function() {
-			var mom = $.fullCalendar.moment.parseZone('2014-06');
-			expect(mom.format()).toBe('2014-06-01');
-			expect(mom.hasTime()).toBe(false);
-		});
-
-	});
-
-	it('is local when given no arguments', function() {
-		var mom = $.fullCalendar.moment.parseZone();
-		var nowDate = new Date();
-		expect(mom.zone()).toBe(nowDate.getTimezoneOffset());
-	});
-
-	it('is local when given a native Date', function() {
-		var date = new Date();
-		var mom = $.fullCalendar.moment.parseZone(date);
-		expect(mom.zone()).toBe(date.getTimezoneOffset());
-	});
-
-	it('is ambiguously zoned when given an array', function() {
-		var a = [ 2014, 5, 8, 10, 0, 0 ];
-		var mom = $.fullCalendar.moment.parseZone(a);
-		expect(mom.format()).toContain('2014-06-08');
-		expect(mom.hasZone()).toBe(false);
-	});
-
-});
-
 describe('ambiguously-zoned moment', function() {
 
 	it('has a false hasZone', function() {
@@ -204,32 +31,43 @@ describe('ambiguously-zoned moment', function() {
 		var clone = mom.clone();
 		expect(clone.hasZone()).toBe(false);
 		expect(clone.format()).toBe('2014-06-08T10:00:00');
+		expect(clone).not.toBe(mom);
+		clone.add('months', 1);
+		expect(+clone).not.toBe(+mom);
 	});
 
-	it('can be give a zone via utc', function() {
+	it('can be given a zone via utc', function() {
 		var mom = $.fullCalendar.moment.parseZone('2014-06-08T10:00:00');
+		expect(mom.hasTime()).toBe(true);
 		expect(mom.hasZone()).toBe(false);
 		expect(mom.zone()).toBe(0);
 		mom.utc();
+		expect(mom.hasTime()).toBe(true);
 		expect(mom.hasZone()).toBe(true);
 		expect(mom.zone()).toBe(0);
 	});
 
-	it('can be give a zone via local', function() {
+	it('can be given a zone via local', function() {
 		var mom = $.fullCalendar.moment.parseZone('2014-06-08T10:00:00');
 		var equivDate = new Date(Date.UTC(2014, 5, 8, 10, 0, 0));
+		expect(mom.toArray()).toEqual([ 2014, 5, 8, 10, 0, 0, 0 ]);
+		expect(mom.hasTime()).toBe(true);
 		expect(mom.hasZone()).toBe(false);
 		expect(mom.zone()).toBe(0);
 		mom.local();
+		expect(mom.toArray()).toEqual([ 2014, 5, 8, 10, 0, 0, 0 ]);
+		expect(mom.hasTime()).toBe(true);
 		expect(mom.hasZone()).toBe(true);
 		expect(mom.zone()).toBe(equivDate.getTimezoneOffset());
 	});
 
-	it('can be give a zone via zone', function() {
+	it('can be given a zone via zone', function() {
 		var mom = $.fullCalendar.moment.parseZone('2014-06-08T10:00:00');
+		expect(mom.hasTime()).toBe(true);
 		expect(mom.hasZone()).toBe(false);
 		expect(mom.zone()).toBe(0);
 		mom.zone(-420);
+		expect(mom.hasTime()).toBe(true);
 		expect(mom.hasZone()).toBe(true);
 		expect(mom.zone()).toBe(-420);
 	});
@@ -269,6 +107,9 @@ describe('ambiguously-timed moment', function() {
 		var clone = mom.clone();
 		expect(clone.hasTime()).toBe(false);
 		expect(clone.format()).toBe('2014-06-08');
+		expect(clone).not.toBe(mom);
+		clone.add('months', 1);
+		expect(+clone).not.toBe(+mom);
 	});
 
 	it('can be given a time', function() {
@@ -280,6 +121,42 @@ describe('ambiguously-timed moment', function() {
 		expect(+mom.time()).toBe(+time);
 	});
 
+	it('can be given a time and zone via utc', function() {
+		var mom = $.fullCalendar.moment.parseZone('2014-06-08');
+		expect(mom.hasTime()).toBe(false);
+		expect(mom.hasZone()).toBe(false);
+		expect(mom.zone()).toBe(0);
+		mom.utc();
+		expect(mom.hasTime()).toBe(true);
+		expect(mom.hasZone()).toBe(true);
+		expect(mom.zone()).toBe(0);
+	});
+
+	it('can be given a time and zone via local', function() {
+		var mom = $.fullCalendar.moment.parseZone('2014-06-08');
+		var equivDate = new Date(2014, 5, 8, 10, 0, 0);
+		expect(mom.toArray()).toEqual([ 2014, 5, 8, 0, 0, 0, 0 ]);
+		expect(mom.hasTime()).toBe(false);
+		expect(mom.hasZone()).toBe(false);
+		expect(mom.zone()).toBe(0);
+		mom.local();
+		expect(mom.toArray()).toEqual([ 2014, 5, 8, 0, 0, 0, 0 ]);
+		expect(mom.hasTime()).toBe(true);
+		expect(mom.hasZone()).toBe(true);
+		expect(mom.zone()).toBe(equivDate.getTimezoneOffset());
+	});
+
+	it('can be given a time and zone via zone', function() {
+		var mom = $.fullCalendar.moment.parseZone('2014-06-08');
+		expect(mom.hasTime()).toBe(false);
+		expect(mom.hasZone()).toBe(false);
+		expect(mom.zone()).toBe(0);
+		mom.zone(-420);
+		expect(mom.hasTime()).toBe(true);
+		expect(mom.hasZone()).toBe(true);
+		expect(mom.zone()).toBe(-420);
+	});
+
 });
 
 describe('unambiguous moment', function() {
@@ -305,57 +182,3 @@ describe('unambiguous moment', function() {
 	});
 
 });
-
-describe('Calendar::moment', function() {
-
-	beforeEach(function() {
-		affix('#cal');
-	});
-
-	it('inherits the calendar\'s lang', function() {
-		$('#cal').fullCalendar({
-			lang: 'fr'
-		});
-		var calendar = $('#cal').fullCalendar('getCalendar');
-		var mom = calendar.moment('2014-06-08T10:00:00');
-		expect(mom.lang()._abbr).toBe('fr');
-	});
-
-	it('is ambiguously-zoned when the calendar has no timezone', function() {
-		$('#cal').fullCalendar({
-			timezone: false
-		});
-		var calendar = $('#cal').fullCalendar('getCalendar');
-		var mom = calendar.moment('2014-06-08T10:00:00');
-		expect(mom.hasZone()).toBe(false);
-	});
-
-	it('is local when calendar is local', function() {
-		$('#cal').fullCalendar({
-			timezone: 'local'
-		});
-		var calendar = $('#cal').fullCalendar('getCalendar');
-		var mom = calendar.moment('2014-06-08T10:00:00');
-		var equivDate = new Date(2014, 5, 8, 10, 0, 0);
-		expect(mom.zone()).toBe(equivDate.getTimezoneOffset());
-	});
-
-	it('is UTC when the calendar is UTC', function() {
-		$('#cal').fullCalendar({
-			timezone: 'UTC'
-		});
-		var calendar = $('#cal').fullCalendar('getCalendar');
-		var mom = calendar.moment('2014-06-08T10:00:00');
-		expect(mom.zone()).toBe(0);
-	});
-
-	it('is ambuously-zoned when the calendar has a custom timezone', function() {
-		$('#cal').fullCalendar({
-			timezone: 'America/Chicago'
-		});
-		var calendar = $('#cal').fullCalendar('getCalendar');
-		var mom = calendar.moment('2014-06-08T10:00:00');
-		expect(mom.hasZone()).toBe(false);
-	});
-
-});

+ 646 - 0
tests/automated/moment-construct.js

@@ -0,0 +1,646 @@
+(function() {
+
+describe('$.fullCalendar.moment', function() {
+	testDefaultProcessing($.fullCalendar.moment);
+});
+
+describe('$.fullCalendar.moment.utc', function() {
+	testForcedUTCProcessing($.fullCalendar.moment.utc);
+});
+
+describe('$.fullCalendar.moment.parseZone', function() {
+	testLiteralProcessing($.fullCalendar.moment.parseZone);
+});
+
+describe('Calendar::moment', function() {
+	[
+		{
+			description: 'when there is no timezone',
+			timezone: false,
+			testMethod: testLiteralProcessing
+		},
+		{
+			description: 'when timezone is local',
+			timezone: 'local',
+			testMethod: testForcedLocalProcessing
+		},
+		{
+			description: 'when timezone is UTC',
+			timezone: 'UTC',
+			testMethod: testForcedUTCProcessing
+		},
+		{
+			description: 'when timezone is custom',
+			timezone: 'America/Unknown',
+			testMethod: testLiteralProcessing
+		}
+	]
+	.forEach(function(scenario) {
+		describe(scenario.description, function() {
+			var calendarObj;
+
+			beforeEach(function() {
+				affix('#cal');
+				$('#cal').fullCalendar({
+					timezone: scenario.timezone
+				});
+				calendarObj = $('#cal').fullCalendar('getCalendar');
+			});
+
+			scenario.testMethod(function() {
+				return calendarObj.moment.apply(calendarObj, arguments);
+			});
+		});
+	});
+});
+
+function testDefaultProcessing(construct) {
+
+	describe('when given an ISO8601 string', function() {
+
+		it('is local regardless of inputted zone', function() {
+			var mom = construct('2014-06-08T10:00:00+0130');
+			var simpleMoment = moment('2014-06-08T10:00:00+0130');
+			expect(mom.hasTime()).toBe(true);
+			expect(mom.hasZone()).toBe(true);
+			expect(mom.zone()).toBe(simpleMoment.zone());
+		});
+
+		it('parses as local when no zone', function() {
+			var mom = construct('2014-06-08T10:00:00');
+			var dateEquiv = new Date(2014, 5, 8, 10);
+			expect(mom.toArray()).toEqual([ 2014, 5, 8, 10, 0, 0, 0 ]);
+			expect(mom.hasTime()).toBe(true);
+			expect(mom.hasZone()).toBe(true);
+			expect(mom.zone()).toBe(dateEquiv.getTimezoneOffset());
+		});
+
+		it('accepts an ambiguous time', function() {
+			var mom = construct('2014-06-08');
+			expect(mom.toArray()).toEqual([ 2014, 5, 8, 0, 0, 0, 0 ]);
+			expect(mom.hasTime()).toBe(false);
+			expect(mom.hasZone()).toBe(false);
+		});
+
+		it('assumes first-of-month and ambiguous time when no date-of-month', function() {
+			var mom = construct('2014-06');
+			expect(mom.toArray()).toEqual([ 2014, 5, 1, 0, 0, 0, 0 ]);
+			expect(mom.hasTime()).toBe(false);
+			expect(mom.hasZone()).toBe(false);
+		});
+	});
+
+	it('parses string/format combo as local', function() {
+		var mom = construct('12-25-1995', 'MM-DD-YYYY');
+		var dateEquiv = new Date(1995, 11, 25);
+		expect(mom.toArray()).toEqual([ 1995, 11, 25, 0, 0, 0, 0 ]);
+		expect(mom.hasZone()).toBe(true);
+		expect(mom.hasTime()).toBe(true);
+		expect(mom.zone()).toBe(dateEquiv.getTimezoneOffset());
+	});
+
+	it('is local when given no arguments', function() {
+		var mom = construct();
+		var dateEquiv = new Date();
+		expect(mom.hasTime()).toBe(true);
+		expect(mom.hasZone()).toBe(true);
+		expect(mom.zone()).toBe(dateEquiv.getTimezoneOffset());
+	});
+
+	it('is local when given a native Date', function() {
+		var date = new Date();
+		var mom = construct(date);
+		expect(mom.hasTime()).toBe(true);
+		expect(mom.hasZone()).toBe(true);
+		expect(mom.zone()).toBe(date.getTimezoneOffset());
+	});
+
+	describe('when given an array', function() {
+
+		it('is local and has a time when given hours/minutes/seconds', function() {
+			var a = [ 2014, 5, 8, 11, 0, 0 ];
+			var dateEquiv = new Date(2014, 5, 8, 11, 0, 0);
+			var mom = construct(a);
+			expect(mom.toArray()).toEqual([ 2014, 5, 8, 11, 0, 0, 0 ]);
+			expect(mom.hasTime()).toBe(true);
+			expect(mom.hasZone()).toBe(true);
+			expect(mom.zone()).toBe(dateEquiv.getTimezoneOffset());
+		});
+
+		it('is local and has a time even when no hours/minutes/seconds', function() {
+			var a = [ 2014, 5, 8 ];
+			var dateEquiv = new Date(2014, 5, 8);
+			var mom = construct(a);
+			expect(mom.toArray()).toEqual([ 2014, 5, 8, 0, 0, 0, 0 ]);
+			expect(mom.hasTime()).toBe(true);
+			expect(mom.hasZone()).toBe(true);
+			expect(mom.zone()).toBe(dateEquiv.getTimezoneOffset());
+		});
+	});
+
+	describe('when given an existing FullCalendar moment', function() {
+
+		it('remains ambiguously-zoned', function() {
+			var noTzMoment = $.fullCalendar.moment.parseZone('2014-05-28T00:00:00');
+			var newMoment = construct(noTzMoment);
+			expect(newMoment.toArray()).toEqual([ 2014, 4, 28, 0, 0, 0, 0 ]);
+			expect(newMoment.hasTime()).toBe(true);
+			expect(newMoment.hasZone()).toBe(false);
+		});
+
+		it('remains ambiguously-timed', function() {
+			var noTimeMoment = $.fullCalendar.moment('2014-05-28');
+			var newMoment = construct(noTimeMoment);
+			expect(newMoment.toArray()).toEqual([ 2014, 4, 28, 0, 0, 0, 0 ]);
+			expect(newMoment.hasTime()).toBe(false);
+			expect(newMoment.hasZone()).toBe(false);
+		});
+	});
+
+	[
+		{ description: 'when given an existing FullCalendar moment', moment: $.fullCalendar.moment },
+		{ description: 'when given an existing basic moment', moment: moment }
+	]
+	.forEach(function(scenario) {
+		describe(scenario.description, function() {
+
+			it('remains local', function() {
+				var localMoment = scenario.moment('2014-05-28T00:00:00');
+				var newMoment = construct(localMoment);
+				expect(newMoment.toArray()).toEqual([ 2014, 4, 28, 0, 0, 0, 0 ]);
+				expect(newMoment.hasTime()).toBe(true);
+				expect(newMoment.hasZone()).toBe(true);
+				expect(newMoment.zone()).toBe(localMoment.zone());
+			});
+
+			it('remains UTC', function() {
+				var utcMoment = scenario.moment.utc('2014-05-28T00:00:00');
+				var newMoment = construct(utcMoment);
+				expect(newMoment.toArray()).toEqual([ 2014, 4, 28, 0, 0, 0, 0 ]);
+				expect(newMoment.hasTime()).toBe(true);
+				expect(newMoment.hasZone()).toBe(true);
+				expect(newMoment.zone()).toBe(0);
+			});
+
+			it('remains in a custom timezone', function() {
+				var tzMoment = scenario.moment.parseZone('2014-05-28T00:00:00+13:00');
+				var newMoment = construct(tzMoment);
+				expect(newMoment.toArray()).toEqual([ 2014, 4, 28, 0, 0, 0, 0 ]);
+				expect(newMoment.hasTime()).toBe(true);
+				expect(newMoment.hasZone()).toBe(true);
+				expect(newMoment.zone()).toBe(-780);
+			});
+
+			it('produces a new moment that is in no way bound to the old', function() {
+				var oldMoment = scenario.moment();
+				var newMoment = construct(oldMoment);
+				expect(newMoment).not.toBe(oldMoment);
+				expect(+newMoment).toBe(+oldMoment);
+				newMoment.add('months', 1);
+				expect(+newMoment).not.toBe(+oldMoment);
+			});
+		});
+	});
+}
+
+function testForcedLocalProcessing(construct) {
+
+	describe('when given an ISO8601 string', function() {
+
+		it('is local regardless of inputted zone', function() {
+			var mom = construct('2014-06-08T10:00:00+0130');
+			var simpleMoment = moment('2014-06-08T10:00:00+0130');
+			expect(mom.hasTime()).toBe(true);
+			expect(mom.hasZone()).toBe(true);
+			expect(mom.zone()).toBe(simpleMoment.zone());
+		});
+
+		it('parses as local when no zone', function() {
+			var mom = construct('2014-06-08T10:00:00');
+			var dateEquiv = new Date(2014, 5, 8, 10);
+			expect(mom.toArray()).toEqual([ 2014, 5, 8, 10, 0, 0, 0 ]);
+			expect(mom.hasTime()).toBe(true);
+			expect(mom.hasZone()).toBe(true);
+			expect(mom.zone()).toBe(dateEquiv.getTimezoneOffset());
+		});
+
+		it('accepts an ambiguous time', function() {
+			var mom = construct('2014-06-08');
+			expect(mom.toArray()).toEqual([ 2014, 5, 8, 0, 0, 0, 0 ]);
+			expect(mom.hasTime()).toBe(false);
+			expect(mom.hasZone()).toBe(false);
+		});
+
+		it('assumes first-of-month and ambiguous time when no date-of-month', function() {
+			var mom = construct('2014-06');
+			expect(mom.toArray()).toEqual([ 2014, 5, 1, 0, 0, 0, 0 ]);
+			expect(mom.hasTime()).toBe(false);
+			expect(mom.hasZone()).toBe(false);
+		});
+	});
+
+	it('parses string/format combo as local', function() {
+		var mom = construct('12-25-1995', 'MM-DD-YYYY');
+		var dateEquiv = new Date(1995, 11, 25);
+		expect(mom.toArray()).toEqual([ 1995, 11, 25, 0, 0, 0, 0 ]);
+		expect(mom.hasZone()).toBe(true);
+		expect(mom.hasTime()).toBe(true);
+		expect(mom.zone()).toBe(dateEquiv.getTimezoneOffset());
+	});
+
+	it('is local when given no arguments', function() {
+		var mom = construct();
+		var dateEquiv = new Date();
+		expect(mom.hasTime()).toBe(true);
+		expect(mom.hasZone()).toBe(true);
+		expect(mom.zone()).toBe(dateEquiv.getTimezoneOffset());
+	});
+
+	it('is local when given a native Date', function() {
+		var date = new Date();
+		var mom = construct(date);
+		expect(mom.hasTime()).toBe(true);
+		expect(mom.hasZone()).toBe(true);
+		expect(mom.zone()).toBe(date.getTimezoneOffset());
+	});
+
+	describe('when given an array', function() {
+
+		it('is local and has a time when given hours/minutes/seconds', function() {
+			var a = [ 2014, 5, 8, 11, 0, 0 ];
+			var dateEquiv = new Date(2014, 5, 8, 11, 0, 0);
+			var mom = construct(a);
+			expect(mom.toArray()).toEqual([ 2014, 5, 8, 11, 0, 0, 0 ]);
+			expect(mom.hasTime()).toBe(true);
+			expect(mom.hasZone()).toBe(true);
+			expect(mom.zone()).toBe(dateEquiv.getTimezoneOffset());
+		});
+
+		it('is local and has a time even when no hours/minutes/seconds', function() {
+			var a = [ 2014, 5, 8 ];
+			var dateEquiv = new Date(2014, 5, 8);
+			var mom = construct(a);
+			expect(mom.toArray()).toEqual([ 2014, 5, 8, 0, 0, 0, 0 ]);
+			expect(mom.hasTime()).toBe(true);
+			expect(mom.hasZone()).toBe(true);
+			expect(mom.zone()).toBe(dateEquiv.getTimezoneOffset());
+		});
+	});
+
+	describe('when given an existing FullCalendar moment', function() {
+
+		it('converts to local when ambiguously-zoned', function() {
+			var noTzMoment = $.fullCalendar.moment.parseZone('2014-05-28T00:00:00');
+			var newMoment = construct(noTzMoment);
+			var dateEquiv = new Date(2014, 4, 28);
+			expect(newMoment.toArray()).toEqual([ 2014, 4, 28, 0, 0, 0, 0 ]);
+			expect(newMoment.hasTime()).toBe(true);
+			expect(newMoment.hasZone()).toBe(true);
+			expect(newMoment.zone()).toBe(dateEquiv.getTimezoneOffset());
+		});
+
+		it('remains ambiguously-timed', function() {
+			var noTimeMoment = $.fullCalendar.moment('2014-05-28');
+			var newMoment = construct(noTimeMoment);
+			expect(newMoment.toArray()).toEqual([ 2014, 4, 28, 0, 0, 0, 0 ]);
+			expect(newMoment.hasTime()).toBe(false);
+			expect(newMoment.hasZone()).toBe(false);
+		});
+	});
+
+	[
+		{ description: 'when given an existing FullCalendar moment', moment: $.fullCalendar.moment },
+		{ description: 'when given an existing basic moment', moment: moment }
+	]
+	.forEach(function(scenario) {
+		describe(scenario.description, function() {
+
+			it('remains local', function() {
+				var localMoment = scenario.moment('2014-05-28T00:00:00');
+				var newMoment = construct(localMoment);
+				expect(newMoment.toArray()).toEqual([ 2014, 4, 28, 0, 0, 0, 0 ]);
+				expect(newMoment.hasTime()).toBe(true);
+				expect(newMoment.hasZone()).toBe(true);
+				expect(newMoment.zone()).toBe(localMoment.zone());
+			});
+
+			it('converts to local when UTC', function() {
+				var utcMoment = scenario.moment.utc('2014-05-28T00:00:00');
+				var newMoment = construct(utcMoment);
+				var dateEquiv = new Date(Date.UTC(2014, 4, 28));
+				expect(+newMoment).toBe(+dateEquiv);
+				expect(newMoment.hasTime()).toBe(true);
+				expect(newMoment.hasZone()).toBe(true);
+				expect(newMoment.zone()).toBe(dateEquiv.getTimezoneOffset());
+			});
+
+			it('converts to local when in a custom zone', function() {
+				var tzMoment = scenario.moment.parseZone('2014-05-28T00:00:00+13:00');
+				var dateEquiv = tzMoment.toDate();
+				var newMoment = construct(tzMoment);
+				expect(+newMoment).toBe(+dateEquiv);
+				expect(newMoment.hasTime()).toBe(true);
+				expect(newMoment.hasZone()).toBe(true);
+				expect(newMoment.zone()).toBe(dateEquiv.getTimezoneOffset());
+			});
+
+			it('produces a new moment that is in no way bound to the old', function() {
+				var oldMoment = scenario.moment();
+				var newMoment = construct(oldMoment);
+				expect(newMoment).not.toBe(oldMoment);
+				expect(+newMoment).toBe(+oldMoment);
+				newMoment.add('months', 1);
+				expect(+newMoment).not.toBe(+oldMoment);
+			});
+		});
+	});
+}
+
+function testForcedUTCProcessing(construct) {
+
+	describe('when given an ISO8601 string', function() {
+
+		it('is UTC regardless of inputted zone', function() {
+			var mom = construct('2014-06-08T10:00:00+0130');
+			expect(mom.hasTime()).toBe(true);
+			expect(mom.hasZone()).toBe(true);
+			expect(mom.zone()).toBe(0);
+		});
+
+		it('parses as UTC when no zone', function() {
+			var mom = construct('2014-06-08T10:00:00');
+			expect(mom.toArray()).toEqual([ 2014, 5, 8, 10, 0, 0, 0 ]);
+			expect(mom.hasTime()).toBe(true);
+			expect(mom.hasZone()).toBe(true);
+			expect(mom.zone()).toBe(0);
+		});
+
+		it('accepts an ambiguous time', function() {
+			var mom = construct('2014-06-08');
+			expect(mom.toArray()).toEqual([ 2014, 5, 8, 0, 0, 0, 0 ]);
+			expect(mom.hasTime()).toBe(false);
+			expect(mom.hasZone()).toBe(false);
+		});
+
+		it('assumes first-of-month and ambiguous time when no date-of-month', function() {
+			var mom = construct('2014-06');
+			expect(mom.toArray()).toEqual([ 2014, 5, 1, 0, 0, 0, 0 ]);
+			expect(mom.hasTime()).toBe(false);
+			expect(mom.hasZone()).toBe(false);
+		});
+	});
+
+	it('parses string/format combo as UTC', function() {
+		var mom = construct('12-25-1995', 'MM-DD-YYYY');
+		expect(mom.toArray()).toEqual([ 1995, 11, 25, 0, 0, 0, 0 ]);
+		expect(mom.hasZone()).toBe(true);
+		expect(mom.hasTime()).toBe(true);
+		expect(mom.zone()).toBe(0);
+	});
+
+	it('is UTC when given no arguments', function() {
+		var mom = construct();
+		expect(mom.hasTime()).toBe(true);
+		expect(mom.hasZone()).toBe(true);
+		expect(mom.zone()).toBe(0);
+	});
+
+	it('is UTC when given a native Date', function() {
+		var date = new Date();
+		var mom = construct(date);
+		expect(mom.hasTime()).toBe(true);
+		expect(mom.hasZone()).toBe(true);
+		expect(mom.zone()).toBe(0);
+	});
+
+	describe('when given an array', function() {
+
+		it('is UTC and has a time when given hours/minutes/seconds', function() {
+			var a = [ 2014, 5, 8, 11, 0, 0 ];
+			var mom = construct(a);
+			expect(mom.toArray()).toEqual([ 2014, 5, 8, 11, 0, 0, 0 ]);
+			expect(mom.hasTime()).toBe(true);
+			expect(mom.hasZone()).toBe(true);
+			expect(mom.zone()).toBe(0);
+		});
+
+		it('is UTC and has a time even when no hours/minutes/seconds', function() {
+			var a = [ 2014, 5, 8 ];
+			var mom = construct(a);
+			expect(mom.toArray()).toEqual([ 2014, 5, 8, 0, 0, 0, 0 ]);
+			expect(mom.hasTime()).toBe(true);
+			expect(mom.hasZone()).toBe(true);
+			expect(mom.zone()).toBe(0);
+		});
+	});
+
+	describe('when given an existing FullCalendar moment', function() {
+
+		it('converts to UTC when ambiguously-zoned', function() {
+			var noTzMoment = $.fullCalendar.moment.utc('2014-05-28T00:00:00');
+			var newMoment = construct(noTzMoment);
+			expect(newMoment.toArray()).toEqual([ 2014, 4, 28, 0, 0, 0, 0 ]);
+			expect(newMoment.hasTime()).toBe(true);
+			expect(newMoment.hasZone()).toBe(true);
+			expect(newMoment.zone()).toBe(0);
+		});
+
+		it('remains ambiguously-timed', function() {
+			var noTimeMoment = $.fullCalendar.moment('2014-05-28');
+			var newMoment = construct(noTimeMoment);
+			expect(newMoment.toArray()).toEqual([ 2014, 4, 28, 0, 0, 0, 0 ]);
+			expect(newMoment.hasTime()).toBe(false);
+			expect(newMoment.hasZone()).toBe(false);
+		});
+	});
+
+	[
+		{ description: 'when given an existing FullCalendar moment', moment: $.fullCalendar.moment },
+		{ description: 'when given an existing basic moment', moment: moment }
+	]
+	.forEach(function(scenario) {
+		describe(scenario.description, function() {
+
+			it('converts to UTC when local', function() {
+				var localMoment = scenario.moment('2014-05-28T00:00:00');
+				var newMoment = construct(localMoment);
+				expect(+newMoment).toBe(+localMoment); // same point in time
+				expect(newMoment.hasTime()).toBe(true);
+				expect(newMoment.hasZone()).toBe(true);
+				expect(newMoment.zone()).toBe(0);
+			});
+
+			it('remains UTC', function() {
+				var utcMoment = scenario.moment.utc('2014-05-28T00:00:00');
+				var newMoment = construct(utcMoment);
+				expect(newMoment.toArray()).toEqual([ 2014, 4, 28, 0, 0, 0, 0 ]);
+				expect(newMoment.hasTime()).toBe(true);
+				expect(newMoment.hasZone()).toBe(true);
+				expect(newMoment.zone()).toBe(0);
+			});
+
+			it('converts to UTC when in a custom zone', function() {
+				var tzMoment = scenario.moment.parseZone('2014-05-28T00:00:00+13:00');
+				var newMoment = construct(tzMoment);
+				expect(+newMoment).toBe(+tzMoment); // same point in time
+				expect(newMoment.hasTime()).toBe(true);
+				expect(newMoment.hasZone()).toBe(true);
+				expect(newMoment.zone()).toBe(0);
+			});
+
+			it('produces a new moment that is in no way bound to the old', function() {
+				var oldMoment = scenario.moment.utc();
+				var newMoment = construct(oldMoment);
+				expect(newMoment).not.toBe(oldMoment);
+				expect(+newMoment).toBe(+oldMoment);
+				newMoment.add('months', 1);
+				expect(+newMoment).not.toBe(+oldMoment);
+			});
+		});
+	});
+}
+
+function testLiteralProcessing(construct) {
+
+	describe('when given an ISO8601 string', function() {
+
+		it('retains the inputted zone', function() {
+			var mom = construct('2014-06-08T11:00:00+0130');
+			expect(mom.toArray()).toEqual([ 2014, 5, 8, 11, 0, 0, 0 ]);
+			expect(mom.hasTime()).toBe(true);
+			expect(mom.hasZone()).toBe(true);
+			expect(mom.zone()).toBe(-90);
+		});
+
+		it('accepts an ambiguous zone', function() {
+			var mom = construct('2014-06-08T11:00:00');
+			expect(mom.toArray()).toEqual([ 2014, 5, 8, 11, 0, 0, 0 ]);
+			expect(mom.hasTime()).toBe(true);
+			expect(mom.hasZone()).toBe(false);
+		});
+
+		it('accepts an ambiguous time', function() {
+			var mom = construct('2014-06-08');
+			expect(mom.toArray()).toEqual([ 2014, 5, 8, 0, 0, 0, 0 ]);
+			expect(mom.hasTime()).toBe(false);
+			expect(mom.hasZone()).toBe(false);
+		});
+
+		it('assumes first-of-month and ambiguous time when no date-of-month', function() {
+			var mom = construct('2014-06');
+			expect(mom.toArray()).toEqual([ 2014, 5, 1, 0, 0, 0, 0 ]);
+			expect(mom.hasTime()).toBe(false);
+			expect(mom.hasZone()).toBe(false);
+		});
+	});
+
+	it('parses string/format combo as UTC', function() {
+		var mom = construct('12-25-1995', 'MM-DD-YYYY');
+		expect(mom.toArray()).toEqual([ 1995, 11, 25, 0, 0, 0, 0 ]);
+		expect(mom.hasZone()).toBe(true);
+		expect(mom.hasTime()).toBe(true);
+		expect(mom.zone()).toBe(0);
+	});
+
+	it('is local when given no arguments', function() {
+		var mom = construct();
+		var dateEquiv = new Date();
+		expect(mom.hasTime()).toBe(true);
+		expect(mom.hasZone()).toBe(true);
+		expect(mom.zone()).toBe(dateEquiv.getTimezoneOffset());
+	});
+
+	it('is local when given a native Date', function() {
+		var date = new Date();
+		var mom = construct(date);
+		expect(mom.hasTime()).toBe(true);
+		expect(mom.hasZone()).toBe(true);
+		expect(mom.zone()).toBe(date.getTimezoneOffset());
+	});
+
+	describe('when given an array', function() {
+
+		it('is ambiguously-zoned and has a time when given hours/minutes/seconds', function() {
+			var a = [ 2014, 5, 8, 11, 0, 0 ];
+			var mom = construct(a);
+			expect(mom.toArray()).toEqual([ 2014, 5, 8, 11, 0, 0, 0 ]);
+			expect(mom.hasTime()).toBe(true);
+			expect(mom.hasZone()).toBe(false);
+		});
+
+		it('is ambiguously-zoned and has a time even when no hours/minutes/seconds', function() {
+			var a = [ 2014, 5, 8 ];
+			var mom = construct(a);
+			expect(mom.toArray()).toEqual([ 2014, 5, 8, 0, 0, 0, 0 ]);
+			expect(mom.hasTime()).toBe(true);
+			expect(mom.hasZone()).toBe(false);
+		});
+	});
+
+	describe('when given an existing FullCalendar moment', function() {
+
+		it('remains ambiguously-zoned', function() {
+			var noTzMoment = $.fullCalendar.moment.parseZone('2014-05-28T00:00:00');
+			var newMoment = construct(noTzMoment);
+			expect(newMoment.toArray()).toEqual([ 2014, 4, 28, 0, 0, 0, 0 ]);
+			expect(newMoment.hasTime()).toBe(true);
+			expect(newMoment.hasZone()).toBe(false);
+		});
+
+		it('remains ambiguously-timed', function() {
+			var noTimeMoment = $.fullCalendar.moment('2014-05-28');
+			var newMoment = construct(noTimeMoment);
+			expect(newMoment.toArray()).toEqual([ 2014, 4, 28, 0, 0, 0, 0 ]);
+			expect(newMoment.hasTime()).toBe(false);
+			expect(newMoment.hasZone()).toBe(false);
+		});
+	});
+
+	[
+		{ description: 'when given an existing FullCalendar moment', moment: $.fullCalendar.moment },
+		{ description: 'when given an existing basic moment', moment: moment }
+	]
+	.forEach(function(scenario) {
+		describe(scenario.description, function() {
+
+			it('remains local', function() {
+				var localMoment = scenario.moment('2014-05-28T00:00:00');
+				var newMoment = construct(localMoment);
+				expect(newMoment.toArray()).toEqual([ 2014, 4, 28, 0, 0, 0, 0 ]);
+				expect(newMoment.hasTime()).toBe(true);
+				expect(newMoment.hasZone()).toBe(true);
+				expect(newMoment.zone()).toBe(localMoment.zone());
+			});
+
+			it('remains UTC', function() {
+				var utcMoment = scenario.moment.utc('2014-05-28T00:00:00');
+				var newMoment = construct(utcMoment);
+				expect(newMoment.toArray()).toEqual([ 2014, 4, 28, 0, 0, 0, 0 ]);
+				expect(newMoment.hasTime()).toBe(true);
+				expect(newMoment.hasZone()).toBe(true);
+				expect(newMoment.zone()).toBe(0);
+			});
+
+			it('remains in a custom timezone', function() {
+				var tzMoment = scenario.moment.parseZone('2014-05-28T00:00:00+13:00');
+				var newMoment = construct(tzMoment);
+				expect(newMoment.toArray()).toEqual([ 2014, 4, 28, 0, 0, 0, 0 ]);
+				expect(newMoment.hasTime()).toBe(true);
+				expect(newMoment.hasZone()).toBe(true);
+				expect(newMoment.zone()).toBe(-780);
+			});
+
+			it('produces a new moment that is in no way bound to the old', function() {
+				var oldMoment = scenario.moment();
+				var newMoment = construct(oldMoment);
+				expect(newMoment).not.toBe(oldMoment);
+				expect(+newMoment).toBe(+oldMoment);
+				newMoment.add('months', 1);
+				expect(+newMoment).not.toBe(+oldMoment);
+			});
+
+		});
+	});
+}
+
+})();