@akemona-org/strapi-plugin-i18n 3.7.0

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 (147) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +19 -0
  3. package/admin/src/assets/images/logo.svg +1 -0
  4. package/admin/src/components/CMEditViewCopyLocale/index.js +183 -0
  5. package/admin/src/components/CMEditViewCopyLocale/utils/cleanData.js +36 -0
  6. package/admin/src/components/CMEditViewCopyLocale/utils/generateOptions.js +22 -0
  7. package/admin/src/components/CMEditViewCopyLocale/utils/index.js +2 -0
  8. package/admin/src/components/CMEditViewCopyLocale/utils/removePasswordAndRelationsFieldFromData.js +54 -0
  9. package/admin/src/components/CMEditViewCopyLocale/utils/tests/cleanData.test.js +83 -0
  10. package/admin/src/components/CMEditViewCopyLocale/utils/tests/data.js +219 -0
  11. package/admin/src/components/CMEditViewCopyLocale/utils/tests/generateOptions.test.js +79 -0
  12. package/admin/src/components/CMEditViewCopyLocale/utils/tests/removePasswordAndRelationsFieldFromData.test.js +40 -0
  13. package/admin/src/components/CMEditViewInjectedComponents/index.js +58 -0
  14. package/admin/src/components/CMEditViewLocalePicker/Option.js +66 -0
  15. package/admin/src/components/CMEditViewLocalePicker/Wrapper.js +8 -0
  16. package/admin/src/components/CMEditViewLocalePicker/index.js +160 -0
  17. package/admin/src/components/CMEditViewLocalePicker/utils/addStatusColorToLocale.js +24 -0
  18. package/admin/src/components/CMEditViewLocalePicker/utils/createLocalesOption.js +20 -0
  19. package/admin/src/components/CMEditViewLocalePicker/utils/index.js +2 -0
  20. package/admin/src/components/CheckboxConfirmation/Wrapper.js +12 -0
  21. package/admin/src/components/CheckboxConfirmation/index.js +70 -0
  22. package/admin/src/components/DeleteModalAdditionalInfos/index.js +25 -0
  23. package/admin/src/components/LocaleList/index.js +101 -0
  24. package/admin/src/components/LocaleListCell/LocaleListCell.js +90 -0
  25. package/admin/src/components/LocaleListCell/tests/LocaleListCell.test.js +128 -0
  26. package/admin/src/components/LocalePicker/index.js +126 -0
  27. package/admin/src/components/LocaleRow/index.js +77 -0
  28. package/admin/src/components/ModalCreate/AdvancedForm.js +45 -0
  29. package/admin/src/components/ModalCreate/BaseForm.js +103 -0
  30. package/admin/src/components/ModalCreate/index.js +136 -0
  31. package/admin/src/components/ModalDelete/index.js +49 -0
  32. package/admin/src/components/ModalEdit/AdvancedForm.js +51 -0
  33. package/admin/src/components/ModalEdit/BaseForm.js +91 -0
  34. package/admin/src/components/ModalEdit/index.js +122 -0
  35. package/admin/src/components/SettingsModal.js +66 -0
  36. package/admin/src/components/index.js +2 -0
  37. package/admin/src/containers/Initializer.js +31 -0
  38. package/admin/src/containers/SettingsPage/LocaleSettingsPage.js +69 -0
  39. package/admin/src/containers/SettingsPage/index.js +33 -0
  40. package/admin/src/containers/SettingsPage/tests/SettingsPage.test.js +744 -0
  41. package/admin/src/containers/SettingsPage/tests/__snapshots__/SettingsPage.test.js.snap +241 -0
  42. package/admin/src/hooks/constants.js +6 -0
  43. package/admin/src/hooks/reducers.js +63 -0
  44. package/admin/src/hooks/tests/reducers.test.js +203 -0
  45. package/admin/src/hooks/useAddLocale/index.js +60 -0
  46. package/admin/src/hooks/useContentTypePermissions/index.js +16 -0
  47. package/admin/src/hooks/useDefaultLocales/index.js +27 -0
  48. package/admin/src/hooks/useDeleteLocale/index.js +45 -0
  49. package/admin/src/hooks/useEditLocale/index.js +46 -0
  50. package/admin/src/hooks/useHasI18n/index.js +13 -0
  51. package/admin/src/hooks/useLocales/index.js +35 -0
  52. package/admin/src/index.js +169 -0
  53. package/admin/src/middlewares/addCommonFieldsToInitialDataMiddleware.js +83 -0
  54. package/admin/src/middlewares/addLocaleColumnToListViewMiddleware.js +32 -0
  55. package/admin/src/middlewares/addLocaleToCollectionTypesMiddleware.js +25 -0
  56. package/admin/src/middlewares/addLocaleToSingleTypesMiddleware.js +25 -0
  57. package/admin/src/middlewares/extendCMEditViewLayoutMiddleware.js +159 -0
  58. package/admin/src/middlewares/extendCTBAttributeInitialDataMiddleware.js +58 -0
  59. package/admin/src/middlewares/extendCTBInitialDataMiddleware.js +33 -0
  60. package/admin/src/middlewares/index.js +21 -0
  61. package/admin/src/middlewares/localePermissionMiddleware.js +39 -0
  62. package/admin/src/middlewares/tests/addCommonFieldsToInitialDataMiddleware.test.js +97 -0
  63. package/admin/src/middlewares/tests/addLocaleColumnToListViewMiddleware.test.js +68 -0
  64. package/admin/src/middlewares/tests/addLocaleToCollectionTypesMiddleware.test.js +200 -0
  65. package/admin/src/middlewares/tests/addLocaleToSingleTypesMiddleware.test.js +193 -0
  66. package/admin/src/middlewares/tests/extendCMEditViewLayoutMiddleware.test.js +556 -0
  67. package/admin/src/middlewares/tests/extendCTBAttrributeInitialDataMiddleware.test.js +124 -0
  68. package/admin/src/middlewares/tests/extendCTBInitialDataMiddleware.test.js +92 -0
  69. package/admin/src/middlewares/tests/localePermissionMiddleware.test.js +150 -0
  70. package/admin/src/middlewares/utils/addLocaleToLinksSearch.js +56 -0
  71. package/admin/src/middlewares/utils/tests/addLocaleToLinksSearch.test.js +137 -0
  72. package/admin/src/permissions.js +9 -0
  73. package/admin/src/pluginId.js +5 -0
  74. package/admin/src/schemas.js +7 -0
  75. package/admin/src/selectors/selectCollectionTypesRelatedPermissions.js +4 -0
  76. package/admin/src/selectors/selectI18nLocales.js +3 -0
  77. package/admin/src/translations/en.json +60 -0
  78. package/admin/src/translations/fr.json +9 -0
  79. package/admin/src/translations/index.js +11 -0
  80. package/admin/src/translations/zh-Hans.json +60 -0
  81. package/admin/src/utils/getDefaultLocale.js +60 -0
  82. package/admin/src/utils/getInitialLocale.js +14 -0
  83. package/admin/src/utils/getLocaleFromQuery.js +7 -0
  84. package/admin/src/utils/getTrad.js +5 -0
  85. package/admin/src/utils/index.js +2 -0
  86. package/admin/src/utils/localizedFields.js +23 -0
  87. package/admin/src/utils/mutateCTBContentTypeSchema.js +66 -0
  88. package/admin/src/utils/tests/getDefaultLocale.test.js +337 -0
  89. package/admin/src/utils/tests/getInitialLocale.test.js +106 -0
  90. package/admin/src/utils/tests/mutateCTBContentTypeSchema.test.js +205 -0
  91. package/config/functions/bootstrap.js +57 -0
  92. package/config/functions/migrations/__tests__/content-type.test.js +255 -0
  93. package/config/functions/migrations/__tests__/field.test.js +150 -0
  94. package/config/functions/migrations/content-type/disable/index.js +34 -0
  95. package/config/functions/migrations/content-type/disable/migrate-for-bookshelf.js +58 -0
  96. package/config/functions/migrations/content-type/disable/migrate-for-mongoose.js +39 -0
  97. package/config/functions/migrations/content-type/enable/index.js +40 -0
  98. package/config/functions/migrations/content-type/utils/index.js +27 -0
  99. package/config/functions/migrations/field/__tests__/utils.test.js +53 -0
  100. package/config/functions/migrations/field/index.js +37 -0
  101. package/config/functions/migrations/field/migrate-for-bookshelf.js +72 -0
  102. package/config/functions/migrations/field/migrate-for-mongoose.js +24 -0
  103. package/config/functions/migrations/field/migrate.js +55 -0
  104. package/config/functions/migrations/field/utils.js +58 -0
  105. package/config/functions/register.js +46 -0
  106. package/config/policies/validateLocaleCreation.js +68 -0
  107. package/config/routes.json +64 -0
  108. package/constants/__tests__/index.test.js +27 -0
  109. package/constants/index.js +36 -0
  110. package/constants/iso-locales.json +2002 -0
  111. package/controllers/__tests__/content-types.test.js +113 -0
  112. package/controllers/__tests__/iso-locales.test.js +26 -0
  113. package/controllers/__tests__/locales.test.js +308 -0
  114. package/controllers/content-types.js +64 -0
  115. package/controllers/iso-locales.js +11 -0
  116. package/controllers/locales.js +104 -0
  117. package/domain/locale.js +10 -0
  118. package/middlewares/i18n/defaults.json +5 -0
  119. package/middlewares/i18n/index.js +28 -0
  120. package/models/Locale.settings.json +31 -0
  121. package/oas.yml +195 -0
  122. package/package.json +31 -0
  123. package/services/__tests__/__snapshots__/iso-locales.test.js.snap +2006 -0
  124. package/services/__tests__/content-types.test.js +545 -0
  125. package/services/__tests__/core-api.test.js +106 -0
  126. package/services/__tests__/entity-service-decorator.test.js +280 -0
  127. package/services/__tests__/iso-locales.test.js +11 -0
  128. package/services/__tests__/locales.test.js +237 -0
  129. package/services/__tests__/localizations.test.js +187 -0
  130. package/services/__tests__/metrics.test.js +90 -0
  131. package/services/content-types.js +200 -0
  132. package/services/core-api.js +296 -0
  133. package/services/entity-service-decorator.js +155 -0
  134. package/services/iso-locales.js +9 -0
  135. package/services/locales.js +97 -0
  136. package/services/localizations.js +65 -0
  137. package/services/metrics.js +24 -0
  138. package/services/permissions/actions.js +124 -0
  139. package/services/permissions/engine.js +63 -0
  140. package/services/permissions/sections-builder.js +48 -0
  141. package/services/permissions.js +11 -0
  142. package/tests/content-manager/list-relation.test.e2e.js +122 -0
  143. package/tests/graphql.test.e2e.js +120 -0
  144. package/tests/locales.test.e2e.js +414 -0
  145. package/utils/index.js +20 -0
  146. package/validation/content-types.js +30 -0
  147. package/validation/locales.js +39 -0
