@centreon/ui 26.12.0 → 26.12.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 (57) hide show
  1. package/package.json +6 -3
  2. package/public/mockServiceWorker.js +31 -8
  3. package/src/Dashboard/Dashboard.browser.test.tsx +107 -0
  4. package/src/Form/Form.browser.test.tsx +445 -0
  5. package/src/Form/Inputs/ExclusiveCheckboxGroup.browser.test.tsx +304 -0
  6. package/src/Form/Inputs/SegmentedButtons.browser.test.tsx +113 -0
  7. package/src/InputField/Number/Number.browser.test.tsx +109 -0
  8. package/src/InputField/Select/Autocomplete/Connected/Multi/MultiConnectedAutocompleteField.browser.test.tsx +250 -0
  9. package/src/Listing/Listing.browser.test.tsx +476 -0
  10. package/src/Module/Module.browser.test.tsx +145 -0
  11. package/src/Pagination/Pagination.browser.test.tsx +184 -0
  12. package/src/Panel/Section/index.test.tsx +1 -1
  13. package/src/RichTextEditor/RichTextEditor.browser.test.tsx +567 -0
  14. package/src/RichTextEditor/plugins/FloatingLinkEditorPlugin.tsx +3 -1
  15. package/src/TimePeriods/ResolutionTimePeriod.browser.test.tsx +143 -0
  16. package/src/TimePeriods/TimePeriods.browser.test.tsx +468 -0
  17. package/src/Typography/FluidTypography/FluidTypography.browser.test.tsx +54 -0
  18. package/src/api/useMutationQuery/useMutationQuery.browser.test.tsx +154 -0
  19. package/src/components/CollapsibleItem/CollapsibleItem.browser.test.tsx +120 -0
  20. package/src/components/CopyCommand/CopyCommand.browser.test.tsx +287 -0
  21. package/src/components/CrudPage/CrudPage.browser.test.tsx +659 -0
  22. package/src/components/DataTable/DataTable.browser.test.tsx +257 -0
  23. package/src/components/Form/AccessRights/AccessRights.browser.test.tsx +527 -0
  24. package/src/components/ItemComposition/ItemComposition.browser.test.tsx +172 -0
  25. package/src/components/Layout/PageLayout.browser.test.tsx +114 -0
  26. package/src/components/Modal/ConfirmationModal/ConfirmationModal.browser.test.tsx +237 -0
  27. package/src/components/SortableEntriesList/SortableEntriesList.browser.test.tsx +981 -0
  28. package/src/components/Tabs/Tabs.browser.test.tsx +115 -0
  29. package/src/utils/useFullscreen/Fullscreen.browser.test.tsx +176 -0
  30. package/src/utils/useLocale/useLocale.browser.test.tsx +49 -0
  31. package/src/utils/useLocaleTimezoneDate/{LocaleTimezoneDate.cypress.spec.tsx → LocaleTimezoneDate.browser.test.tsx} +24 -11
  32. package/src/Dashboard/Dashboard.cypress.spec.tsx +0 -71
  33. package/src/Form/Form.cypress.spec.tsx +0 -306
  34. package/src/Form/Inputs/ExclusiveCheckboxGroup.cypress.spec.tsx +0 -236
  35. package/src/Form/Inputs/SegmentedButtons.cypress.spec.tsx +0 -101
  36. package/src/InputField/Number/Number.cypress.spec.tsx +0 -89
  37. package/src/InputField/Select/Autocomplete/Connected/Multi/MultiConnectedAutocompleteField.cypress.spec.tsx +0 -182
  38. package/src/Listing/Listing.cypress.spec.tsx +0 -349
  39. package/src/Module/Module.cypress.spec.tsx +0 -127
  40. package/src/Pagination/Pagination.cypress.spec.tsx +0 -140
  41. package/src/RichTextEditor/RichTextEditor.cypress.spec.tsx +0 -408
  42. package/src/TimePeriods/ResolutionTimePeriod.cypress.spec.tsx +0 -74
  43. package/src/TimePeriods/TimePeriods.cypress.spec.tsx +0 -488
  44. package/src/Typography/FluidTypography/FluidTypography.cypress.spec.tsx +0 -27
  45. package/src/api/useMutationQuery/useMutationQuery.cypress.spec.tsx +0 -93
  46. package/src/components/CollapsibleItem/CollapsibleItem.cypress.spec.tsx +0 -76
  47. package/src/components/CopyCommand/CopyCommand.cypress.spec.tsx +0 -167
  48. package/src/components/CrudPage/CrudPage.cypress.spec.tsx +0 -561
  49. package/src/components/DataTable/DataTable.cypress.spec.tsx +0 -174
  50. package/src/components/Form/AccessRights/AccessRights.cypress.spec.tsx +0 -376
  51. package/src/components/ItemComposition/ItemComposition.cypress.spec.tsx +0 -112
  52. package/src/components/Layout/PageLayout.cypress.spec.tsx +0 -64
  53. package/src/components/Modal/ConfirmationModal/ConfirmationModal.cypress.spec.tsx +0 -171
  54. package/src/components/SortableEntriesList/SortableEntriesList.cypress.spec.tsx +0 -792
  55. package/src/components/Tabs/Tabs.cypress.spec.tsx +0 -69
  56. package/src/utils/useFullscreen/Fullscreen.cypress.spec.tsx +0 -130
  57. package/src/utils/useLocale/useLocale.cypress.spec.tsx +0 -40
