@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.
- package/package.json +6 -3
- package/public/mockServiceWorker.js +31 -8
- package/src/Dashboard/Dashboard.browser.test.tsx +107 -0
- package/src/Form/Form.browser.test.tsx +445 -0
- package/src/Form/Inputs/ExclusiveCheckboxGroup.browser.test.tsx +304 -0
- package/src/Form/Inputs/SegmentedButtons.browser.test.tsx +113 -0
- 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/Panel/Section/index.test.tsx +1 -1
- 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
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@centreon/ui",
|
|
3
|
-
"version": "26.12.
|
|
3
|
+
"version": "26.12.2",
|
|
4
4
|
"description": "Centreon UI Components",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"update:deps": "pnpx npm-check-updates -i --format group",
|
|
7
|
-
"type-check": "tsc --project tsconfig.typecheck.json",
|
|
7
|
+
"type-check": "tsc --project tsconfig.typecheck.json && tsc --project tsconfig.rstest.json",
|
|
8
8
|
"type-check:staged": "node typecheck-staged.mjs",
|
|
9
9
|
"lint": "biome check ./src/ --error-on-warnings",
|
|
10
10
|
"lint:fix": "pnpm lint --fix",
|
|
@@ -21,7 +21,10 @@
|
|
|
21
21
|
"cypress:install": "cypress install",
|
|
22
22
|
"cypress:cli": "./cypress/scripts/cypress-cli.sh",
|
|
23
23
|
"tokens:transform": "TS_NODE_PROJECT=tsconfig.node.json ts-node style-dictionary.transform.ts",
|
|
24
|
-
"install:arm:binaries": "pnpm i -D @swc/core-linux-arm64-gnu@1.6.6 @rspack/binding-linux-arm64-gnu@1.1.6 -w && pnpm remove @swc/core-linux-arm64-gnu @rspack/binding-linux-arm64-gnu"
|
|
24
|
+
"install:arm:binaries": "pnpm i -D @swc/core-linux-arm64-gnu@1.6.6 @rspack/binding-linux-arm64-gnu@1.1.6 -w && pnpm remove @swc/core-linux-arm64-gnu @rspack/binding-linux-arm64-gnu",
|
|
25
|
+
"rstest:run": "TZ=Etc/UTC rstest run",
|
|
26
|
+
"rstest:ui": "TZ=Etc/UTC rstest run --browser.headless=false --pool.maxWorkers=1",
|
|
27
|
+
"rstest:install": "playwright install --with-deps --only-shell chromium"
|
|
25
28
|
},
|
|
26
29
|
"type": "module",
|
|
27
30
|
"sideEffects": false,
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
* - Please do NOT serve this file on production.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
const PACKAGE_VERSION = '2.
|
|
12
|
-
const INTEGRITY_CHECKSUM = '
|
|
11
|
+
const PACKAGE_VERSION = '2.7.3'
|
|
12
|
+
const INTEGRITY_CHECKSUM = '00729d72e3b82faf54ca8b9621dbb96f'
|
|
13
13
|
const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
|
|
14
14
|
const activeClientIds = new Set()
|
|
15
15
|
|
|
@@ -62,7 +62,12 @@ self.addEventListener('message', async function (event) {
|
|
|
62
62
|
|
|
63
63
|
sendToClient(client, {
|
|
64
64
|
type: 'MOCKING_ENABLED',
|
|
65
|
-
payload:
|
|
65
|
+
payload: {
|
|
66
|
+
client: {
|
|
67
|
+
id: client.id,
|
|
68
|
+
frameType: client.frameType,
|
|
69
|
+
},
|
|
70
|
+
},
|
|
66
71
|
})
|
|
67
72
|
break
|
|
68
73
|
}
|
|
@@ -155,6 +160,10 @@ async function handleRequest(event, requestId) {
|
|
|
155
160
|
async function resolveMainClient(event) {
|
|
156
161
|
const client = await self.clients.get(event.clientId)
|
|
157
162
|
|
|
163
|
+
if (activeClientIds.has(event.clientId)) {
|
|
164
|
+
return client
|
|
165
|
+
}
|
|
166
|
+
|
|
158
167
|
if (client?.frameType === 'top-level') {
|
|
159
168
|
return client
|
|
160
169
|
}
|
|
@@ -183,12 +192,26 @@ async function getResponse(event, client, requestId) {
|
|
|
183
192
|
const requestClone = request.clone()
|
|
184
193
|
|
|
185
194
|
function passthrough() {
|
|
186
|
-
|
|
195
|
+
// Cast the request headers to a new Headers instance
|
|
196
|
+
// so the headers can be manipulated with.
|
|
197
|
+
const headers = new Headers(requestClone.headers)
|
|
198
|
+
|
|
199
|
+
// Remove the "accept" header value that marked this request as passthrough.
|
|
200
|
+
// This prevents request alteration and also keeps it compliant with the
|
|
201
|
+
// user-defined CORS policies.
|
|
202
|
+
const acceptHeader = headers.get('accept')
|
|
203
|
+
if (acceptHeader) {
|
|
204
|
+
const values = acceptHeader.split(',').map((value) => value.trim())
|
|
205
|
+
const filteredValues = values.filter(
|
|
206
|
+
(value) => value !== 'msw/passthrough',
|
|
207
|
+
)
|
|
187
208
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
209
|
+
if (filteredValues.length > 0) {
|
|
210
|
+
headers.set('accept', filteredValues.join(', '))
|
|
211
|
+
} else {
|
|
212
|
+
headers.delete('accept')
|
|
213
|
+
}
|
|
214
|
+
}
|
|
192
215
|
|
|
193
216
|
return fetch(requestClone, { headers })
|
|
194
217
|
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { Typography } from '@mui/material';
|
|
2
|
+
|
|
3
|
+
import { render } from '@test-setup/render';
|
|
4
|
+
import { screen, waitFor } from '@testing-library/react';
|
|
5
|
+
import type { LayoutItem } from 'react-grid-layout';
|
|
6
|
+
|
|
7
|
+
import { DashboardLayout } from '.';
|
|
8
|
+
|
|
9
|
+
interface CustomLayout extends LayoutItem {
|
|
10
|
+
content: string;
|
|
11
|
+
shouldUseFluidTypography?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const dashboardLayout: Array<CustomLayout> = [
|
|
15
|
+
{
|
|
16
|
+
content: 'Panel 1',
|
|
17
|
+
h: 4,
|
|
18
|
+
i: 'a',
|
|
19
|
+
shouldUseFluidTypography: false,
|
|
20
|
+
w: 6,
|
|
21
|
+
x: 0,
|
|
22
|
+
y: 0
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
content: 'Panel 2',
|
|
26
|
+
h: 3,
|
|
27
|
+
i: 'b',
|
|
28
|
+
minW: 2,
|
|
29
|
+
w: 7,
|
|
30
|
+
x: 1,
|
|
31
|
+
y: 7
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
content: 'Panel 3',
|
|
35
|
+
h: 7,
|
|
36
|
+
i: 'c',
|
|
37
|
+
w: 6,
|
|
38
|
+
x: 6,
|
|
39
|
+
y: 6
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
content: 'Panel 4',
|
|
43
|
+
h: 3,
|
|
44
|
+
i: 'd',
|
|
45
|
+
minW: 2,
|
|
46
|
+
w: 5,
|
|
47
|
+
x: 4,
|
|
48
|
+
y: 10
|
|
49
|
+
}
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
const initialize = (): void => {
|
|
53
|
+
render(
|
|
54
|
+
<DashboardLayout.Layout layout={dashboardLayout}>
|
|
55
|
+
{dashboardLayout.map(({ i, content }) => (
|
|
56
|
+
<DashboardLayout.Item header={<div>header</div>} id={i} key={i}>
|
|
57
|
+
{[<Typography key="content">{content}</Typography>]}
|
|
58
|
+
</DashboardLayout.Item>
|
|
59
|
+
))}
|
|
60
|
+
</DashboardLayout.Layout>
|
|
61
|
+
);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const panelBox = (content: string): DOMRect =>
|
|
65
|
+
(
|
|
66
|
+
screen.getByText(content).closest('.react-grid-item') as HTMLElement
|
|
67
|
+
).getBoundingClientRect();
|
|
68
|
+
|
|
69
|
+
describe('Dashboard', () => {
|
|
70
|
+
beforeEach(() => {
|
|
71
|
+
expect.hasAssertions();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('displays placeholder when panels is out of viewport', async () => {
|
|
75
|
+
initialize();
|
|
76
|
+
|
|
77
|
+
// The four panels render with their header. Panels 1 and 3 share the
|
|
78
|
+
// first row (Panel 3 taller), Panel 2 sits below Panel 3 starting within
|
|
79
|
+
// Panel 1's width, and Panel 4 is below Panel 2.
|
|
80
|
+
expect(await screen.findByText('Panel 1')).toBeVisible();
|
|
81
|
+
expect(screen.getByText('Panel 2')).toBeVisible();
|
|
82
|
+
expect(screen.getByText('Panel 3')).toBeVisible();
|
|
83
|
+
expect(screen.getByText('Panel 4')).toBeInTheDocument();
|
|
84
|
+
expect(screen.getAllByText('header')).toHaveLength(4);
|
|
85
|
+
|
|
86
|
+
await waitFor(() => {
|
|
87
|
+
const panel1 = panelBox('Panel 1');
|
|
88
|
+
const panel3 = panelBox('Panel 3');
|
|
89
|
+
|
|
90
|
+
expect(panel1.width).toBeGreaterThan(0);
|
|
91
|
+
expect(panel3.top).toBe(panel1.top);
|
|
92
|
+
expect(panel3.left).toBeGreaterThan(panel1.right);
|
|
93
|
+
expect(panel3.width).toBe(panel1.width);
|
|
94
|
+
expect(panel3.height).toBeGreaterThan(panel1.height);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const panel1 = panelBox('Panel 1');
|
|
98
|
+
const panel2 = panelBox('Panel 2');
|
|
99
|
+
const panel3 = panelBox('Panel 3');
|
|
100
|
+
const panel4 = panelBox('Panel 4');
|
|
101
|
+
|
|
102
|
+
expect(panel2.top).toBeGreaterThan(panel3.bottom);
|
|
103
|
+
expect(panel2.left).toBeGreaterThan(panel1.left);
|
|
104
|
+
expect(panel2.left).toBeLessThan(panel1.right);
|
|
105
|
+
expect(panel4.top).toBeGreaterThan(panel2.bottom);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
import { Typography } from '@mui/material';
|
|
2
|
+
|
|
3
|
+
import { faker } from '@faker-js/faker';
|
|
4
|
+
import { rs } from '@rstest/core';
|
|
5
|
+
import { render } from '@test-setup/render';
|
|
6
|
+
import { screen, waitFor, within } from '@testing-library/react';
|
|
7
|
+
import userEvent from '@testing-library/user-event';
|
|
8
|
+
import { useFormikContext } from 'formik';
|
|
9
|
+
import { object } from 'yup';
|
|
10
|
+
|
|
11
|
+
import type { SelectEntry } from '..';
|
|
12
|
+
import { Button } from '../components';
|
|
13
|
+
import { Form } from './Form';
|
|
14
|
+
import { type InputProps, InputType } from './Inputs/models';
|
|
15
|
+
|
|
16
|
+
faker.seed(42);
|
|
17
|
+
|
|
18
|
+
interface Item {
|
|
19
|
+
alias: string;
|
|
20
|
+
id: number;
|
|
21
|
+
name: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface ListValues {
|
|
25
|
+
list: Array<Item>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const AddItem = ({
|
|
29
|
+
addItem
|
|
30
|
+
}: {
|
|
31
|
+
addItem: (item: SelectEntry) => void;
|
|
32
|
+
}): JSX.Element => {
|
|
33
|
+
const { values } = useFormikContext<ListValues>();
|
|
34
|
+
const add = (): void => {
|
|
35
|
+
addItem({
|
|
36
|
+
alias: faker.company.name(),
|
|
37
|
+
id: values.list.length,
|
|
38
|
+
name: faker.person.firstName()
|
|
39
|
+
} as SelectEntry);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<Button onClick={add} variant="ghost">
|
|
44
|
+
Add item
|
|
45
|
+
</Button>
|
|
46
|
+
);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const SortContent = ({
|
|
50
|
+
name,
|
|
51
|
+
alias
|
|
52
|
+
}: {
|
|
53
|
+
alias: string;
|
|
54
|
+
name: string;
|
|
55
|
+
}): JSX.Element => (
|
|
56
|
+
<Typography>
|
|
57
|
+
{name} ({alias})
|
|
58
|
+
</Typography>
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const initializeFormList = (): void => {
|
|
62
|
+
render(
|
|
63
|
+
<Form
|
|
64
|
+
initialValues={{
|
|
65
|
+
list: []
|
|
66
|
+
}}
|
|
67
|
+
inputs={[
|
|
68
|
+
{
|
|
69
|
+
fieldName: 'list',
|
|
70
|
+
group: '',
|
|
71
|
+
label: '',
|
|
72
|
+
list: {
|
|
73
|
+
AddItem,
|
|
74
|
+
addItemLabel: 'Add an item to the list',
|
|
75
|
+
itemProps: ['id', 'name', 'alias'],
|
|
76
|
+
SortContent: SortContent as React.ComponentType<object>,
|
|
77
|
+
sortLabel: 'Sort items'
|
|
78
|
+
},
|
|
79
|
+
type: InputType.List
|
|
80
|
+
}
|
|
81
|
+
]}
|
|
82
|
+
submit={rs.fn()}
|
|
83
|
+
validationSchema={object()}
|
|
84
|
+
/>
|
|
85
|
+
);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// A keyboard drag of the handle: space to pick the item up, an arrow to move
|
|
89
|
+
// it one place, space to drop it. dnd-kit handles a key once the previous one
|
|
90
|
+
// took effect, so each step is awaited: the item pressed once picked up, the
|
|
91
|
+
// other item making way after the arrow, no item pressed once dropped.
|
|
92
|
+
const moveSortableElementUsingAriaLabel = async ({
|
|
93
|
+
ariaLabel,
|
|
94
|
+
direction
|
|
95
|
+
}: {
|
|
96
|
+
ariaLabel: string;
|
|
97
|
+
direction: 'down' | 'up';
|
|
98
|
+
}): Promise<void> => {
|
|
99
|
+
const handle = screen.getByLabelText(ariaLabel);
|
|
100
|
+
const item = handle.closest('[aria-roledescription]') as HTMLElement;
|
|
101
|
+
const isShifted = (element: Element): boolean =>
|
|
102
|
+
/translate3d\(0px, -?[1-9]/.test((element as HTMLElement).style.transform);
|
|
103
|
+
|
|
104
|
+
handle.focus();
|
|
105
|
+
await userEvent.keyboard(' ');
|
|
106
|
+
await waitFor(() => {
|
|
107
|
+
expect(item).toHaveAttribute('aria-pressed', 'true');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
await userEvent.keyboard(direction === 'down' ? '{ArrowDown}' : '{ArrowUp}');
|
|
111
|
+
await waitFor(() => {
|
|
112
|
+
expect(
|
|
113
|
+
Array.from(document.querySelectorAll('[aria-roledescription]')).some(
|
|
114
|
+
(other) => other !== item && isShifted(other)
|
|
115
|
+
)
|
|
116
|
+
).toBe(true);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
await userEvent.keyboard(' ');
|
|
120
|
+
await waitFor(() => {
|
|
121
|
+
expect(document.querySelector('[aria-pressed="true"]')).toBeNull();
|
|
122
|
+
});
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const getItemTexts = (): Array<string | null> =>
|
|
126
|
+
Array.from(document.querySelectorAll('[aria-label^="sort-"]')).map(
|
|
127
|
+
(handle) => handle.parentElement?.textContent ?? null
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const getSaveButton = (): HTMLElement => screen.getByLabelText('save button');
|
|
131
|
+
|
|
132
|
+
const expectFormButtons = (): void => {
|
|
133
|
+
expect(screen.getByRole('button', { name: 'Reset' })).toBeVisible();
|
|
134
|
+
expect(getSaveButton()).toBeVisible();
|
|
135
|
+
expect(getSaveButton()).toHaveTextContent('Save');
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
describe('Form list', () => {
|
|
139
|
+
beforeEach(() => {
|
|
140
|
+
expect.hasAssertions();
|
|
141
|
+
});
|
|
142
|
+
beforeEach(initializeFormList);
|
|
143
|
+
|
|
144
|
+
it('adds an element to the list', async () => {
|
|
145
|
+
expect(await screen.findByText('Add an item to the list')).toBeVisible();
|
|
146
|
+
expect(screen.getByText('Sort items')).toBeVisible();
|
|
147
|
+
|
|
148
|
+
await userEvent.click(screen.getByText('Add item'));
|
|
149
|
+
|
|
150
|
+
expect(await screen.findByLabelText('sort-0')).toBeVisible();
|
|
151
|
+
expect(screen.getByLabelText('delete-0')).toBeVisible();
|
|
152
|
+
expect(await screen.findByText('Lavinia (Wiegand LLC)')).toBeVisible();
|
|
153
|
+
|
|
154
|
+
expect(getItemTexts()).toEqual(['Lavinia (Wiegand LLC)']);
|
|
155
|
+
expectFormButtons();
|
|
156
|
+
await waitFor(() => {
|
|
157
|
+
expect(getSaveButton()).toBeEnabled();
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('sorts elements in the list', async () => {
|
|
162
|
+
expect(await screen.findByText('Add an item to the list')).toBeVisible();
|
|
163
|
+
expect(screen.getByText('Sort items')).toBeVisible();
|
|
164
|
+
|
|
165
|
+
await userEvent.click(screen.getByText('Add item'));
|
|
166
|
+
await userEvent.click(screen.getByText('Add item'));
|
|
167
|
+
|
|
168
|
+
expect(await screen.findByLabelText('sort-0')).toBeVisible();
|
|
169
|
+
expect(screen.getByLabelText('delete-0')).toBeVisible();
|
|
170
|
+
expect(await screen.findByText('Sammie (Crist - Beer)')).toBeVisible();
|
|
171
|
+
expect(await screen.findByLabelText('sort-1')).toBeVisible();
|
|
172
|
+
expect(screen.getByLabelText('delete-1')).toBeVisible();
|
|
173
|
+
expect(await screen.findByText('Waino (Quigley Group)')).toBeVisible();
|
|
174
|
+
|
|
175
|
+
expect(getItemTexts()).toEqual([
|
|
176
|
+
'Sammie (Crist - Beer)',
|
|
177
|
+
'Waino (Quigley Group)'
|
|
178
|
+
]);
|
|
179
|
+
|
|
180
|
+
await moveSortableElementUsingAriaLabel({
|
|
181
|
+
ariaLabel: 'sort-0',
|
|
182
|
+
direction: 'down'
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
expect(await screen.findByText('Waino (Quigley Group)')).toBeVisible();
|
|
186
|
+
expect(screen.getAllByText('Sammie (Crist - Beer)')[0]).toBeVisible();
|
|
187
|
+
|
|
188
|
+
// The moved item is below the other one.
|
|
189
|
+
await waitFor(() => {
|
|
190
|
+
expect(getItemTexts()).toEqual([
|
|
191
|
+
'Waino (Quigley Group)',
|
|
192
|
+
'Sammie (Crist - Beer)'
|
|
193
|
+
]);
|
|
194
|
+
});
|
|
195
|
+
expectFormButtons();
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('removes an element from the list', async () => {
|
|
199
|
+
expect(await screen.findByText('Add an item to the list')).toBeVisible();
|
|
200
|
+
expect(screen.getByText('Sort items')).toBeVisible();
|
|
201
|
+
|
|
202
|
+
await userEvent.click(screen.getByText('Add item'));
|
|
203
|
+
await userEvent.click(screen.getByText('Add item'));
|
|
204
|
+
|
|
205
|
+
expect(await screen.findByLabelText('sort-0')).toBeVisible();
|
|
206
|
+
expect(screen.getByLabelText('delete-0')).toBeVisible();
|
|
207
|
+
expect(
|
|
208
|
+
await screen.findByText('Elliott (Effertz, Deckow and Deckow)')
|
|
209
|
+
).toBeVisible();
|
|
210
|
+
expect(await screen.findByLabelText('sort-1')).toBeVisible();
|
|
211
|
+
expect(screen.getByLabelText('delete-1')).toBeVisible();
|
|
212
|
+
expect(await screen.findByText('Leopoldo (Kemmer Inc)')).toBeVisible();
|
|
213
|
+
|
|
214
|
+
await userEvent.click(screen.getByLabelText('delete-0'));
|
|
215
|
+
|
|
216
|
+
await waitFor(() => {
|
|
217
|
+
expect(
|
|
218
|
+
screen.queryByText('Elliott (Effertz, Deckow and Deckow)')
|
|
219
|
+
).not.toBeInTheDocument();
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
expect(getItemTexts()).toEqual(['Leopoldo (Kemmer Inc)']);
|
|
223
|
+
expectFormButtons();
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// The rstest viewport (1000px tall) fits the whole form: the spacer keeps the
|
|
228
|
+
// page scrollable for the scroll-to-section test.
|
|
229
|
+
const initializeFormWithSections = (): void => {
|
|
230
|
+
render(
|
|
231
|
+
<>
|
|
232
|
+
<Form
|
|
233
|
+
groups={[
|
|
234
|
+
{
|
|
235
|
+
name: 'First group',
|
|
236
|
+
order: 1
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
name: 'Third group',
|
|
240
|
+
order: 3
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
name: 'Second group',
|
|
244
|
+
order: 2
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
name: 'Fourth group',
|
|
248
|
+
order: 4
|
|
249
|
+
}
|
|
250
|
+
]}
|
|
251
|
+
initialValues={{
|
|
252
|
+
list: []
|
|
253
|
+
}}
|
|
254
|
+
inputs={[
|
|
255
|
+
{
|
|
256
|
+
fieldName: 'First name',
|
|
257
|
+
group: 'First group',
|
|
258
|
+
label: 'Name',
|
|
259
|
+
type: InputType.Text
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
fieldName: 'Divider',
|
|
263
|
+
group: 'First group',
|
|
264
|
+
label: 'Divider',
|
|
265
|
+
type: InputType.Divider
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
fieldName: 'Second name',
|
|
269
|
+
group: 'First group',
|
|
270
|
+
label: 'Name',
|
|
271
|
+
type: InputType.Text
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
fieldName: 'Third name',
|
|
275
|
+
group: 'First group',
|
|
276
|
+
label: 'Name',
|
|
277
|
+
type: InputType.Text
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
fieldName: 'Fourth name',
|
|
281
|
+
group: 'First group',
|
|
282
|
+
label: 'Name',
|
|
283
|
+
type: InputType.Text
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
fieldName: 'Fifth name',
|
|
287
|
+
group: 'First group',
|
|
288
|
+
label: 'Name',
|
|
289
|
+
type: InputType.Text
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
fieldName: 'Sixth name',
|
|
293
|
+
group: 'First group',
|
|
294
|
+
label: 'Name',
|
|
295
|
+
type: InputType.Text
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
fieldName: 'Seventh name',
|
|
299
|
+
group: 'First group',
|
|
300
|
+
label: 'Name',
|
|
301
|
+
type: InputType.Text
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
fieldName: 'Eighth name',
|
|
305
|
+
group: 'First group',
|
|
306
|
+
label: 'Name',
|
|
307
|
+
type: InputType.Text
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
fieldName: 'Ninth name',
|
|
311
|
+
group: 'First group',
|
|
312
|
+
label: 'Name',
|
|
313
|
+
type: InputType.Text
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
fieldName: 'First second group name',
|
|
317
|
+
group: 'Second group',
|
|
318
|
+
label: 'Name',
|
|
319
|
+
type: InputType.Text
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
fieldName: 'First third group name',
|
|
323
|
+
group: 'Third group',
|
|
324
|
+
label: 'Name',
|
|
325
|
+
type: InputType.Text
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
fieldName: 'First fourth group name',
|
|
329
|
+
group: 'Fourth group',
|
|
330
|
+
label: 'Name',
|
|
331
|
+
type: InputType.Text
|
|
332
|
+
}
|
|
333
|
+
]}
|
|
334
|
+
isCollapsible
|
|
335
|
+
submit={rs.fn()}
|
|
336
|
+
validationSchema={object()}
|
|
337
|
+
/>
|
|
338
|
+
<div style={{ height: 1000 }} />
|
|
339
|
+
</>
|
|
340
|
+
);
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
const initializeFile = (): void => {
|
|
344
|
+
render(
|
|
345
|
+
<Form
|
|
346
|
+
initialValues={{
|
|
347
|
+
list: []
|
|
348
|
+
}}
|
|
349
|
+
inputs={[
|
|
350
|
+
{
|
|
351
|
+
fieldName: 'list',
|
|
352
|
+
// No custom drop zone content: the default one is shown.
|
|
353
|
+
file: {
|
|
354
|
+
accept: '.json'
|
|
355
|
+
} as InputProps['file'],
|
|
356
|
+
group: '',
|
|
357
|
+
label: 'json',
|
|
358
|
+
type: InputType.File
|
|
359
|
+
}
|
|
360
|
+
]}
|
|
361
|
+
submit={rs.fn()}
|
|
362
|
+
validationSchema={object()}
|
|
363
|
+
/>
|
|
364
|
+
);
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
describe('File', () => {
|
|
368
|
+
beforeEach(() => {
|
|
369
|
+
expect.hasAssertions();
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it('uploads a file when a file is selected', async () => {
|
|
373
|
+
initializeFile();
|
|
374
|
+
|
|
375
|
+
expect(await screen.findByText('Drop or select a file')).toBeVisible();
|
|
376
|
+
await userEvent.upload(
|
|
377
|
+
screen.getByLabelText('select a file'),
|
|
378
|
+
new File(['{}'], 'package.json', { type: 'application/json' })
|
|
379
|
+
);
|
|
380
|
+
expect(await screen.findByText('package.json')).toBeVisible();
|
|
381
|
+
|
|
382
|
+
// The field label above the drop zone, and the selected
|
|
383
|
+
// file listed with a file icon.
|
|
384
|
+
expect(screen.getByRole('heading', { name: 'json' })).toBeVisible();
|
|
385
|
+
expect(
|
|
386
|
+
within(
|
|
387
|
+
screen.getByText('package.json').parentElement as HTMLElement
|
|
388
|
+
).getByTestId('DescriptionOutlinedIcon')
|
|
389
|
+
).toBeVisible();
|
|
390
|
+
});
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
describe('Form with sections', () => {
|
|
394
|
+
beforeEach(() => {
|
|
395
|
+
expect.hasAssertions();
|
|
396
|
+
});
|
|
397
|
+
beforeEach(initializeFormWithSections);
|
|
398
|
+
|
|
399
|
+
it('displays sections when correct amount of sections', async () => {
|
|
400
|
+
expect((await screen.findAllByText('First group'))[0]).toBeVisible();
|
|
401
|
+
expect(screen.getAllByText('Second group')[0]).toBeVisible();
|
|
402
|
+
expect(screen.getAllByText('Third group')[0]).toBeVisible();
|
|
403
|
+
expect(screen.getAllByText('Fourth group')[0]).toBeVisible();
|
|
404
|
+
|
|
405
|
+
// The section tabs sorted by group order, the first one
|
|
406
|
+
// selected, and the first group expanded with its nine fields.
|
|
407
|
+
expect(screen.getAllByRole('tab').map((tab) => tab.textContent)).toEqual([
|
|
408
|
+
'First group',
|
|
409
|
+
'Second group',
|
|
410
|
+
'Third group',
|
|
411
|
+
'Fourth group'
|
|
412
|
+
]);
|
|
413
|
+
expect(screen.getByRole('tab', { name: 'First group' })).toHaveAttribute(
|
|
414
|
+
'aria-selected',
|
|
415
|
+
'true'
|
|
416
|
+
);
|
|
417
|
+
expect(
|
|
418
|
+
screen
|
|
419
|
+
.getAllByLabelText('Name')
|
|
420
|
+
.filter((input) => input.tagName === 'INPUT')
|
|
421
|
+
.filter(
|
|
422
|
+
(input) =>
|
|
423
|
+
input.checkVisibility() &&
|
|
424
|
+
getComputedStyle(input).visibility !== 'hidden'
|
|
425
|
+
)
|
|
426
|
+
).toHaveLength(9);
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
it('scrolls correctly to section', async () => {
|
|
430
|
+
const initialScrollY = window.scrollY;
|
|
431
|
+
|
|
432
|
+
await userEvent.click(
|
|
433
|
+
await screen.findByRole('tab', { name: 'Third group' })
|
|
434
|
+
);
|
|
435
|
+
|
|
436
|
+
await waitFor(() => {
|
|
437
|
+
expect(window.scrollY).toBeGreaterThan(initialScrollY);
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
expect(screen.getByRole('tab', { name: 'Third group' })).toHaveAttribute(
|
|
441
|
+
'aria-selected',
|
|
442
|
+
'true'
|
|
443
|
+
);
|
|
444
|
+
});
|
|
445
|
+
});
|