@@ -0,0 +1,744 @@
1
+ /* eslint-disable react/prop-types */
2
+
3
+ import React from 'react';
4
+ import { createStore } from 'redux';
5
+ import { combineReducers } from 'redux-immutable';
6
+ import { fromJS } from 'immutable';
7
+
8
+ import { Provider } from 'react-redux';
9
+ import { request, useUserPermissions } from 'strapi-helper-plugin';
10
+ import { fireEvent, render, screen, within, waitFor } from '@testing-library/react';
11
+ import { ThemeProvider } from 'styled-components';
12
+ import { QueryClient, QueryClientProvider } from 'react-query';
13
+ import LocaleSettingsPage from '..';
14
+ import themes from '../../../../../../strapi-admin/admin/src/themes';
15
+ import i18nReducers, { initialState } from '../../../hooks/reducers';
16
+
17
+ const TestWrapper = ({ children }) => {
18
+ const queryClient = new QueryClient();
19
+
20
+ const initialStoreState = fromJS(initialState);
21
+ const rootReducer = combineReducers(i18nReducers);
22
+ const store = createStore(rootReducer, initialStoreState);
23
+
24
+ return (
25
+ <Provider store={store}>
26
+ <QueryClientProvider client={queryClient}>
27
+ <ThemeProvider theme={themes}>{children}</ThemeProvider>
28
+ </QueryClientProvider>
29
+ </Provider>
30
+ );
31
+ };
32
+
33
+ // TODO: we should not be forced to mock this module
34
+ // but it bugs somehow when run with jest
35
+ jest.mock('strapi-helper-plugin', () => ({
36
+ EmptyState: ({ title, description }) => (
37
+ <div data-testid="empty-list">
38
+ <p>{title}</p>
39
+ <p>{description}</p>
40
+ </div>
41
+ ),
42
+ BaselineAlignment: () => <div />,
43
+ ModalConfirm: ({ onConfirm, isOpen }) =>
44
+ isOpen ? (
45
+ <div role="dialog">
46
+ <button onClick={onConfirm} type="button">
47
+ Confirm
48
+ </button>
49
+ </div>
50
+ ) : null,
51
+
52
+ Modal: ({ isOpen, children }) => isOpen && <div role="dialog">{children}</div>,
53
+ ModalHeader: ({ children }) => <div>{children}</div>,
54
+ ModalSection: ({ children }) => <div>{children}</div>,
55
+ ModalFooter: ({ children }) => <div>{children}</div>,
56
+ HeaderModal: ({ children }) => <div>{children}</div>,
57
+ HeaderModalTitle: ({ children }) => <div>{children}</div>,
58
+ ModalForm: ({ children }) => <div>{children}</div>,
59
+ ListButton: () => <div />,
60
+ Tabs: ({ children }) => <div>{children}</div>,
61
+ TabsNav: ({ children }) => <div>{children}</div>,
62
+ Tab: ({ children }) => <div>{children}</div>,
63
+ TabsPanel: ({ children }) => <div>{children}</div>,
64
+ TabPanel: ({ children }) => <div>{children}</div>,
65
+ useUserPermissions: jest.fn(),
66
+ request: jest.fn(),
67
+ selectStyles: () => ({ control: () => ({}), indicatorsContainer: () => ({}) }),
68
+ useGlobalContext: () => ({ updateMenu: jest.fn() }),
69
+ useUser: () => ({ fetchUserPermissions: jest.fn() }),
70
+ }));
71
+
72
+ jest.mock('../../../utils', () => ({
73
+ getTrad: x => x,
74
+ }));
75
+
76
+ jest.mock('react-intl', () => ({
77
+ useIntl: () => ({
78
+ formatMessage: ({ id }) => id,
79
+ }),
80
+ }));
81
+
82
+ describe('i18n settings page', () => {
83
+ beforeEach(() => {
84
+ request.mockImplementation(() =>
85
+ Promise.resolve([
86
+ {
87
+ id: 1,
88
+ name: 'French',
89
+ code: 'fr-FR',
90
+ isDefault: false,
91
+ },
92
+ {
93
+ id: 2,
94
+ name: 'English',
95
+ code: 'en-US',
96
+ isDefault: true,
97
+ },
98
+ ])
99
+ );
100
+
101
+ useUserPermissions.mockImplementation(() => ({
102
+ isLoading: false,
103
+ allowedActions: { canRead: true, canUpdate: true, canCreate: true, canDelete: true },
104
+ }));
105
+
106
+ strapi.notification.toggle = jest.fn();
107
+ });
108
+
109
+ afterEach(() => {
110
+ jest.resetAllMocks();
111
+ });
112
+
113
+ describe('initial state', () => {
114
+ it('shows default EN locale with edit button but no delete button', async () => {
115
+ render(
116
+ <TestWrapper>
117
+ <LocaleSettingsPage />
118
+ </TestWrapper>
119
+ );
120
+
121
+ const row = await waitFor(() => screen.getByText('English').closest('tr'));
122
+ const rowUtils = within(row);
123
+
124
+ expect(rowUtils.queryByLabelText('Settings.list.actions.delete')).toBeFalsy();
125
+ expect(rowUtils.getByLabelText('Settings.list.actions.edit')).toBeVisible();
126
+ expect(rowUtils.getByText('Settings.locales.row.default-locale')).toBeVisible();
127
+ expect(rowUtils.getByText('en-US')).toBeVisible();
128
+ });
129
+
130
+ it('shows FR locale with edit button and delete button', async () => {
131
+ render(
132
+ <TestWrapper>
133
+ <LocaleSettingsPage />
134
+ </TestWrapper>
135
+ );
136
+
137
+ const row = await waitFor(() => screen.getByText('French').closest('tr'));
138
+ const rowUtils = within(row);
139
+
140
+ expect(rowUtils.getByLabelText('Settings.list.actions.delete')).toBeVisible();
141
+ expect(rowUtils.getByLabelText('Settings.list.actions.edit')).toBeVisible();
142
+ expect(rowUtils.getByText('fr-FR')).toBeVisible();
143
+ });
144
+ });
145
+
146
+ describe('delete', () => {
147
+ it('removes the locale when clicking the confirmation button', async () => {
148
+ request.mockImplementation((_, opts) =>
149
+ opts.method === 'DELETE'
150
+ ? Promise.resolve({ id: 1 })
151
+ : Promise.resolve([
152
+ {
153
+ id: 1,
154
+ name: 'French',
155
+ code: 'fr-FR',
156
+ isDefault: false,
157
+ },
158
+ {
159
+ id: 2,
160
+ name: 'English',
161
+ code: 'en-US',
162
+ isDefault: true,
163
+ },
164
+ ])
165
+ );
166
+
167
+ render(
168
+ <TestWrapper>
169
+ <LocaleSettingsPage />
170
+ </TestWrapper>
171
+ );
172
+
173
+ const row = await waitFor(() => screen.getByText('French').closest('tr'));
174
+ const rowUtils = within(row);
175
+
176
+ fireEvent.click(rowUtils.getByLabelText('Settings.list.actions.delete'));
177
+ fireEvent.click(screen.getByText('Confirm'));
178
+
179
+ await waitFor(() =>
180
+ expect(strapi.notification.toggle).toBeCalledWith({
181
+ type: 'success',
182
+ message: { id: 'Settings.locales.modal.delete.success' },
183
+ })
184
+ );
185
+ });
186
+
187
+ it('shows an error when something went wrong when deleting', async () => {
188
+ request.mockImplementation((_, opts) =>
189
+ opts.method === 'DELETE'
190
+ ? Promise.reject(new Error('An error'))
191
+ : Promise.resolve([
192
+ {
193
+ id: 1,
194
+ name: 'French',
195
+ code: 'fr-FR',
196
+ isDefault: false,
197
+ },
198
+ {
199
+ id: 2,
200
+ name: 'English',
201
+ code: 'en-US',
202
+ isDefault: true,
203
+ },
204
+ ])
205
+ );
206
+
207
+ render(
208
+ <TestWrapper>
209
+ <LocaleSettingsPage />
210
+ </TestWrapper>
211
+ );
212
+
213
+ const row = await waitFor(() => screen.getByText('French').closest('tr'));
214
+ const rowUtils = within(row);
215
+
216
+ fireEvent.click(rowUtils.getByLabelText('Settings.list.actions.delete'));
217
+ fireEvent.click(screen.getByText('Confirm'));
218
+
219
+ await waitFor(() =>
220
+ expect(strapi.notification.toggle).toBeCalledWith({
221
+ type: 'warning',
222
+ message: { id: 'notification.error' },
223
+ })
224
+ );
225
+ });
226
+ });
227
+
228
+ describe('edit', () => {
229
+ it('shows the default edit modal layout with disabled value', async () => {
230
+ render(
231
+ <TestWrapper>
232
+ <LocaleSettingsPage />
233
+ </TestWrapper>
234
+ );
235
+
236
+ const row = await waitFor(() => screen.getByText('English').closest('tr'));
237
+ const rowUtils = within(row);
238
+
239
+ fireEvent.click(rowUtils.getByLabelText('Settings.list.actions.edit'));
240
+
241
+ expect(screen.getByText(`Settings.locales.modal.edit.confirmation`)).toBeVisible();
242
+ expect(screen.getByLabelText(`Settings.locales.modal.edit.locales.label`)).toBeDisabled();
243
+ });
244
+
245
+ it('shows a warning and disabled the confirmation button when display name length is over 50', async () => {
246
+ render(
247
+ <TestWrapper>
248
+ <LocaleSettingsPage />
249
+ </TestWrapper>
250
+ );
251
+
252
+ const row = await waitFor(() => screen.getByText('English').closest('tr'));
253
+ const rowUtils = within(row);
254
+
255
+ fireEvent.click(rowUtils.getByLabelText('Settings.list.actions.edit'));
256
+ fireEvent.change(screen.getByLabelText('Settings.locales.modal.locales.displayName'), {
257
+ target: {
258
+ value:
259
+ 'a very very very very long string that has more than fifty characters in order to show a warning',
260
+ },
261
+ });
262
+ fireEvent.blur(screen.getByLabelText('Settings.locales.modal.locales.displayName'));
263
+
264
+ await waitFor(() =>
265
+ expect(screen.getByText('Settings.locales.modal.edit.confirmation')).toBeDisabled()
266
+ );
267
+ expect(screen.getByText(`Settings.locales.modal.locales.displayName.error`)).toBeVisible();
268
+ });
269
+
270
+ it('closes the edit modal when clicking on cancel', async () => {
271
+ render(
272
+ <TestWrapper>
273
+ <LocaleSettingsPage />
274
+ </TestWrapper>
275
+ );
276
+
277
+ const row = await waitFor(() => screen.getByText('English').closest('tr'));
278
+ const rowUtils = within(row);
279
+
280
+ fireEvent.click(rowUtils.getByLabelText('Settings.list.actions.edit'));
281
+ fireEvent.click(screen.getByText('app.components.Button.cancel'));
282
+
283
+ expect(screen.queryByText(`Settings.list.actions.edit`)).toBeFalsy();
284
+ });
285
+
286
+ it('shows an error when something went wrong when editing', async () => {
287
+ const requestGetResponse = [
288
+ {
289
+ id: 1,
290
+ name: 'French',
291
+ code: 'fr-FR',
292
+ isDefault: false,
293
+ },
294
+ {
295
+ id: 2,
296
+ name: 'English',
297
+ code: 'en-US',
298
+ isDefault: true,
299
+ },
300
+ ];
301
+
302
+ request.mockImplementation((_, opts) =>
303
+ opts.method === 'PUT'
304
+ ? Promise.reject(new Error('Something wrong occured'))
305
+ : Promise.resolve(requestGetResponse)
306
+ );
307
+
308
+ render(
309
+ <TestWrapper>
310
+ <LocaleSettingsPage />
311
+ </TestWrapper>
312
+ );
313
+
314
+ const row = await waitFor(() => screen.getByText('English').closest('tr'));
315
+ const rowUtils = within(row);
316
+
317
+ fireEvent.click(rowUtils.getByLabelText('Settings.list.actions.edit'));
318
+ fireEvent.click(screen.getByText('Settings.locales.modal.edit.confirmation'));
319
+
320
+ await waitFor(() =>
321
+ expect(strapi.notification.toggle).toBeCalledWith({
322
+ type: 'warning',
323
+ message: { id: 'notification.error' },
324
+ })
325
+ );
326
+ });
327
+
328
+ it('shows a success message when editing succeeds', async () => {
329
+ const requestGetResponse = [
330
+ {
331
+ id: 1,
332
+ name: 'French',
333
+ code: 'fr-FR',
334
+ isDefault: false,
335
+ },
336
+ {
337
+ id: 2,
338
+ name: 'English',
339
+ code: 'en-US',
340
+ isDefault: true,
341
+ },
342
+ ];
343
+
344
+ request.mockImplementation((_, opts) =>
345
+ opts.method === 'PUT'
346
+ ? Promise.resolve({
347
+ id: 2,
348
+ name: 'Frenchie',
349
+ code: 'fr-FR',
350
+ isDefault: false,
351
+ })
352
+ : Promise.resolve(requestGetResponse)
353
+ );
354
+
355
+ render(
356
+ <TestWrapper>
357
+ <LocaleSettingsPage />
358
+ </TestWrapper>
359
+ );
360
+
361
+ const row = await waitFor(() => screen.getByText('English').closest('tr'));
362
+ const rowUtils = within(row);
363
+
364
+ fireEvent.click(rowUtils.getByLabelText('Settings.list.actions.edit'));
365
+ fireEvent.click(screen.getByText('Settings.locales.modal.edit.confirmation'));
366
+
367
+ await waitFor(() =>
368
+ expect(strapi.notification.toggle).toBeCalledWith({
369
+ type: 'success',
370
+ message: { id: 'Settings.locales.modal.edit.success' },
371
+ })
372
+ );
373
+
374
+ expect(request).toBeCalledWith('/i18n/locales/2', {
375
+ method: 'PUT',
376
+ body: { name: 'English', isDefault: true },
377
+ });
378
+ });
379
+
380
+ it('shows edits the locale with code as displayName when displayName is empty', async () => {
381
+ const requestGetResponse = [
382
+ {
383
+ id: 1,
384
+ name: 'French',
385
+ code: 'fr-FR',
386
+ isDefault: false,
387
+ },
388
+ {
389
+ id: 2,
390
+ name: 'English',
391
+ code: 'en-US',
392
+ isDefault: true,
393
+ },
394
+ ];
395
+
396
+ request.mockImplementation((_, opts) =>
397
+ opts.method === 'PUT'
398
+ ? Promise.resolve({
399
+ id: 2,
400
+ name: 'Frenchie',
401
+ code: 'fr-FR',
402
+ isDefault: false,
403
+ })
404
+ : Promise.resolve(requestGetResponse)
405
+ );
406
+
407
+ render(
408
+ <TestWrapper>
409
+ <LocaleSettingsPage />
410
+ </TestWrapper>
411
+ );
412
+
413
+ const row = await waitFor(() => screen.getByText('English').closest('tr'));
414
+ const rowUtils = within(row);
415
+
416
+ fireEvent.click(rowUtils.getByLabelText('Settings.list.actions.edit'));
417
+
418
+ fireEvent.change(screen.getByLabelText('Settings.locales.modal.locales.displayName'), {
419
+ target: {
420
+ value: '',
421
+ },
422
+ });
423
+
424
+ fireEvent.click(screen.getByText('Settings.locales.modal.edit.confirmation'));
425
+
426
+ await waitFor(() =>
427
+ expect(strapi.notification.toggle).toBeCalledWith({
428
+ type: 'success',
429
+ message: { id: 'Settings.locales.modal.edit.success' },
430
+ })
431
+ );
432
+
433
+ expect(request).toBeCalledWith('/i18n/locales/2', {
434
+ method: 'PUT',
435
+ body: { name: 'en-US', isDefault: true },
436
+ });
437
+ });
438
+ });
439
+
440
+ describe('retrieve', () => {
441
+ it('shows an error when something went wrong when fetching', async () => {
442
+ request.mockImplementation(() =>
443
+ Promise.reject(new Error('Something went wrong on the server'))
444
+ );
445
+
446
+ render(
447
+ <TestWrapper>
448
+ <LocaleSettingsPage />
449
+ </TestWrapper>
450
+ );
451
+
452
+ await waitFor(() =>
453
+ expect(strapi.notification.toggle).toBeCalledWith({
454
+ type: 'warning',
455
+ message: { id: 'notification.error' },
456
+ })
457
+ );
458
+ });
459
+
460
+ it('shows an empty state when the array of locale is empty', async () => {
461
+ request.mockImplementation(() => Promise.resolve([]));
462
+
463
+ render(
464
+ <TestWrapper>
465
+ <LocaleSettingsPage />
466
+ </TestWrapper>
467
+ );
468
+
469
+ await waitFor(() => expect(screen.getByTestId('empty-list')).toBeVisible());
470
+ });
471
+ });
472
+
473
+ describe('permissions', () => {
474
+ it('shows a loading information when resolving the permissions', () => {
475
+ useUserPermissions.mockImplementation(() => ({
476
+ isLoading: true,
477
+ allowedActions: { canRead: false, canUpdate: true, canCreate: true, canDelete: true },
478
+ }));
479
+
480
+ render(
481
+ <TestWrapper>
482
+ <LocaleSettingsPage />
483
+ </TestWrapper>
484
+ );
485
+
486
+ expect(screen.getByText(`Settings.permissions.loading`));
487
+ });
488
+
489
+ it("shows nothing when the user doesn't have read permission", () => {
490
+ const canRead = false;
491
+
492
+ useUserPermissions.mockImplementation(() => ({
493
+ isLoading: false,
494
+ allowedActions: { canRead, canUpdate: true, canCreate: true, canDelete: true },
495
+ }));
496
+
497
+ const { container } = render(
498
+ <TestWrapper>
499
+ <LocaleSettingsPage />
500
+ </TestWrapper>
501
+ );
502
+
503
+ expect(container).toMatchSnapshot();
504
+ });
505
+
506
+ it('hides "Add locale" buttons when the user is not allowed to create a locale', async () => {
507
+ const canCreate = false;
508
+
509
+ request.mockImplementation(() => Promise.resolve([]));
510
+ useUserPermissions.mockImplementation(() => ({
511
+ isLoading: false,
512
+ allowedActions: { canRead: true, canUpdate: true, canCreate, canDelete: true },
513
+ }));
514
+
515
+ render(
516
+ <TestWrapper>
517
+ <LocaleSettingsPage />
518
+ </TestWrapper>
519
+ );
520
+
521
+ await waitFor(() =>
522
+ expect(screen.queryAllByText(`Settings.list.actions.add`).length).toBe(0)
523
+ );
524
+ });
525
+
526
+ it('hides the "Edit locale" button (pencil) when the user is not allowed to update a locale', async () => {
527
+ const canUpdate = false;
528
+
529
+ useUserPermissions.mockImplementation(() => ({
530
+ isLoading: false,
531
+ allowedActions: { canRead: true, canUpdate, canCreate: true, canDelete: true },
532
+ }));
533
+
534
+ render(
535
+ <TestWrapper>
536
+ <LocaleSettingsPage />
537
+ </TestWrapper>
538
+ );
539
+
540
+ await waitFor(() => expect(screen.getByText('English')).toBeVisible());
541
+ expect(screen.queryAllByLabelText(`Settings.list.actions.edit`).length).toBe(0);
542
+ });
543
+
544
+ it('hides the "Delete locale" button (garbage) when the user is not allowed to delete a locale', async () => {
545
+ const canDelete = false;
546
+
547
+ useUserPermissions.mockImplementation(() => ({
548
+ isLoading: false,
549
+ allowedActions: { canRead: true, canUpdate: false, canCreate: true, canDelete },
550
+ }));
551
+
552
+ render(
553
+ <TestWrapper>
554
+ <LocaleSettingsPage />
555
+ </TestWrapper>
556
+ );
557
+
558
+ await waitFor(() => expect(screen.getByText('English')).toBeVisible());
559
+ expect(screen.queryAllByLabelText(`Settings.list.actions.delete`).length).toBe(0);
560
+ });
561
+ });
562
+
563
+ describe('create', () => {
564
+ beforeEach(() => {
565
+ request.mockImplementation(url =>
566
+ url.includes('/i18n/locales')
567
+ ? Promise.resolve([])
568
+ : Promise.resolve([
569
+ { code: 'fr-FR', name: 'Francais' },
570
+ { code: 'en-EN', name: 'English' },
571
+ ])
572
+ );
573
+ });
574
+
575
+ it('shows the default create modal layout', async () => {
576
+ render(
577
+ <TestWrapper>
578
+ <LocaleSettingsPage />
579
+ </TestWrapper>
580
+ );
581
+
582
+ fireEvent.click(screen.getByText('Settings.list.actions.add'));
583
+
584
+ expect(
585
+ screen.getByText(`Settings.locales.modal.create.defaultLocales.loading`)
586
+ ).toBeVisible();
587
+
588
+ await waitFor(() =>
589
+ expect(screen.getByText(`Settings.locales.modal.create.confirmation`)).toBeVisible()
590
+ );
591
+
592
+ expect(screen.getByText(`fr-FR`)).toBeVisible();
593
+ expect(screen.getByLabelText('Settings.locales.modal.locales.displayName')).toHaveValue(
594
+ 'Francais'
595
+ );
596
+ });
597
+
598
+ it('closes the create modal when clicking on cancel', async () => {
599
+ render(
600
+ <TestWrapper>
601
+ <LocaleSettingsPage />
602
+ </TestWrapper>
603
+ );
604
+
605
+ fireEvent.click(screen.getByText('Settings.list.actions.add'));
606
+
607
+ await waitFor(() =>
608
+ expect(screen.getByText(`Settings.locales.modal.create.confirmation`)).toBeVisible()
609
+ );
610
+
611
+ fireEvent.click(screen.getByText('app.components.Button.cancel'));
612
+
613
+ expect(screen.queryByText(`Settings.locales.modal.create.confirmation`)).toBeFalsy();
614
+ });
615
+
616
+ it('shows a warning and disabled the confirmation button when display name length is over 50', async () => {
617
+ render(
618
+ <TestWrapper>
619
+ <LocaleSettingsPage />
620
+ </TestWrapper>
621
+ );
622
+
623
+ fireEvent.click(screen.getByText('Settings.list.actions.add'));
624
+
625
+ await waitFor(() =>
626
+ expect(screen.getByText(`Settings.locales.modal.create.confirmation`)).toBeVisible()
627
+ );
628
+
629
+ fireEvent.change(screen.getByLabelText('Settings.locales.modal.locales.displayName'), {
630
+ target: {
631
+ value:
632
+ 'a very very very very long string that has more than fifty characters in order to show a warning',
633
+ },
634
+ });
635
+
636
+ fireEvent.blur(screen.getByLabelText('Settings.locales.modal.locales.displayName'));
637
+
638
+ await waitFor(() =>
639
+ expect(screen.getByText(`Settings.locales.modal.locales.displayName.error`)).toBeVisible()
640
+ );
641
+ });
642
+
643
+ it('sync the select and the text input', async () => {
644
+ render(
645
+ <TestWrapper>
646
+ <LocaleSettingsPage />
647
+ </TestWrapper>
648
+ );
649
+
650
+ fireEvent.click(screen.getByText('Settings.list.actions.add'));
651
+
652
+ await waitFor(() =>
653
+ expect(screen.getByText(`Settings.locales.modal.create.confirmation`)).toBeVisible()
654
+ );
655
+
656
+ // Put some data in the input in order to make sure it resets well when changing locale
657
+ fireEvent.change(screen.getByLabelText('Settings.locales.modal.locales.displayName'), {
658
+ target: {
659
+ value:
660
+ 'a very very very very long string that has more than fifty characters in order to show a warning',
661
+ },
662
+ });
663
+
664
+ const DOWN_ARROW = { keyCode: 40 };
665
+ fireEvent.keyDown(screen.getByLabelText('Settings.locales.modal.locales.label'), DOWN_ARROW);
666
+
667
+ fireEvent.click(screen.getByText('en-EN'));
668
+
669
+ expect(screen.getByLabelText('Settings.locales.modal.locales.displayName')).toHaveValue(
670
+ 'English'
671
+ );
672
+ });
673
+
674
+ it('shows an error when something went wrong when adding a locale', async () => {
675
+ request.mockImplementation((url, opts) => {
676
+ if (opts.method === 'POST') {
677
+ return Promise.reject(new Error('Something went wrong when adding a locale'));
678
+ }
679
+ if (url.includes('/i18n/locales')) return Promise.resolve([]);
680
+
681
+ return Promise.resolve([
682
+ { code: 'fr-FR', name: 'Francais' },
683
+ { code: 'en-EN', name: 'English' },
684
+ ]);
685
+ });
686
+
687
+ render(
688
+ <TestWrapper>
689
+ <LocaleSettingsPage />
690
+ </TestWrapper>
691
+ );
692
+
693
+ fireEvent.click(screen.getByText('Settings.list.actions.add'));
694
+
695
+ const confirmationButton = await waitFor(() =>
696
+ screen.getByText(`Settings.locales.modal.create.confirmation`)
697
+ );
698
+
699
+ fireEvent.click(confirmationButton);
700
+
701
+ await waitFor(() =>
702
+ expect(strapi.notification.toggle).toBeCalledWith({
703
+ type: 'warning',
704
+ message: { id: 'notification.error' },
705
+ })
706
+ );
707
+ });
708
+
709
+ it('shows an success toast when adding a locale is successful', async () => {
710
+ request.mockImplementation((url, opts) => {
711
+ if (opts.method === 'POST') {
712
+ return Promise.resolve({ id: 3, code: 'en-CA', name: 'Canadien' });
713
+ }
714
+ if (url.includes('/i18n/locales')) return Promise.resolve([]);
715
+
716
+ return Promise.resolve([
717
+ { code: 'fr-FR', name: 'Francais' },
718
+ { code: 'en-EN', name: 'English' },
719
+ ]);
720
+ });
721
+
722
+ render(
723
+ <TestWrapper>
724
+ <LocaleSettingsPage />
725
+ </TestWrapper>
726
+ );
727
+
728
+ fireEvent.click(screen.getByText('Settings.list.actions.add'));
729
+
730
+ const confirmationButton = await waitFor(() =>
731
+ screen.getByText(`Settings.locales.modal.create.confirmation`)
732
+ );
733
+
734
+ fireEvent.click(confirmationButton);
735
+
736
+ await waitFor(() =>
737
+ expect(strapi.notification.toggle).toBeCalledWith({
738
+ type: 'success',
739
+ message: { id: 'Settings.locales.modal.create.success' },
740
+ })
741
+ );
742
+ });
743
+ });
744
+ });