From 83e4f6d4ab561b77c7aea495d790c394814f60e0 Mon Sep 17 00:00:00 2001 From: Ahmad Amireh Date: Wed, 7 Apr 2021 11:58:44 -0600 Subject: [PATCH] extract date-time-moment-parser refs FOO-1891 this is the start of a series to break apart the inter-dependencies between timezone and moment.js the formatting that is done by moment now stands alone in packages/date-time-moment-parser and is used by ui/shared/timezone. The formatter depends on phrases coming from the locale files, which are available to ui/* but not to packages/*, so in an initializer we inject our custom formats into the package, allowing it to make use of them. there should be no breaking API changes, even though several APIs were dropped, as they were all used in test but not in the app code. To minimize the changes, a specHelpers.js file is now provided by the timezone package that maintains backwards compat to a reasonable extent. TEST PLAN ==== ==== although the tests should be covering this, it wouldn't hurt to manually exercise any of the date picker widgets Change-Id: I0c59ad2df8f7392425debb6ec448ec1b4fb029c6 Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/265313 Tested-by: Service Cloud Jenkins Reviewed-by: Charley Kline Product-Review: Charley Kline QA-Review: Charley Kline --- jest.config.js | 2 +- jest/jest-setup.js | 5 + packages/date-time-moment-parser/formats.js | 106 +++ packages/date-time-moment-parser/index.js | 86 ++ packages/date-time-moment-parser/package.json | 18 + packages/date-time-moment-parser/test.js | 139 +++ spec/coffeescripts/calendar/AgendaViewSpec.js | 49 +- spec/coffeescripts/calendar/CalendarSpec.js | 13 +- .../calendar/EditAssignmentDetailsSpec.js | 2 +- .../calendar/EditPlannerNoteDetailsSpec.js | 2 +- spec/coffeescripts/calendar/fcUtilSpec.js | 12 +- spec/coffeescripts/handlebars_helpersSpec.js | 38 +- spec/coffeescripts/helpers/I18nStubber.js | 7 + spec/coffeescripts/i18nSpec.js | 4 +- .../instructureDateAndTimeSpec.js | 145 +++- .../due_dates/DueDateCalendarPickerSpec.js | 12 +- .../jsx/shared/helpers/dateHelperSpec.js | 57 +- spec/coffeescripts/views/SyllabusViewSpec.js | 14 +- .../assignments/AssignmentListItemViewSpec.js | 32 +- .../assignments/CreateAssignmentViewSpec.js | 34 +- .../views/outcomes/OutcomeViewSpec.js | 2 +- .../widgets/DatetimeFieldSpec.js | 48 +- .../jsx/conferences/EditConferenceViewSpec.js | 2 +- .../jsx/grading/GradingPeriodFormSpec.js | 29 +- spec/javascripts/jsx/i18nDateTime.js | 188 ---- spec/javascripts/jsx/moment_formatsSpec.js | 10 +- .../jsx/shared/helpers/numberHelperSpec.js | 2 +- .../timezoneBackwardsCompatLayer.js | 25 + spec/javascripts/jsx/timezoneSpec.js | 651 -------------- spec/javascripts/webpack_spec_index.js | 6 + ui/boot/index.js | 15 +- .../configureDateTimeMomentParser.js | 79 ++ ui/boot/initializers/configureTimezone.js | 47 + .../bulk_edit/__tests__/BulkEdit.test.js | 27 +- .../__tests__/buildAuditTrail.test.js | 13 +- .../__tests__/mergeI18nTranslations.test.js | 12 + .../RequestDispatch/__tests__/index.test.js | 2 +- .../timezone/__tests__/bigeasyLocales.js | 59 ++ ui/shared/timezone/__tests__/format.test.js | 111 +++ ui/shared/timezone/__tests__/helpers.js | 40 + .../timezone/__tests__/integration.test.js | 305 +++++++ .../__tests__/integrationWithI18n.t3st.js | 161 ++++ .../__tests__/mergeDateAndTime.test.js | 25 + ui/shared/timezone/__tests__/meridian.test.js | 102 +++ ui/shared/timezone/__tests__/parse.test.js | 55 ++ ui/shared/timezone/__tests__/shift.test.js | 37 + ui/shared/timezone/index.js | 554 +++++------- ui/shared/timezone/moment_formats.js | 168 ---- ui/shared/timezone/specHelpers.js | 45 + yarn.lock | 812 ++++++++++++++++-- 50 files changed, 2861 insertions(+), 1548 deletions(-) create mode 100644 packages/date-time-moment-parser/formats.js create mode 100644 packages/date-time-moment-parser/index.js create mode 100644 packages/date-time-moment-parser/package.json create mode 100644 packages/date-time-moment-parser/test.js delete mode 100644 spec/javascripts/jsx/i18nDateTime.js create mode 100644 spec/javascripts/jsx/spec-support/timezoneBackwardsCompatLayer.js delete mode 100644 spec/javascripts/jsx/timezoneSpec.js create mode 100644 ui/boot/initializers/configureDateTimeMomentParser.js create mode 100644 ui/boot/initializers/configureTimezone.js create mode 100644 ui/shared/timezone/__tests__/bigeasyLocales.js create mode 100644 ui/shared/timezone/__tests__/format.test.js create mode 100644 ui/shared/timezone/__tests__/helpers.js create mode 100644 ui/shared/timezone/__tests__/integration.test.js create mode 100644 ui/shared/timezone/__tests__/integrationWithI18n.t3st.js create mode 100644 ui/shared/timezone/__tests__/mergeDateAndTime.test.js create mode 100644 ui/shared/timezone/__tests__/meridian.test.js create mode 100644 ui/shared/timezone/__tests__/parse.test.js create mode 100644 ui/shared/timezone/__tests__/shift.test.js delete mode 100644 ui/shared/timezone/moment_formats.js create mode 100644 ui/shared/timezone/specHelpers.js diff --git a/jest.config.js b/jest.config.js index 6c1fde3de2b..c73d1d8243e 100644 --- a/jest.config.js +++ b/jest.config.js @@ -33,7 +33,7 @@ module.exports = { // mock the tinymce-react Editor react component '@tinymce/tinymce-react': '/packages/canvas-rce/src/rce/__mocks__/tinymceReact.js' }, - roots: ['ui', 'gems/plugins', 'public/javascripts'], + roots: ['/ui', 'gems/plugins', 'public/javascripts'], moduleDirectories: ['ui/shims', 'public/javascripts', 'node_modules'], reporters: [ 'default', diff --git a/jest/jest-setup.js b/jest/jest-setup.js index 9ff6380412b..5de5d52120b 100644 --- a/jest/jest-setup.js +++ b/jest/jest-setup.js @@ -19,6 +19,9 @@ import Enzyme from 'enzyme' import Adapter from 'enzyme-adapter-react-16' import {filterUselessConsoleMessages} from '@instructure/js-utils' +import { + up as configureDateTimeMomentParser +} from '../ui/boot/initializers/configureDateTimeMomentParser' filterUselessConsoleMessages(console) @@ -34,6 +37,8 @@ Enzyme.configure({adapter: new Adapter()}) // because InstUI themeable components need an explicit "dir" attribute on the element document.documentElement.setAttribute('dir', 'ltr') +configureDateTimeMomentParser() + // because everyone implements `flat()` and `flatMap()` except JSDOM 🤦🏼‍♂️ if (!Array.prototype.flat) { // eslint-disable-next-line no-extend-native diff --git a/packages/date-time-moment-parser/formats.js b/packages/date-time-moment-parser/formats.js new file mode 100644 index 00000000000..23532dc59ba --- /dev/null +++ b/packages/date-time-moment-parser/formats.js @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2021 - present Instructure, Inc. + * + * This file is part of Canvas. + * + * Canvas is free software: you can redistribute it and/or modify it under + * the terms of the GNU Affero General Public License as published by the Free + * Software Foundation, version 3 of the License. + * + * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License along + * with this program. If not, see . + */ + +import moment from 'moment' + +export default function getFormats({ customI18nFormats }) { + const i18nFormatsForCurrentLocale = customI18nFormats.map(x => x()).filter(x => !!x) + const momentCompatibleI18nFormats = specifyMinutesImplicitly( + i18nFormatsForCurrentLocale + ).map(convertI18nFormatToMomentFormat) + + return union(momentStockFormats, momentCompatibleI18nFormats) +} + +const union = (a, b) => b.reduce((acc, x) => { + if (!acc.includes(x)) { + acc.push(x) + } + + return acc +}, [].concat(a)) + +const i18nToMomentTokenMapping = { + '%A': 'dddd', + '%B': 'MMMM', + '%H': 'HH', + '%M': 'mm', + '%S': 'ss', + '%P': 'a', + '%Y': 'YYYY', + '%a': 'ddd', + '%b': 'MMM', + '%m': 'M', + '%d': 'D', + '%k': 'H', + '%l': 'h', + '%z': 'Z', + + '%-H': 'H', + '%-M': 'm', + '%-S': 's', + '%-m': 'M', + '%-d': 'D', + '%-k': 'H', + '%-l': 'h' +} + +const momentStockFormats = [ + moment.ISO_8601, + 'YYYY', + 'LT', + 'LTS', + 'L', + 'l', + 'LL', + 'll', + 'LLL', + 'lll', + 'LLLL', + 'llll', + 'D MMM YYYY', + 'H:mm' +] + +// expand every i18n format that specifies minutes (%M or %-M) into two: one +// that specifies the minutes and another that doesn't +// +// why? don't ask me +const specifyMinutesImplicitly = (formats) => formats.map(format => + format.match(/:%-?M/) ? + [format, format.replace(/:%-?M/, '')] : + [format] +).flat(1) + +const convertI18nFormatToMomentFormat = i18nFormat => { + const escapeNonI18nTokens = (string) => ( + string.split(' ').map(escapeUnlessIsI18nToken).join(' ') + ) + + const escapeUnlessIsI18nToken = (string) => { + const isKey = Object.keys(i18nToMomentTokenMapping).find(k => string.indexOf(k) > -1) + + return isKey ? string : `[${string}]` + } + + const escapedI18nFormat = escapeNonI18nTokens(i18nFormat) + + return Object.keys(i18nToMomentTokenMapping).reduce((acc, i18nToken) => ( + acc.replace(i18nToken, i18nToMomentTokenMapping[i18nToken]) + ), escapedI18nFormat) +} diff --git a/packages/date-time-moment-parser/index.js b/packages/date-time-moment-parser/index.js new file mode 100644 index 00000000000..bc96ebeb4c2 --- /dev/null +++ b/packages/date-time-moment-parser/index.js @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2021 - present Instructure, Inc. + * + * This file is part of Canvas. + * + * Canvas is free software: you can redistribute it and/or modify it under + * the terms of the GNU Affero General Public License as published by the Free + * Software Foundation, version 3 of the License. + * + * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License along + * with this program. If not, see . + */ + +import moment from 'moment' +import getFormats from './formats' + +const config = { customI18nFormats: [] } + +// Parse a DateTime string according to any of the pre-defined formats. The +// formats may come from Moment itself, or from the locale dictionary for +// date and time. +// +// See ./formats.js for the possible formats. +// +// @param {Object.<{ +// t: (String) -> String, +// lookup: (String) -> String +// }>} +// An object that can query the locale dictionary for datetime formats. +// +// @param {String} +// The string to parse. +// +// @return {Moment?} +// A moment instance in case the string could be parsed. +export default function parseDateTime(input, locale) { + const formats = getFormats(config) + const momentInstance = createDateTimeMoment(input, formats, locale) + + return momentInstance.isValid() ? momentInstance : null +} + +export function useI18nFormats(customI18nFormats) { + config.customI18nFormats = customI18nFormats +} + +// Check a moment instance to see if its format (and input value) explicitly +// specify a timezone. This query is useful to know whether the date needs to +// be unfudged in case it does NOT specify a timezone (e.g. using tz-parse). +export function specifiesTimezone(m) { + return !!(m._f.match(/Z/) && m._pf.unusedTokens.indexOf('Z') === -1) +} + +export function toRFC3339WithoutTZ(m) { + return moment(m).locale('en').format('YYYY-MM-DD[T]HH:mm:ss') +} + +// wrap's moment() for parsing datetime strings. assumes the string to be +// parsed is in the profile timezone unless if contains an offset string +// *and* a format token to parse it, and unfudges the result. +export function createDateTimeMoment(input, format, locale) { + // ensure first argument is a string and second is a format or an array + // of formats + if (typeof input !== 'string' || !(typeof format === 'string' || Array.isArray(format))) { + throw new Error( + 'createDateTimeMoment only works on string+format(s). just use moment() directly for any other signature' + ) + } + + let m = moment.apply(null, [input, format, locale]) + + if (m._pf.unusedTokens.length > 0) { + // we didn't use strict at first, because we want to accept when + // there's unused input as long as we're using all tokens. but if the + // best non-strict match has unused tokens, reparse with strict + return moment.apply(null, [input, format, locale, true]) + } + else { + return m + } +} diff --git a/packages/date-time-moment-parser/package.json b/packages/date-time-moment-parser/package.json new file mode 100644 index 00000000000..b7773e364d6 --- /dev/null +++ b/packages/date-time-moment-parser/package.json @@ -0,0 +1,18 @@ +{ + "name": "date-time-moment-parser", + "version": "1.0.0", + "description": "parse DateTime strings into Date objects using moment.js", + "main": "index.js", + "type": "module", + "jest": { + "transform": {} + }, + "scripts": { + "test": "NODE_OPTIONS=--experimental-vm-modules jest" + }, + "author": "Ahmad Amireh ", + "license": "MIT", + "devDependencies": { + "jest": "^26.6.3" + } +} diff --git a/packages/date-time-moment-parser/test.js b/packages/date-time-moment-parser/test.js new file mode 100644 index 00000000000..a6aa666c9e8 --- /dev/null +++ b/packages/date-time-moment-parser/test.js @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2021 - present Instructure, Inc. + * + * This file is part of Canvas. + * + * Canvas is free software: you can redistribute it and/or modify it under + * the terms of the GNU Affero General Public License as published by the Free + * Software Foundation, version 3 of the License. + * + * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License along + * with this program. If not, see . + */ + +import parseDateTime, { createDateTimeMoment, specifiesTimezone, toRFC3339WithoutTZ } from './index' +import tz from 'timezone' +import moment from 'moment' +import detroit from 'timezone/America/Detroit' + +const moonwalk = new Date(Date.UTC(1969, 6, 21, 2, 56)) +const tzDetroit = tz(detroit, 'America/Detroit') +const I18nStub = { + lookup: () => null, + t: () => null +} + +let originalMomentLocale + +beforeEach(() => { + originalMomentLocale = moment.locale() +}) + +afterEach(() => { + moment.locale(originalMomentLocale) + originalMomentLocale = null +}) + +test('moment(one-arg) complains', () => { + expect(() => { + createDateTimeMoment('June 24 at 10:00pm') + }).toThrowError(/ only works on /) +}) + +test('moment(non-string, fmt-string) complains', () => { + expect(() => { + createDateTimeMoment(moonwalk, 'MMMM D h:mmA') + }).toThrowError(/ only works on /) +}) + +test('moment(date-string, non-string) complains', () => { + expect(() => { + createDateTimeMoment('June 24 at 10:00pm', 123) + }).toThrowError(/ only works on /) +}) + +test('moment(date-string, fmt-string) works', () => + expect(createDateTimeMoment('June 24 at 10:00pm', 'MMMM D h:mmA')).toBeTruthy() +) + +test('moment(date-string, [fmt-strings]) works', () => + expect(createDateTimeMoment('June 24 at 10:00pm', ['MMMM D h:mmA', 'L'])).toBeTruthy() +) + +test('moment passes through invalid results', () => { + const m = createDateTimeMoment('not a valid date', 'L') + expect(m.isValid()).toEqual(false) +}) + +test('moment accepts excess input, but all format used', () => { + const m = createDateTimeMoment('12pm and more', 'ha') + expect(m.isValid()).toEqual(true) +}) + +test('moment rejects excess format', () => { + const m = createDateTimeMoment('12pm', 'h:mma') + expect(m.isValid()).toEqual(false) +}) + +test('moment returns moment for valid results', () => { + const m = createDateTimeMoment('June 24, 2015 at 10:00pm -04:00', 'MMMM D, YYYY h:mmA Z') + expect(m.isValid()).toEqual(true) +}) + +test('moment sans-timezone info parses according to profile timezone', () => { + const expected = new Date(1435197600000) // 10pm EDT on June 24, 2015 + const m = createDateTimeMoment('June 24, 2015 at 10:00pm', 'MMMM D, YYYY h:mmA') + expect(specifiesTimezone(m)).toEqual(false) + expect(tzDetroit(toRFC3339WithoutTZ(m))).toEqual(+expected) +}) + +test('moment with-timezone info parses according to that timezone', () => { + const expected = new Date(1435204800000) // 10pm MDT on June 24, 2015 + const m = createDateTimeMoment('June 24, 2015 at 10:00pm -06:00', 'MMMM D, YYYY h:mmA Z') + expect(specifiesTimezone(m)).toEqual(true) + expect(+m.toDate()).toEqual(+expected) + expect(tzDetroit(m.format())).toEqual(+expected) +}) + +test('moment can change locales with single arity', () => { + moment.locale('en') + const m1 = createDateTimeMoment('mercredi 1 juillet 2015 15:00', 'LLLL') + expect(m1._locale._abbr.match(/fr/)).toBeFalsy() + expect(m1.isValid()).toBeFalsy() + + moment.locale('fr') + const m2 = createDateTimeMoment('mercredi 1 juillet 2015 15:00', 'LLLL') + expect(m2._locale._abbr.match(/fr/)).toBeTruthy() + expect(m2.isValid()).toBeTruthy() +}) + +describe('specifiesTimezone', () => { + it('is true when format contains Z and input contains a timezone', () => { + expect( + specifiesTimezone( + createDateTimeMoment('June 24, 2015 at 10:00pm -06:00', 'MMMM D, YYYY h:mmA Z') + ) + ).toEqual(true) + }) + + it('is false when format contains Z but input has no timezone', () => { + expect( + specifiesTimezone( + createDateTimeMoment('June 24, 2015 at 10:00pm', 'MMMM D, YYYY h:mmA Z') + ) + ).toEqual(false) + }) + + it('is false when format does not contain Z even if the input contains a timezone', () => { + expect( + specifiesTimezone( + createDateTimeMoment('June 24, 2015 at 10:00pm -06:00', 'MMMM D, YYYY h:mmA') + ) + ).toEqual(false) + }) +}) diff --git a/spec/coffeescripts/calendar/AgendaViewSpec.js b/spec/coffeescripts/calendar/AgendaViewSpec.js index 280bbd73a28..206e9383b23 100644 --- a/spec/coffeescripts/calendar/AgendaViewSpec.js +++ b/spec/coffeescripts/calendar/AgendaViewSpec.js @@ -18,7 +18,9 @@ import $ from 'jquery' import {isArray, isObject, uniq} from 'lodash' -import tz from '@canvas/timezone' +import tzInTest from '@canvas/timezone/specHelpers' +import { configure as configureTimezone } from '@canvas/timezone' +import timezone from 'timezone' import fcUtil from '@canvas/calendar/jquery/fcUtil.coffee' import denver from 'timezone/America/Denver' import juneau from 'timezone/America/Juneau' @@ -67,16 +69,21 @@ QUnit.module('AgendaView', { fcUtil.addMinuteDelta(this.startDate, -60 * 24 * 15) this.dataSource = new EventDataSource(this.contexts) this.server = sinon.fakeServer.create() - this.snapshot = tz.snapshot() - tz.changeZone(denver, 'America/Denver') + tzInTest.configureAndRestoreLater({ + tz: timezone(denver, 'America/Denver'), + tzData: { + 'America/Denver': denver + }, + momentLocale: 'en' + }) I18nStubber.pushFrame() fakeENV.setup({CALENDAR: {}}) }, teardown() { this.container.remove() this.server.restore() - tz.restore(this.snapshot) - I18nStubber.popFrame() + tzInTest.restore() + I18nStubber.clear() fakeENV.teardown() } }) @@ -188,7 +195,14 @@ test('should only include days on page breaks once', function() { }) test('renders non-assignment events with locale-appropriate format string', function() { - tz.changeLocale(french, 'fr_FR', 'fr') + tzInTest.configureAndRestoreLater({ + tz: timezone(denver, 'America/Denver', french, 'fr_FR'), + tzData: { + 'America/Denver': denver, + }, + momentLocale: 'fr' + }) + // tz.changeLocale(french, 'fr_FR', 'fr') I18nStubber.setLocale('fr_FR') I18nStubber.stub('fr_FR', {'time.formats.tiny': '%k:%M'}) const view = new AgendaView({ @@ -210,7 +224,13 @@ test('renders non-assignment events with locale-appropriate format string', func }) test('renders assignment events with locale-appropriate format string', function() { - tz.changeLocale(french, 'fr_FR', 'fr') + tzInTest.configureAndRestoreLater({ + tz: timezone(denver, 'America/Denver', french, 'fr_FR'), + tzData: { + 'America/Denver': denver, + }, + momentLocale: 'fr' + }) I18nStubber.setLocale('fr_FR') I18nStubber.stub('fr_FR', {'time.formats.tiny': '%k:%M'}) const view = new AgendaView({ @@ -232,7 +252,13 @@ test('renders assignment events with locale-appropriate format string', function }) test('renders non-assignment events in appropriate timezone', function() { - tz.changeZone(juneau, 'America/Juneau') + tzInTest.configureAndRestoreLater({ + tz: timezone(juneau, 'America/Juneau'), + tzData: { + 'America/Juneau': juneau + } + }) + I18nStubber.stub('en', { 'time.formats.tiny': '%l:%M%P', date: {} @@ -256,7 +282,12 @@ test('renders non-assignment events in appropriate timezone', function() { }) test('renders assignment events in appropriate timezone', function() { - tz.changeZone(juneau, 'America/Juneau') + tzInTest.configureAndRestoreLater({ + tz: timezone(juneau, 'America/Juneau'), + tzData: { + 'America/Juneau': juneau + } + }) I18nStubber.stub('en', { 'time.formats.tiny': '%l:%M%P', date: {} diff --git a/spec/coffeescripts/calendar/CalendarSpec.js b/spec/coffeescripts/calendar/CalendarSpec.js index 1af7d630c69..8f36ba30cae 100644 --- a/spec/coffeescripts/calendar/CalendarSpec.js +++ b/spec/coffeescripts/calendar/CalendarSpec.js @@ -22,6 +22,8 @@ import I18n from 'i18n!calendar' import fcUtil from '@canvas/calendar/jquery/fcUtil.coffee' import moment from 'moment' import tz from '@canvas/timezone' +import tzInTest from '@canvas/timezone/specHelpers' +import timezone from 'timezone' import denver from 'timezone/America/Denver' import fixtures from 'helpers/fixtures' import $ from 'jquery' @@ -30,14 +32,19 @@ import fakeENV from 'helpers/fakeENV' QUnit.module('Calendar', { setup() { - this.snapshot = tz.snapshot() - tz.changeZone(denver, 'America/Denver') + tzInTest.configureAndRestoreLater({ + tz: timezone(denver, 'America/Denver'), + tzData: { + 'America/Denver': denver + } + }) + fixtures.setup() sinon.stub($, 'getJSON') fakeENV.setup() }, teardown() { - tz.restore(this.snapshot) + tzInTest.restore() const calendar = $('#fixtures .calendar').data('fullCalendar') if (calendar) { calendar.destroy() diff --git a/spec/coffeescripts/calendar/EditAssignmentDetailsSpec.js b/spec/coffeescripts/calendar/EditAssignmentDetailsSpec.js index 22408b86f02..70f5611b073 100644 --- a/spec/coffeescripts/calendar/EditAssignmentDetailsSpec.js +++ b/spec/coffeescripts/calendar/EditAssignmentDetailsSpec.js @@ -71,6 +71,7 @@ QUnit.module('EditAssignmentDetails', { document.getElementById('fixtures').innerHTML = '' fakeENV.teardown() tz.restore(this.snapshot) + I18nStubber.clear() } }) const createView = function(model, event) { @@ -135,7 +136,6 @@ test('should localize start date', function() { }) const view = createView(commonEvent(), this.event) equal(view.$('.datetime_field').val(), 'ven. 7 août 2015 17:00') - I18nStubber.popFrame() }) test('requires name to save assignment event', function() { diff --git a/spec/coffeescripts/calendar/EditPlannerNoteDetailsSpec.js b/spec/coffeescripts/calendar/EditPlannerNoteDetailsSpec.js index 2dfb47744c5..16f2a62af02 100644 --- a/spec/coffeescripts/calendar/EditPlannerNoteDetailsSpec.js +++ b/spec/coffeescripts/calendar/EditPlannerNoteDetailsSpec.js @@ -49,6 +49,7 @@ QUnit.module('EditAssignmentDetails', { document.getElementById('fixtures').innerHTML = '' fakeENV.teardown() tz.restore(this.snapshot) + I18nStubber.clear() } }) const createView = function(event = note) { @@ -74,7 +75,6 @@ test('should localize start date', () => { }) const view = createView(commonEvent()) equal(view.$('.date_field').val(), '22 juil. 2017') - I18nStubber.popFrame() }) test('requires name to save assignment note', () => { diff --git a/spec/coffeescripts/calendar/fcUtilSpec.js b/spec/coffeescripts/calendar/fcUtilSpec.js index 7aa9399ac70..c1fcccc33f2 100644 --- a/spec/coffeescripts/calendar/fcUtilSpec.js +++ b/spec/coffeescripts/calendar/fcUtilSpec.js @@ -18,15 +18,21 @@ import fcUtil from '@canvas/calendar/jquery/fcUtil.coffee' import tz from '@canvas/timezone' +import tzInTest from '@canvas/timezone/specHelpers' +import timezone from 'timezone' import denver from 'timezone/America/Denver' QUnit.module('Calendar', { setup() { - this.snapshot = tz.snapshot() - tz.changeZone(denver, 'America/Denver') + tzInTest.configureAndRestoreLater({ + tz: timezone(denver, 'America/Denver'), + tzData: { + 'America/Denver': denver + } + }) }, teardown() { - tz.restore(this.snapshot) + tzInTest.restore() } }) diff --git a/spec/coffeescripts/handlebars_helpersSpec.js b/spec/coffeescripts/handlebars_helpersSpec.js index 3f96c8fb312..10d14a5776d 100644 --- a/spec/coffeescripts/handlebars_helpersSpec.js +++ b/spec/coffeescripts/handlebars_helpersSpec.js @@ -23,6 +23,8 @@ import assertions from 'helpers/assertions' import fakeENV from 'helpers/fakeENV' import numberFormat from '@canvas/i18n/numberFormat' import tz from '@canvas/timezone' +import tzInTest from '@canvas/timezone/specHelpers' +import timezone from 'timezone' import detroit from 'timezone/America/Detroit' import chicago from 'timezone/America/Chicago' import newYork from 'timezone/America/New_York' @@ -125,12 +127,11 @@ test('supports truncation left', () => { QUnit.module('friendlyDatetime', { setup() { - this.snapshot = tz.snapshot() return tz.changeZone(detroit, 'America/Detroit') }, teardown() { - tz.restore(this.snapshot) + tzInTest.restore() } }) @@ -166,16 +167,20 @@ test('includes a visible version', () => QUnit.module('contextSensitive FriendlyDatetime', { setup() { - this.snapshot = tz.snapshot() fakeENV.setup() ENV.CONTEXT_TIMEZONE = 'America/Chicago' - tz.changeZone(detroit, 'America/Detroit') - return tz.preload('America/Chicago', chicago) + tzInTest.configureAndRestoreLater({ + tz: timezone(detroit, 'America/Detroit'), + tzData: { + 'America/Chicago': chicago, + 'America/Detroit': detroit, + } + }) }, teardown() { fakeENV.teardown() - tz.restore(this.snapshot) + tzInTest.restore() } }) @@ -216,14 +221,19 @@ QUnit.module('contextSensitiveDatetimeTitle', { this.snapshot = tz.snapshot() fakeENV.setup() ENV.CONTEXT_TIMEZONE = 'America/Chicago' - tz.changeZone(detroit, 'America/Detroit') - tz.preload('America/Chicago', chicago) - return tz.preload('America/New_York', newYork) + tzInTest.configureAndRestoreLater({ + tz: timezone(detroit, 'America/Detroit'), + tzData: { + 'America/Chicago': chicago, + 'America/Detroit': detroit, + 'America/New_York': newYork + } + }) }, teardown() { fakeENV.teardown() - tz.restore(this.snapshot) + tzInTest.restore() } }) @@ -244,7 +254,13 @@ test('splits title text to both zones', () => { test('properly spans day boundaries', () => { ENV.TIMEZONE = 'America/Chicago' - tz.changeZone(chicago, 'America/Chicago') + tzInTest.configureAndRestoreLater({ + tz: timezone(chicago, 'America/Chicago'), + tzData: { + 'America/Chicago': chicago, + 'America/New_York': newYork + } + }) ENV.CONTEXT_TIMEZONE = 'America/New_York' const titleText = helpers.contextSensitiveDatetimeTitle('1970-01-01 05:30:00Z', { hash: {justText: true} diff --git a/spec/coffeescripts/helpers/I18nStubber.js b/spec/coffeescripts/helpers/I18nStubber.js index 8339b829cad..5835a1b978e 100644 --- a/spec/coffeescripts/helpers/I18nStubber.js +++ b/spec/coffeescripts/helpers/I18nStubber.js @@ -17,8 +17,10 @@ */ import I18n from '@canvas/i18n' +import 'translations/_core_en' const frames = [] +const enTranslations = I18n.translations.en export default { pushFrame() { @@ -40,6 +42,11 @@ export default { clear() { while (frames.length > 0) this.popFrame() }, + useInitialTranslations() { + this.pushFrame() + I18n.locale = 'en' + I18n.translations = { en: enTranslations } + }, stub(locale, translations, cb) { if (cb) { return this.withFrame(() => this.stub(locale, translations), cb) diff --git a/spec/coffeescripts/i18nSpec.js b/spec/coffeescripts/i18nSpec.js index f3f63f8f2e6..ba60fe77a47 100644 --- a/spec/coffeescripts/i18nSpec.js +++ b/spec/coffeescripts/i18nSpec.js @@ -34,7 +34,7 @@ QUnit.module('I18n', { }, teardown() { - return I18nStubber.popFrame() + return I18nStubber.clear() } }) @@ -159,7 +159,7 @@ QUnit.module('I18n localize number', { }, teardown() { - return I18nStubber.popFrame() + return I18nStubber.clear() } }) diff --git a/spec/coffeescripts/instructureDateAndTimeSpec.js b/spec/coffeescripts/instructureDateAndTimeSpec.js index 8d9c411132c..a441f500138 100644 --- a/spec/coffeescripts/instructureDateAndTimeSpec.js +++ b/spec/coffeescripts/instructureDateAndTimeSpec.js @@ -18,6 +18,8 @@ import $ from 'jquery' import tz from '@canvas/timezone' +import tzInTest from '@canvas/timezone/specHelpers' +import timezone from 'timezone' import detroit from 'timezone/America/Detroit' import juneau from 'timezone/America/Juneau' import kolkata from 'timezone/Asia/Kolkata' @@ -27,11 +29,10 @@ import '@canvas/datetime' QUnit.module('fudgeDateForProfileTimezone', { setup() { - this.snapshot = tz.snapshot() this.original = new Date(Date.UTC(2013, 8, 1)) }, teardown() { - tz.restore(this.snapshot) + tzInTest.restore() } }) @@ -42,7 +43,17 @@ test('should produce a date that formats via toString same as the original forma test('should parse dates before the year 1000', () => { // using specific string (and specific timezone to guarantee it) since tz.format has a bug pre-1000 - tz.changeZone(detroit, 'America Detroit') + // + // TODO: in 2021, this appears to be bogus as it's never actually specifying + // the timezone as the comment above states because "America Detroit" doesn't + // resolve to one (America/Detroit does) and tz just ends up using UTC + tzInTest.configureAndRestoreLater({ + tz: timezone(detroit, 'America Detroit'), + tzData: { + 'America Detroit': detroit, + } + }) + const oldDate = new Date(Date.UTC(900, 1, 1, 0, 0, 0)) const oldFudgeDate = $.fudgeDateForProfileTimezone(oldDate) equal(oldFudgeDate.toString('yyyy-MM-dd HH:mm:ss'), '0900-02-01 00:00:00') @@ -65,21 +76,30 @@ test('should not return treat 0 as invalid', () => equal(+$.fudgeDateForProfileTimezone(0), +$.fudgeDateForProfileTimezone(new Date(0)))) test('should be sensitive to profile time zone', function () { - tz.changeZone(detroit, 'America/Detroit') + tzInTest.configureAndRestoreLater({ + tz: timezone(detroit, 'America/Detroit'), + tzData: { + 'America/Detroit': detroit, + } + }) let fudged = $.fudgeDateForProfileTimezone(this.original) equal(fudged.toString('yyyy-MM-dd HH:mm:ss'), tz.format(this.original, '%F %T')) - tz.changeZone(juneau, 'America/Juneau') + tzInTest.configureAndRestoreLater({ + tz: timezone(juneau, 'America/Juneau'), + tzData: { + 'America/Juneau': juneau, + } + }) fudged = $.fudgeDateForProfileTimezone(this.original) equal(fudged.toString('yyyy-MM-dd HH:mm:ss'), tz.format(this.original, '%F %T')) }) QUnit.module('unfudgeDateForProfileTimezone', { setup() { - this.snapshot = tz.snapshot() this.original = new Date(Date.UTC(2013, 8, 1)) }, teardown() { - tz.restore(this.snapshot) + tzInTest.restore() } }) @@ -105,20 +125,28 @@ test('should not return treat 0 as invalid', () => equal(+$.unfudgeDateForProfileTimezone(0), +$.unfudgeDateForProfileTimezone(new Date(0)))) test('should be sensitive to profile time zone', function () { - tz.changeZone(detroit, 'America/Detroit') + tzInTest.configureAndRestoreLater({ + tz: timezone(detroit, 'America/Detroit'), + tzData: { + 'America/Detroit': detroit, + } + }) + let unfudged = $.unfudgeDateForProfileTimezone(this.original) equal(tz.format(unfudged, '%F %T'), this.original.toString('yyyy-MM-dd HH:mm:ss')) - tz.changeZone(juneau, 'America/Juneau') + tzInTest.configureAndRestoreLater({ + tz: timezone(juneau, 'America/Juneau'), + tzData: { + 'America/Juneau': juneau, + } + }) unfudged = $.unfudgeDateForProfileTimezone(this.original) equal(tz.format(unfudged, '%F %T'), this.original.toString('yyyy-MM-dd HH:mm:ss')) }) QUnit.module('sameYear', { - setup() { - this.snapshot = tz.snapshot() - }, teardown() { - tz.restore(this.snapshot) + tzInTest.restore() } }) @@ -131,7 +159,12 @@ test('should return true iff both dates from same year', () => { }) test('should compare relative to profile timezone', () => { - tz.changeZone(detroit, 'America/Detroit') + tzInTest.configureAndRestoreLater({ + tz: timezone(detroit, 'America/Detroit'), + tzData: { + 'America/Detroit': detroit, + } + }) const date1 = new Date(5 * 3600000) // 5am UTC = 12am EST const date2 = new Date(+date1 + 1000) // Jan 1, 1970 at 11:59:59pm EST const date3 = new Date(+date1 - 1000) // Jan 2, 1970 at 00:00:01am EST @@ -140,11 +173,8 @@ test('should compare relative to profile timezone', () => { }) QUnit.module('sameDate', { - setup() { - this.snapshot = tz.snapshot() - }, teardown() { - tz.restore(this.snapshot) + tzInTest.restore() } }) @@ -157,7 +187,12 @@ test('should return true iff both times from same day', () => { }) test('should compare relative to profile timezone', () => { - tz.changeZone(detroit, 'America/Detroit') + tzInTest.configureAndRestoreLater({ + tz: timezone(detroit, 'America/Detroit'), + tzData: { + 'America/Detroit': detroit, + } + }) const date1 = new Date(86400000 + 5 * 3600000) const date2 = new Date(+date1 + 1000) const date3 = new Date(+date1 - 1000) @@ -167,35 +202,43 @@ test('should compare relative to profile timezone', () => { QUnit.module('dateString', { setup() { - this.snapshot = tz.snapshot() I18nStubber.pushFrame() }, teardown() { - tz.restore(this.snapshot) - I18nStubber.popFrame() + tzInTest.restore() + I18nStubber.clear() } }) test('should format in profile timezone', () => { I18nStubber.stub('en', {'date.formats.medium': '%b %-d, %Y'}) - tz.changeZone(detroit, 'America/Detroit') + tzInTest.configureAndRestoreLater({ + tz: timezone(detroit, 'America/Detroit'), + tzData: { + 'America/Detroit': detroit, + } + }) equal($.dateString(new Date(0)), 'Dec 31, 1969') }) QUnit.module('timeString', { setup() { - this.snapshot = tz.snapshot() I18nStubber.pushFrame() }, teardown() { - tz.restore(this.snapshot) - I18nStubber.popFrame() + tzInTest.restore() + I18nStubber.clear() } }) test('should format in profile timezone', () => { I18nStubber.stub('en', {'time.formats.tiny': '%l:%M%P'}) - tz.changeZone(detroit, 'America/Detroit') + tzInTest.configureAndRestoreLater({ + tz: timezone(detroit, 'America/Detroit'), + tzData: { + 'America/Detroit': detroit, + } + }) equal($.timeString(new Date(60000)), '7:01pm') }) @@ -207,7 +250,12 @@ test('should format according to profile locale', () => { test('should use the tiny_on_the_hour format on the hour', () => { I18nStubber.stub('en', {'time.formats.tiny_on_the_hour': '%l%P'}) - tz.changeZone(detroit, 'America/Detroit') + tzInTest.configureAndRestoreLater({ + tz: timezone(detroit, 'America/Detroit'), + tzData: { + 'America/Detroit': detroit, + } + }) equal($.timeString(new Date(0)), '7pm') }) @@ -215,23 +263,33 @@ test('should use the tiny format on the hour, when timezone difference is not in I18nStubber.stub('en', {'time.formats.tiny': '%l:%M%P'}) I18nStubber.stub('en', {'time.formats.tiny_on_the_hour': '%l%P'}) // kolkata: +05:30 - tz.changeZone(kolkata, 'Asia/Kolkata') + tzInTest.configureAndRestoreLater({ + tz: timezone(kolkata, 'Asia/Kolkata'), + tzData: { + 'Asia/Kolkata': kolkata, + 'America/Detroit': detroit + } + }) equal($.timeString(new Date(30 * 60 * 1000), {timezone: 'America/Detroit'}), '7:30pm') }) QUnit.module('datetimeString', { setup() { - this.snapshot = tz.snapshot() I18nStubber.pushFrame() }, teardown() { - tz.restore(this.snapshot) - I18nStubber.popFrame() + tzInTest.restore() + I18nStubber.clear() } }) test('should format in profile timezone', () => { - tz.changeZone(detroit, 'America/Detroit') + tzInTest.configureAndRestoreLater({ + tz: timezone(detroit, 'America/Detroit'), + tzData: { + 'America/Detroit': detroit, + } + }) I18nStubber.stub('en', { 'date.formats.medium': '%b %-d, %Y', 'time.formats.tiny': '%l:%M%P', @@ -241,7 +299,11 @@ test('should format in profile timezone', () => { }) test('should translate into the profile locale', () => { - tz.changeLocale(portuguese, 'pt_PT', 'pt') + tzInTest.configureAndRestoreLater({ + tz: timezone(portuguese, 'pt_PT'), + momentLocale: 'pt' + }) + I18nStubber.setLocale('pt') I18nStubber.stub('pt', { 'date.formats.medium': '%-d %b %Y', @@ -253,18 +315,23 @@ test('should translate into the profile locale', () => { QUnit.module('$.datepicker.parseDate', { setup() { - this.snapshot = tz.snapshot() I18nStubber.pushFrame() }, teardown() { - tz.restore(this.snapshot) - I18nStubber.popFrame() + tzInTest.restore() + I18nStubber.clear() } }) test('should accept localized strings and return them fudged', () => { - tz.changeZone(detroit, 'America/Detroit') - tz.changeLocale(portuguese, 'pt_PT', 'pt') + tzInTest.configureAndRestoreLater({ + tz: timezone(detroit, 'America/Detroit', portuguese, 'pt_PT'), + tzData: { + 'America/Detroit': detroit + }, + momentLocale: 'pt' + }) + I18nStubber.setLocale('pt') I18nStubber.stub('pt', { // this isn't the real format, but we want the %Y in here to make it diff --git a/spec/coffeescripts/jsx/due_dates/DueDateCalendarPickerSpec.js b/spec/coffeescripts/jsx/due_dates/DueDateCalendarPickerSpec.js index 8a0e3d576f8..ba780d003b7 100644 --- a/spec/coffeescripts/jsx/due_dates/DueDateCalendarPickerSpec.js +++ b/spec/coffeescripts/jsx/due_dates/DueDateCalendarPickerSpec.js @@ -21,6 +21,7 @@ import {mount} from 'enzyme' import chicago from 'timezone/America/Chicago' import DueDateCalendarPicker from '@canvas/due-dates/react/DueDateCalendarPicker' import tz from '@canvas/timezone' +import tzInTest from '@canvas/timezone/specHelpers' import french from 'timezone/fr_FR' import I18nStubber from 'helpers/I18nStubber' import fakeENV from 'helpers/fakeENV' @@ -52,6 +53,7 @@ QUnit.module('DueDateCalendarPicker', suiteHooks => { wrapper.unmount() clock.restore() fakeENV.teardown() + tzInTest.restore() }) function mountComponent() { @@ -79,10 +81,8 @@ QUnit.module('DueDateCalendarPicker', suiteHooks => { test('converts to fancy midnight in the timezone of the user', () => { props.isFancyMidnight = true mountComponent() - const snapshot = tz.snapshot() - tz.changeZone(chicago, 'America/Chicago') + tzInTest.changeZone(chicago, 'America/Chicago') simulateChange('2015-08-31T00:00:00') - tz.restore(snapshot) equal(getEnteredDate().toUTCString(), 'Tue, 01 Sep 2015 04:59:59 GMT') }) @@ -99,8 +99,7 @@ QUnit.module('DueDateCalendarPicker', suiteHooks => { test('#formattedDate() returns a localized Date', () => { mountComponent() - const snapshot = tz.snapshot() - tz.changeLocale(french, 'fr_FR', 'fr') + tzInTest.changeLocale(french, 'fr_FR', 'fr') I18nStubber.pushFrame() I18nStubber.setLocale('fr_FR') I18nStubber.stub('fr_FR', { @@ -108,8 +107,7 @@ QUnit.module('DueDateCalendarPicker', suiteHooks => { 'time.formats.tiny': '%-k:%M' }) equal(wrapper.instance().formattedDate(), '1 févr. 2012 7:01') - I18nStubber.popFrame() - tz.restore(snapshot) + I18nStubber.clear() }) test('call the update prop when changed', () => { diff --git a/spec/coffeescripts/jsx/shared/helpers/dateHelperSpec.js b/spec/coffeescripts/jsx/shared/helpers/dateHelperSpec.js index a15872cf2a8..5c7687252f3 100644 --- a/spec/coffeescripts/jsx/shared/helpers/dateHelperSpec.js +++ b/spec/coffeescripts/jsx/shared/helpers/dateHelperSpec.js @@ -19,6 +19,8 @@ import DateHelper from '@canvas/datetime/dateHelper' import {isDate, isNull, isUndefined} from 'lodash' import tz from '@canvas/timezone' +import tzInTest from '@canvas/timezone/specHelpers' +import timezone from 'timezone' import detroit from 'timezone/America/Detroit' import juneau from 'timezone/America/Juneau' @@ -59,19 +61,28 @@ test('gracefully handles undefined values', () => { QUnit.module('DateHelper#formatDatetimeForDisplay', { setup() { - this.snapshot = tz.snapshot() }, teardown() { - tz.restore(this.snapshot) + tzInTest.restore() } }) test('formats the date for display, adjusted for the timezone', () => { const assignment = defaultAssignment() - tz.changeZone(detroit, 'America/Detroit') + tzInTest.configureAndRestoreLater({ + tz: timezone(detroit, 'America/Detroit'), + tzData: { + 'America/Detroit': detroit + } + }) let formattedDate = DateHelper.formatDatetimeForDisplay(assignment.due_at) equal(formattedDate, 'Jul 14, 2015 at 2:35pm') - tz.changeZone(juneau, 'America/Juneau') + tzInTest.configureAndRestoreLater({ + tz: timezone(juneau, 'America/Juneau'), + tzData: { + 'America/Juneau': juneau + } + }) formattedDate = DateHelper.formatDatetimeForDisplay(assignment.due_at) equal(formattedDate, 'Jul 14, 2015 at 10:35am') }) @@ -93,37 +104,51 @@ test("can specify 'short' format which excludes the year if it matches the curre }) QUnit.module('DateHelper#formatDateForDisplay', { - setup() { - this.snapshot = tz.snapshot() - }, teardown() { - tz.restore(this.snapshot) + tzInTest.restore() } }) test('formats the date for display, adjusted for the timezone, excluding the time', () => { const assignment = defaultAssignment() - tz.changeZone(detroit, 'America/Detroit') + tzInTest.configureAndRestoreLater({ + tz: timezone(detroit, 'America/Detroit'), + tzData: { + 'America/Detroit': detroit + } + }) let formattedDate = DateHelper.formatDateForDisplay(assignment.due_at) equal(formattedDate, 'Jul 14, 2015') - tz.changeZone(juneau, 'America/Juneau') + tzInTest.configureAndRestoreLater({ + tz: timezone(juneau, 'America/Juneau'), + tzData: { + 'America/Juneau': juneau + } + }) formattedDate = DateHelper.formatDateForDisplay(assignment.due_at) equal(formattedDate, 'Jul 14, 2015') }) QUnit.module('DateHelper#isMidnight', { - setup() { - this.snapshot = tz.snapshot() - }, teardown() { - tz.restore(this.snapshot) + tzInTest.restore() } }) test('returns true if the time is midnight, adjusted for the timezone', () => { const date = '2015-07-14T04:00:00Z' - tz.changeZone(detroit, 'America/Detroit') + tzInTest.configureAndRestoreLater({ + tz: timezone(detroit, 'America/Detroit'), + tzData: { + 'America/Detroit': detroit + } + }) ok(DateHelper.isMidnight(date)) - tz.changeZone(juneau, 'America/Juneau') + tzInTest.configureAndRestoreLater({ + tz: timezone(juneau, 'America/Juneau'), + tzData: { + 'America/Juneau': juneau + } + }) notOk(DateHelper.isMidnight(date)) }) diff --git a/spec/coffeescripts/views/SyllabusViewSpec.js b/spec/coffeescripts/views/SyllabusViewSpec.js index 6a2d4318d74..fe15b51df74 100644 --- a/spec/coffeescripts/views/SyllabusViewSpec.js +++ b/spec/coffeescripts/views/SyllabusViewSpec.js @@ -19,6 +19,8 @@ import $ from 'jquery' import _ from 'lodash' import tz from '@canvas/timezone' +import tzInTest from '@canvas/timezone/specHelpers' +import timezone from 'timezone' import denver from 'timezone/America/Denver' import newYork from 'timezone/America/New_York' import SyllabusBehaviors from '@canvas/syllabus/backbone/behaviors/SyllabusBehaviors' @@ -119,9 +121,13 @@ QUnit.module('Syllabus', { // Setup stubs/mocks this.server = setupServerResponses() - this.tzSnapshot = tz.snapshot() - tz.changeZone(denver, 'America/Denver') - tz.preload('America/New_York', newYork) + tzInTest.configureAndRestoreLater({ + tz: timezone(denver, 'America/Denver'), + tzData: { + 'America/Denver': denver, + 'America/New_York': newYork + } + }) this.clock = sinon.useFakeTimers(new Date(2012, 0, 23, 15, 30).getTime()) @@ -190,7 +196,7 @@ QUnit.module('Syllabus', { this.miniMonth.remove() this.jumpToToday.remove() this.clock.restore() - tz.restore(this.tzSnapshot) + tzInTest.restore() this.server.restore() document.getElementById('fixtures').innerHTML = '' }, diff --git a/spec/coffeescripts/views/assignments/AssignmentListItemViewSpec.js b/spec/coffeescripts/views/assignments/AssignmentListItemViewSpec.js index 83750169aa5..8f9402d8b52 100644 --- a/spec/coffeescripts/views/assignments/AssignmentListItemViewSpec.js +++ b/spec/coffeescripts/views/assignments/AssignmentListItemViewSpec.js @@ -23,7 +23,8 @@ import Assignment from '@canvas/assignments/backbone/models/Assignment.coffee' import Submission from '@canvas/assignments/backbone/models/Submission' import AssignmentListItemView from 'ui/features/assignment_index/backbone/views/AssignmentListItemView.coffee' import $ from 'jquery' -import tz from '@canvas/timezone' +import tzInTest from '@canvas/timezone/specHelpers' +import timezone from 'timezone' import juneau from 'timezone/America/Juneau' import french from 'timezone/fr_FR' import I18nStubber from 'helpers/I18nStubber' @@ -169,14 +170,13 @@ QUnit.module('AssignmentListItemViewSpec', { URLS: {assignment_sort_base_url: 'test'} }) genSetup.call(this) - this.snapshot = tz.snapshot() return I18nStubber.pushFrame() }, teardown() { fakeENV.teardown() genTeardown.call(this) - tz.restore(this.snapshot) - return I18nStubber.popFrame() + tzInTest.restore() + return I18nStubber.clear() } }) @@ -542,7 +542,10 @@ test('does not render score template without permission', function () { }) test('renders lockAt/unlockAt with locale-appropriate format string', function () { - tz.changeLocale(french, 'fr_FR', 'fr') + tzInTest.configureAndRestoreLater({ + tz: timezone(french, 'fr_FR'), + momentLocale: 'fr' + }) I18nStubber.setLocale('fr_FR') I18nStubber.stub('fr_FR', { 'date.formats.short': '%-d %b', @@ -570,7 +573,12 @@ test('renders lockAt/unlockAt with locale-appropriate format string', function ( }) test('renders lockAt/unlockAt in appropriate time zone', function () { - tz.changeZone(juneau, 'America/Juneau') + tzInTest.configureAndRestoreLater({ + tz: timezone(juneau, 'America/Juneau'), + tzData: { + 'America/Juneau': juneau + } + }) I18nStubber.stub('en', { 'date.formats.short': '%b %-d', 'date.formats.date_at_time': '%b %-d at %l:%M%P', @@ -646,7 +654,10 @@ test('does not render lockAt/unlockAt when not locking in future', () => { }) test('renders due date column with locale-appropriate format string', function () { - tz.changeLocale(french, 'fr_FR', 'fr') + tzInTest.configureAndRestoreLater({ + tz: timezone(french, 'fr_FR'), + momentLocale: 'fr' + }) I18nStubber.setLocale('fr_FR') I18nStubber.stub('fr_FR', { 'date.formats.short': '%-d %b', @@ -660,7 +671,12 @@ test('renders due date column with locale-appropriate format string', function ( }) test('renders due date column in appropriate time zone', function () { - tz.changeZone(juneau, 'America/Juneau') + tzInTest.configureAndRestoreLater({ + tz: timezone(juneau, 'America/Juneau'), + tzData: { + 'America/Juneau': juneau + } + }) I18nStubber.stub('en', { 'date.formats.short': '%b %-d', 'date.abbr_month_names.8': 'Aug' diff --git a/spec/coffeescripts/views/assignments/CreateAssignmentViewSpec.js b/spec/coffeescripts/views/assignments/CreateAssignmentViewSpec.js index 9b17ac6a779..cd92d678e59 100644 --- a/spec/coffeescripts/views/assignments/CreateAssignmentViewSpec.js +++ b/spec/coffeescripts/views/assignments/CreateAssignmentViewSpec.js @@ -23,6 +23,8 @@ import CreateAssignmentView from 'ui/features/assignment_index/backbone/views/Cr import DialogFormView from '@canvas/forms/backbone/views/DialogFormView.coffee' import $ from 'jquery' import tz from '@canvas/timezone' +import tzInTest from '@canvas/timezone/specHelpers' +import timezone from 'timezone' import juneau from 'timezone/America/Juneau' import french from 'timezone/fr_FR' import I18nStubber from 'helpers/I18nStubber' @@ -142,14 +144,13 @@ QUnit.module('CreateAssignmentView', { this.assignment4 = new Assignment(buildAssignment4()) this.assignment5 = new Assignment(buildAssignment5()) this.group = assignmentGroup() - this.snapshot = tz.snapshot() I18nStubber.pushFrame() fakeENV.setup() }, teardown() { fakeENV.teardown() - tz.restore(this.snapshot) - I18nStubber.popFrame() + tzInTest.restore() + I18nStubber.clear() } }) @@ -325,12 +326,7 @@ test('openAgain adds datetime picker', function() { test('adjust datetime to the end of a day for midnight time', function() { sandbox.stub(DialogFormView.prototype, 'openAgain') - I18nStubber.setLocale('fr_FR') - // tz use the key/values to get formats for different locale - I18nStubber.stub('fr_FR', { - 'date.formats.short': '%b %-d', - 'time.formats.tiny': '%l:%M%P' - }) + I18nStubber.useInitialTranslations() const tmp = $.screenReaderFlashMessageExclusive $.screenReaderFlashMessageExclusive = sinon.spy() const view = createView(this.assignment2) @@ -345,12 +341,7 @@ test('adjust datetime to the end of a day for midnight time', function() { test('it does not adjust datetime for other date time', function() { sandbox.stub(DialogFormView.prototype, 'openAgain') - I18nStubber.setLocale('fr_FR') - // tz use the key/values to get formats for different locale - I18nStubber.stub('fr_FR', { - 'date.formats.short': '%b %-d', - 'time.formats.tiny': '%l:%M%P' - }) + I18nStubber.useInitialTranslations() const tmp = $.screenReaderFlashMessageExclusive $.screenReaderFlashMessageExclusive = sinon.spy() const view = createView(this.assignment2) @@ -553,7 +544,10 @@ test('validates due date for lock and unlock', function() { }) test('renders due dates with locale-appropriate format string', function() { - tz.changeLocale(french, 'fr_FR', 'fr') + tzInTest.configureAndRestoreLater({ + tz: timezone(french, 'fr_FR'), + momentLocale: 'fr' + }) I18nStubber.setLocale('fr_FR') I18nStubber.stub('fr_FR', { 'date.formats.short': '%-d %b', @@ -571,7 +565,13 @@ test('renders due dates with locale-appropriate format string', function() { }) test('renders due dates in appropriate time zone', function() { - tz.changeZone(juneau, 'America/Juneau') + tzInTest.configureAndRestoreLater({ + tz: timezone(juneau, 'America/Juneau'), + tzData: { + 'America/Juneau': juneau + } + }) + I18nStubber.stub('en', { 'date.formats.short': '%b %-d', 'date.abbr_month_names.8': 'Aug' diff --git a/spec/coffeescripts/views/outcomes/OutcomeViewSpec.js b/spec/coffeescripts/views/outcomes/OutcomeViewSpec.js index bf0bc8ac7fa..da9e211450b 100644 --- a/spec/coffeescripts/views/outcomes/OutcomeViewSpec.js +++ b/spec/coffeescripts/views/outcomes/OutcomeViewSpec.js @@ -667,7 +667,7 @@ test('validates i18n mastery points', function() { view.$('input[name="mastery_points"]').val('1 234,5') ok(view.isValid()) view.remove() - I18nStubber.popFrame() + I18nStubber.clear() }) test('getModifiedFields returns false for all fields when not modified', () => { diff --git a/spec/coffeescripts/widgets/DatetimeFieldSpec.js b/spec/coffeescripts/widgets/DatetimeFieldSpec.js index 7c0507172ab..f43460ade36 100644 --- a/spec/coffeescripts/widgets/DatetimeFieldSpec.js +++ b/spec/coffeescripts/widgets/DatetimeFieldSpec.js @@ -19,6 +19,8 @@ import DatetimeField from '@canvas/datetime/jquery/DatetimeField' import $ from 'jquery' import tz from '@canvas/timezone' +import tzInTest from '@canvas/timezone/specHelpers' +import timezone from 'timezone' import detroit from 'timezone/America/Detroit' import juneau from 'timezone/America/Juneau' import portuguese from 'timezone/pt_PT' @@ -253,13 +255,12 @@ test('should set suggest text', function() { QUnit.module('parseValue', { setup() { - this.snapshot = tz.snapshot() this.$field = $('') this.field = new DatetimeField(this.$field, {}) }, teardown() { - tz.restore(this.snapshot) + tzInTest.restore() } }) @@ -355,7 +356,6 @@ test('interprets time-only fields as occurring on implicit date if set', functio QUnit.module('updateData', { setup() { - this.snapshot = tz.snapshot() tz.changeZone(detroit, 'America/Detroit') this.$field = $('') this.$field.val('Jan 1, 1970 at 12:01am') @@ -365,7 +365,7 @@ QUnit.module('updateData', { }, teardown() { - tz.restore(this.snapshot) + tzInTest.restore() } }) @@ -403,7 +403,13 @@ test('sets time-* to fudged, 12-hour values', function() { }) test('sets time-* to fudged, 24-hour values', function() { - tz.changeLocale(portuguese, 'pt_PT', 'pt') + tzInTest.configureAndRestoreLater({ + tz: timezone(detroit, 'America/Detroit', portuguese, 'pt_PT'), + tzData: { + 'America/Detroit': detroit, + }, + momentLocale: 'pt' + }) this.field.updateData() equal(this.$field.data('time-hour'), '21') equal(this.$field.data('time-minute'), '56') @@ -573,19 +579,29 @@ test('returns time only if @showDate false', function() { }) test('localizes formatting of dates and times', function() { - tz.changeLocale(portuguese, 'pt_PT', 'pt') + tzInTest.configureAndRestoreLater({ + tz: timezone(detroit, 'America/Detroit', portuguese, 'pt_PT'), + tzData: { + 'America/Detroit': detroit, + }, + momentLocale: 'pt' + }) I18nStubber.pushFrame() I18nStubber.setLocale('pt_PT') I18nStubber.stub('pt_PT', {'date.formats.full_with_weekday': '%a, %-d %b %Y %k:%M'}) equal(this.field.formatSuggest(), 'Dom, 20 Jul 1969 21:56') - I18nStubber.popFrame() + I18nStubber.clear() }) QUnit.module('formatSuggestCourse', { setup() { - this.snapshot = tz.snapshot() - tz.changeZone(detroit, 'America/Detroit') - tz.preload('America/Juneau', juneau) + tzInTest.configureAndRestoreLater({ + tz: timezone(detroit, 'America/Detroit'), + tzData: { + 'America/Detroit': detroit, + 'America/Juneau': juneau, + } + }) fakeENV.setup({TIMEZONE: 'America/Detroit', CONTEXT_TIMEZONE: 'America/Juneau'}) this.$field = $('') this.$field.val('Jul 20, 1969 at 9:56pm') @@ -594,7 +610,7 @@ QUnit.module('formatSuggestCourse', { teardown() { fakeENV.teardown() - tz.restore(this.snapshot) + tzInTest.restore() } }) @@ -719,13 +735,19 @@ test('formats value into val() according to format parameter', function() { }) test('localizes value', function() { - tz.changeLocale(portuguese, 'pt_PT', 'pt') + tzInTest.configureAndRestoreLater({ + tz: timezone(detroit, 'America/Detroit', portuguese, 'pt_PT'), + tzData: { + 'America/Detroit': detroit, + }, + momentLocale: 'pt' + }) I18nStubber.pushFrame() I18nStubber.setLocale('pt_PT') I18nStubber.stub('pt_PT', {'date.formats.full': '%-d %b %Y %-k:%M'}) this.field.setFormattedDatetime(moonwalk, 'date.formats.full') equal(this.$field.val(), '20 Jul 1969 21:56') - I18nStubber.popFrame() + I18nStubber.clear() }) QUnit.module('setDate/setTime/setDatetime', { diff --git a/spec/javascripts/jsx/conferences/EditConferenceViewSpec.js b/spec/javascripts/jsx/conferences/EditConferenceViewSpec.js index 172b2efc6d1..4874276dfe2 100644 --- a/spec/javascripts/jsx/conferences/EditConferenceViewSpec.js +++ b/spec/javascripts/jsx/conferences/EditConferenceViewSpec.js @@ -37,6 +37,7 @@ QUnit.module('EditConferenceView', { this.view.$el.remove() fakeENV.teardown() tz.restore(this.snapshot) + I18nStubber.clear() } }) @@ -49,7 +50,6 @@ test('updateConferenceUserSettingDetailsForConference localizes values for datep const conferenceData = {user_settings: {datepickerSetting: '2015-08-07T17:00:00Z'}} this.view.updateConferenceUserSettingDetailsForConference(conferenceData) equal(this.datepickerSetting.value, 'ven. 7 août, 2015 17:00') - I18nStubber.popFrame() }) test('#show sets the proper title for new conferences', function() { diff --git a/spec/javascripts/jsx/grading/GradingPeriodFormSpec.js b/spec/javascripts/jsx/grading/GradingPeriodFormSpec.js index 1bb7ed71b85..ef1f39d5d30 100644 --- a/spec/javascripts/jsx/grading/GradingPeriodFormSpec.js +++ b/spec/javascripts/jsx/grading/GradingPeriodFormSpec.js @@ -22,6 +22,8 @@ import {mount} from 'enzyme' import GradingPeriodForm from 'ui/features/account_grading_standards/react/GradingPeriodForm.js' import chicago from 'timezone/America/Chicago' import tz from '@canvas/timezone' +import tzInTest from '@canvas/timezone/specHelpers' +import timezone from 'timezone' import fakeENV from 'helpers/fakeENV' QUnit.module('GradingPeriodForm', suiteHooks => { @@ -123,10 +125,17 @@ QUnit.module('GradingPeriodForm', suiteHooks => { QUnit.module('when local and server time are different', hooks => { hooks.beforeEach(() => { Object.assign(ENV, {CONTEXT_TIMEZONE: 'America/Chicago'}) - tz.preload('America/Chicago', chicago) + tzInTest.configureAndRestoreLater({ + tz: timezone('UTC'), + tzData: { + 'America/Chicago': chicago + } + }) mountComponent() }) + hooks.afterEach(tzInTest.restore) + test('shows both local and context time suggestions for start date', () => { strictEqual(getDateTimeSuggestions('Start Date').length, 2) }) @@ -168,10 +177,17 @@ QUnit.module('GradingPeriodForm', suiteHooks => { QUnit.module('when local and server time are different', hooks => { hooks.beforeEach(() => { Object.assign(ENV, {CONTEXT_TIMEZONE: 'America/Chicago'}) - tz.preload('America/Chicago', chicago) + tzInTest.configureAndRestoreLater({ + tz: timezone('UTC'), + tzData: { + 'America/Chicago': chicago + } + }) mountComponent() }) + hooks.afterEach(tzInTest.restore) + test('shows both local and context time suggestions for end date', () => { strictEqual(getDateTimeSuggestions('End Date').length, 2) }) @@ -213,10 +229,17 @@ QUnit.module('GradingPeriodForm', suiteHooks => { QUnit.module('when local and server time are different', hooks => { hooks.beforeEach(() => { Object.assign(ENV, {CONTEXT_TIMEZONE: 'America/Chicago'}) - tz.preload('America/Chicago', chicago) + tzInTest.configureAndRestoreLater({ + tz: timezone('UTC'), + tzData: { + 'America/Chicago': chicago + } + }) mountComponent() }) + hooks.afterEach(tzInTest.restore) + test('shows both local and context time suggestions for close date', () => { strictEqual(getDateTimeSuggestions('Close Date').length, 2) }) diff --git a/spec/javascripts/jsx/i18nDateTime.js b/spec/javascripts/jsx/i18nDateTime.js deleted file mode 100644 index 1bcbcf06229..00000000000 --- a/spec/javascripts/jsx/i18nDateTime.js +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright (C) 2017 - present Instructure, Inc. - * - * This file is part of Canvas. - * - * Canvas is free software: you can redistribute it and/or modify it under - * the terms of the GNU Affero General Public License as published by the Free - * Software Foundation, version 3 of the License. - * - * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more - * details. - * - * You should have received a copy of the GNU Affero General Public License along - * with this program. If not, see . - */ - -import sinon from 'sinon' -import localeConfig from 'json-loader!yaml-loader!../../../config/locales/locales.yml' -import tz from 'timezone_core' -import DatetimeField from 'compiled/widget/DatetimeField' -import $ from 'jquery' -import I18n from 'i18nObj' -import 'translations/_core' -import 'translations/_core_en' - -import bigeasyLocales from 'timezone/locales' -import bigeasyLocale_ca_ES from 'custom_timezone_locales/ca_ES' -import bigeasyLocale_cy_GB from 'custom_timezone_locales/cy_GB' -import bigeasyLocale_de_DE from 'custom_timezone_locales/de_DE' -import bigeasyLocale_fr_FR from 'custom_timezone_locales/fr_FR' -import bigeasyLocale_fr_CA from 'custom_timezone_locales/fr_CA' -import bigeasyLocale_he_IL from 'custom_timezone_locales/he_IL' -import bigeasyLocale_pl_PL from 'custom_timezone_locales/pl_PL' -import bigeasyLocale_is_IS from 'custom_timezone_locales/is_IS' -import bigeasyLocale_ar_SA from 'custom_timezone_locales/ar_SA' -import bigeasyLocale_da_DK from 'custom_timezone_locales/da_DK' -import bigeasyLocale_fa_IR from 'custom_timezone_locales/fa_IR' -import bigeasyLocale_ht_HT from 'custom_timezone_locales/ht_HT' -import bigeasyLocale_hy_AM from 'custom_timezone_locales/hy_AM' -import bigeasyLocale_mi_NZ from 'custom_timezone_locales/mi_NZ' -import bigeasyLocale_nn_NO from 'custom_timezone_locales/nn_NO' -import bigeasyLocale_tr_TR from 'custom_timezone_locales/tr_TR' -import bigeasyLocale_uk_UA from 'custom_timezone_locales/uk_UA' -import bigeasyLocale_el_GR from 'custom_timezone_locales/el_GR' - -import 'custom_moment_locales/ca' -import 'custom_moment_locales/de' -import 'custom_moment_locales/he' -import 'custom_moment_locales/pl' -import 'custom_moment_locales/fa' -import 'custom_moment_locales/fr' -import 'custom_moment_locales/fr_ca' -import 'custom_moment_locales/ht_ht' -import 'custom_moment_locales/mi_nz' -import 'custom_moment_locales/hy_am' -import 'custom_moment_locales/sl' - -let originalLocale -let originalFallbacksMap - -const bigeasyLocalesWithCustom = [ - ...bigeasyLocales, - bigeasyLocale_ca_ES, - bigeasyLocale_cy_GB, - bigeasyLocale_de_DE, - bigeasyLocale_fr_FR, - bigeasyLocale_fr_CA, - bigeasyLocale_he_IL, - bigeasyLocale_pl_PL, - bigeasyLocale_is_IS, - bigeasyLocale_ar_SA, - bigeasyLocale_da_DK, - bigeasyLocale_fa_IR, - bigeasyLocale_ht_HT, - bigeasyLocale_hy_AM, - bigeasyLocale_mi_NZ, - bigeasyLocale_nn_NO, - bigeasyLocale_tr_TR, - bigeasyLocale_uk_UA, - bigeasyLocale_el_GR -] - -const preloadedData = bigeasyLocalesWithCustom.reduce((memo, locale) => { - memo[locale.name] = locale - return memo -}, {}) - -QUnit.module('Parsing locale formatted dates', { - setup() { - originalLocale = I18n.locale - sinon.stub(tz, 'preload').callsFake(name => preloadedData[name]) - originalFallbacksMap = I18n.fallbacksMap - I18n.fallbacksMap = null - }, - - teardown() { - I18n.locale = originalLocale - I18n.fallbacksMap = originalFallbacksMap - tz.preload.restore() - } -}) - -const locales = Object.keys(localeConfig) - .map(key => { - const locale = localeConfig[key] - const base = key.split('-')[0] - return { - key, - moment: locale.moment_locale || key.toLowerCase(), - bigeasy: locale.bigeasy_locale || localeConfig[base].bigeasy_locale - } - }) - .filter(l => l.key) - -const dates = [] -const currentYear = parseInt(tz.format(new Date(), '%Y'), 10) -const otherYear = currentYear + 4 -for (let i = 0; i < 12; ++i) { - dates.push(new Date(Date.UTC(currentYear, i, 1, 23, 59))) - dates.push(new Date(Date.UTC(currentYear, i, 28, 23, 59))) - dates.push(new Date(Date.UTC(otherYear, i, 7, 23, 59))) - dates.push(new Date(Date.UTC(otherYear, i, 15, 23, 59))) -} - -function assertFormattedParsesToDate(formatted, date) { - const parsed = tz.parse(formatted) - const formattedDate = tz.format(parsed, 'date.formats.medium') - const formattedTime = tz.format(parsed, 'time.formats.tiny') - const formattedParsed = `${formattedDate} ${formattedTime}` - equal(date.getTime(), parsed.getTime(), `${formatted} incorrectly parsed as ${formattedParsed}`) -} - -locales.forEach(locale => { - test(`timezone -> moment for ${locale.key}`, () => { - I18n.locale = locale.key - try { - tz.changeLocale(locale.bigeasy, locale.moment) - dates.forEach(date => { - const formattedDate = $.dateString(date) - const formattedTime = tz.format(date, 'time.formats.tiny') - const formatted = `${formattedDate} ${formattedTime}` - assertFormattedParsesToDate(formatted, date) - }) - } catch (err) { - ok(false, err.message) - } - }) - - test(`datepicker -> moment for ${locale.key}`, () => { - I18n.locale = locale.key - const config = DatetimeField.prototype.datepickerDefaults() - try { - tz.changeLocale(locale.bigeasy, locale.moment) - dates.forEach(date => { - const formattedDate = $.datepicker.formatDate(config.dateFormat, date, config) - const formattedTime = $.timeString(date) - - const formatted = `${formattedDate} ${formattedTime}` - assertFormattedParsesToDate(formatted, date) - }) - } catch (err) { - ok(false, err.message) - } - }) - - test(`hour format matches timezone locale for ${locale.key}`, () => { - I18n.locale = locale.key - tz.changeLocale(locale.bigeasy, locale.moment) - const formats = [ - 'date.formats.date_at_time', - 'date.formats.full', - 'date.formats.full_with_weekday', - 'time.formats.tiny', - 'time.formats.tiny_on_the_hour' - ] - const invalid = key => { - const format = I18n.lookup(key) - // ok(/%p/i.test(format) === tz.hasMeridian(), `format: ${format}, hasMeridian: ${tz.hasMeridian()}`) - ok( - tz.hasMeridian() || !/%p/i.test(format), - `format: ${format}, hasMeridian: ${tz.hasMeridian()}` - ) - } - ok(!formats.forEach(invalid)) - }) -}) diff --git a/spec/javascripts/jsx/moment_formatsSpec.js b/spec/javascripts/jsx/moment_formatsSpec.js index 03956aad51a..a16b7f6f8ec 100644 --- a/spec/javascripts/jsx/moment_formatsSpec.js +++ b/spec/javascripts/jsx/moment_formatsSpec.js @@ -16,8 +16,10 @@ * with this program. If not, see . */ -import moment_formats from '@canvas/timezone/moment_formats' import I18nStubber from 'helpers/I18nStubber' +import { + prepareFormats +} from '../../../ui/boot/initializers/configureDateTimeMomentParser' QUnit.module('Moment formats', { setup() { @@ -32,18 +34,18 @@ QUnit.module('Moment formats', { }) }, teardown() { - I18nStubber.popFrame() + I18nStubber.clear() } }) test('formatsForLocale include formats matching datepicker', () => { - const formats = moment_formats.formatsForLocale() + const formats = prepareFormats().map(x => x()) ok(formats.includes('%b %-d, %Y %l:%M%P')) ok(formats.includes('%b %-d, %Y %l%P')) }) test('formatsForLocale includes all event formats', () => { - const formats = moment_formats.formatsForLocale() + const formats = prepareFormats().map(x => x()) ok(formats.includes('%b %-d, %Y event %l:%M%P')) ok(formats.includes('%b %-d event %l:%M%P')) ok(formats.includes('%b %-d, %Y event %l%P')) diff --git a/spec/javascripts/jsx/shared/helpers/numberHelperSpec.js b/spec/javascripts/jsx/shared/helpers/numberHelperSpec.js index a156946e634..ac6fd1653d4 100644 --- a/spec/javascripts/jsx/shared/helpers/numberHelperSpec.js +++ b/spec/javascripts/jsx/shared/helpers/numberHelperSpec.js @@ -45,7 +45,7 @@ QUnit.module('Number Helper Parse and Validate', { }, teardown() { - I18nStubber.popFrame() + I18nStubber.clear() if (numberHelper._parseNumber.restore) { numberHelper._parseNumber.restore() } diff --git a/spec/javascripts/jsx/spec-support/timezoneBackwardsCompatLayer.js b/spec/javascripts/jsx/spec-support/timezoneBackwardsCompatLayer.js new file mode 100644 index 00000000000..9eb651ac753 --- /dev/null +++ b/spec/javascripts/jsx/spec-support/timezoneBackwardsCompatLayer.js @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2021 - present Instructure, Inc. + * + * This file is part of Canvas. + * + * Canvas is free software: you can redistribute it and/or modify it under + * the terms of the GNU Affero General Public License as published by the Free + * Software Foundation, version 3 of the License. + * + * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License along + * with this program. If not, see . + */ + +import tz from '@canvas/timezone' +import tzInTest from '@canvas/timezone/specHelpers' + +tz.snapshot = () => {} +tz.restore = tzInTest.restore +tz.changeZone = tzInTest.changeZone +tz.changeLocale = tzInTest.changeLocale diff --git a/spec/javascripts/jsx/timezoneSpec.js b/spec/javascripts/jsx/timezoneSpec.js deleted file mode 100644 index 53c900c5ee2..00000000000 --- a/spec/javascripts/jsx/timezoneSpec.js +++ /dev/null @@ -1,651 +0,0 @@ -// -// Copyright (C) 2013 - present Instructure, Inc. -// -// This file is part of Canvas. -// -// Canvas is free software: you can redistribute it and/or modify it under -// the terms of the GNU Affero General Public License as published by the Free -// Software Foundation, version 3 of the License. -// -// Canvas is distributed in the hope that it will be useful, but WITHOUT ANY -// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR -// A PARTICULAR PURPOSE. See the GNU Affero General Public License for more -// details. -// -// You should have received a copy of the GNU Affero General Public License along -// with this program. If not, see . - -import tz from '@canvas/timezone' -import i18nObj from '@canvas/i18n' -import detroit from 'timezone/America/Detroit' -import french from 'timezone/fr_FR' -import portuguese from 'timezone/pt_PT' -import chinese from 'timezone/zh_CN' -import I18nStubber from 'helpers/I18nStubber' -import trans from 'translations/_core_en' -import en_US from 'timezone/en_US' -import MockDate from 'mockdate' - -QUnit.module('timezone', { - setup() { - this.snapshot = tz.snapshot() - I18nStubber.pushFrame() - }, - - teardown() { - tz.restore(this.snapshot) - I18nStubber.popFrame() - } -}) - -const moonwalk = new Date(Date.UTC(1969, 6, 21, 2, 56)) -const epoch = new Date(Date.UTC(1970, 0, 1, 0, 0)) - -test('moment(one-arg) complains', () => { - let err = null - try { - tz.moment('June 24 at 10:00pm') - } catch (error) { - err = error - } - ok(err.toString().match(/^Error: tz.moment only works on /)) -}) - -test('moment(non-string, fmt-string) complains', () => { - let err = null - try { - tz.moment(moonwalk, 'MMMM D h:mmA') - } catch (error) { - err = error - } - ok(err.toString().match(/^Error: tz.moment only works on /)) -}) - -test('moment(date-string, non-string) complains', () => { - let err = null - try { - tz.moment('June 24 at 10:00pm', 123) - } catch (error) { - err = error - } - ok(err.toString().match(/^Error: tz.moment only works on /)) -}) - -test('moment(date-string, fmt-string) works', () => - ok(tz.moment('June 24 at 10:00pm', 'MMMM D h:mmA'))) - -test('moment(date-string, [fmt-strings]) works', () => - ok(tz.moment('June 24 at 10:00pm', ['MMMM D h:mmA', 'L']))) - -test('moment passes through invalid results', () => { - const m = tz.moment('not a valid date', 'L') - ok(!m.isValid()) -}) - -test('moment accepts excess input, but all format used', () => { - const m = tz.moment('12pm and more', 'ha') - ok(m.isValid()) -}) - -test('moment rejects excess format', () => { - const m = tz.moment('12pm', 'h:mma') - ok(!m.isValid()) -}) - -test('moment returns moment for valid results', () => { - const m = tz.moment('June 24, 2015 at 10:00pm -04:00', 'MMMM D, YYYY h:mmA Z') - ok(m.isValid()) -}) - -test('moment sans-timezone info parses according to profile timezone', () => { - tz.changeZone(detroit, 'America/Detroit') - const expected = new Date(1435197600000) // 10pm EDT on June 24, 2015 - const m = tz.moment('June 24, 2015 at 10:00pm', 'MMMM D, YYYY h:mmA') - equal(+m.toDate(), +expected) -}) - -test('moment with-timezone info parses according to that timezone', () => { - tz.changeZone(detroit, 'America/Detroit') - const expected = new Date(1435204800000) // 10pm MDT on June 24, 2015 - const m = tz.moment('June 24, 2015 at 10:00pm -06:00', 'MMMM D, YYYY h:mmA Z') - equal(+m.toDate(), +expected) -}) - -test('moment can change locales with single arity', () => { - tz.changeLocale('en_US', 'en') - const m1 = tz.moment('mercredi 1 juillet 2015 15:00', 'LLLL') - ok(!m1._locale._abbr.match(/fr/)) - ok(!m1.isValid()) - - tz.changeLocale('fr_FR', 'fr') - const m2 = tz.moment('mercredi 1 juillet 2015 15:00', 'LLLL') - ok(m2._locale._abbr.match(/fr/)) - ok(m2.isValid()) -}) - -test('moment can change locales with multiple arity', () => { - tz.changeLocale('en_US', 'en') - const m1 = tz.moment('mercredi 1 juillet 2015 15:00', 'LLLL') - ok(!m1._locale._abbr.match(/fr/)) - ok(!m1.isValid()) - - tz.changeLocale(french, 'fr_FR', 'fr') - const m2 = tz.moment('mercredi 1 juillet 2015 15:00', 'LLLL') - ok(m2._locale._abbr.match(/fr/)) - ok(m2.isValid()) -}) - -test('parse(valid datetime string)', () => { - equal(+tz.parse(moonwalk.toISOString()), +moonwalk) -}) - -test('parse(timestamp integer)', () => { - equal(+tz.parse(+moonwalk), +moonwalk) -}) - -test('parse(Date object)', () => { - equal(+tz.parse(moonwalk), +moonwalk) -}) - -test('parse(date array)', () => { - equal(+tz.parse([1969, 7, 21, 2, 56]), +moonwalk) -}) - -test('parse() should return null on failure', () => equal(tz.parse('bogus'), null)) - -test('parse() should return a date on success', () => equal(typeof tz.parse(+moonwalk), 'object')) - -test('parse("") should fail', () => equal(tz.parse(''), null)) - -test('parse(null) should fail', () => equal(tz.parse(null), null)) - -test('parse(integer) should be ms since epoch', () => equal(+tz.parse(2016), +tz.raw_parse(2016))) - -test('parse("looks like integer") should be a year', () => - equal(+tz.parse('2016'), +tz.parse('2016-01-01'))) - -test('parse() should parse relative to UTC by default', () => - equal(+tz.parse('1969-07-21 02:56'), +moonwalk)) - -test('format() should format relative to UTC by default', () => - equal(tz.format(moonwalk, '%F %T%:z'), '1969-07-21 02:56:00+00:00')) - -test('format() should format in en_US by default', () => - equal(tz.format(moonwalk, '%c'), 'Mon 21 Jul 1969 02:56:00 AM UTC')) - -test('format() should parse the value if necessary', () => - equal(tz.format('1969-07-21 02:56', '%F %T%:z'), '1969-07-21 02:56:00+00:00')) - -test('format() should return null if the parse fails', () => - equal(tz.format('bogus', '%F %T%:z'), null)) - -test('format() should return null if the format string is unrecognized', () => - equal(tz.format(moonwalk, 'bogus'), null)) - -test('format() should preserve 12-hour+am/pm if the locale does define am/pm', () => { - const time = tz.parse('1969-07-21 15:00:00') - equal(tz.format(time, '%-l%P'), '3pm') - equal(tz.format(time, '%I%P'), '03pm') - equal(tz.format(time, '%r'), '03:00:00 PM') -}) - -test("format() should promote 12-hour+am/pm into 24-hour if the locale doesn't define am/pm", () => { - const time = tz.parse('1969-07-21 15:00:00') - tz.changeLocale(french, 'fr_FR', 'fr') - equal(tz.format(time, '%-l%P'), '15') - equal(tz.format(time, '%I%P'), '15') - equal(tz.format(time, '%r'), '15:00:00') -}) - -test('format() should recognize date.formats.*', () => { - I18nStubber.stub('en', {'date.formats.short': '%b %-d'}) - equal(tz.format(moonwalk, 'date.formats.short'), 'Jul 21') -}) - -test('format() should recognize time.formats.*', () => { - I18nStubber.stub('en', {'time.formats.tiny': '%-l:%M%P'}) - equal(tz.format(epoch, 'time.formats.tiny'), '12:00am') -}) - -test('format() should localize when given a localization key', () => { - tz.changeLocale(french, 'fr_FR', 'fr') - I18nStubber.setLocale('fr_FR') - I18nStubber.stub('fr_FR', {'date.formats.full': '%-d %b %Y %-l:%M%P'}) - equal(tz.format(moonwalk, 'date.formats.full'), '21 juil. 1969 2:56') -}) - -test('format() should automatically convert %l to %-l when given a localization key', () => { - I18nStubber.stub('en', {'time.formats.tiny': '%l:%M%P'}) - equal(tz.format(moonwalk, 'time.formats.tiny'), '2:56am') -}) - -test('format() should automatically convert %k to %-k when given a localization key', () => { - I18nStubber.stub('en', {'time.formats.tiny': '%k:%M'}) - equal(tz.format(moonwalk, 'time.formats.tiny'), '2:56') -}) - -test('format() should automatically convert %e to %-e when given a localization key', () => { - I18nStubber.stub('en', {'date.formats.short': '%b %e'}) - equal(tz.format(epoch, 'date.formats.short'), 'Jan 1') -}) - -test('shift() should adjust the date as appropriate', () => - equal(+tz.shift(moonwalk, '-1 day'), moonwalk - 86400000)) - -test('shift() should apply multiple directives', () => - equal(+tz.shift(moonwalk, '-1 day', '-1 hour'), moonwalk - 86400000 - 3600000)) - -test('shift() should parse the value if necessary', () => - equal(+tz.shift('1969-07-21 02:56', '-1 day'), moonwalk - 86400000)) - -test('shift() should return null if the parse fails', () => - equal(tz.shift('bogus', '-1 day'), null)) - -test('shift() should return null if the directives includes a format string', () => - equal(tz.shift('bogus', '-1 day', '%F %T%:z'), null)) - -test('extendConfiguration() should curry the options into tz', () => { - tz.extendConfiguration(detroit, 'America/Detroit') - equal(+tz.parse('1969-07-20 21:56'), +moonwalk) - equal(tz.format(moonwalk, '%c'), 'Sun 20 Jul 1969 09:56:00 PM EST') -}) - -test('snapshotting should let you restore tz to a previous un-curried state', () => { - const snapshot = tz.snapshot() - tz.extendConfiguration(detroit, 'America/Detroit') - tz.restore(snapshot) - equal(+tz.parse('1969-07-21 02:56'), +moonwalk) - equal(tz.format(moonwalk, '%c'), 'Mon 21 Jul 1969 02:56:00 AM UTC') -}) - -test('changeZone(...) should synchronously curry in a loaded zone', () => { - tz.changeZone(detroit, 'America/Detroit') - equal(+tz.parse('1969-07-20 21:56'), +moonwalk) - equal(tz.format(moonwalk, '%c'), 'Sun 20 Jul 1969 09:56:00 PM EST') -}) - -test('changeZone(...) should asynchronously curry in a zone by name', assert => { - const done = assert.async() - assert.expect(2) - tz.changeZone('America/Detroit').then(() => { - equal(+tz.parse('1969-07-20 21:56'), +moonwalk) - equal(tz.format(moonwalk, '%c'), 'Sun 20 Jul 1969 09:56:00 PM EST') - done() - }) -}) - -test('changeLocale(...) should synchronously curry in a loaded locale', () => { - tz.changeLocale(french, 'fr_FR', 'fr') - equal(tz.format(moonwalk, '%c'), 'lun. 21 juil. 1969 02:56:00 UTC') -}) - -test('changeLocale(...) should asynchronously curry in a locale by name', assert => { - const done = assert.async() - assert.expect(1) - tz.changeLocale('fr_FR', 'fr').then(() => { - equal(tz.format(moonwalk, '%c'), 'lun. 21 juil. 1969 02:56:00 UTC') - done() - }) -}) - -test('changeZone(...) should synchronously curry if pre-loaded', () => { - tz.preload('America/Detroit', detroit) - tz.changeZone('America/Detroit') - equal(tz.format(moonwalk, '%c'), 'Sun 20 Jul 1969 09:56:00 PM EST') -}) - -test('hasMeridian() true if locale defines am/pm', () => ok(tz.hasMeridian())) - -test("hasMeridian() false if locale doesn't define am/pm", () => { - tz.changeLocale(french, 'fr_FR', 'fr') - ok(!tz.hasMeridian()) -}) - -test('useMeridian() true if locale defines am/pm and uses 12-hour format', () => { - I18nStubber.stub('en', {'time.formats.tiny': '%l:%M%P'}) - ok(tz.hasMeridian()) - ok(tz.useMeridian()) -}) - -test('useMeridian() false if locale defines am/pm but uses 24-hour format', () => { - I18nStubber.stub('en', {'time.formats.tiny': '%k:%M'}) - ok(tz.hasMeridian()) - ok(!tz.useMeridian()) -}) - -test("useMeridian() false if locale doesn't define am/pm and instead uses 24-hour format", () => { - tz.changeLocale(french, 'fr_FR', 'fr') - I18nStubber.setLocale('fr_FR') - I18nStubber.stub('fr_FR', {'time.formats.tiny': '%-k:%M'}) - ok(!tz.hasMeridian()) - ok(!tz.useMeridian()) -}) - -test("useMeridian() false if locale doesn't define am/pm but still uses 12-hour format (format will be corrected)", () => { - tz.changeLocale(french, 'fr_FR', 'fr') - I18nStubber.setLocale('fr_FR') - I18nStubber.stub('fr_FR', {'time.formats.tiny': '%-l:%M%P'}) - ok(!tz.hasMeridian()) - ok(!tz.useMeridian()) -}) - -test('isMidnight() is false when no argument given.', () => ok(!tz.isMidnight())) - -test('isMidnight() is false when invalid date is given.', () => { - const date = new Date('invalid date') - ok(!tz.isMidnight(date)) -}) - -test('isMidnight() is true when date given is at midnight.', () => ok(tz.isMidnight(epoch))) - -test("isMidnight() is false when date given isn't at midnight.", () => ok(!tz.isMidnight(moonwalk))) - -test('isMidnight() is false when date is midnight in a different zone.', () => { - tz.changeZone(detroit, 'America/Detroit') - ok(!tz.isMidnight(epoch)) -}) - -test('changeToTheSecondBeforeMidnight() returns null when no argument given.', () => - equal(tz.changeToTheSecondBeforeMidnight(), null)) - -test('changeToTheSecondBeforeMidnight() returns null when invalid date is given.', () => { - const date = new Date('invalid date') - equal(tz.changeToTheSecondBeforeMidnight(date), null) -}) - -test('changeToTheSecondBeforeMidnight() returns fancy midnight when a valid date is given.', () => { - const fancyMidnight = tz.changeToTheSecondBeforeMidnight(epoch) - equal(fancyMidnight.toGMTString(), 'Thu, 01 Jan 1970 23:59:59 GMT') -}) - -test('mergeTimeAndDate() finds the given time of day on the given date.', () => - equal(+tz.mergeTimeAndDate(moonwalk, epoch), +new Date(Date.UTC(1970, 0, 1, 2, 56)))) - -QUnit.module('english tz', { - setup() { - MockDate.set('2015-02-01', 'UTC') - this.snapshot = tz.snapshot() - I18nStubber.pushFrame() - I18nStubber.setLocale('en_US') - I18nStubber.stub('en_US', { - 'date.formats.date_at_time': '%b %-d at %l:%M%P', - 'date.formats.default': '%Y-%m-%d', - 'date.formats.full': '%b %-d, %Y %-l:%M%P', - 'date.formats.full_with_weekday': '%a %b %-d, %Y %-l:%M%P', - 'date.formats.long': '%B %-d, %Y', - 'date.formats.long_with_weekday': '%A, %B %-d', - 'date.formats.medium': '%b %-d, %Y', - 'date.formats.medium_month': '%b %Y', - 'date.formats.medium_with_weekday': '%a %b %-d, %Y', - 'date.formats.short': '%b %-d', - 'date.formats.short_month': '%b', - 'date.formats.short_weekday': '%a', - 'date.formats.short_with_weekday': '%a, %b %-d', - 'date.formats.weekday': '%A', - 'time.formats.default': '%a, %d %b %Y %H:%M:%S %z', - 'time.formats.long': '%B %d, %Y %H:%M', - 'time.formats.short': '%d %b %H:%M', - 'time.formats.tiny': '%l:%M%P', - 'time.formats.tiny_on_the_hour': '%l%P' - }) - tz.changeLocale('en_US', 'en') - }, - - teardown() { - tz.restore(this.snapshot) - I18nStubber.popFrame() - MockDate.reset() - } -}) - -test('parses english dates', () => { - const engDates = [ - '08/03/2015', - '8/3/2015', - 'August 3, 2015', - 'Aug 3, 2015', - '3 Aug 2015', - '2015-08-03', - '2015 08 03', - 'August 3, 2015', - 'Monday, August 3', - 'Mon Aug 3, 2015', - 'Mon, Aug 3', - 'Aug 3' - ] - - engDates.forEach(date => { - const d = tz.parse(date) - equal(tz.format(d, '%d'), '03', `this works: ${date}`) - }) -}) - -test('parses english times', () => { - const engTimes = ['6:06 PM', '6:06:22 PM', '6:06pm', '6pm'] - - engTimes.forEach(time => { - const d = tz.parse(time) - equal(tz.format(d, '%H'), '18', `this works: ${time}`) - }) -}) - -test('parses english date times', () => { - const engDateTimes = [ - '2015-08-03 18:06:22', - 'August 3, 2015 6:06 PM', - 'Aug 3, 2015 6:06 PM', - 'Aug 3, 2015 6pm', - 'Monday, August 3, 2015 6:06 PM', - 'Mon, Aug 3, 2015 6:06 PM', - 'Aug 3 at 6:06pm', - 'Aug 3, 2015 6:06pm', - 'Mon Aug 3, 2015 6:06pm' - ] - - engDateTimes.forEach(dateTime => { - const d = tz.parse(dateTime) - equal(tz.format(d, '%d %H'), '03 18', `this works: ${dateTime}`) - }) -}) - -test('parses 24hr times even if the locale lacks them', () => { - const d = tz.parse('18:06') - equal(tz.format(d, '%H:%M'), '18:06') -}) - -QUnit.module('french tz', { - setup() { - MockDate.set('2015-02-01', 'UTC') - this.snapshot = tz.snapshot() - I18nStubber.pushFrame() - I18nStubber.setLocale('fr_FR') - I18nStubber.stub('fr_FR', { - 'date.formats.date_at_time': '%-d %b à %k:%M', - 'date.formats.default': '%d/%m/%Y', - 'date.formats.full': '%b %-d, %Y %-k:%M', - 'date.formats.full_with_weekday': '%a %-d %b, %Y %-k:%M', - 'date.formats.long': 'le %-d %B %Y', - 'date.formats.long_with_weekday': '%A, %-d %B', - 'date.formats.medium': '%-d %b %Y', - 'date.formats.medium_month': '%b %Y', - 'date.formats.medium_with_weekday': '%a %-d %b %Y', - 'date.formats.short': '%-d %b', - 'date.formats.short_month': '%b', - 'date.formats.short_weekday': '%a', - 'date.formats.short_with_weekday': '%a, %-d %b', - 'date.formats.weekday': '%A', - 'time.formats.default': '%a, %d %b %Y %H:%M:%S %z', - 'time.formats.long': ' %d %B, %Y %H:%M', - 'time.formats.short': '%d %b %H:%M', - 'time.formats.tiny': '%k:%M', - 'time.formats.tiny_on_the_hour': '%k:%M' - }) - tz.changeLocale(french, 'fr_FR', 'fr') - }, - - teardown() { - tz.restore(this.snapshot) - I18nStubber.popFrame() - MockDate.reset() - } -}) - -test('parses french dates', () => { - const frenchDates = [ - '03/08/2015', - '3/8/2015', - '3 août 2015', - '2015-08-03', - 'le 3 août 2015', - 'lundi, 3 août', - 'lun. 3 août 2015', - '3 août', - 'lun., 3 août', - '3 août 2015', - '3 août' - ] - - frenchDates.forEach(date => { - const d = tz.parse(date) - equal(tz.format(d, '%d'), '03', `this works: ${date}`) - }) -}) - -test('parses french times', () => { - const frenchTimes = ['18:06', '18:06:22'] - - frenchTimes.forEach(time => { - const d = tz.parse(time) - equal(tz.format(d, '%H'), '18', `this works: ${time}`) - }) -}) - -test('parses french date times', () => { - const frenchDateTimes = [ - '2015-08-03 18:06:22', - '3 août 2015 18:06', - 'lundi 3 août 2015 18:06', - 'lun. 3 août 2015 18:06', - '3 août à 18:06', - 'août 3, 2015 18:06', - 'lun. 3 août, 2015 18:06' - ] - - frenchDateTimes.forEach(dateTime => { - const d = tz.parse(dateTime) - equal(tz.format(d, '%d %H'), '03 18', `this works: ${dateTime}`) - }) -}) - -QUnit.module('chinese tz', { - setup() { - MockDate.set('2015-02-01', 'UTC') - this.snapshot = tz.snapshot() - I18nStubber.pushFrame() - I18nStubber.setLocale('zh_CN') - I18nStubber.stub('zh_CN', { - 'date.formats.date_at_time': '%b %-d 于 %H:%M', - 'date.formats.default': '%Y-%m-%d', - 'date.formats.full': '%b %-d, %Y %-l:%M%P', - 'date.formats.full_with_weekday': '%a %b %-d, %Y %-l:%M%P', - 'date.formats.long': '%Y %B %-d', - 'date.formats.long_with_weekday': '%A, %B %-d', - 'date.formats.medium': '%Y %b %-d', - 'date.formats.medium_month': '%Y %b', - 'date.formats.medium_with_weekday': '%a %Y %b %-d', - 'date.formats.short': '%b %-d', - 'date.formats.short_month': '%b', - 'date.formats.short_weekday': '%a', - 'date.formats.short_with_weekday': '%a, %b %-d', - 'date.formats.weekday': '%A', - 'time.formats.default': '%a, %Y %b %d %H:%M:%S %z', - 'time.formats.long': '%Y %B %d %H:%M', - 'time.formats.short': '%b %d %H:%M', - 'time.formats.tiny': '%H:%M', - 'time.formats.tiny_on_the_hour': '%k:%M' - }) - tz.changeLocale(chinese, 'zh_CN', 'zh-cn') - }, - - teardown() { - tz.restore(this.snapshot) - I18nStubber.popFrame() - MockDate.reset() - } -}) - -test('parses chinese dates', () => { - const chineseDates = [ - '2015-08-03', - '2015年8月3日', - '2015 八月 3', - '2015 8月 3', - '星期一, 八月 3', - '一 2015 8月 3', - '一, 8月 3', - '8月 3' - ] - - chineseDates.forEach(date => { - const d = tz.parse(date) - equal(tz.format(d, '%d'), '03', `this works: ${date}`) - }) -}) - -test('parses chinese PM times', () => { - // 晚上 means evening. moment doesn't seem to be handling that correctly, - // though I don't believe this will cause any problems in canvas. - const chineseTimes = [ - // '晚上6点06分', - // '晚上6点6分22秒', - '18:06' - ] - chineseTimes.forEach(time => { - const d = tz.parse(time) - equal(tz.format(d, '%H'), '18', `this works: ${time}`) - }) -}) - -// the 2 chinese AM specs pass in isolation, but when run as part of the whole js test suite -// with COVERAGE=1, which serializes the tests, it fails. I suspect it's due to -// pollution from another test, but cannot find it. -// Skipping for now so the master build completes w/o error. -QUnit.skip('parses chinese AM times', () => { - const chineseTimes = ['6点06分', '6点6分22秒', '06:06'] - chineseTimes.forEach(time => { - const d = tz.parse(time) - equal(tz.format(d, '%H'), '06', `this works: ${time}`) - }) -}) - -QUnit.skip('parses chinese date AM times', () => { - const chineseDateTimes = [ - '2015-08-03 06:06:22', - '2015年8月3日6点06分', - '2015年8月3日星期一6点06分', - '8月 3日 于 6:06', - '20158月3日, 6:06', - '一 20158月3日, 6:06' // this is incorrectly parsing as "Fri, 20 Mar 1908 06:06:00 GMT" - ] - - chineseDateTimes.forEach(dateTime => { - const d = tz.parse(dateTime) - equal(tz.format(d, '%d %H'), '03 06', `this works: ${dateTime}`) - }) -}) - -test('parses chinese date PM times', () => { - const chineseDateTimes = [ - '2015-08-03 18:06:22', - '2015年8月3日晚上6点06分', - // '2015年8月3日星期一晚上6点06分', // parsing as "Mon, 03 Aug 2015 06:06:00 GMT" - '8月 3日, 于 18:06' - // '2015 8月 3日, 6:06下午', // doesn't recognize 下午 as implying PM - // '一 2015 8月 3日, 6:06下午' - ] - - chineseDateTimes.forEach(dateTime => { - const d = tz.parse(dateTime) - equal(tz.format(d, '%d %H'), '03 18', `this works: ${dateTime}`) - }) -}) diff --git a/spec/javascripts/webpack_spec_index.js b/spec/javascripts/webpack_spec_index.js index 066633ea28a..6ded3d3d213 100644 --- a/spec/javascripts/webpack_spec_index.js +++ b/spec/javascripts/webpack_spec_index.js @@ -22,8 +22,14 @@ import en_US from 'timezone/en_US' import './jsx/spec-support/specProtection' import setupRavenConsoleLoggingPlugin from '../../ui/boot/initializers/setupRavenConsoleLoggingPlugin.js' import {filterUselessConsoleMessages} from '@instructure/js-utils' +import './jsx/spec-support/timezoneBackwardsCompatLayer' +import { + up as configureDateTimeMomentParser +} from '../../ui/boot/initializers/configureDateTimeMomentParser' + filterUselessConsoleMessages(console) +configureDateTimeMomentParser() Enzyme.configure({adapter: new Adapter()}) diff --git a/ui/boot/index.js b/ui/boot/index.js index 616ae22631b..2cf6af72c57 100644 --- a/ui/boot/index.js +++ b/ui/boot/index.js @@ -21,18 +21,19 @@ import canvasHighContrastTheme from '@instructure/canvas-high-contrast-theme' import moment from 'moment' import tz from '@canvas/timezone' import './initializers/fakeRequireJSFallback.js' +import { + up as configureDateTimeMomentParser +} from './initializers/configureDateTimeMomentParser' +import { + up as configureTimezone +} from './initializers/configureTimezone' // we already put a