prefer tDateToString/tTimeToString over strftime

refs CNVS-19516

the {{strftime}} handlebars helper requires use of a hard-coded format
string that's locale-unaware. tDateToString and tTimeToString take
localization keys so that you get a format string appropriate to the
locale.

the remaining calls to {{strftime}} are for fixed-format
machine-oriented strings, where they shouldn't be localized. this is an
appropriate use, but really the only time {{strftime}} should be used.

additionally, for all but the calendar agenda view, these should have
been using fudged values ({{strftime}}, {{tTimeToString}} and
{{tDateToString}} all expect fudged dates as input). fix that, too,
while we're in here.

finally, while adding specs, found that Date.parse doesn't like
milliseconds, which meant dates weren't actually happening in the
assignment-related specs; fixed that by switching to tz.parse. Since the
values being parsed were explicitly UTC, this doesn't change the parsing
semantics (e.g. fudged vs. not)

test-plan:
 - set Norwegian as your locale

 [edit assignment dialog]
  - create an assignment with multiple due dates
  - go back to the assignment list, and choose the edit dialog from the
    cog menu
  - the tool-tip on the "Due" field should include the multiple dates
    formatted as "<day> <month>" rather than "<month> <day>"
  - also check same field for timezone consistency when profile timezone
    does not match browser timezone

 [assignment page]
  - for the same assignment, click through to the assignment's page
  - the due dates should display as "<day> <month>" rather than "<month>
    <day>"
  - also check same field for timezone consistency when profile timezone
    does not match browser timezone

 [availability column in assignment list]
  - for the same assignment
    - make one due date have an unlock at in the past and lock at in the
      future
    - make another due date have an unlock at in the future
  - view the assignment list
  - the tool-tip on "Multiple Dates" next to "Available" should have
    "<day> <month>" instead of "<month> <day>" for both the "Available
    until" (first section) value and the "Not available until" (second
    section) value
  - also check same field for timezone consistency when profile timezone
    does not match browser timezone

 [calendar agenda view]
  - go to the calendar agenda view
  - the due date times for future assignments should have 24-hour
    formatting, not 12-hour
  - the event dates for future non-assignment events should also have
    24-hour formatting
  - also check same fields for timezone consistency when profile
    timezone does not match browser timezone

 [feature flags]
  - go to the feature flags pane in course settings
  - the "Estimated Release" for "New Quiz Statistics Page" should format
    "<day> <month> <year>", not "<month> <day>, <year>"

Change-Id: I0226cc14adcd39eceaa0e5f4bf66e0eefdc0bbf3
Reviewed-on: https://gerrit.instructure.com/51448
Reviewed-by: Cody Cutrer <cody@instructure.com>
Reviewed-by: Jeremy Stanley <jeremy@instructure.com>
Reviewed-by: Jonathan Featherstone <jfeatherstone@instructure.com>
Reviewed-by: John Corrigan <jcorrigan@instructure.com>
Tested-by: Jenkins
QA-Review: August Thornton <august@instructure.com>
Product-Review: Jacob Fugal <jacob@instructure.com>
This commit is contained in:
Jacob Fugal 2015-04-01 15:15:08 -06:00
parent 0805dd3143
commit 43f8be381e
10 changed files with 222 additions and 16 deletions

View File

