@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
|
@@ -0,0 +1,567 @@
|
|
|
1
|
+
import { render } from '@test-setup/render';
|
|
2
|
+
import {
|
|
3
|
+
cleanup,
|
|
4
|
+
fireEvent,
|
|
5
|
+
screen,
|
|
6
|
+
waitFor,
|
|
7
|
+
within
|
|
8
|
+
} from '@testing-library/react';
|
|
9
|
+
import userEvent from '@testing-library/user-event';
|
|
10
|
+
|
|
11
|
+
import { standardMacros } from './plugins/ToolbarPlugin/MacrosButton';
|
|
12
|
+
import RichTextEditor from './RichTextEditor';
|
|
13
|
+
import { labelAlignPicker } from './translatedLabels';
|
|
14
|
+
|
|
15
|
+
interface CheckElementStyleOnRichTextEditorProps {
|
|
16
|
+
check: boolean;
|
|
17
|
+
element: HTMLElement;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const mockInititalStateToEditor =
|
|
21
|
+
'{"root": {"children": [{"children": [{"detail": 0,"format": 0,"mode": "normal","style": "","text": "test Cypress","type": "text","version": 1}],"direction": "ltr","format": "","indent": 0,"type": "paragraph","version": 1}],"direction": "ltr","format": "","indent": 0,"type": "root","version": 1}}';
|
|
22
|
+
|
|
23
|
+
const getEditor = (namespace = 'RichTextEditor'): HTMLElement =>
|
|
24
|
+
screen.getByTestId(namespace);
|
|
25
|
+
|
|
26
|
+
// The first `tag` element, in document order, whose text contains `text`.
|
|
27
|
+
const getElementContaining = (
|
|
28
|
+
container: HTMLElement,
|
|
29
|
+
tag: string,
|
|
30
|
+
text: string
|
|
31
|
+
): HTMLElement | undefined =>
|
|
32
|
+
Array.from(container.querySelectorAll<HTMLElement>(tag)).find((element) =>
|
|
33
|
+
element.textContent?.includes(text)
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const checkElementStyleOnRichTextEditor = async ({
|
|
37
|
+
element,
|
|
38
|
+
check
|
|
39
|
+
}: CheckElementStyleOnRichTextEditorProps): Promise<void> => {
|
|
40
|
+
expect(element).toBeVisible();
|
|
41
|
+
|
|
42
|
+
if (check) {
|
|
43
|
+
await waitFor(() => {
|
|
44
|
+
const strong = getElementContaining(element, 'strong', 'cypress test');
|
|
45
|
+
|
|
46
|
+
expect(strong).toBeDefined();
|
|
47
|
+
expect(getComputedStyle(strong as HTMLElement).fontStyle).toMatch(
|
|
48
|
+
/italic/
|
|
49
|
+
);
|
|
50
|
+
expect(getComputedStyle(strong as HTMLElement).textDecoration).toMatch(
|
|
51
|
+
/underline line-through/
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
await waitFor(() => {
|
|
59
|
+
const paragraph = getElementContaining(element, 'p', 'cypress test');
|
|
60
|
+
|
|
61
|
+
expect(paragraph).toBeDefined();
|
|
62
|
+
expect(getComputedStyle(paragraph as HTMLElement).fontStyle).not.toMatch(
|
|
63
|
+
/italic/
|
|
64
|
+
);
|
|
65
|
+
expect(
|
|
66
|
+
getComputedStyle(paragraph as HTMLElement).textDecoration
|
|
67
|
+
).not.toMatch(/underline line-through/);
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// Selects the whole content, as `.focus().type('{selectAll}')`.
|
|
72
|
+
const selectAll = async (editor: HTMLElement): Promise<void> => {
|
|
73
|
+
editor.focus();
|
|
74
|
+
await userEvent.keyboard('{Control>}a{/Control}');
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// A menu item click closes the menu: wait for it to be gone before the next
|
|
78
|
+
// one, so the queries do not pick an item of the closing menu.
|
|
79
|
+
const waitForMenusToClose = async (): Promise<void> => {
|
|
80
|
+
await waitFor(() => {
|
|
81
|
+
expect(screen.queryByRole('menu')).not.toBeInTheDocument();
|
|
82
|
+
});
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const clickMenuEntry = async (
|
|
86
|
+
menuLabel: string,
|
|
87
|
+
entryLabel: string
|
|
88
|
+
): Promise<void> => {
|
|
89
|
+
await userEvent.click(screen.getByLabelText(menuLabel));
|
|
90
|
+
await userEvent.click(await screen.findByLabelText(entryLabel));
|
|
91
|
+
await waitForMenusToClose();
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const insertLink = async (editor: HTMLElement): Promise<void> => {
|
|
95
|
+
await userEvent.click(screen.getByLabelText('link'));
|
|
96
|
+
|
|
97
|
+
const savedLink = await screen.findByLabelText('Saved link');
|
|
98
|
+
|
|
99
|
+
expect(savedLink).toHaveTextContent(/^https:\/\/$/);
|
|
100
|
+
|
|
101
|
+
// The link editor opens 30 px under the selected text, not where its
|
|
102
|
+
// position starts (0, 0).
|
|
103
|
+
const linkEditor = savedLink.closest('.MuiPaper-root') as HTMLElement;
|
|
104
|
+
const text = screen.getByText('cypress').getBoundingClientRect();
|
|
105
|
+
const root = editor.getBoundingClientRect();
|
|
106
|
+
|
|
107
|
+
await waitFor(() => {
|
|
108
|
+
const { m41: x, m42: y } = new DOMMatrix(
|
|
109
|
+
getComputedStyle(linkEditor).transform
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
expect(Math.abs(x - (text.x - root.x))).toBeLessThan(1);
|
|
113
|
+
expect(Math.abs(y - (text.bottom - root.y + 30))).toBeLessThan(1);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
await userEvent.click(screen.getByLabelText('Edit link'));
|
|
117
|
+
|
|
118
|
+
const input = await waitFor(() => {
|
|
119
|
+
const field = document.querySelector<HTMLInputElement>('#InputLinkField');
|
|
120
|
+
|
|
121
|
+
expect(field).not.toBeNull();
|
|
122
|
+
|
|
123
|
+
return field as HTMLInputElement;
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
await userEvent.type(input, 'www.centreon.com');
|
|
127
|
+
await userEvent.type(input, '{Enter}');
|
|
128
|
+
|
|
129
|
+
expect(editor).toBeInTheDocument();
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
describe('Rich Text Editor', () => {
|
|
133
|
+
beforeEach(() => {
|
|
134
|
+
expect.hasAssertions();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe('Editable Rich Text Editor', () => {
|
|
138
|
+
beforeEach(() => {
|
|
139
|
+
render(<RichTextEditor editable />);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('displays all elements when RichTextEditor is called with required props', async () => {
|
|
143
|
+
expect(await screen.findByLabelText('Undo')).toBeVisible();
|
|
144
|
+
expect(screen.getByLabelText('Redo')).toBeVisible();
|
|
145
|
+
expect(screen.getByLabelText('align picker')).toBeVisible();
|
|
146
|
+
expect(screen.getByLabelText('block')).toBeVisible();
|
|
147
|
+
expect(screen.getByLabelText('format')).toBeVisible();
|
|
148
|
+
expect(screen.getByLabelText('link')).toBeVisible();
|
|
149
|
+
expect(screen.getByLabelText('list')).toBeVisible();
|
|
150
|
+
|
|
151
|
+
const editor = screen.getByLabelText('RichTextEditor');
|
|
152
|
+
|
|
153
|
+
expect(editor).toBeVisible();
|
|
154
|
+
expect(editor).toHaveTextContent(/^$/);
|
|
155
|
+
|
|
156
|
+
expect(
|
|
157
|
+
within(editor.parentElement as HTMLElement).getByText('Type here...')
|
|
158
|
+
).toBeVisible();
|
|
159
|
+
|
|
160
|
+
// Nothing to undo nor redo yet, and no menu open.
|
|
161
|
+
expect(screen.getByLabelText('Undo')).toBeDisabled();
|
|
162
|
+
expect(screen.getByLabelText('Redo')).toBeDisabled();
|
|
163
|
+
expect(screen.queryByRole('menu')).not.toBeInTheDocument();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('displays all format actions when the format button is clicked', async () => {
|
|
167
|
+
await userEvent.click(screen.getByLabelText('format'));
|
|
168
|
+
|
|
169
|
+
expect(await screen.findByLabelText('bold')).toBeVisible();
|
|
170
|
+
expect(screen.getByLabelText('italic')).toBeVisible();
|
|
171
|
+
expect(screen.getByLabelText('underline')).toBeVisible();
|
|
172
|
+
expect(screen.getByLabelText('strikethrough')).toBeVisible();
|
|
173
|
+
|
|
174
|
+
// The four actions in that order, in the menu of the highlighted format
|
|
175
|
+
// button, none of them active.
|
|
176
|
+
const items = within(screen.getByRole('menu')).getAllByRole('menuitem');
|
|
177
|
+
|
|
178
|
+
expect(
|
|
179
|
+
items.map((item) =>
|
|
180
|
+
item.querySelector('svg')?.getAttribute('aria-label')
|
|
181
|
+
)
|
|
182
|
+
).toEqual(['bold', 'italic', 'underline', 'strikethrough']);
|
|
183
|
+
items.forEach((item) => {
|
|
184
|
+
expect(item).toHaveAttribute('data-is-active', 'false');
|
|
185
|
+
});
|
|
186
|
+
expect(screen.getByLabelText('format')).toHaveAttribute(
|
|
187
|
+
'data-is-active',
|
|
188
|
+
'true'
|
|
189
|
+
);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('displays changes with undo redo buttons', async () => {
|
|
193
|
+
const editor = getEditor();
|
|
194
|
+
|
|
195
|
+
await userEvent.type(editor, 'cypress test');
|
|
196
|
+
await waitFor(() => {
|
|
197
|
+
expect(editor).toHaveTextContent(/^cypress test$/);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
await userEvent.click(screen.getByLabelText('Undo'));
|
|
201
|
+
|
|
202
|
+
await waitFor(() => {
|
|
203
|
+
expect(editor).toHaveTextContent(/^$/);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
await userEvent.click(screen.getByLabelText('Redo'));
|
|
207
|
+
|
|
208
|
+
await waitFor(() => {
|
|
209
|
+
expect(editor).toHaveTextContent(/^cypress test$/);
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('displays changes with bold, italic, underline and strikethrough buttons', async () => {
|
|
214
|
+
const editor = getEditor();
|
|
215
|
+
|
|
216
|
+
await userEvent.type(editor, 'cypress test');
|
|
217
|
+
|
|
218
|
+
await selectAll(editor);
|
|
219
|
+
|
|
220
|
+
await clickMenuEntry('format', 'bold');
|
|
221
|
+
await clickMenuEntry('format', 'italic');
|
|
222
|
+
await clickMenuEntry('format', 'underline');
|
|
223
|
+
await clickMenuEntry('format', 'strikethrough');
|
|
224
|
+
|
|
225
|
+
await checkElementStyleOnRichTextEditor({
|
|
226
|
+
check: true,
|
|
227
|
+
element: getEditor()
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
await clickMenuEntry('format', 'bold');
|
|
231
|
+
await clickMenuEntry('format', 'italic');
|
|
232
|
+
await clickMenuEntry('format', 'underline');
|
|
233
|
+
await clickMenuEntry('format', 'strikethrough');
|
|
234
|
+
|
|
235
|
+
await checkElementStyleOnRichTextEditor({
|
|
236
|
+
check: false,
|
|
237
|
+
element: getEditor()
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('adds link with an url when link button is clicked', async () => {
|
|
242
|
+
const editor = getEditor();
|
|
243
|
+
|
|
244
|
+
await userEvent.type(editor, 'cypress');
|
|
245
|
+
await selectAll(editor);
|
|
246
|
+
|
|
247
|
+
await insertLink(editor);
|
|
248
|
+
|
|
249
|
+
await waitFor(() => {
|
|
250
|
+
const link = screen.getByText('cypress').parentElement as HTMLElement;
|
|
251
|
+
|
|
252
|
+
expect(link.getAttribute('href')).toMatch(
|
|
253
|
+
/^https:\/\/www.centreon.com/
|
|
254
|
+
);
|
|
255
|
+
expect(link.getAttribute('rel')).toMatch(/^noreferrer/);
|
|
256
|
+
expect(link.getAttribute('target')).toMatch(/_blank/);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
await selectAll(editor);
|
|
260
|
+
|
|
261
|
+
await userEvent.click(screen.getByLabelText('link'));
|
|
262
|
+
|
|
263
|
+
await waitFor(() => {
|
|
264
|
+
const element = screen.getByText('cypress')
|
|
265
|
+
.parentElement as HTMLElement;
|
|
266
|
+
|
|
267
|
+
expect(
|
|
268
|
+
getElementContaining(
|
|
269
|
+
element.parentElement as HTMLElement,
|
|
270
|
+
'p',
|
|
271
|
+
'cypress'
|
|
272
|
+
)
|
|
273
|
+
).not.toHaveAttribute('href');
|
|
274
|
+
expect(element).not.toHaveAttribute('rel');
|
|
275
|
+
expect(element).not.toHaveAttribute('target');
|
|
276
|
+
expect(screen.getByText('cypress').closest('a')).toBeNull();
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it('does not allow to open links in a new tab', async () => {
|
|
281
|
+
cleanup();
|
|
282
|
+
render(<RichTextEditor editable openLinkInNewTab={false} />);
|
|
283
|
+
|
|
284
|
+
const editor = getEditor();
|
|
285
|
+
|
|
286
|
+
await userEvent.type(editor, 'cypress');
|
|
287
|
+
await selectAll(editor);
|
|
288
|
+
|
|
289
|
+
await insertLink(editor);
|
|
290
|
+
|
|
291
|
+
await waitFor(() => {
|
|
292
|
+
const link = screen.getByText('cypress').parentElement as HTMLElement;
|
|
293
|
+
|
|
294
|
+
expect(link).toHaveAttribute('href');
|
|
295
|
+
});
|
|
296
|
+
expect(screen.getByText('cypress').parentElement).not.toHaveAttribute(
|
|
297
|
+
'target'
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
editor.focus();
|
|
301
|
+
await userEvent.clear(editor);
|
|
302
|
+
|
|
303
|
+
editor.focus();
|
|
304
|
+
await userEvent.type(editor, 'https://centreon.com');
|
|
305
|
+
|
|
306
|
+
const autoLink = (await screen.findByText('https://centreon.com'))
|
|
307
|
+
.parentElement;
|
|
308
|
+
|
|
309
|
+
expect(autoLink).toHaveAttribute('href');
|
|
310
|
+
expect(autoLink).not.toHaveAttribute('target');
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
describe('Rich Text Editor not editable', () => {
|
|
315
|
+
beforeEach(() => {
|
|
316
|
+
render(
|
|
317
|
+
<RichTextEditor
|
|
318
|
+
editable={false}
|
|
319
|
+
initialEditorState={mockInititalStateToEditor}
|
|
320
|
+
/>
|
|
321
|
+
);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
it('displays editor when editable props is false and an initialState exist', () => {
|
|
325
|
+
expect(screen.queryByLabelText('Undo')).not.toBeInTheDocument();
|
|
326
|
+
expect(screen.queryByLabelText('Redo')).not.toBeInTheDocument();
|
|
327
|
+
expect(screen.queryByLabelText(labelAlignPicker)).not.toBeInTheDocument();
|
|
328
|
+
expect(screen.queryByLabelText('block')).not.toBeInTheDocument();
|
|
329
|
+
expect(screen.queryByLabelText('format')).not.toBeInTheDocument();
|
|
330
|
+
expect(screen.queryByLabelText('link')).not.toBeInTheDocument();
|
|
331
|
+
expect(screen.queryByLabelText('list')).not.toBeInTheDocument();
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
describe('Rich Text Editor is disabled', () => {
|
|
336
|
+
beforeEach(() => {
|
|
337
|
+
render(
|
|
338
|
+
<RichTextEditor
|
|
339
|
+
disabled
|
|
340
|
+
editable
|
|
341
|
+
initialEditorState={mockInititalStateToEditor}
|
|
342
|
+
/>
|
|
343
|
+
);
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
it('displays editor when editable props is false and an initialState exist', async () => {
|
|
347
|
+
expect(await screen.findByLabelText('Undo')).toBeDisabled();
|
|
348
|
+
expect(screen.getByLabelText('Redo')).toBeDisabled();
|
|
349
|
+
expect(screen.getByLabelText(labelAlignPicker)).toBeDisabled();
|
|
350
|
+
expect(screen.getByLabelText('block')).toBeDisabled();
|
|
351
|
+
expect(screen.getByLabelText('format')).toBeDisabled();
|
|
352
|
+
expect(screen.getByLabelText('link')).toBeDisabled();
|
|
353
|
+
expect(screen.getByLabelText('list')).toBeDisabled();
|
|
354
|
+
});
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
describe('Custom style editable rich text editor ', () => {
|
|
358
|
+
beforeEach(() => {
|
|
359
|
+
render(
|
|
360
|
+
<RichTextEditor
|
|
361
|
+
editable
|
|
362
|
+
minInputHeight={200}
|
|
363
|
+
namespace="CypressComponentTest"
|
|
364
|
+
placeholder="Type Cypress test..."
|
|
365
|
+
/>
|
|
366
|
+
);
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
it('displays editor with custom namespace', async () => {
|
|
370
|
+
await waitFor(() => {
|
|
371
|
+
expect(
|
|
372
|
+
document.querySelector('[data-testid="CypressComponentTest"] > p')
|
|
373
|
+
).toBeInTheDocument();
|
|
374
|
+
});
|
|
375
|
+
expect(
|
|
376
|
+
document.querySelector('[data-testid="CypressComponentTest"]')
|
|
377
|
+
).toBeInTheDocument();
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it('displays editor with custom placeholder', async () => {
|
|
381
|
+
expect(
|
|
382
|
+
await within(
|
|
383
|
+
getEditor('CypressComponentTest').parentElement as HTMLElement
|
|
384
|
+
).findByText('Type Cypress test...')
|
|
385
|
+
).toBeInTheDocument();
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
it('displays editor with custom minimum input height', () => {
|
|
389
|
+
expect(
|
|
390
|
+
getComputedStyle(getEditor('CypressComponentTest')).minHeight
|
|
391
|
+
).toBe('200px');
|
|
392
|
+
});
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
describe('Rich Text Editor with Macros Plugin', () => {
|
|
396
|
+
beforeEach(() => {
|
|
397
|
+
render(<RichTextEditor displayMacrosButton editable />);
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
it('displays the Macros button when the "displayMacrosButton" prop is set to true', async () => {
|
|
401
|
+
await userEvent.click(screen.getByLabelText('Macros'));
|
|
402
|
+
|
|
403
|
+
for (const macro of standardMacros) {
|
|
404
|
+
expect(await screen.findByText(macro)).toBeVisible();
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
it('ensures that the selected macro is inserted into the RichTextEditor', async () => {
|
|
409
|
+
const editor = getEditor();
|
|
410
|
+
|
|
411
|
+
await userEvent.type(editor, 'macro : ');
|
|
412
|
+
|
|
413
|
+
for (const macro of standardMacros) {
|
|
414
|
+
await userEvent.click(screen.getByLabelText('Macros'));
|
|
415
|
+
// The macro is inserted with `document.execCommand`, at the page
|
|
416
|
+
// selection. `userEvent.click` would move that selection into the
|
|
417
|
+
// menu item, as a pointer down does: `fireEvent.click` leaves it in
|
|
418
|
+
// the editor.
|
|
419
|
+
fireEvent.click(await screen.findByText(macro));
|
|
420
|
+
|
|
421
|
+
await waitFor(() => {
|
|
422
|
+
expect(editor).toHaveTextContent(macro);
|
|
423
|
+
});
|
|
424
|
+
await waitForMenusToClose();
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
describe('Block type', () => {
|
|
430
|
+
beforeEach(() => {
|
|
431
|
+
render(<RichTextEditor editable />);
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
const testCases = [
|
|
435
|
+
{
|
|
436
|
+
blockType: 'Huge',
|
|
437
|
+
tag: 'h3'
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
blockType: 'Large',
|
|
441
|
+
tag: 'h5'
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
blockType: 'Normal',
|
|
445
|
+
tag: 'p'
|
|
446
|
+
},
|
|
447
|
+
{
|
|
448
|
+
blockType: 'Small',
|
|
449
|
+
tag: 'h6'
|
|
450
|
+
}
|
|
451
|
+
];
|
|
452
|
+
|
|
453
|
+
testCases.forEach(({ blockType, tag }) => {
|
|
454
|
+
it(`displays ${blockType} when the corresponding block type button is selected`, async () => {
|
|
455
|
+
const editor = getEditor();
|
|
456
|
+
|
|
457
|
+
await userEvent.type(editor, 'Example');
|
|
458
|
+
|
|
459
|
+
// The text starts as a paragraph: for Normal, it is made Huge first,
|
|
460
|
+
// so choosing Normal has something to change.
|
|
461
|
+
if (blockType === 'Normal') {
|
|
462
|
+
await userEvent.click(screen.getByLabelText('block'));
|
|
463
|
+
await userEvent.click(await screen.findByText('Huge'));
|
|
464
|
+
await waitFor(() => {
|
|
465
|
+
expect(editor.querySelector('h3')).toBeVisible();
|
|
466
|
+
});
|
|
467
|
+
await waitForMenusToClose();
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
await userEvent.click(screen.getByLabelText('block'));
|
|
471
|
+
await userEvent.click(await screen.findByText(blockType));
|
|
472
|
+
|
|
473
|
+
await waitFor(() => {
|
|
474
|
+
expect(editor.querySelector(tag)).toBeVisible();
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
// The text is in that block alone, the menu is closed
|
|
478
|
+
// and the change can be undone.
|
|
479
|
+
expect(editor.querySelector(tag)).toHaveTextContent(/^Example$/);
|
|
480
|
+
expect(editor.querySelectorAll('p, h3, h5, h6')).toHaveLength(1);
|
|
481
|
+
await waitForMenusToClose();
|
|
482
|
+
expect(screen.getByLabelText('Undo')).toBeEnabled();
|
|
483
|
+
});
|
|
484
|
+
});
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
describe('List type', () => {
|
|
488
|
+
beforeEach(() => {
|
|
489
|
+
render(<RichTextEditor editable />);
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
const testCases = [
|
|
493
|
+
{
|
|
494
|
+
icon: 'FormatListBulletedIcon',
|
|
495
|
+
listType: 'Unordered List',
|
|
496
|
+
tag: 'ul'
|
|
497
|
+
},
|
|
498
|
+
{
|
|
499
|
+
icon: 'FormatListNumberedIcon',
|
|
500
|
+
listType: 'Ordered List',
|
|
501
|
+
tag: 'ol'
|
|
502
|
+
}
|
|
503
|
+
];
|
|
504
|
+
|
|
505
|
+
testCases.forEach(({ icon, listType, tag }) => {
|
|
506
|
+
it(`displays ${listType} when the corresponding list type button is selected`, async () => {
|
|
507
|
+
const editor = getEditor();
|
|
508
|
+
|
|
509
|
+
await userEvent.type(editor, 'Example');
|
|
510
|
+
await userEvent.click(screen.getByLabelText('list'));
|
|
511
|
+
await userEvent.click(await screen.findByLabelText(listType));
|
|
512
|
+
|
|
513
|
+
await waitFor(() => {
|
|
514
|
+
expect(editor.querySelector(tag)).toBeVisible();
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
// The text is the list item, the menu is closed
|
|
518
|
+
// and the list button shows the icon of the selected list type.
|
|
519
|
+
expect(editor.querySelector(`${tag} > li`)).toHaveTextContent(
|
|
520
|
+
/^Example$/
|
|
521
|
+
);
|
|
522
|
+
await waitForMenusToClose();
|
|
523
|
+
await waitFor(() => {
|
|
524
|
+
expect(
|
|
525
|
+
within(screen.getByLabelText('list')).getByTestId(icon)
|
|
526
|
+
).toBeInTheDocument();
|
|
527
|
+
});
|
|
528
|
+
});
|
|
529
|
+
});
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
describe('Aligns', () => {
|
|
533
|
+
beforeEach(() => {
|
|
534
|
+
render(<RichTextEditor editable />);
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
const testCases = ['Center', 'Right', 'Left'];
|
|
538
|
+
|
|
539
|
+
testCases.forEach((label) => {
|
|
540
|
+
it(`aligns the text to the ${label} when the corresponding button was clicked`, async () => {
|
|
541
|
+
await userEvent.type(getEditor(), 'Example');
|
|
542
|
+
|
|
543
|
+
await userEvent.click(screen.getByLabelText(labelAlignPicker));
|
|
544
|
+
await userEvent.click(await screen.findByLabelText(label));
|
|
545
|
+
|
|
546
|
+
await waitFor(() => {
|
|
547
|
+
expect(
|
|
548
|
+
getComputedStyle(
|
|
549
|
+
screen.getByText('Example').parentElement as HTMLElement
|
|
550
|
+
).textAlign
|
|
551
|
+
).toBe(label.toLowerCase());
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
// The menu is closed and the align picker shows
|
|
555
|
+
// the icon of the selected alignment.
|
|
556
|
+
await waitForMenusToClose();
|
|
557
|
+
await waitFor(() => {
|
|
558
|
+
expect(
|
|
559
|
+
within(screen.getByLabelText(labelAlignPicker)).getByTestId(
|
|
560
|
+
`FormatAlign${label}Icon`
|
|
561
|
+
)
|
|
562
|
+
).toBeInTheDocument();
|
|
563
|
+
});
|
|
564
|
+
});
|
|
565
|
+
});
|
|
566
|
+
});
|
|
567
|
+
});
|
|
@@ -122,7 +122,9 @@ const FloatingLinkEditor = ({
|
|
|
122
122
|
const nodeHeight = rangeRect.height;
|
|
123
123
|
|
|
124
124
|
setTooltipPosition({ x: nodeX, y: nodeY + nodeHeight });
|
|
125
|
-
|
|
125
|
+
// The coordinates, not `rangeRect`: it is a new object on every render, so
|
|
126
|
+
// depending on it re-ran this effect, whose state update re-rendered endlessly.
|
|
127
|
+
}, [rangeRect?.x, rangeRect?.y, rangeRect?.height, nativeSelection]);
|
|
126
128
|
|
|
127
129
|
if (isNil(rangeRect)) {
|
|
128
130
|
return null;
|