@centreon/ui 26.12.1 → 26.12.3
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.
- package/package.json +6 -3
- package/public/mockServiceWorker.js +31 -8
- package/src/Dashboard/Dashboard.browser.test.tsx +107 -0
- package/src/Form/CollapsibleGroup.tsx +6 -33
- package/src/Form/Form.browser.test.tsx +553 -0
- package/src/Form/Inputs/ExclusiveCheckboxGroup.browser.test.tsx +304 -0
- package/src/Form/Inputs/Grid.tsx +1 -2
- package/src/Form/Inputs/SegmentedButtons.browser.test.tsx +113 -0
- package/src/Form/Inputs/index.tsx +10 -52
- package/src/InputField/Number/Number.browser.test.tsx +109 -0
- package/src/InputField/Select/Autocomplete/Connected/Multi/MultiConnectedAutocompleteField.browser.test.tsx +250 -0
- package/src/Listing/Listing.browser.test.tsx +476 -0
- package/src/Module/Module.browser.test.tsx +145 -0
- package/src/Pagination/Pagination.browser.test.tsx +184 -0
- package/src/RichTextEditor/RichTextEditor.browser.test.tsx +567 -0
- package/src/RichTextEditor/plugins/FloatingLinkEditorPlugin.tsx +3 -1
- package/src/TimePeriods/ResolutionTimePeriod.browser.test.tsx +143 -0
- package/src/TimePeriods/TimePeriods.browser.test.tsx +468 -0
- package/src/Typography/FluidTypography/FluidTypography.browser.test.tsx +54 -0
- package/src/api/useMutationQuery/useMutationQuery.browser.test.tsx +154 -0
- package/src/components/CollapsibleItem/CollapsibleItem.browser.test.tsx +120 -0
- package/src/components/CopyCommand/CopyCommand.browser.test.tsx +287 -0
- package/src/components/CrudPage/CrudPage.browser.test.tsx +659 -0
- package/src/components/DataTable/DataTable.browser.test.tsx +257 -0
- package/src/components/Form/AccessRights/AccessRights.browser.test.tsx +527 -0
- package/src/components/ItemComposition/ItemComposition.browser.test.tsx +172 -0
- package/src/components/Layout/PageLayout.browser.test.tsx +114 -0
- package/src/components/Modal/ConfirmationModal/ConfirmationModal.browser.test.tsx +237 -0
- package/src/components/SortableEntriesList/SortableEntriesList.browser.test.tsx +981 -0
- package/src/components/Tabs/Tabs.browser.test.tsx +115 -0
- package/src/utils/useFullscreen/Fullscreen.browser.test.tsx +176 -0
- package/src/utils/useLocale/useLocale.browser.test.tsx +49 -0
- package/src/utils/useLocaleTimezoneDate/{LocaleTimezoneDate.cypress.spec.tsx → LocaleTimezoneDate.browser.test.tsx} +24 -11
- package/src/Dashboard/Dashboard.cypress.spec.tsx +0 -71
- package/src/Form/Form.cypress.spec.tsx +0 -306
- package/src/Form/Inputs/ExclusiveCheckboxGroup.cypress.spec.tsx +0 -236
- package/src/Form/Inputs/SegmentedButtons.cypress.spec.tsx +0 -101
- package/src/InputField/Number/Number.cypress.spec.tsx +0 -89
- package/src/InputField/Select/Autocomplete/Connected/Multi/MultiConnectedAutocompleteField.cypress.spec.tsx +0 -182
- package/src/Listing/Listing.cypress.spec.tsx +0 -349
- package/src/Module/Module.cypress.spec.tsx +0 -127
- package/src/Pagination/Pagination.cypress.spec.tsx +0 -140
- package/src/RichTextEditor/RichTextEditor.cypress.spec.tsx +0 -408
- package/src/TimePeriods/ResolutionTimePeriod.cypress.spec.tsx +0 -74
- package/src/TimePeriods/TimePeriods.cypress.spec.tsx +0 -488
- package/src/Typography/FluidTypography/FluidTypography.cypress.spec.tsx +0 -27
- package/src/api/useMutationQuery/useMutationQuery.cypress.spec.tsx +0 -93
- package/src/components/CollapsibleItem/CollapsibleItem.cypress.spec.tsx +0 -76
- package/src/components/CopyCommand/CopyCommand.cypress.spec.tsx +0 -167
- package/src/components/CrudPage/CrudPage.cypress.spec.tsx +0 -561
- package/src/components/DataTable/DataTable.cypress.spec.tsx +0 -174
- package/src/components/Form/AccessRights/AccessRights.cypress.spec.tsx +0 -376
- package/src/components/ItemComposition/ItemComposition.cypress.spec.tsx +0 -112
- package/src/components/Layout/PageLayout.cypress.spec.tsx +0 -64
- package/src/components/Modal/ConfirmationModal/ConfirmationModal.cypress.spec.tsx +0 -171
- package/src/components/SortableEntriesList/SortableEntriesList.cypress.spec.tsx +0 -792
- package/src/components/Tabs/Tabs.cypress.spec.tsx +0 -69
- package/src/utils/useFullscreen/Fullscreen.cypress.spec.tsx +0 -130
- 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
|
+
});
|