diff --git a/src/components/views/messages/DateSeparator.tsx b/src/components/views/messages/DateSeparator.tsx index b20319e800e..27d3643e70e 100644 --- a/src/components/views/messages/DateSeparator.tsx +++ b/src/components/views/messages/DateSeparator.tsx @@ -20,6 +20,8 @@ import React from 'react'; import { _t } from '../../../languageHandler'; import { formatFullDateNoTime } from '../../../DateUtils'; import { replaceableComponent } from "../../../utils/replaceableComponent"; +import SettingsStore from '../../../settings/SettingsStore'; +import { UIFeature } from '../../../settings/UIFeature'; function getDaysArray(): string[] { return [ @@ -42,9 +44,10 @@ interface IProps { export default class DateSeparator extends React.Component { private getLabel() { const date = new Date(this.props.ts); + const disableRelativeTimestamps = !SettingsStore.getValue(UIFeature.TimelineEnableRelativeDates); // During the time the archive is being viewed, a specific day might not make sense, so we return the full date - if (this.props.forExport) return formatFullDateNoTime(date); + if (this.props.forExport || disableRelativeTimestamps) return formatFullDateNoTime(date); const today = new Date(); const yesterday = new Date(); @@ -63,11 +66,12 @@ export default class DateSeparator extends React.Component { } render() { + const label = this.getLabel(); // ARIA treats
s as separators, here we abuse them slightly so manually treat this entire thing as one // tab-index=-1 to allow it to be focusable but do not add tab stop for it, primarily for screen readers - return

+ return


- +

; } diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index 4edc4884d5f..a96ad66e044 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -965,4 +965,8 @@ export const SETTINGS: {[setting: string]: ISetting} = { supportedLevels: LEVELS_UI_FEATURE, default: true, }, + [UIFeature.TimelineEnableRelativeDates]: { + supportedLevels: LEVELS_UI_FEATURE, + default: true, + }, }; diff --git a/src/settings/UIFeature.ts b/src/settings/UIFeature.ts index 847da2537ee..225f785614f 100644 --- a/src/settings/UIFeature.ts +++ b/src/settings/UIFeature.ts @@ -32,6 +32,7 @@ export enum UIFeature { Communities = "UIFeature.communities", AdvancedSettings = "UIFeature.advancedSettings", RoomHistorySettings = "UIFeature.roomHistorySettings", + TimelineEnableRelativeDates = "UIFeature.timelineEnableRelativeDates" } export enum UIComponent { diff --git a/test/components/views/messages/DateSeparator-test.tsx b/test/components/views/messages/DateSeparator-test.tsx new file mode 100644 index 00000000000..0a3db003f19 --- /dev/null +++ b/test/components/views/messages/DateSeparator-test.tsx @@ -0,0 +1,98 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from "react"; +import { mount } from "enzyme"; + +import sdk from "../../../skinned-sdk"; +import * as TestUtils from "../../../test-utils"; +import { formatFullDateNoTime } from "../../../../src/DateUtils"; +import SettingsStore from "../../../../src/settings/SettingsStore"; +import { UIFeature } from "../../../../src/settings/UIFeature"; + +jest.mock("../../../../src/settings/SettingsStore"); + +const _DateSeparator = sdk.getComponent("views.messages.DateSeparator"); +const DateSeparator = TestUtils.wrapInMatrixClientContext(_DateSeparator); + +describe("DateSeparator", () => { + const HOUR_MS = 3600000; + const DAY_MS = HOUR_MS * 24; + // Friday Dec 17 2021, 9:09am + const now = '2021-12-17T08:09:00.000Z'; + const nowMs = 1639728540000; + const defaultProps = { + ts: nowMs, + now, + }; + const RealDate = global.Date; + class MockDate extends Date { + constructor(date) { + super(date || now); + } + } + + const getComponent = (props = {}) => + mount(); + + type TestCase = [string, number, string]; + const testCases: TestCase[] = [ + ['the exact same moment', nowMs, 'Today'], + ['same day as current day', nowMs - HOUR_MS, 'Today'], + ['day before the current day', nowMs - (HOUR_MS * 12), 'Yesterday'], + ['2 days ago', nowMs - DAY_MS * 2, 'Wednesday'], + ['144 hours ago', nowMs - HOUR_MS * 144, 'Sat, Dec 11 2021'], + [ + '6 days ago, but less than 144h', + new Date('Saturday Dec 11 2021 23:59:00 GMT+0100 (Central European Standard Time)').getTime(), + 'Saturday', + ], + ]; + + beforeEach(() => { + global.Date = MockDate as unknown as DateConstructor; + (SettingsStore.getValue as jest.Mock).mockReturnValue(true); + }); + + afterAll(() => { + global.Date = RealDate; + }); + + it('renders the date separator correctly', () => { + const component = getComponent(); + expect(component).toMatchSnapshot(); + expect(SettingsStore.getValue).toHaveBeenCalledWith(UIFeature.TimelineEnableRelativeDates); + }); + + it.each(testCases)('formats date correctly when current time is %s', (_d, ts, result) => { + expect(getComponent({ ts, forExport: false }).text()).toEqual(result); + }); + + describe('when forExport is true', () => { + it.each(testCases)('formats date in full when current time is %s', (_d, ts) => { + expect(getComponent({ ts, forExport: true }).text()).toEqual(formatFullDateNoTime(new Date(ts))); + }); + }); + + describe('when Settings.TimelineEnableRelativeDates is falsy', () => { + beforeEach(() => { + (SettingsStore.getValue as jest.Mock).mockReturnValue(false); + }); + it.each(testCases)('formats date in full when current time is %s', (_d, ts) => { + expect(getComponent({ ts, forExport: false }).text()).toEqual(formatFullDateNoTime(new Date(ts))); + }); + }); +}); diff --git a/test/components/views/messages/__snapshots__/DateSeparator-test.tsx.snap b/test/components/views/messages/__snapshots__/DateSeparator-test.tsx.snap new file mode 100644 index 00000000000..69b5a34defc --- /dev/null +++ b/test/components/views/messages/__snapshots__/DateSeparator-test.tsx.snap @@ -0,0 +1,32 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`DateSeparator renders the date separator correctly 1`] = ` + + +

+
+ +
+

+
+
+`; diff --git a/test/skinned-sdk.js b/test/skinned-sdk.js index 9de13d20a1d..0a3cc85a3b5 100644 --- a/test/skinned-sdk.js +++ b/test/skinned-sdk.js @@ -19,7 +19,6 @@ components['structures.RoomDirectory'] = stubComponent(); components['views.globals.GuestWarningBar'] = stubComponent(); components['views.globals.NewVersionBar'] = stubComponent(); components['views.elements.Spinner'] = stubComponent({ displayName: 'Spinner' }); -components['views.messages.DateSeparator'] = stubComponent({ displayName: 'DateSeparator' }); components['views.messages.MessageTimestamp'] = stubComponent({ displayName: 'MessageTimestamp' }); components['views.messages.SenderProfile'] = stubComponent({ displayName: 'SenderProfile' }); components['views.rooms.SearchBar'] = stubComponent();