@formatjs/intl 2.3.1 → 2.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/BUILD +79 -0
  2. package/CHANGELOG.md +537 -0
  3. package/LICENSE.md +0 -0
  4. package/README.md +0 -0
  5. package/index.ts +38 -0
  6. package/package.json +12 -8
  7. package/src/create-intl.ts +159 -0
  8. package/src/dateTime.ts +204 -0
  9. package/src/displayName.ts +54 -0
  10. package/src/error.ts +106 -0
  11. package/src/list.ts +119 -0
  12. package/src/message.ts +246 -0
  13. package/src/number.ts +101 -0
  14. package/src/plural.ts +45 -0
  15. package/src/relativeTime.ts +74 -0
  16. package/src/types.ts +234 -0
  17. package/src/utils.ts +177 -0
  18. package/tests/__snapshots__/formatDate.test.ts.snap +13 -0
  19. package/tests/__snapshots__/formatMessage.test.ts.snap +45 -0
  20. package/tests/__snapshots__/formatNumber.test.ts.snap +13 -0
  21. package/tests/__snapshots__/formatRelativeTime.test.ts.snap +19 -0
  22. package/tests/__snapshots__/formatTime.test.ts.snap +13 -0
  23. package/tests/create-intl.test.ts +60 -0
  24. package/tests/error.test.ts +25 -0
  25. package/tests/formatDate.test.ts +167 -0
  26. package/tests/formatDisplayNames.test.ts +63 -0
  27. package/tests/formatList.test.ts +51 -0
  28. package/tests/formatMessage.test.ts +495 -0
  29. package/tests/formatNumber.test.ts +172 -0
  30. package/tests/formatPlural.test.ts +119 -0
  31. package/tests/formatRelativeTime.test.ts +145 -0
  32. package/tests/formatTime.test.ts +234 -0
  33. package/tests/global.d.ts +7 -0
  34. package/tsconfig.json +5 -0
  35. package/index.d.ts +0 -17
  36. package/index.d.ts.map +0 -1
  37. package/index.js +0 -40
  38. package/lib/index.d.ts +0 -17
  39. package/lib/index.d.ts.map +0 -1
  40. package/lib/index.js +0 -17
  41. package/lib/src/create-intl.d.ts +0 -11
  42. package/lib/src/create-intl.d.ts.map +0 -1
  43. package/lib/src/create-intl.js +0 -51
  44. package/lib/src/dateTime.d.ts +0 -38
  45. package/lib/src/dateTime.d.ts.map +0 -1
  46. package/lib/src/dateTime.js +0 -117
  47. package/lib/src/displayName.d.ts +0 -6
  48. package/lib/src/displayName.d.ts.map +0 -1
  49. package/lib/src/displayName.js +0 -22
  50. package/lib/src/error.d.ts +0 -34
  51. package/lib/src/error.d.ts.map +0 -1
  52. package/lib/src/error.js +0 -88
  53. package/lib/src/list.d.ts +0 -11
  54. package/lib/src/list.d.ts.map +0 -1
  55. package/lib/src/list.js +0 -61
  56. package/lib/src/message.d.ts +0 -16
  57. package/lib/src/message.d.ts.map +0 -1
  58. package/lib/src/message.js +0 -103
  59. package/lib/src/number.d.ts +0 -17
  60. package/lib/src/number.d.ts.map +0 -1
  61. package/lib/src/number.js +0 -54
  62. package/lib/src/plural.d.ts +0 -7
  63. package/lib/src/plural.d.ts.map +0 -1
  64. package/lib/src/plural.js +0 -19
  65. package/lib/src/relativeTime.d.ts +0 -7
  66. package/lib/src/relativeTime.d.ts.map +0 -1
  67. package/lib/src/relativeTime.js +0 -29
  68. package/lib/src/types.d.ts +0 -103
  69. package/lib/src/types.d.ts.map +0 -1
  70. package/lib/src/types.js +0 -1
  71. package/lib/src/utils.d.ts +0 -12
  72. package/lib/src/utils.d.ts.map +0 -1
  73. package/lib/src/utils.js +0 -162
  74. package/src/create-intl.d.ts +0 -11
  75. package/src/create-intl.d.ts.map +0 -1
  76. package/src/create-intl.js +0 -55
  77. package/src/dateTime.d.ts +0 -38
  78. package/src/dateTime.d.ts.map +0 -1
  79. package/src/dateTime.js +0 -126
  80. package/src/displayName.d.ts +0 -6
  81. package/src/displayName.d.ts.map +0 -1
  82. package/src/displayName.js +0 -26
  83. package/src/error.d.ts +0 -34
  84. package/src/error.d.ts.map +0 -1
  85. package/src/error.js +0 -91
  86. package/src/list.d.ts +0 -11
  87. package/src/list.d.ts.map +0 -1
  88. package/src/list.js +0 -66
  89. package/src/message.d.ts +0 -16
  90. package/src/message.d.ts.map +0 -1
  91. package/src/message.js +0 -107
  92. package/src/number.d.ts +0 -17
  93. package/src/number.d.ts.map +0 -1
  94. package/src/number.js +0 -60
  95. package/src/plural.d.ts +0 -7
  96. package/src/plural.d.ts.map +0 -1
  97. package/src/plural.js +0 -23
  98. package/src/relativeTime.d.ts +0 -7
  99. package/src/relativeTime.d.ts.map +0 -1
  100. package/src/relativeTime.js +0 -33
  101. package/src/types.d.ts +0 -103
  102. package/src/types.d.ts.map +0 -1
  103. package/src/types.js +0 -2
  104. package/src/utils.d.ts +0 -12
  105. package/src/utils.d.ts.map +0 -1
  106. package/src/utils.js +0 -169