@@ -0,0 +1,143 @@
1
+ import dayjs from 'dayjs';
2
+ import 'dayjs/locale/en';
3
+
4
+ import { Switch } from '@mui/material';
5
+
6
+ import { type User, userAtom } from '@centreon/ui-context';
7
+
8
+ import { render } from '@test-setup/render';
9
+ import { screen, within } from '@testing-library/react';
10
+ import localizedFormatPlugin from 'dayjs/plugin/localizedFormat';
11
+ import timezonePlugin from 'dayjs/plugin/timezone';
12
+ import utcPlugin from 'dayjs/plugin/utc';
13
+ import { createStore, Provider } from 'jotai';
14
+
15
+ import TimePeriod from './index';
16
+ import { retrievedUser } from './mockedData';
17
+
18
+ dayjs.extend(timezonePlugin);
19
+ dayjs.extend(utcPlugin);
20
+ dayjs.extend(localizedFormatPlugin);
21
+
22
+ const data = [
23
+ {
24
+ labels: ['1 day', '7 days', '31 days'],
25
+ locale: 'en_US',
26
+ resolution: { height: 590, width: 500 },
27
+ timezone: 'Europe/Paris'
28
+ },
29
+ {
30
+ labels: ['1 day', '7 days', '31 days'],
31
+ locale: 'en_US',
32
+ resolution: { height: 590, width: 200 },
33
+ timezone: 'Europe/Paris'
34
+ },
35
+ {
36
+ labels: ['Last day', 'Last 7 days', 'Last 31 days'],
37
+ locale: 'en_US',
38
+ resolution: { height: 590, width: 1024 },
39
+ timezone: 'Europe/Paris'
40
+ }
41
+ ];
42
+
43
+ // The date format of the en_US locale, e.g. "01/23/2024 4:27 PM".
44
+ const datePattern = /^\d{2}\/\d{2}\/\d{4} \d{1,2}:\d{2} (AM|PM)$/;
45
+
46
+ const getRect = (element: Element): DOMRect => element.getBoundingClientRect();
47
+
48
+ data.forEach((item) => {
49
+ describe('Time period', () => {
50
+ const { width } = item.resolution;
51
+
52
+ beforeEach(() => {
53
+ expect.hasAssertions();
54
+
55
+ const store = createStore();
56
+
57
+ // A legacy snake_case fixture: a partial user.
58
+ store.set(userAtom, {
59
+ ...retrievedUser,
60
+ locale: 'en_US',
61
+ timezone: 'Europe/Paris'
62
+ } as unknown as User);
63
+
64
+ // The component condenses when its measured width is below 600px (the
65
+ // viewport media query does not apply at 1280px): a parent of each
66
+ // tested width sets it.
67
+ render(
68
+ <div style={{ width }}>
69
+ <Provider store={store}>
70
+ <TimePeriod renderExternalComponent={<Switch />} />
71
+ </Provider>
72
+ </div>
73
+ );
74
+ });
75
+
76
+ it(`displays correctly the dates design when screen resolution is ${width}px`, async () => {
77
+ // A substring match: "1 day" is also found in "Last 31 days".
78
+ expect(
79
+ (await screen.findAllByText('1 day', { exact: false }))[0]
80
+ ).toBeVisible();
81
+ expect(screen.getAllByText('7 days', { exact: false })[0]).toBeVisible();
82
+ expect(screen.getAllByText('31 days', { exact: false })[0]).toBeVisible();
83
+ expect(screen.getByText('From', { exact: false })).toBeVisible();
84
+ expect(screen.getByText('To', { exact: false })).toBeVisible();
85
+
86
+ // The short or the long labels, in order, the first one selected.
87
+ const timePeriodGroup = screen.getByRole('group');
88
+ const timePeriodButtons = within(timePeriodGroup).getAllByRole('button');
89
+
90
+ expect(timePeriodButtons.map((button) => button.textContent)).toEqual(
91
+ item.labels
92
+ );
93
+ expect(timePeriodButtons[0]).toHaveClass('MuiButton-contained');
94
+ expect(timePeriodButtons[1]).toHaveClass('MuiButton-outlined');
95
+ expect(timePeriodButtons[2]).toHaveClass('MuiButton-outlined');
96
+
97
+ // The custom period with its two dates, stacked when condensed.
98
+ const from = screen.getByText('From:');
99
+ const to = screen.getByText('To:');
100
+ const fromDate = from.nextElementSibling as HTMLElement;
101
+ const toDate = to.nextElementSibling as HTMLElement;
102
+
103
+ expect(fromDate).toHaveTextContent(datePattern);
104
+ expect(toDate).toHaveTextContent(datePattern);
105
+
106
+ const isCondensed = width < 600;
107
+
108
+ if (isCondensed) {
109
+ expect(getRect(to).top).toBeGreaterThanOrEqual(getRect(from).bottom);
110
+ } else {
111
+ expect(getRect(to).top).toBe(getRect(from).top);
112
+ expect(getRect(to).left).toBeGreaterThanOrEqual(
113
+ getRect(fromDate).right
114
+ );
115
+ }
116
+
117
+ // The external component, on the same line as the rest, or wrapped
118
+ // below them when the space is too narrow.
119
+ const externalSwitch = screen
120
+ .getByRole('checkbox')
121
+ .closest('.MuiSwitch-root') as HTMLElement;
122
+ const customPeriod = screen.getByTestId('Compact time period');
123
+
124
+ expect(externalSwitch).toBeVisible();
125
+
126
+ if (width < 300) {
127
+ expect(getRect(customPeriod).top).toBeGreaterThanOrEqual(
128
+ getRect(timePeriodGroup).bottom
129
+ );
130
+ expect(getRect(externalSwitch).top).toBeGreaterThanOrEqual(
131
+ getRect(customPeriod).bottom
132
+ );
133
+ } else {
134
+ expect(getRect(customPeriod).left).toBeGreaterThan(
135
+ getRect(timePeriodGroup).right
136
+ );
137
+ expect(getRect(externalSwitch).left).toBeGreaterThan(
138
+ getRect(customPeriod).right
139
+ );
140
+ }
141
+ });
142
+ });
143
+ });
@@ -0,0 +1,468 @@
1
+ import { useLocaleDateTimeFormat } from '@centreon/ui';
2
+ import { ListingVariant, type User, userAtom } from '@centreon/ui-context';
3
+
4
+ import { rs } from '@rstest/core';
5
+ import { render } from '@test-setup/render';
6
+ import { renderHook, screen, waitFor } from '@testing-library/react';
7
+ import userEvent from '@testing-library/user-event';
8
+ import dayjs from 'dayjs';
9
+ import { createStore, Provider } from 'jotai';
10
+ import { equals } from 'ramda';
11
+ import { config as transitionConfig } from 'react-transition-group';
12
+
13
+ import DateTimePickerInput from './DateTimePickerInput';
14
+ import { CustomTimePeriodProperty } from './models';
15
+
16
+ const retrievedUser = {
17
+ alias: 'Admin alias',
18
+ default_page: '/monitoring/resources',
19
+ is_export_button_enabled: true,
20
+ isExportButtonEnabled: true,
21
+ locale: 'fr_FR.UTF8',
22
+ name: 'Admin',
23
+ use_deprecated_pages: false,
24
+ user_interface_density: ListingVariant.compact
25
+ };
26
+
27
+ // The calendar slides for ~375 ms on every month change, and these tests
28
+ // change month 12 times. The setup only turns CSS transitions off; this turns
29
+ // the calendar's JavaScript-timed ones off too, for this file. The tests read
30
+ // the month the calendar ends on, which is the same.
31
+ beforeAll(() => {
32
+ transitionConfig.disabled = true;
33
+ });
34
+
35
+ afterAll(() => {
36
+ transitionConfig.disabled = false;
37
+ });
38
+
39
+ const numberDaysInWeek = 7;
40
+
41
+ const days = [
42
+ 'Sunday',
43
+ 'Monday',
44
+ 'Tuesday',
45
+ 'Wednesday',
46
+ 'Thursday',
47
+ 'Friday',
48
+ 'Saturday'
49
+ ];
50
+
51
+ interface Day {
52
+ day: string;
53
+ indexDayInRowWeek: number;
54
+ value: number;
55
+ }
56
+
57
+ interface MonthData {
58
+ date: string;
59
+ firstDay: Day;
60
+ lastDay: Day;
61
+ numberWeeks: number;
62
+ }
63
+
64
+ type Month = Record<string, MonthData>;
65
+
66
+ const months2023: Array<Month> = [
67
+ {
68
+ January: {
69
+ date: '2023-01-01T12:59:41.041Z',
70
+ firstDay: { day: days[0], indexDayInRowWeek: 0, value: 1 },
71
+ lastDay: { day: days[2], indexDayInRowWeek: 2, value: 31 },
72
+ numberWeeks: 5
73
+ }
74
+ },
75
+ {
76
+ February: {
77
+ date: '2023-02-01T12:59:41.041Z',
78
+ firstDay: { day: days[3], indexDayInRowWeek: 3, value: 1 },
79
+ lastDay: { day: days[2], indexDayInRowWeek: 2, value: 28 },
80
+ numberWeeks: 5
81
+ }
82
+ },
83
+ {
84
+ March: {
85
+ date: '2023-03-01T12:59:41.041Z',
86
+ firstDay: { day: days[3], indexDayInRowWeek: 3, value: 1 },
87
+ lastDay: { day: days[5], indexDayInRowWeek: 5, value: 31 },
88
+ numberWeeks: 5
89
+ }
90
+ },
91
+ {
92
+ April: {
93
+ date: '2023-04-01T12:59:41.041Z',
94
+ firstDay: { day: days[6], indexDayInRowWeek: 6, value: 1 },
95
+ lastDay: { day: days[0], indexDayInRowWeek: 0, value: 30 },
96
+ numberWeeks: 6
97
+ }
98
+ },
99
+ {
100
+ May: {
101
+ date: '2023-05-01T12:59:41.041Z',
102
+ firstDay: { day: days[1], indexDayInRowWeek: 1, value: 1 },
103
+ lastDay: { day: days[3], indexDayInRowWeek: 3, value: 31 },
104
+ numberWeeks: 5
105
+ }
106
+ },
107
+ {
108
+ June: {
109
+ date: '2023-06-01T12:59:41.041Z',
110
+ firstDay: { day: days[4], indexDayInRowWeek: 4, value: 1 },
111
+ lastDay: { day: days[5], indexDayInRowWeek: 5, value: 30 },
112
+ numberWeeks: 5
113
+ }
114
+ },
115
+ {
116
+ July: {
117
+ date: '2023-07-01T12:59:41.041Z',
118
+ firstDay: { day: days[6], indexDayInRowWeek: 6, value: 1 },
119
+ lastDay: { day: days[1], indexDayInRowWeek: 1, value: 31 },
120
+ numberWeeks: 6
121
+ }
122
+ },
123
+ {
124
+ August: {
125
+ date: '2023-08-01T12:59:41.041Z',
126
+ firstDay: { day: days[2], indexDayInRowWeek: 2, value: 1 },
127
+ lastDay: { day: days[4], indexDayInRowWeek: 4, value: 31 },
128
+ numberWeeks: 5
129
+ }
130
+ },
131
+ {
132
+ September: {
133
+ date: '2023-09-01T12:59:41.041Z',
134
+ firstDay: { day: days[5], indexDayInRowWeek: 5, value: 1 },
135
+ lastDay: { day: days[6], indexDayInRowWeek: 6, value: 30 },
136
+ numberWeeks: 5
137
+ }
138
+ },
139
+ {
140
+ October: {
141
+ date: '2023-10-01T12:59:41.041Z',
142
+ firstDay: { day: days[0], indexDayInRowWeek: 0, value: 1 },
143
+ lastDay: { day: days[2], indexDayInRowWeek: 2, value: 31 },
144
+ numberWeeks: 5
145
+ }
146
+ },
147
+ {
148
+ November: {
149
+ date: '2023-11-01T12:59:41.041Z',
150
+ firstDay: { day: days[3], indexDayInRowWeek: 3, value: 1 },
151
+ lastDay: { day: days[4], indexDayInRowWeek: 4, value: 30 },
152
+ numberWeeks: 5
153
+ }
154
+ },
155
+ {
156
+ December: {
157
+ date: '2023-12-01T12:59:41.041Z',
158
+ firstDay: { day: days[5], indexDayInRowWeek: 5, value: 1 },
159
+ lastDay: { day: days[0], indexDayInRowWeek: 0, value: 31 },
160
+ numberWeeks: 6
161
+ }
162
+ }
163
+ ];
164
+
165
+ const month2023Reverse: Array<Month> = [...months2023].reverse();
166
+
167
+ interface TestData {
168
+ button: string;
169
+ data: Array<Month>;
170
+ initialDate: string;
171
+ // The date-time field shows the initial date in the user's timezone.
172
+ initialDateInTimezone: string;
173
+ timezone: string;
174
+ }
175
+
176
+ const testData: Array<TestData> = [
177
+ {
178
+ button: 'Next month',
179
+ data: months2023,
180
+ initialDate: '2023-01-01T12:59:41.041Z',
181
+ initialDateInTimezone: '01/01/2023 01:59 PM',
182
+ timezone: 'Europe/Paris'
183
+ },
184
+ {
185
+ button: 'Previous month',
186
+ data: month2023Reverse,
187
+ initialDate: '2023-12-01T12:59:41.041Z',
188
+ initialDateInTimezone: '12/01/2023 01:59 PM',
189
+ timezone: 'Europe/Paris'
190
+ }
191
+ ];
192
+
193
+ enum ButtonCalendar {
194
+ NMONTH = 'Next month',
195
+ PMONTH = 'Previous month'
196
+ }
197
+ interface GetPreviousNextMonth {
198
+ currentMonth: string;
199
+ labelButton: string;
200
+ }
201
+
202
+ const getNextMonth = async ({
203
+ currentMonth,
204
+ labelButton
205
+ }: GetPreviousNextMonth): Promise<void> => {
206
+ if (equals(currentMonth, 'December')) {
207
+ return;
208
+ }
209
+
210
+ await userEvent.click(screen.getByLabelText(labelButton));
211
+ };
212
+
213
+ const getPreviousMonth = async ({
214
+ currentMonth,
215
+ labelButton
216
+ }: GetPreviousNextMonth): Promise<void> => {
217
+ if (equals(currentMonth, 'January')) {
218
+ return;
219
+ }
220
+
221
+ await userEvent.click(screen.getByLabelText(labelButton));
222
+ };
223
+
224
+ const checkIfDuplicateExists = (arr: Array<unknown>): boolean => {
225
+ return new Set(arr).size !== arr.length;
226
+ };
227
+
228
+ const getMonth = (element: Month): { data: MonthData; name: string } => {
229
+ const [name, data] = Object.entries(element)[0];
230
+
231
+ return { data, name };
232
+ };
233
+
234
+ // The month the calendar should show for a date, as its header writes it.
235
+ const getMonthLabel = (date: string, timezone: string): string =>
236
+ dayjs(date).tz(timezone).format('MMMM YYYY');
237
+
238
+ // The calendar slides from one month to the next: wait until it shows only
239
+ // the expected month before reading its days.
240
+ const waitForMonth = async ({
241
+ date,
242
+ timezone
243
+ }: {
244
+ date: string;
245
+ timezone: string;
246
+ }): Promise<void> => {
247
+ await waitFor(() => {
248
+ expect(
249
+ screen.getByText(getMonthLabel(date, timezone), {
250
+ selector: '.MuiPickersCalendarHeader-label'
251
+ })
252
+ ).toBeVisible();
253
+ expect(document.querySelectorAll('[role="rowgroup"]')).toHaveLength(1);
254
+ });
255
+ };
256
+
257
+ const getWeeks = (): Array<Element> =>
258
+ Array.from(document.querySelectorAll('[role="rowgroup"]')).flatMap(
259
+ (rowgroup) => Array.from(rowgroup.children)
260
+ );
261
+
262
+ const getDays = (): Array<Element> =>
263
+ getWeeks().flatMap((week) =>
264
+ Array.from(week.children).filter((child) => child.tagName === 'BUTTON')
265
+ );
266
+
267
+ const openCalendar = async (): Promise<void> => {
268
+ const button = document.querySelector('button');
269
+
270
+ if (!button) {
271
+ throw new Error('No button to open the calendar');
272
+ }
273
+
274
+ await userEvent.click(button);
275
+ };
276
+
277
+ testData.forEach((item) => {
278
+ const goToFollowingMonth = async (currentMonth: string): Promise<void> => {
279
+ if (equals(item.button, ButtonCalendar.PMONTH)) {
280
+ await getPreviousMonth({ currentMonth, labelButton: item.button });
281
+ } else {
282
+ await getNextMonth({ currentMonth, labelButton: item.button });
283
+ }
284
+ };
285
+
286
+ describe(`DateTimePicker ${item.button}`, () => {
287
+ beforeEach(() => {
288
+ expect.hasAssertions();
289
+
290
+ const store = createStore();
291
+ store.set(userAtom, {
292
+ ...retrievedUser,
293
+ locale: 'en_US',
294
+ timezone: item.timezone
295
+ } as unknown as User);
296
+
297
+ render(
298
+ <Provider store={store}>
299
+ <DateTimePickerInput
300
+ changeDate={rs.fn()}
301
+ date={new Date(item.initialDate)}
302
+ property={CustomTimePeriodProperty.start}
303
+ />
304
+ </Provider>
305
+ );
306
+ });
307
+
308
+ it(`displays the correct number of days for the current month when the ${item.button} button is clicked`, async () => {
309
+ await openCalendar();
310
+
311
+ for (const element of item.data) {
312
+ const { data, name: currentMonth } = getMonth(element);
313
+
314
+ await waitForMonth({ date: data.date, timezone: item.timezone });
315
+ expect(getDays()).toHaveLength(data.lastDay.value);
316
+
317
+ await goToFollowingMonth(currentMonth);
318
+ }
319
+ });
320
+
321
+ it(`does not duplicate days in any of the month in the year when the ${item.button} button is clicked`, async () => {
322
+ await openCalendar();
323
+
324
+ for (const element of item.data) {
325
+ const { data, name: currentMonth } = getMonth(element);
326
+
327
+ await waitForMonth({ date: data.date, timezone: item.timezone });
328
+
329
+ const daysInCurrentMonth = getDays().map((day) => day.textContent);
330
+
331
+ expect(daysInCurrentMonth.length).toBeGreaterThan(0);
332
+ expect(checkIfDuplicateExists(daysInCurrentMonth)).toBe(false);
333
+
334
+ await goToFollowingMonth(currentMonth);
335
+ }
336
+ });
337
+
338
+ it(`displays the first day as the start of the week when the ${item.button} button is clicked`, async () => {
339
+ await openCalendar();
340
+
341
+ for (const element of item.data) {
342
+ const { data, name: currentMonth } = getMonth(element);
343
+ const { firstDay, numberWeeks } = data;
344
+
345
+ await waitForMonth({ date: data.date, timezone: item.timezone });
346
+
347
+ const listWeeks = getWeeks();
348
+
349
+ expect(listWeeks).toHaveLength(numberWeeks);
350
+
351
+ const listDaysInFirstWeek = Array.from(listWeeks[0].children);
352
+
353
+ expect(listDaysInFirstWeek).toHaveLength(numberDaysInWeek);
354
+ expect(
355
+ listDaysInFirstWeek[firstDay.indexDayInRowWeek]
356
+ ).toHaveTextContent(String(firstDay.value));
357
+
358
+ await goToFollowingMonth(currentMonth);
359
+ }
360
+ });
361
+
362
+ it(`displays the last day as the end of the week when the ${item.button} button is clicked`, async () => {
363
+ await openCalendar();
364
+
365
+ for (const element of item.data) {
366
+ const { data, name: currentMonth } = getMonth(element);
367
+ const { lastDay, numberWeeks } = data;
368
+
369
+ await waitForMonth({ date: data.date, timezone: item.timezone });
370
+
371
+ const listWeeks = getWeeks();
372
+
373
+ expect(listWeeks).toHaveLength(numberWeeks);
374
+
375
+ const listDaysInLastWeek = Array.from(
376
+ listWeeks[numberWeeks - 1].children
377
+ );
378
+
379
+ expect(listDaysInLastWeek).toHaveLength(numberDaysInWeek);
380
+ expect(listDaysInLastWeek[lastDay.indexDayInRowWeek]).toHaveTextContent(
381
+ String(lastDay.value)
382
+ );
383
+
384
+ await goToFollowingMonth(currentMonth);
385
+ }
386
+ });
387
+
388
+ it(`displays the month and the year in the calendar's header when the ${item.button} button is clicked`, async () => {
389
+ await openCalendar();
390
+ const { result } = renderHook(() => useLocaleDateTimeFormat());
391
+
392
+ for (const element of item.data) {
393
+ const { data, name: currentMonth } = getMonth(element);
394
+
395
+ const { format } = result.current;
396
+ const monthAndYear = format({
397
+ date: dayjs(data.date).tz(item.timezone).toDate(),
398
+ formatString: 'MMMM YYYY'
399
+ });
400
+
401
+ expect(await screen.findByText(monthAndYear)).toBeInTheDocument();
402
+
403
+ await goToFollowingMonth(currentMonth);
404
+ }
405
+ });
406
+
407
+ it(`displays the correct day name on calendar's header when the ${item.button} button is clicked`, async () => {
408
+ await openCalendar();
409
+ const { result } = renderHook(() => useLocaleDateTimeFormat());
410
+
411
+ for (const element of item.data) {
412
+ const { data, name: currentMonth } = getMonth(element);
413
+ const dateByTimeZone = dayjs(data.date).tz(item.timezone);
414
+ const firstDay = dateByTimeZone.isUTC()
415
+ ? dateByTimeZone.utc().startOf('month').startOf('week')
416
+ : dateByTimeZone.startOf('month').startOf('week');
417
+
418
+ const { format } = result.current;
419
+ const daysArray = [0, 1, 2, 3, 4, 5, 6].map((diff) =>
420
+ format({
421
+ date: firstDay.add(diff, 'day').toDate(),
422
+ formatString: 'dd'
423
+ })
424
+ );
425
+
426
+ for (const day of daysArray) {
427
+ expect(
428
+ await screen.findByText(day.toUpperCase())
429
+ ).toBeInTheDocument();
430
+ }
431
+
432
+ await goToFollowingMonth(currentMonth);
433
+ }
434
+ });
435
+
436
+ it(`displays the calendar for the timezone ${item.timezone} when the ${item.button} button is clicked`, async () => {
437
+ await openCalendar();
438
+
439
+ for (const [index, element] of item.data.entries()) {
440
+ const { name: currentMonth } = getMonth(element);
441
+
442
+ await goToFollowingMonth(currentMonth);
443
+
444
+ // After each click: the following month (the last one stays), the
445
+ // initial date in the user's timezone in the field, and its hour
446
+ // selected.
447
+ const displayedMonth = getMonth(
448
+ item.data[Math.min(index + 1, item.data.length - 1)]
449
+ ).data;
450
+
451
+ await waitForMonth({
452
+ date: displayedMonth.date,
453
+ timezone: item.timezone
454
+ });
455
+ expect(screen.getByRole('textbox')).toHaveValue(
456
+ item.initialDateInTimezone
457
+ );
458
+ expect(getDays()).toHaveLength(displayedMonth.lastDay.value);
459
+ expect(
460
+ screen.getByRole('option', { name: '1 hours', selected: true })
461
+ ).toBeVisible();
462
+ expect(
463
+ screen.getByRole('option', { name: 'PM', selected: true })
464
+ ).toBeVisible();
465
+ }
466
+ });
467
+ });
468
+ });
@@ -0,0 +1,54 @@
1
+ import { render } from '@test-setup/render';
2
+ import { screen } from '@testing-library/react';
3
+
4
+ import FluidTypography from '.';
5
+
6
+ interface InitializeProps {
7
+ max?: string;
8
+ min?: string;
9
+ pref?: number;
10
+ width: number;
11
+ }
12
+
13
+ // The viewport width is fixed, so the component gets a parent of each tested
14
+ // width: its font size depends on its container width only (`cqi` units).
15
+ const initialize = ({ width, min, max, pref }: InitializeProps): void => {
16
+ render(
17
+ <div style={{ width }}>
18
+ <FluidTypography max={max} min={min} pref={pref} text="Unreachable" />
19
+ </div>
20
+ );
21
+ };
22
+
23
+ // clamp(min = 10px, pref = 19cqi, max = 1000px) with the one prop the test
24
+ // overrides, 1cqi being 1% of the container width.
25
+ const expectedFontSizes: Record<number, Record<string, string>> = {
26
+ 150: { max: '28.5px', min: '30px', pref: '15px' },
27
+ 300: { max: '57px', min: '57px', pref: '30px' },
28
+ 500: { max: '80px', min: '95px', pref: '50px' }
29
+ };
30
+
31
+ describe('FluidTypography', () => {
32
+ beforeEach(() => {
33
+ expect.hasAssertions();
34
+ });
35
+
36
+ [500, 300, 150].forEach((width) => {
37
+ Object.entries({ max: '80px', min: '30px', pref: 10 }).forEach(
38
+ ([key, value]) => {
39
+ it(`displays the text when the viewport is ${width}px and the ${key} is ${value}`, async () => {
40
+ initialize({ [key]: value, width });
41
+
42
+ const text = await screen.findByText('Unreachable');
43
+
44
+ expect(text).toBeVisible();
45
+
46
+ // The text size at that width.
47
+ expect(getComputedStyle(text).fontSize).toBe(
48
+ expectedFontSizes[width][key]
49
+ );
50
+ });
51
+ }
52
+ );
53
+ });
54
+ });