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"