package/src/utils.ts ADDED
@@ -0,0 +1,177 @@
1
+ import {
2
+ IntlCache,
3
+ CustomFormats,
4
+ Formatters,
5
+ OnErrorFn,
6
+ OnWarnFn,
7
+ ResolvedIntlConfig,
8
+ } from './types'
9
+ import {IntlMessageFormat} from 'intl-messageformat'
10
+ import memoize, {Cache, strategies} from '@formatjs/fast-memoize'
11
+ import {UnsupportedFormatterError} from './error'
12
+ import {DateTimeFormat, NumberFormatOptions} from '@formatjs/ecma402-abstract'
13
+
14
+ export function filterProps<T extends Record<string, any>, K extends string>(
15
+ props: T,
16
+ allowlist: Array<K>,
17
+ defaults: Partial<T> = {}
18
+ ): Pick<T, K> {
19
+ return allowlist.reduce((filtered, name) => {
20
+ if (name in props) {
21
+ filtered[name] = props[name]
22
+ } else if (name in defaults) {
23
+ filtered[name] = defaults[name]!
24
+ }
25
+
26
+ return filtered
27
+ }, {} as Pick<T, K>)
28
+ }
29
+
30
+ const defaultErrorHandler: OnErrorFn = error => {
31
+ // @ts-ignore just so we don't need to declare dep on @types/node
32
+ if (process.env.NODE_ENV !== 'production') {
33
+ console.error(error)
34
+ }
35
+ }
36
+
37
+ const defaultWarnHandler: OnWarnFn = (warning: string) => {
38
+ // @ts-ignore just so we don't need to declare dep on @types/node
39
+ if (process.env.NODE_ENV !== 'production') {
40
+ console.warn(warning)
41
+ }
42
+ }
43
+
44
+ export const DEFAULT_INTL_CONFIG: Pick<
45
+ ResolvedIntlConfig<any>,
46
+ | 'fallbackOnEmptyString'
47
+ | 'formats'
48
+ | 'messages'
49
+ | 'timeZone'
50
+ | 'defaultLocale'
51
+ | 'defaultFormats'
52
+ | 'onError'
53
+ | 'onWarn'
54
+ > = {
55
+ formats: {},
56
+ messages: {},
57
+ timeZone: undefined,
58
+
59
+ defaultLocale: 'en',
60
+ defaultFormats: {},
61
+
62
+ fallbackOnEmptyString: true,
63
+
64
+ onError: defaultErrorHandler,
65
+ onWarn: defaultWarnHandler,
66
+ }
67
+
68
+ export function createIntlCache(): IntlCache {
69
+ return {
70
+ dateTime: {},
71
+ number: {},
72
+ message: {},
73
+ relativeTime: {},
74
+ pluralRules: {},
75
+ list: {},
76
+ displayNames: {},
77
+ }
78
+ }
79
+
80
+ function createFastMemoizeCache<V>(store: Record<string, V>): Cache<string, V> {
81
+ return {
82
+ create() {
83
+ return {
84
+ get(key) {
85
+ return store[key]
86
+ },
87
+ set(key, value) {
88
+ store[key] = value
89
+ },
90
+ }
91
+ },
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Create intl formatters and populate cache
97
+ * @param cache explicit cache to prevent leaking memory
98
+ */
99
+ export function createFormatters(
100
+ cache: IntlCache = createIntlCache()
101
+ ): Formatters {
102
+ const RelativeTimeFormat = (Intl as any).RelativeTimeFormat
103
+ const ListFormat = (Intl as any).ListFormat
104
+ const DisplayNames = (Intl as any).DisplayNames
105
+ const getDateTimeFormat = memoize(
106
+ (...args) => new Intl.DateTimeFormat(...args) as DateTimeFormat,
107
+ {
108
+ cache: createFastMemoizeCache(cache.dateTime),
109
+ strategy: strategies.variadic,
110
+ }
111
+ )
112
+ const getNumberFormat = memoize((...args) => new Intl.NumberFormat(...args), {
113
+ cache: createFastMemoizeCache(cache.number),
114
+ strategy: strategies.variadic,
115
+ })
116
+ const getPluralRules = memoize((...args) => new Intl.PluralRules(...args), {
117
+ cache: createFastMemoizeCache(cache.pluralRules),
118
+ strategy: strategies.variadic,
119
+ })
120
+ return {
121
+ getDateTimeFormat,
122
+ getNumberFormat,
123
+ getMessageFormat: memoize(
124
+ (message, locales, overrideFormats, opts) =>
125
+ new IntlMessageFormat(message, locales, overrideFormats, {
126
+ formatters: {
127
+ getNumberFormat,
128
+ getDateTimeFormat,
129
+ getPluralRules,
130
+ },
131
+ ...(opts || {}),
132
+ }),
133
+ {
134
+ cache: createFastMemoizeCache(cache.message),
135
+ strategy: strategies.variadic,
136
+ }
137
+ ),
138
+ getRelativeTimeFormat: memoize(
139
+ (...args) => new RelativeTimeFormat(...args),
140
+ {
141
+ cache: createFastMemoizeCache(cache.relativeTime),
142
+ strategy: strategies.variadic,
143
+ }
144
+ ),
145
+ getPluralRules,
146
+ getListFormat: memoize((...args) => new ListFormat(...args), {
147
+ cache: createFastMemoizeCache(cache.list),
148
+ strategy: strategies.variadic,
149
+ }),
150
+ getDisplayNames: memoize((...args) => new DisplayNames(...args), {
151
+ cache: createFastMemoizeCache(cache.displayNames),
152
+ strategy: strategies.variadic,
153
+ }),
154
+ }
155
+ }
156
+
157
+ export function getNamedFormat<T extends keyof CustomFormats>(
158
+ formats: CustomFormats,
159
+ type: T,
160
+ name: string,
161
+ onError: OnErrorFn
162
+ ):
163
+ | NumberFormatOptions
164
+ | Intl.DateTimeFormatOptions
165
+ | Intl.RelativeTimeFormatOptions
166
+ | undefined {
167
+ const formatType = formats && formats[type]
168
+ let format
169
+ if (formatType) {
170
+ format = formatType[name]
171
+ }
172
+ if (format) {
173
+ return format
174
+ }
175
+
176
+ onError(new UnsupportedFormatterError(`No ${type} format named: ${name}`))
177
+ }
@@ -0,0 +1,13 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`format API formatDate() options falls back and warns on invalid Intl.DateTimeFormat options 1`] = `
4
+ Array [
5
+ "FORMAT_ERROR",
6
+ ]
7
+ `;
8
+
9
+ exports[`format API formatDate() options handles missing named formats and warns 1`] = `
10
+ Array [
11
+ "UNSUPPORTED_FORMATTER",
12
+ ]
13
+ `;
@@ -0,0 +1,45 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`format API formatMessage() fallbacks formats \`defaultMessage\` when message has a syntax error 1`] = `
4
+ Array [
5
+ "FORMAT_ERROR",
6
+ ]
7
+ `;
8
+
9
+ exports[`format API formatMessage() fallbacks formats \`defaultMessage\` when message has missing values 1`] = `
10
+ Array [
11
+ "FORMAT_ERROR",
12
+ ]
13
+ `;
14
+
15
+ exports[`format API formatMessage() fallbacks returns \`defaultMessage\` source when formatting errors and missing message 1`] = `
16
+ Array [
17
+ "MISSING_TRANSLATION",
18
+ "FORMAT_ERROR",
19
+ ]
20
+ `;
21
+
22
+ exports[`format API formatMessage() fallbacks returns message \`id\` when message and \`defaultMessage\` are empty 1`] = `
23
+ Array [
24
+ "MISSING_TRANSLATION",
25
+ ]
26
+ `;
27
+
28
+ exports[`format API formatMessage() fallbacks returns message \`id\` when message and \`defaultMessage\` are missing 1`] = `
29
+ Array [
30
+ "MISSING_TRANSLATION",
31
+ ]
32
+ `;
33
+
34
+ exports[`format API formatMessage() fallbacks returns message source when formatting error and missing \`defaultMessage\` 1`] = `
35
+ Array [
36
+ "FORMAT_ERROR",
37
+ ]
38
+ `;
39
+
40
+ exports[`format API formatMessage() fallbacks returns message source when message and \`defaultMessage\` have formatting errors 1`] = `
41
+ Array [
42
+ "FORMAT_ERROR",
43
+ "FORMAT_ERROR",
44
+ ]
45
+ `;
@@ -0,0 +1,13 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`format API formatNumber() options falls back and warns on invalid Intl.NumberFormat options 1`] = `
4
+ Array [
5
+ "FORMAT_ERROR",
6
+ ]
7
+ `;
8
+
9
+ exports[`format API formatNumber() options handles missing named formats and warns 1`] = `
10
+ Array [
11
+ "UNSUPPORTED_FORMATTER",
12
+ ]
13
+ `;
@@ -0,0 +1,19 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`format API formatRelativeTime() falls back and warns when no value is provided 1`] = `
4
+ Array [
5
+ "FORMAT_ERROR",
6
+ ]
7
+ `;
8
+
9
+ exports[`format API formatRelativeTime() options falls back and warns on invalid IntlRelativeFormat options 1`] = `
10
+ Array [
11
+ "FORMAT_ERROR",
12
+ ]
13
+ `;
14
+
15
+ exports[`format API formatRelativeTime() options handles missing named formats and warns 1`] = `
16
+ Array [
17
+ "UNSUPPORTED_FORMATTER",
18
+ ]
19
+ `;
@@ -0,0 +1,13 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`format API formatTime() options falls back and warns on invalid Intl.DateTimeFormat options 1`] = `
4
+ Array [
5
+ "FORMAT_ERROR",
6
+ ]
7
+ `;
8
+
9
+ exports[`format API formatTime() options handles missing named formats and warns 1`] = `
10
+ Array [
11
+ "UNSUPPORTED_FORMATTER",
12
+ ]
13
+ `;
@@ -0,0 +1,60 @@
1
+ import {createIntl} from '../src/create-intl'
2
+
3
+ test('createIntl', function () {
4
+ const intl = createIntl({
5
+ locale: 'en',
6
+ messages: {
7
+ foo: 'bar',
8
+ },
9
+ })
10
+ expect(
11
+ intl.formatMessage({
12
+ id: 'foo',
13
+ })
14
+ ).toBe('bar')
15
+ })
16
+
17
+ test('should warn when defaultRichTextElements is used with messages', function () {
18
+ const onWarn = jest.fn()
19
+ createIntl({
20
+ locale: 'en',
21
+ messages: {
22
+ foo: 'bar',
23
+ },
24
+ defaultRichTextElements: {},
25
+ onWarn,
26
+ })
27
+ expect(onWarn).toHaveBeenCalledWith(
28
+ expect.stringContaining(
29
+ `defaultRichTextElements" was specified but "message" was not pre-compiled.`
30
+ )
31
+ )
32
+ })
33
+
34
+ test('should not warn when defaultRichTextElements is not used', function () {
35
+ const onWarn = jest.fn()
36
+ createIntl({
37
+ locale: 'en',
38
+ messages: {
39
+ foo: 'bar',
40
+ },
41
+ onWarn,
42
+ })
43
+ expect(onWarn).not.toHaveBeenCalled()
44
+ })
45
+
46
+ test('should use the default warn handler when none is passed', function () {
47
+ const warnFn = jest.spyOn(console, 'warn')
48
+ createIntl({
49
+ locale: 'en',
50
+ messages: {
51
+ foo: 'bar',
52
+ },
53
+ defaultRichTextElements: {},
54
+ })
55
+ expect(warnFn).toHaveBeenCalledWith(
56
+ expect.stringContaining(
57
+ `defaultRichTextElements" was specified but "message" was not pre-compiled.`
58
+ )
59
+ )
60
+ })
@@ -0,0 +1,25 @@
1
+ import {MissingTranslationError} from '../src/error'
2
+
3
+ describe('MissingTranslationError', () => {
4
+ it('records the actual default message', () => {
5
+ const e = new MissingTranslationError(
6
+ {defaultMessage: 'some message'},
7
+ 'en'
8
+ )
9
+ expect(e.toString()).toMatch(/default message \(some message\)/)
10
+ })
11
+
12
+ it('records the actual default message for MessageFormatElement[]', () => {
13
+ // this works for all `MessageFormatElement` except for `PoundElement`
14
+ const e = new MissingTranslationError(
15
+ {defaultMessage: [{type: 0, value: 'some message'}]},
16
+ 'en'
17
+ )
18
+ expect(e.toString()).toMatch(/default message \(some message\)/)
19
+ })
20
+
21
+ it('records the actual default message for PoundElement[]', () => {
22
+ const e = new MissingTranslationError({defaultMessage: [{type: 7}]}, 'en')
23
+ expect(e.toString()).toMatch(/default message \(\{"type":7\}\)/)
24
+ })
25
+ })
@@ -0,0 +1,167 @@
1
+ /* eslint-disable @typescript-eslint/camelcase */
2
+ import {formatDate as formatDateFn} from '../src/dateTime'
3
+ import {IntlConfig, IntlFormatters} from '../src/types'
4
+
5
+ describe('format API', () => {
6
+ const {NODE_ENV} = process.env
7
+
8
+ let config: IntlConfig<any>
9
+
10
+ let getDateTimeFormat: any
11
+ beforeEach(() => {
12
+ config = {
13
+ locale: 'en',
14
+
15
+ messages: {},
16
+
17
+ formats: {
18
+ date: {
19
+ 'year-only': {
20
+ year: 'numeric',
21
+ },
22
+ missing: undefined,
23
+ },
24
+ } as any,
25
+
26
+ defaultLocale: 'en',
27
+ defaultFormats: {},
28
+
29
+ onError: jest.fn(),
30
+ }
31
+
32
+ getDateTimeFormat = jest
33
+ .fn()
34
+ .mockImplementation((...args) => new Intl.DateTimeFormat(...args))
35
+ })
36
+
37
+ afterEach(() => {
38
+ process.env.NODE_ENV = NODE_ENV
39
+ })
40
+
41
+ describe('formatDate()', () => {
42
+ let df: Intl.DateTimeFormat
43
+ let formatDate: IntlFormatters['formatDate']
44
+
45
+ beforeEach(() => {
46
+ df = new Intl.DateTimeFormat(config.locale)
47
+
48
+ // @ts-ignore
49
+ formatDate = formatDateFn.bind(null, config, getDateTimeFormat)
50
+ })
51
+
52
+ it('no value should render today', () => {
53
+ // @ts-ignore
54
+ expect(formatDate()).toBe(df.format())
55
+ })
56
+
57
+ it('falls back and warns when a non-finite value is provided', () => {
58
+ expect(formatDate(NaN)).toBe('NaN')
59
+ expect(config.onError as jest.Mock).toHaveBeenCalledTimes(1)
60
+ })
61
+
62
+ it('formats falsy finite values', () => {
63
+ // @ts-ignore
64
+ expect(formatDate(null)).toBe(df.format(null))
65
+ expect(formatDate(0)).toBe(df.format(0))
66
+ })
67
+
68
+ it('formats date instance values', () => {
69
+ expect(formatDate(new Date(0))).toBe(df.format(new Date(0)))
70
+ })
71
+
72
+ it('formats date string values', () => {
73
+ expect(formatDate(new Date(0).toString())).toBe(df.format(0))
74
+ })
75
+
76
+ it('formats date ms timestamp values', () => {
77
+ const timestamp = Date.now()
78
+ expect(formatDate(timestamp)).toBe(df.format(timestamp))
79
+ })
80
+
81
+ it('uses the time zone specified by the provider', () => {
82
+ const timestamp = Date.now()
83
+ config.timeZone = 'Pacific/Wake'
84
+ // @ts-ignore
85
+ formatDate = formatDateFn.bind(null, config, getDateTimeFormat)
86
+ const wakeDf = new Intl.DateTimeFormat(config.locale, {
87
+ timeZone: 'Pacific/Wake',
88
+ })
89
+ expect(formatDate(timestamp)).toBe(wakeDf.format(timestamp))
90
+ config.timeZone = 'Asia/Shanghai'
91
+
92
+ // @ts-ignore
93
+ formatDate = formatDateFn.bind(null, config, getDateTimeFormat)
94
+ const shanghaiDf = new Intl.DateTimeFormat(config.locale, {
95
+ timeZone: 'Asia/Shanghai',
96
+ })
97
+ expect(formatDate(timestamp)).toBe(shanghaiDf.format(timestamp))
98
+ })
99
+
100
+ describe('options', () => {
101
+ it('accepts empty options', () => {
102
+ expect(formatDate(0, {})).toBe(df.format(0))
103
+ })
104
+
105
+ it('accepts valid Intl.DateTimeFormat options', () => {
106
+ expect(() => formatDate(0, {year: 'numeric'})).not.toThrow()
107
+ })
108
+
109
+ it('falls back and warns on invalid Intl.DateTimeFormat options', () => {
110
+ // @ts-expect-error invalid year just for testing
111
+ expect(formatDate(0, {year: 'invalid'})).toBe('0')
112
+ expect(
113
+ (config.onError as jest.Mock).mock.calls.map(c => c[0].code)
114
+ ).toMatchSnapshot()
115
+ })
116
+
117
+ it('uses configured named formats', () => {
118
+ const date = new Date()
119
+ const format = 'year-only'
120
+
121
+ const {locale, formats} = config
122
+ df = new Intl.DateTimeFormat(locale, formats!.date![format])
123
+
124
+ expect(formatDate(date, {format})).toBe(df.format(date))
125
+ })
126
+
127
+ it('uses named formats as defaults', () => {
128
+ const date = new Date()
129
+ const opts: Intl.DateTimeFormatOptions = {month: 'numeric'}
130
+ const format = 'year-only'
131
+
132
+ const {locale, formats} = config
133
+ df = new Intl.DateTimeFormat(locale, {
134
+ ...opts,
135
+ ...formats!.date![format],
136
+ })
137
+
138
+ expect(formatDate(date, {...opts, format})).toBe(df.format(date))
139
+ })
140
+
141
+ it('handles missing named formats and warns', () => {
142
+ const date = new Date()
143
+ const format = 'missing'
144
+
145
+ df = new Intl.DateTimeFormat(config.locale)
146
+
147
+ expect(formatDate(date, {format})).toBe(df.format(date))
148
+ expect(
149
+ (config.onError as jest.Mock).mock.calls.map(c => c[0].code)
150
+ ).toMatchSnapshot()
151
+ })
152
+
153
+ it('uses time zone specified in options over the one passed through by the provider', () => {
154
+ const timestamp = Date.now()
155
+ config.timeZone = 'Pacific/Wake'
156
+ // @ts-ignore
157
+ formatDate = formatDateFn.bind(null, config, getDateTimeFormat)
158
+ const shanghaiDf = new Intl.DateTimeFormat(config.locale, {
159
+ timeZone: 'Asia/Shanghai',
160
+ })
161
+ expect(formatDate(timestamp, {timeZone: 'Asia/Shanghai'})).toBe(
162
+ shanghaiDf.format(timestamp)
163
+ )
164
+ })
165
+ })
166
+ })
167
+ })
@@ -0,0 +1,63 @@
1
+ /* eslint-disable @typescript-eslint/camelcase */
2
+ import '@formatjs/intl-displaynames/polyfill'
3
+ import '@formatjs/intl-displaynames/locale-data/en'
4
+ import '@formatjs/intl-displaynames/locale-data/zh-Hans-SG'
5
+ import {formatDisplayName as formatDisplayNameFn} from '../src/displayName'
6
+ import {IntlConfig, IntlFormatters} from '../src/types'
7
+
8
+ describe('format API', () => {
9
+ const {NODE_ENV} = process.env
10
+
11
+ let config: IntlConfig<any>
12
+ let getDisplayNames: any
13
+ beforeEach(() => {
14
+ config = {
15
+ locale: 'en',
16
+
17
+ messages: {},
18
+
19
+ defaultLocale: 'en',
20
+ defaultFormats: {},
21
+
22
+ onError: jest.fn(),
23
+ }
24
+
25
+ getDisplayNames = jest
26
+ .fn()
27
+ .mockImplementation((...args) => new (Intl as any).DisplayNames(...args))
28
+ })
29
+
30
+ afterEach(() => {
31
+ process.env.NODE_ENV = NODE_ENV
32
+ })
33
+
34
+ describe('formatDisplayNames()', function () {
35
+ let formatDisplayName!: IntlFormatters['formatDisplayName']
36
+
37
+ beforeEach(() => {
38
+ // @ts-ignore
39
+ formatDisplayName = formatDisplayNameFn.bind(
40
+ null,
41
+ config,
42
+ getDisplayNames
43
+ )
44
+ })
45
+
46
+ it('should return locale display name as string', function () {
47
+ expect(formatDisplayName('zh-Hans-SG', {type: 'language'})).toBe(
48
+ 'Simplified Chinese (Singapore)'
49
+ )
50
+ })
51
+
52
+ it('will return undefined if Intl.DisplayName would return undefined', function () {
53
+ const displayName = new (Intl as any).DisplayNames('en', {
54
+ type: 'language',
55
+ fallback: 'none',
56
+ })
57
+ expect(displayName.of('xx-XX')).toBeUndefined()
58
+ expect(
59
+ formatDisplayName('xx-XX', {type: 'language', fallback: 'none'})
60
+ ).toBeUndefined()
61
+ })
62
+ })
63
+ })
@@ -0,0 +1,51 @@
1
+ /* eslint-disable @typescript-eslint/camelcase */
2
+ import {formatList as formatListFn} from '../src/list'
3
+
4
+ import {IntlConfig, IntlFormatters} from '../src/types'
5
+
6
+ describe('format API', () => {
7
+ const {NODE_ENV} = process.env
8
+
9
+ let config: IntlConfig<any>
10
+ let getListFormat: any
11
+ beforeEach(() => {
12
+ config = {
13
+ locale: 'en',
14
+
15
+ messages: {},
16
+
17
+ defaultLocale: 'en',
18
+ defaultFormats: {},
19
+
20
+ onError: jest.fn(),
21
+ }
22
+
23
+ getListFormat = jest
24
+ .fn()
25
+ .mockImplementation((...args) => new Intl.ListFormat(...args))
26
+ })
27
+
28
+ afterEach(() => {
29
+ process.env.NODE_ENV = NODE_ENV
30
+ })
31
+
32
+ describe('formatList()', function () {
33
+ let formatList: IntlFormatters['formatList']
34
+
35
+ beforeEach(() => {
36
+ // @ts-ignore
37
+ formatList = formatListFn.bind(null, config, getListFormat)
38
+ })
39
+
40
+ it('should handle regular element', function () {
41
+ expect(formatList(['me', 'myself', 'I'])).toBe('me, myself, and I')
42
+ })
43
+ it('should handle regular element', function () {
44
+ expect(formatList(['me', {foo: 'myself'}, 'I'])).toEqual([
45
+ 'me, ',
46
+ {foo: 'myself'},
47
+ ', and I',
48
+ ])
49
+ })
50
+ })
51
+ })