@ -3,7 +3,8 @@ define [
'underscore'
'jquery'
'i18n!assignments'
], (Backbone, _, $, I18n) ->
'timezone'
], (Backbone, _, $, I18n, tz) ->
class DateGroup extends Backbone.Model
@ -15,19 +16,19 @@ define [
dueAt: ->
dueAt = @get("due_at")
if dueAt then Date.parse(dueAt) else null
if dueAt then tz.parse(dueAt) else null
unlockAt: ->
unlockAt = @get("unlock_at")
if unlockAt then Date.parse(unlockAt) else null
if unlockAt then tz.parse(unlockAt) else null
lockAt: ->
lockAt = @get("lock_at")
if lockAt then Date.parse(lockAt) else null
if lockAt then tz.parse(lockAt) else null
now: ->
now = @get("now")
if now then Date.parse(now) else new Date()
if now then tz.parse(now) else new Date()
# no lock/unlock dates
@ -61,4 +62,4 @@ define [
available: @available()
pending: @pending()
open: @open()
closed: @closed()
closed: @closed()

View File

@ -57,7 +57,7 @@
{{#each allDates}}
<div class="clearfix">
<dt>{{dueFor}}</dt>
<dd>{{#if dueAt}} {{strftime dueAt "%b %-d"}} {{else}} - {{/if}}</dd>
<dd>{{#if dueAt}} {{tDateToString (fudge dueAt) 'short'}} {{else}} - {{/if}}</dd>
</div>
{{/each}}
</dl>

View File

@ -15,7 +15,7 @@
<dd>
{{#if dueAt}}
<span {{contextSensitiveDatetimeTitle dueAt}}>
{{strftime dueAt "%b %-d"}}
{{tDateToString (fudge dueAt) 'short'}}
</span>
{{else}}
-

View File

@ -1,14 +1,14 @@
{{#if pending}}
<span class="status-description">{{#t "not_available_until"}}Not available until{{/t}}</span>
<span {{ contextSensitiveDatetimeTitle unlockAt }}>
{{strftime unlockAt "%b %-d"}}
{{tDateToString (fudge unlockAt) 'short'}}
</span>
{{/if}}
{{#if open}}
<span class="status-description">{{#t "available_until"}}Available until{{/t}}</span>
<span {{ contextSensitiveDatetimeTitle lockAt }}>
{{strftime lockAt "%b %-d"}}
{{tDateToString (fudge lockAt) 'short'}}
</span>
{{/if}}

View File

@ -23,11 +23,11 @@
<div class="ig-details">
{{#if assignment}}
<b>{{#t "due"}}Due{{/t}}</b><span class="screenreader-only">,</span>
{{strftime originalStart "%l:%M%P"}}
{{tTimeToString originalStart "tiny"}}
{{else}}
{{#unless all_day}}
<span class="screenreader-only">{{#t "starts_at"}}Starts at{{/t}},</span>
{{strftime originalStart "%l:%M%P"}}
{{tTimeToString originalStart "tiny"}}
{{/unless}}
{{/if}}
</div>

View File

@ -22,7 +22,7 @@
{{#if releaseOn}}
{{#t "estimated_release"}}Estimated Release:{{/t}}
<span class="feature-release-date">
{{strftime releaseOn "%B %-d, %Y"}}
{{tDateToString (fudge releaseOn) "medium"}}
</span>
{{/if}}
</div>

View File

@ -3,11 +3,14 @@ define [
'underscore'
'timezone'
'vendor/timezone/America/Denver'
'vendor/timezone/America/Juneau'
'vendor/timezone/fr_FR'
'compiled/views/calendar/AgendaView'
'compiled/calendar/EventDataSource'
'helpers/ajax_mocks/api/v1/calendarEvents'
'helpers/ajax_mocks/api/v1/calendarAssignments'
], ($, _, tz, denver, AgendaView, EventDataSource, eventResponse, assignmentResponse) ->
'helpers/I18nStubber'
], ($, _, tz, denver, juneau, french, AgendaView, EventDataSource, eventResponse, assignmentResponse, I18nStubber) ->
loadEventPage = (server, includeNext = false) ->
sendCustomEvents(server, eventResponse, assignmentResponse, includeNext)
@ -28,11 +31,13 @@ define [
@server = sinon.fakeServer.create()
@snapshot = tz.snapshot()
tz.changeZone(denver, 'America/Denver')
I18nStubber.pushFrame()
teardown: ->
@container.remove()
@server.restore()
tz.restore(@snapshot)
I18nStubber.popFrame()
test 'should render results', ->
view = new AgendaView(el: @container, dataSource: @dataSource)
@ -57,6 +62,11 @@ define [
ok @container.find('.agenda-load-btn').length
test 'toJSON should properly serialize results', ->
I18nStubber.stub 'en',
'date.formats.short_with_weekday': '%a, %b %-d'
'date.abbr_day_names.1': 'Mon'
'date.abbr_month_names.10': 'Oct'
view = new AgendaView(el: @container, dataSource: @dataSource)
view.fetch(@contextCodes, @startDate)
loadEventPage(@server)
@ -101,3 +111,53 @@ define [
sendCustomEvents(@server, JSON.stringify(events), JSON.stringify([]), false, 2)
ok @container.find('.ig-row').length == 60, 'finds 60 ig-rows'
test 'renders non-assignment events with locale-appropriate format string', ->
tz.changeLocale(french, 'fr_FR')
I18nStubber.setLocale 'fr_FR'
I18nStubber.stub 'fr_FR', 'time.formats.tiny': '%k:%M'
view = new AgendaView(el: @container, dataSource: @dataSource)
view.fetch(@contextCodes, @startDate)
loadEventPage(@server)
# this event has a start_at of 2013-10-08T20:30:00Z, or 1pm MDT
ok @container.find('.ig-details').slice(2, 3).text().match(/13:00/), 'formats according to locale'
test 'renders assignment events with locale-appropriate format string', ->
tz.changeLocale(french, 'fr_FR')
I18nStubber.setLocale 'fr_FR'
I18nStubber.stub 'fr_FR', 'time.formats.tiny': '%k:%M'
view = new AgendaView(el: @container, dataSource: @dataSource)
view.fetch(@contextCodes, @startDate)
loadEventPage(@server)
# this event has a start_at of 2013-10-13T05:59:59Z, or 11:59pm MDT
ok @container.find('.ig-details').slice(12, 13).text().match(/23:59/), 'formats according to locale'
test 'renders non-assignment events in appropriate timezone', ->
tz.changeZone(juneau, 'America/Juneau')
I18nStubber.stub 'en',
'time.formats.tiny': '%l:%M%P'
'date': {}
view = new AgendaView(el: @container, dataSource: @dataSource)
view.fetch(@contextCodes, @startDate)
loadEventPage(@server)
# this event has a start_at of 2013-10-08T20:30:00Z, or 11:00am AKDT
ok @container.find('.ig-details').slice(2, 3).text().match(/11:00am/), 'formats in correct timezone'
test 'renders assignment events in appropriate timezone', ->
tz.changeZone(juneau, 'America/Juneau')
I18nStubber.stub 'en',
'time.formats.tiny': '%l:%M%P'
'date': {}
view = new AgendaView(el: @container, dataSource: @dataSource)
view.fetch(@contextCodes, @startDate)
loadEventPage(@server)
# this event has a start_at of 2013-10-13T05:59:59Z, or 9:59pm AKDT
ok @container.find('.ig-details').slice(12, 13).text().match(/9:59pm/), 'formats in correct timezone'

View File

@ -4,9 +4,13 @@ define [
'compiled/models/Submission'
'compiled/views/assignments/AssignmentListItemView'
'jquery'
'timezone'
'vendor/timezone/America/Juneau'
'vendor/timezone/fr_FR'
'helpers/I18nStubber'
'helpers/fakeENV'
'helpers/jquery.simulate'
], (Backbone, Assignment, Submission, AssignmentListItemView, $, fakeENV) ->
], (Backbone, Assignment, Submission, AssignmentListItemView, $, tz, juneau, french, I18nStubber, fakeENV) ->
screenreaderText = null
nonScreenreaderText = null
@ -144,9 +148,15 @@ define [
setup: ->
genSetup.call @
@snapshot = tz.snapshot()
I18nStubber.pushFrame()
teardown: ->
genTeardown.call @
tz.restore(@snapshot)
I18nStubber.popFrame()
test "initializes child views if can manage", ->
view = createView(@model, canManage: true)
ok view.publishIconView
@ -310,6 +320,57 @@ define [
equal spy.callCount, 0
AssignmentListItemView.prototype.updateScore.restore()
test "renders lockAt/unlockAt with locale-appropriate format string", ->
tz.changeLocale(french, 'fr_FR')
I18nStubber.setLocale 'fr_FR'
I18nStubber.stub 'fr_FR',
'date.formats.short': '%-d %b'
'date.abbr_month_names.8': 'août'
model = new AssignmentCollection([buildAssignment
id: 1
all_dates: [
{ lock_at: "2113-08-28T04:00:00Z", title: "Summer Session" }
{ unlock_at: "2113-08-28T04:00:00Z", title: "Winter Session" }]]).at(0)
view = createView(model, canManage: true)
$dds = view.dateAvailableColumnView.$("#vdd_tooltip_#{@model.id}_lock div")
equal $("span", $dds.first()).last().text().trim(), '28 août'
equal $("span", $dds.last()).last().text().trim(), '28 août'
test "renders lockAt/unlockAt in appropriate time zone", ->
tz.changeZone(juneau, 'America/Juneau')
I18nStubber.stub 'en',
'date.formats.short': '%b %-d'
'date.abbr_month_names.8': 'Aug'
model = new AssignmentCollection([buildAssignment
id: 1
all_dates: [
{ lock_at: "2113-08-28T04:00:00Z", title: "Summer Session" }
{ unlock_at: "2113-08-28T04:00:00Z", title: "Winter Session" }]]).at(0)
view = createView(model, canManage: true)
$dds = view.dateAvailableColumnView.$("#vdd_tooltip_#{@model.id}_lock div")
equal $("span", $dds.first()).last().text().trim(), 'Aug 27'
equal $("span", $dds.last()).last().text().trim(), 'Aug 27'
test "renders due date column with locale-appropriate format string", ->
tz.changeLocale(french, 'fr_FR')
I18nStubber.setLocale 'fr_FR'
I18nStubber.stub 'fr_FR',
'date.formats.short': '%-d %b'
'date.abbr_month_names.8': 'août'
view = createView(@model, canManage: true)
equal view.dateDueColumnView.$("#vdd_tooltip_#{@model.id}_due div dd").first().text().trim(), '29 août'
test "renders due date column in appropriate time zone", ->
tz.changeZone(juneau, 'America/Juneau')
I18nStubber.stub 'en',
'date.formats.short': '%b %-d'
'date.abbr_month_names.8': 'Aug'
view = createView(@model, canManage: true)
equal view.dateDueColumnView.$("#vdd_tooltip_#{@model.id}_due div dd").first().text().trim(), 'Aug 28'
module 'AssignmentListItemViewSpec—alternate grading type: percent',
setup: ->
genSetup.call @, assignment_grade_percent()

View File

@ -6,9 +6,13 @@ define [
'compiled/views/assignments/CreateAssignmentView'
'compiled/views/DialogFormView'
'jquery'
'timezone'
'vendor/timezone/America/Juneau'
'vendor/timezone/fr_FR'
'helpers/I18nStubber'
'helpers/jquery.simulate'
'compiled/behaviors/tooltip'
], (Backbone, AssignmentGroupCollection, AssignmentGroup, Assignment, CreateAssignmentView, DialogFormView, $) ->
], (Backbone, AssignmentGroupCollection, AssignmentGroup, Assignment, CreateAssignmentView, DialogFormView, $, tz, juneau, french, I18nStubber) ->
fixtures = $('#fixtures')
@ -113,12 +117,18 @@ define [
@assignment4 = assignment4()
@group = assignmentGroup()
@snapshot = tz.snapshot()
I18nStubber.pushFrame()
teardown: ->
ENV.VALID_DATE_RANGE = {
start_at: {date: null, date_context: null}
end_at: {date: null, date_context: null}
}
tz.restore(@snapshot)
I18nStubber.popFrame()
test "initialize generates a new assignment for creation", ->
view = createView(@group)
equal view.model.get("assignment_group_id"), @group.get("id")
@ -297,3 +307,19 @@ define [
ok errors["due_at"]
equal errors['due_at'][0]['message'], 'Due date cannot be before unlock date'
test "renders due dates with locale-appropriate format string", ->
tz.changeLocale(french, 'fr_FR')
I18nStubber.setLocale 'fr_FR'
I18nStubber.stub 'fr_FR',
'date.formats.short': '%-d %b'
'date.abbr_month_names.8': 'août'
view = createView(@assignment1)
equal view.$("#vdd_tooltip_assign_1 div dd").first().text().trim(), '28 août'
test "renders due dates in appropriate time zone", ->
tz.changeZone(juneau, 'America/Juneau')
I18nStubber.stub 'en',
'date.formats.short': '%b %-d'
'date.abbr_month_names.8': 'Aug'
view = createView(@assignment1)
equal view.$("#vdd_tooltip_assign_1 div dd").first().text().trim(), 'Aug 27'

View File

@ -0,0 +1,58 @@
define [
'compiled/views/feature_flags/FeatureFlagView'
'compiled/models/FeatureFlag'
'jquery'
'timezone'
'vendor/timezone/America/Juneau'
'vendor/timezone/fr_FR'
'helpers/I18nStubber'
'helpers/fakeENV'
], (FeatureFlagView, FeatureFlag, $, tz, juneau, french, I18nStubber, fakeENV) ->
module "FeatureFlagView",
setup: ->
@container = $('<div />', id: 'feature-flags').appendTo('#fixtures')
@snapshot = tz.snapshot()
I18nStubber.pushFrame()
fakeENV.setup()
teardown: ->
@container.remove()
tz.restore(@snapshot)
I18nStubber.popFrame()
fakeENV.teardown()
test 'should format release date with locale-appropriate format string', ->
releaseDate = tz.parse('2100-07-04T00:00:00Z')
tz.changeLocale(french, 'fr_FR')
I18nStubber.setLocale 'fr_FR'
I18nStubber.stub 'fr_FR',
'date.formats.medium': '%-d %b %Y'
'date.abbr_month_names.7': 'juil.'
flag = new FeatureFlag
releaseOn: releaseDate
feature_flag:
transitions: {}
view = new FeatureFlagView el: @container, model: flag
view.render()
equal view.$('.feature-release-date').text().trim(), '4 juil. 2100'
test 'should format release date in locale-appropriate format string', ->
releaseDate = tz.parse('2100-07-04T00:00:00Z')
tz.changeZone(juneau, 'America/Juneau')
I18nStubber.stub 'en',
'date.formats.medium': '%b %-d, %Y'
'date.abbr_month_names.7': 'Jul'
flag = new FeatureFlag
releaseOn: releaseDate
feature_flag:
transitions: {}
view = new FeatureFlagView el: @container, model: flag
view.render()
equal view.$('.feature-release-date').text().trim(), 'Jul 3, 2100'