From b4140d4310f934cca3862e70ed91b3165783511e Mon Sep 17 00:00:00 2001 From: Matt Hanley <3798302+matthanley@users.noreply.github.com> Date: Fri, 5 Mar 2021 13:35:31 +0000 Subject: [PATCH] Updated default format for date helper to locale-based date string (#12733) refs https://github.com/TryGhost/Casper/pull/741 closes https://github.com/TryGhost/Team/issues/524 - Use a local-based format as the default format as suggested in https://github.com/TryGhost/Casper/pull/741 - reworked the helper to be easier to read and follow the different use cases - introduced setting and resetting locale in tests via settingsCache and themei18n - updated tests to cover more cases e.g. passing a date, this.published_at and no date - added validation for user inputted dates because they could literally be anything Co-authored-by: Hannah Wolfe --- core/frontend/helpers/date.js | 45 +++++++----- test/unit/helpers/date_spec.js | 128 ++++++++++++++++++++++++++++++--- test/unit/helpers/lang_spec.js | 2 + 3 files changed, 149 insertions(+), 26 deletions(-) diff --git a/core/frontend/helpers/date.js b/core/frontend/helpers/date.js index 4201fbf63a..fbe5308972 100644 --- a/core/frontend/helpers/date.js +++ b/core/frontend/helpers/date.js @@ -5,42 +5,51 @@ const {SafeString, themeI18n} = require('../services/proxy'); const moment = require('moment-timezone'); +const _ = require('lodash'); -module.exports = function (date, options) { - let timezone; +module.exports = function (...attrs) { + // Options is the last argument + const options = attrs.pop(); + let date; - if (!options && Object.prototype.hasOwnProperty.call(date, 'hash')) { - options = date; - date = undefined; - timezone = options.data.site.timezone; + // If there is any more arguments, date is the first one + if (!_.isEmpty(attrs)) { + date = attrs.shift(); - // set to published_at by default, if it's available - // otherwise, this will print the current date - if (this.published_at) { - date = moment(this.published_at).tz(timezone).format(); - } + // If there is no date argument & the current context contains published_at use that by default, + // else date being undefined means moment will use the current date + } else if (this.published_at) { + date = this.published_at; } + // ensure that date is undefined, not null, as that can cause errors + date = date === null ? undefined : date; + + const timezone = options.data.site.timezone; const { - format = 'MMM DD, YYYY', + format = 'll', timeago } = options.hash; - // ensure that context is undefined, not null, as that can cause errors - date = date === null ? undefined : date; - timezone = options.data.site.timezone; const timeNow = moment().tz(timezone); + // Our date might be user input + let testDateInput = Date.parse(date); + let dateMoment; + if (isNaN(testDateInput) === false) { + dateMoment = moment.parseZone(date); + } else { + dateMoment = timeNow; + } // i18n: Making dates, including month names, translatable to any language. // Documentation: http://momentjs.com/docs/#/i18n/ // Locales: https://github.com/moment/moment/tree/develop/locale - const dateMoment = moment(date); dateMoment.locale(themeI18n.locale()); if (timeago) { - date = timezone ? dateMoment.tz(timezone).from(timeNow) : dateMoment.fromNow(); + date = dateMoment.tz(timezone).from(timeNow); } else { - date = timezone ? dateMoment.tz(timezone).format(format) : dateMoment.format(format); + date = dateMoment.tz(timezone).format(format); } return new SafeString(date); diff --git a/test/unit/helpers/date_spec.js b/test/unit/helpers/date_spec.js index 5d63b99e83..dabb30d9e2 100644 --- a/test/unit/helpers/date_spec.js +++ b/test/unit/helpers/date_spec.js @@ -1,11 +1,20 @@ +const sinon = require('sinon'); const should = require('should'); // Stuff we are testing const helpers = require('../../../core/frontend/helpers'); +const proxy = require('../../../core/frontend/services/proxy'); +const settingsCache = require('../../../core/server/services/settings/cache'); + const moment = require('moment-timezone'); describe('{{date}} helper', function () { + afterEach(function () { + settingsCache.reset(); + proxy.themeI18n._loadLocale(); + }); + it('creates properly formatted date strings', function () { const testDates = [ '2013-12-31T11:28:58.593+02:00', @@ -14,7 +23,7 @@ describe('{{date}} helper', function () { '2014-03-01T01:28:58.593+00:00' ]; - const timezones = 'Europe/Dublin'; + const timezone = 'Europe/Dublin'; const format = 'MMM Do, YYYY'; const context = { @@ -23,16 +32,78 @@ describe('{{date}} helper', function () { }, data: { site: { - timezone: 'Europe/Dublin' + timezone } } }; + let rendered; + testDates.forEach(function (d) { - const rendered = helpers.date.call({published_at: d}, context); + rendered = helpers.date.call({published_at: d}, context); should.exist(rendered); - String(rendered).should.equal(moment(d).tz(timezones).format(format)); + String(rendered).should.equal(moment(d).tz(timezone).format(format)); + + rendered = helpers.date.call({}, d, context); + + should.exist(rendered); + String(rendered).should.equal(moment(d).tz(timezone).format(format)); + }); + + // No date falls back to now + rendered = helpers.date.call({}, context); + should.exist(rendered); + String(rendered).should.equal(moment().tz(timezone).format(format)); + }); + + it('creates properly localised date strings', function () { + const testDates = [ + '2013-12-31T23:58:58.593+02:00', + '2014-01-01T00:28:58.593+11:00', + '2014-11-20T01:28:58.593-04:00', + '2014-03-01T01:28:58.593+00:00' + ]; + + const locales = [ + 'en', + 'en-gb', + 'de' + ]; + + const timezone = 'Europe/Dublin'; + const format = 'll'; + + const context = { + hash: {}, + data: { + site: { + timezone + } + } + }; + + locales.forEach(function (l) { + settingsCache.set('lang', {value: l}); + proxy.themeI18n._loadLocale(); + let rendered; + + testDates.forEach(function (d) { + rendered = helpers.date.call({published_at: d}, context); + + should.exist(rendered); + String(rendered).should.equal(moment(d).tz(timezone).locale(l).format(format)); + + rendered = helpers.date.call({}, d, context); + + should.exist(rendered); + String(rendered).should.equal(moment(d).tz(timezone).locale(l).format(format)); + }); + + // No date falls back to now + rendered = helpers.date.call({}, context); + should.exist(rendered); + String(rendered).should.equal(moment().tz(timezone).locale(l).format(format)); }); }); @@ -44,7 +115,7 @@ describe('{{date}} helper', function () { '2014-03-01T01:28:58.593+00:00' ]; - const timezones = 'Europe/Dublin'; + const timezone = 'Europe/Dublin'; const timeNow = moment().tz('Europe/Dublin'); const context = { @@ -53,16 +124,57 @@ describe('{{date}} helper', function () { }, data: { site: { - timezone: 'Europe/Dublin' + timezone } } }; + let rendered; + testDates.forEach(function (d) { - const rendered = helpers.date.call({published_at: d}, context); + rendered = helpers.date.call({published_at: d}, context); should.exist(rendered); - String(rendered).should.equal(moment(d).tz(timezones).from(timeNow)); + String(rendered).should.equal(moment(d).tz(timezone).from(timeNow)); + + rendered = helpers.date.call({}, d, context); + + should.exist(rendered); + String(rendered).should.equal(moment(d).tz(timezone).from(timeNow)); }); + + // No date falls back to now + rendered = helpers.date.call({}, context); + should.exist(rendered); + String(rendered).should.equal('a few seconds ago'); + }); + + it('ignores an invalid date, defaulting to now', function () { + const timezone = 'Europe/Dublin'; + const timeNow = moment().tz('Europe/Dublin'); + + const context = { + hash: { + timeago: true + }, + data: { + site: { + timezone + } + } + }; + + let invalidDate = 'Fred'; + let rendered; + + rendered = helpers.date.call({published_at: invalidDate}, context); + + should.exist(rendered); + String(rendered).should.equal('a few seconds ago'); + + rendered = helpers.date.call({}, invalidDate, context); + + should.exist(rendered); + String(rendered).should.equal('a few seconds ago'); }); }); diff --git a/test/unit/helpers/lang_spec.js b/test/unit/helpers/lang_spec.js index e3c5a8eecf..a3e090565b 100644 --- a/test/unit/helpers/lang_spec.js +++ b/test/unit/helpers/lang_spec.js @@ -6,10 +6,12 @@ const proxy = require('../../../core/frontend/services/proxy'); describe('{{lang}} helper', function () { beforeEach(function () { settingsCache.set('lang', {value: 'en'}); + proxy.themeI18n._loadLocale(); }); afterEach(function () { settingsCache.shutdown(); + proxy.themeI18n._loadLocale(); }); it('returns correct language tag', function () {