diff --git a/app/components/__snapshots__/formatted_time.test.js.snap b/app/components/__snapshots__/formatted_time.test.js.snap
new file mode 100644
index 000000000..5fb0786f5
--- /dev/null
+++ b/app/components/__snapshots__/formatted_time.test.js.snap
@@ -0,0 +1,26 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`FormattedTime should render correctly 1`] = `
+
+
+
+ 7:02 PM
+
+
+
+`;
diff --git a/app/components/__snapshots__/swiper.test.js.snap b/app/components/__snapshots__/swiper.test.js.snap
index cc5bad0f4..9545911fb 100644
--- a/app/components/__snapshots__/swiper.test.js.snap
+++ b/app/components/__snapshots__/swiper.test.js.snap
@@ -13,7 +13,7 @@ exports[`Swiper should match snapshot 1`] = `
]
}
>
-
-
-
+
`;
diff --git a/app/components/formatted_time.js b/app/components/formatted_time.js
index 88c903882..686747e1b 100644
--- a/app/components/formatted_time.js
+++ b/app/components/formatted_time.js
@@ -4,17 +4,19 @@
import React from 'react';
import PropTypes from 'prop-types';
import {Text} from 'react-native';
+import {injectIntl, intlShape} from 'react-intl';
import moment from 'moment-timezone';
import CustomPropTypes from 'app/constants/custom_prop_types';
-export default class FormattedTime extends React.PureComponent {
+class FormattedTime extends React.PureComponent {
static propTypes = {
value: PropTypes.any.isRequired,
timeZone: PropTypes.string,
children: PropTypes.func,
hour12: PropTypes.bool,
style: CustomPropTypes.Style,
+ intl: intlShape.isRequired,
};
getFormattedTime = () => {
@@ -22,8 +24,22 @@ export default class FormattedTime extends React.PureComponent {
value,
timeZone,
hour12,
+ intl,
} = this.props;
+ const timezoneProps = timeZone ? {timeZone} : {};
+ const options = {
+ ...timezoneProps,
+ hour12,
+ };
+ const formattedTime = intl.formatTime(value, options);
+
+ // `formatTime` returns unformatted date string on error like in the case of (react-intl) unsupported timezone.
+ // Therefore, use react-intl by default or moment-timezone for unsupported timezone.
+ if (formattedTime !== String(new Date(value))) {
+ return formattedTime;
+ }
+
const format = hour12 ? 'hh:mm A' : 'HH:mm';
if (timeZone) {
return moment.tz(value, timeZone).format(format);
@@ -43,3 +59,5 @@ export default class FormattedTime extends React.PureComponent {
return {formattedTime};
}
}
+
+export default injectIntl(FormattedTime);
diff --git a/app/components/formatted_time.test.js b/app/components/formatted_time.test.js
new file mode 100644
index 000000000..70825990d
--- /dev/null
+++ b/app/components/formatted_time.test.js
@@ -0,0 +1,97 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import React from 'react';
+import {render} from '@testing-library/react-native';
+import {IntlProvider} from 'react-intl';
+import IntlPolyfill from 'intl';
+import 'intl/locale-data/jsonp/es';
+import 'intl/locale-data/jsonp/ko';
+
+import FormattedTime from './formatted_time';
+
+describe('FormattedTime', () => {
+ const baseProps = {
+ value: 1548788533405,
+ timeZone: 'UTC',
+ hour12: true,
+ };
+
+ setupTest();
+
+ it('should render correctly', () => {
+ console.error = jest.fn();
+
+ let wrapper = renderWithIntl(
+
+ );
+
+ expect(wrapper.baseElement).toMatchSnapshot();
+ expect(wrapper.getByText('7:02 PM')).toBeTruthy();
+
+ wrapper = renderWithIntl(
+
+ );
+
+ expect(wrapper.getByText('19:02')).toBeTruthy();
+ });
+
+ it('should support localization', () => {
+ let wrapper = renderWithIntl(
+ ,
+ 'es',
+ );
+
+ expect(wrapper.getByText('7:02 p. m.')).toBeTruthy();
+
+ wrapper = renderWithIntl(
+ ,
+ 'ko',
+ );
+
+ expect(wrapper.getByText('오후 7:02')).toBeTruthy();
+
+ wrapper = renderWithIntl(
+ ,
+ 'ko',
+ );
+
+ expect(wrapper.getByText('19:02')).toBeTruthy();
+ });
+
+ it('should fallback to default short format for unsupported locale of react-intl ', () => {
+ let wrapper = renderWithIntl(
+ ,
+ 'es',
+ );
+
+ expect(wrapper.getByText('08:47 AM')).toBeTruthy();
+
+ wrapper = renderWithIntl(
+
+ );
+
+ expect(wrapper.getByText('08:47')).toBeTruthy();
+ });
+});
+
+function renderWithIntl(component, locale = 'en') {
+ return render({component});
+}
+
+function setupTest() {
+ global.Intl = IntlPolyfill;
+}
diff --git a/app/components/markdown/markdown_table/__snapshots__/markdown_table.test.js.snap b/app/components/markdown/markdown_table/__snapshots__/markdown_table.test.js.snap
index cb7a2b7c8..cc1ce4c58 100644
--- a/app/components/markdown/markdown_table/__snapshots__/markdown_table.test.js.snap
+++ b/app/components/markdown/markdown_table/__snapshots__/markdown_table.test.js.snap
@@ -10,7 +10,7 @@ exports[`MarkdownTable should match snapshot 1`] = `
}
type="opacity"
>
-
>
-
+
-
-
-
-
-
-
-
-
-
-
-
+
`;
diff --git a/app/screens/interactive_dialog/__snapshots__/interactive_dialog.test.js.snap b/app/screens/interactive_dialog/__snapshots__/interactive_dialog.test.js.snap
index 8bfa4f29b..eb15a328a 100644
--- a/app/screens/interactive_dialog/__snapshots__/interactive_dialog.test.js.snap
+++ b/app/screens/interactive_dialog/__snapshots__/interactive_dialog.test.js.snap
@@ -8,7 +8,7 @@ exports[`InteractiveDialog should display introduction text if present 1`] = `
}
}
>
-
-
+
`;
diff --git a/app/screens/long_post/__snapshots__/long_post.test.js.snap b/app/screens/long_post/__snapshots__/long_post.test.js.snap
index e28205ee7..de0b6c295 100644
--- a/app/screens/long_post/__snapshots__/long_post.test.js.snap
+++ b/app/screens/long_post/__snapshots__/long_post.test.js.snap
@@ -318,7 +318,7 @@ LongPost {
/>
-
-
+
-
-
+
`;
diff --git a/app/screens/settings/notification_settings/__snapshots__/notification_settings.test.js.snap b/app/screens/settings/notification_settings/__snapshots__/notification_settings.test.js.snap
index b260db05d..c3f4796aa 100644
--- a/app/screens/settings/notification_settings/__snapshots__/notification_settings.test.js.snap
+++ b/app/screens/settings/notification_settings/__snapshots__/notification_settings.test.js.snap
@@ -239,7 +239,7 @@ NotificationSettings {
}
>
-
-
+
,
"_rendering": false,
"_updater": [Circular],
diff --git a/app/screens/settings/notification_settings_email/__snapshots__/notification_settings_email.android.test.js.snap b/app/screens/settings/notification_settings_email/__snapshots__/notification_settings_email.android.test.js.snap
index fcaf2ed75..aae03f1c7 100644
--- a/app/screens/settings/notification_settings_email/__snapshots__/notification_settings_email.android.test.js.snap
+++ b/app/screens/settings/notification_settings_email/__snapshots__/notification_settings_email.android.test.js.snap
@@ -49,7 +49,7 @@ exports[`NotificationSettingsEmailAndroid should match snapshot 1`] = `
`;
exports[`NotificationSettingsEmailAndroid should match snapshot 2`] = `
-
-
+
`;
diff --git a/app/screens/settings/notification_settings_mentions_keywords/__snapshots__/notification_settings_mentions_keywords.test.js.snap b/app/screens/settings/notification_settings_mentions_keywords/__snapshots__/notification_settings_mentions_keywords.test.js.snap
index 2a0b15098..aa4a68047 100644
--- a/app/screens/settings/notification_settings_mentions_keywords/__snapshots__/notification_settings_mentions_keywords.test.js.snap
+++ b/app/screens/settings/notification_settings_mentions_keywords/__snapshots__/notification_settings_mentions_keywords.test.js.snap
@@ -167,7 +167,7 @@ NotificationSettingsMentionsKeywords {
}
>
-
-
+
,
"_rendering": false,
"_updater": [Circular],
diff --git a/app/screens/terms_of_service/__snapshots__/terms_of_service.test.js.snap b/app/screens/terms_of_service/__snapshots__/terms_of_service.test.js.snap
index 6b850ccac..12055e11d 100644
--- a/app/screens/terms_of_service/__snapshots__/terms_of_service.test.js.snap
+++ b/app/screens/terms_of_service/__snapshots__/terms_of_service.test.js.snap
@@ -3,7 +3,7 @@
exports[`TermsOfService should enable/disable navigator buttons on setNavigatorButtons true/false 1`] = `
-
-
+
`;
exports[`TermsOfService should enable/disable navigator buttons on setNavigatorButtons true/false 2`] = `
-
-
+
`;
@@ -338,7 +338,7 @@ exports[`TermsOfService should match snapshot for get terms 1`] = `
exports[`TermsOfService should match snapshot on enableNavigatorLogout 1`] = `
-
-
+
`;
diff --git a/app/screens/user_profile/__snapshots__/user_profile.test.js.snap b/app/screens/user_profile/__snapshots__/user_profile.test.js.snap
index a0566f8f7..db0501f58 100644
--- a/app/screens/user_profile/__snapshots__/user_profile.test.js.snap
+++ b/app/screens/user_profile/__snapshots__/user_profile.test.js.snap
@@ -9,7 +9,7 @@ exports[`user_profile should match snapshot 1`] = `
}
>
-
-
+
`;
diff --git a/package-lock.json b/package-lock.json
index f408b8da0..9bd09b4b5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1487,6 +1487,16 @@
"yargs": "^12.0.2"
}
},
+ "@testing-library/react-native": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/@testing-library/react-native/-/react-native-5.0.3.tgz",
+ "integrity": "sha512-lQH7vUgwESfagFw4BlKsfpX7Rv/m7h2NYfubY0aoQromSwI1slCxrhwZws8gABTXweob/DyLATsOamHsWdwDnA==",
+ "dev": true,
+ "requires": {
+ "pretty-format": "^24.9.0",
+ "wait-for-expect": "^3.0.0"
+ }
+ },
"@types/babel__core": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.3.tgz",
@@ -5500,7 +5510,8 @@
"intl": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/intl/-/intl-1.2.5.tgz",
- "integrity": "sha1-giRKIZDE5Bn4Nx9ao02qNCDiq94="
+ "integrity": "sha1-giRKIZDE5Bn4Nx9ao02qNCDiq94=",
+ "dev": true
},
"intl-format-cache": {
"version": "2.2.9",
@@ -12498,6 +12509,12 @@
"browser-process-hrtime": "^0.1.2"
}
},
+ "wait-for-expect": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/wait-for-expect/-/wait-for-expect-3.0.1.tgz",
+ "integrity": "sha512-3Ha7lu+zshEG/CeHdcpmQsZnnZpPj/UsG3DuKO8FskjuDbkx3jE3845H+CuwZjA2YWYDfKMU2KhnCaXMLd3wVw==",
+ "dev": true
+ },
"walker": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz",
diff --git a/package.json b/package.json
index 850f0dbf5..13edfc910 100644
--- a/package.json
+++ b/package.json
@@ -87,6 +87,7 @@
"@babel/plugin-transform-runtime": "7.6.2",
"@babel/preset-env": "7.6.3",
"@babel/register": "7.6.2",
+ "@testing-library/react-native": "5.0.3",
"babel-eslint": "10.0.3",
"babel-jest": "24.9.0",
"babel-plugin-module-resolver": "3.2.0",
@@ -139,7 +140,7 @@
"lcov",
"text-summary"
],
- "preset": "react-native",
+ "preset": "@testing-library/react-native",
"setupFilesAfterEnv": [
"/test/setup.js",
"/node_modules/jest-enzyme/lib/index.js"