@eccenca/gui-elements 23.6.0-rc.2 → 23.7.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/CHANGELOG.md +38 -19
  2. package/dist/cjs/cmem/markdown/Markdown.js +10 -2
  3. package/dist/cjs/cmem/markdown/Markdown.js.map +1 -1
  4. package/dist/cjs/cmem/react-flow/StickyNoteModal/StickyNoteModal.js +4 -4
  5. package/dist/cjs/cmem/react-flow/StickyNoteModal/StickyNoteModal.js.map +1 -1
  6. package/dist/cjs/components/MultiSelect/MultiSelect.js +67 -41
  7. package/dist/cjs/components/MultiSelect/MultiSelect.js.map +1 -1
  8. package/dist/cjs/extensions/codemirror/CodeMirror.js +1 -1
  9. package/dist/cjs/extensions/codemirror/CodeMirror.js.map +1 -1
  10. package/dist/cjs/extensions/react-flow/edges/EdgeDefault.js +7 -7
  11. package/dist/cjs/extensions/react-flow/edges/EdgeDefault.js.map +1 -1
  12. package/dist/cjs/extensions/react-flow/edges/EdgeLabel.js +3 -2
  13. package/dist/cjs/extensions/react-flow/edges/EdgeLabel.js.map +1 -1
  14. package/dist/cjs/extensions/react-flow/handles/HandleContent.js +14 -2
  15. package/dist/cjs/extensions/react-flow/handles/HandleContent.js.map +1 -1
  16. package/dist/cjs/extensions/react-flow/handles/HandleTools.js +1 -1
  17. package/dist/cjs/extensions/react-flow/handles/HandleTools.js.map +1 -1
  18. package/dist/cjs/extensions/react-flow/minimap/MiniMap.js +6 -8
  19. package/dist/cjs/extensions/react-flow/minimap/MiniMap.js.map +1 -1
  20. package/dist/esm/cmem/markdown/Markdown.js +10 -2
  21. package/dist/esm/cmem/markdown/Markdown.js.map +1 -1
  22. package/dist/esm/cmem/react-flow/StickyNoteModal/StickyNoteModal.js +5 -5
  23. package/dist/esm/cmem/react-flow/StickyNoteModal/StickyNoteModal.js.map +1 -1
  24. package/dist/esm/components/MultiSelect/MultiSelect.js +73 -52
  25. package/dist/esm/components/MultiSelect/MultiSelect.js.map +1 -1
  26. package/dist/esm/extensions/codemirror/CodeMirror.js +1 -1
  27. package/dist/esm/extensions/codemirror/CodeMirror.js.map +1 -1
  28. package/dist/esm/extensions/react-flow/edges/EdgeDefault.js +7 -7
  29. package/dist/esm/extensions/react-flow/edges/EdgeDefault.js.map +1 -1
  30. package/dist/esm/extensions/react-flow/edges/EdgeLabel.js +3 -2
  31. package/dist/esm/extensions/react-flow/edges/EdgeLabel.js.map +1 -1
  32. package/dist/esm/extensions/react-flow/handles/HandleContent.js +13 -2
  33. package/dist/esm/extensions/react-flow/handles/HandleContent.js.map +1 -1
  34. package/dist/esm/extensions/react-flow/handles/HandleTools.js +1 -1
  35. package/dist/esm/extensions/react-flow/handles/HandleTools.js.map +1 -1
  36. package/dist/esm/extensions/react-flow/minimap/MiniMap.js +6 -8
  37. package/dist/esm/extensions/react-flow/minimap/MiniMap.js.map +1 -1
  38. package/dist/types/cmem/react-flow/StickyNoteModal/StickyNoteModal.d.ts +5 -0
  39. package/dist/types/cmem/react-flow/configuration/graph.d.ts +9 -9
  40. package/dist/types/cmem/react-flow/configuration/unspecified.d.ts +2 -2
  41. package/dist/types/components/MultiSelect/MultiSelect.d.ts +1 -1
  42. package/dist/types/extensions/react-flow/edges/EdgeDefault.d.ts +6 -1
  43. package/dist/types/extensions/react-flow/edges/EdgeLabel.d.ts +1 -1
  44. package/dist/types/extensions/react-flow/edges/edgeTypes.d.ts +10 -10
  45. package/dist/types/extensions/react-flow/handles/HandleContent.d.ts +2 -3
  46. package/dist/types/extensions/react-flow/minimap/MiniMap.d.ts +12 -1
  47. package/package.json +5 -2
  48. package/src/cmem/markdown/Markdown.stories.tsx +8 -1
  49. package/src/cmem/markdown/Markdown.tsx +22 -1
  50. package/src/cmem/react-flow/ReactFlow/ReactFlow.stories.tsx +10 -4
  51. package/src/cmem/react-flow/StickyNoteModal/StickyNoteModal.tsx +8 -3
  52. package/src/components/Card/card.scss +17 -5
  53. package/src/components/MultiSelect/MultiSelect.tsx +74 -47
  54. package/src/components/MultiSuggestField/MultiSuggestField.stories.tsx +70 -25
  55. package/src/components/MultiSuggestField/tests/MultiSuggestField.test.tsx +136 -14
  56. package/src/extensions/codemirror/CodeMirror.tsx +1 -1
  57. package/src/extensions/react-flow/edges/EdgeDefault.tsx +70 -62
  58. package/src/extensions/react-flow/edges/EdgeLabel.tsx +14 -2
  59. package/src/extensions/react-flow/edges/stories/EdgeDefault.stories.tsx +11 -5
  60. package/src/extensions/react-flow/edges/stories/EdgeLabel.stories.tsx +2 -0
  61. package/src/extensions/react-flow/handles/HandleContent.tsx +28 -25
  62. package/src/extensions/react-flow/handles/HandleTools.tsx +1 -0
  63. package/src/extensions/react-flow/handles/stories/HandleDefault.stories.tsx +5 -1
  64. package/src/extensions/react-flow/minimap/MiniMap.stories.tsx +62 -0
  65. package/src/extensions/react-flow/minimap/MiniMap.tsx +23 -7
  66. package/src/extensions/react-flow/nodes/stories/NodeContent.stories.tsx +2 -0
  67. package/src/extensions/react-flow/nodes/stories/NodeContentExtension.stories.tsx +2 -0
  68. package/src/extensions/react-flow/nodes/stories/NodeDefault.stories.tsx +0 -1
@@ -13,7 +13,7 @@ import { TestableComponent } from "../interfaces";
13
13
  import { ContextOverlayProps, Highlighter, IconButton, MenuItem, OverflowText, Spinner } from "./../../index";
14
14
 
15
15
  export interface MultiSelectSelectionProps<T> {
16
- newlySelected: T;
16
+ newlySelected?: T;
17
17
  selectedItems: T[];
18
18
  createdItems: Partial<T>[];
19
19
  }
@@ -136,7 +136,7 @@ export interface MultiSelectProps<T>
136
136
  */
137
137
  export function MultiSelect<T>({
138
138
  items,
139
- selectedItems: externalSelectedItems = [],
139
+ selectedItems: externalSelectedItems,
140
140
  prePopulateWithItems,
141
141
  itemId,
142
142
  itemLabel,
@@ -163,13 +163,17 @@ export function MultiSelect<T>({
163
163
  wrapperProps,
164
164
  ...otherMultiSelectProps
165
165
  }: MultiSelectProps<T>) {
166
- const [createdItems, setCreatedItems] = React.useState<T[]>([]);
167
- const [createdSelectedItems, setCreatedSelectedItems] = React.useState<T[]>([]);
168
- const [itemsCopy, setItemsCopy] = React.useState<T[]>([...items]);
169
- const [filteredItemList, setFilteredItemList] = React.useState<T[]>([]);
166
+ // Options created by a user
167
+ const createdItems = useRef<T[]>([]);
168
+ // Options passed ouside (f.e. from the backend)
169
+ const [externalItems, setExternalItems] = React.useState<T[]>([...items]);
170
+ // All options (created and passed) that match the query
171
+ const [filteredItems, setFilteredItems] = React.useState<T[]>([]);
172
+ // All options (created and passed) selected by a user, if the component is uncontrolled
170
173
  const [selectedItems, setSelectedItems] = React.useState<T[]>(() =>
171
- prePopulateWithItems ? [...items] : [...externalSelectedItems]
174
+ prePopulateWithItems ? [...items] : externalSelectedItems ? [...externalSelectedItems] : []
172
175
  );
176
+
173
177
  //currently focused element in popover list
174
178
  const [focusedItem, setFocusedItem] = React.useState<T | null>(null);
175
179
  const [showSpinner, setShowSpinner] = React.useState(false);
@@ -197,27 +201,45 @@ export function MultiSelect<T>({
197
201
  break;
198
202
  }
199
203
 
200
- /** update items copy when the items change
204
+ // If the component is contolled from outside, we don't need to store selected state within the component
205
+ // when user selects or removes selection - options will be set in a parent component
206
+ const isControlled = !!(externalSelectedItems && onSelection);
207
+
208
+ /** Update external items when they change
201
209
  * e.g for auto-complete when query change
202
210
  */
203
211
  React.useEffect(() => {
204
- setItemsCopy([...items, ...createdItems]);
205
- setFilteredItemList([...items, ...createdItems]);
212
+ setExternalItems(items);
213
+ setFilteredItems([...items, ...createdItems.current]);
206
214
  }, [items.map((item) => itemId(item)).join("|")]);
207
215
 
208
216
  React.useEffect(() => {
209
- onSelection &&
217
+ !isControlled &&
218
+ onSelection &&
210
219
  onSelection({
211
220
  newlySelected: selectedItems.slice(-1)[0],
212
- createdItems: createdSelectedItems,
221
+ createdItems: createdItems.current,
213
222
  selectedItems,
214
223
  });
215
224
  }, [
225
+ isControlled,
216
226
  onSelection,
217
227
  selectedItems.map((item) => itemId(item)).join("|"),
218
- createdSelectedItems.map((item) => itemId(item)).join("|"),
228
+ createdItems.current.map((item) => itemId(item)).join("|"),
219
229
  ]);
220
230
 
231
+ /**
232
+ * Update selected items if the component is controlled and we get
233
+ * new selected items from outside
234
+ */
235
+ React.useEffect(() => {
236
+ if (!isControlled) {
237
+ return;
238
+ }
239
+
240
+ setSelectedItems(externalSelectedItems);
241
+ }, [isControlled, externalSelectedItems]);
242
+
221
243
  /**
222
244
  * using the equality prop specified checks if an item has already been selected
223
245
  * @param matcher
@@ -232,7 +254,16 @@ export function MultiSelect<T>({
232
254
  * @param matcher
233
255
  */
234
256
  const removeItemSelection = (matcher: string) => {
235
- setSelectedItems((items) => items.filter((item) => itemId(item) !== matcher));
257
+ const filteredItems = selectedItems.filter((item) => itemId(item) !== matcher);
258
+
259
+ if (isControlled) {
260
+ onSelection({
261
+ createdItems: createdItems.current,
262
+ selectedItems: filteredItems,
263
+ });
264
+ } else {
265
+ setSelectedItems(filteredItems);
266
+ }
236
267
  };
237
268
 
238
269
  /**
@@ -243,24 +274,16 @@ export function MultiSelect<T>({
243
274
  const onItemSelect = (item: T) => {
244
275
  if (itemHasBeenSelectedAlready(itemId(item))) {
245
276
  removeItemSelection(itemId(item));
277
+ } else if (isControlled) {
278
+ onSelection({
279
+ newlySelected: item,
280
+ createdItems: createdItems.current,
281
+ selectedItems: [...selectedItems, item],
282
+ });
246
283
  } else {
247
284
  setSelectedItems((items) => [...items, item]);
248
285
  }
249
286
 
250
- //remove if already exist
251
- if (createdSelectedItems.find((t) => itemLabel(t) === itemLabel(item))) {
252
- setCreatedSelectedItems((prevItems) =>
253
- prevItems.filter((prevItem) => itemLabel(prevItem) !== itemLabel(item))
254
- );
255
- } else {
256
- const wasNewlyCreated = createdItems.find((t) => itemLabel(t) === itemLabel(item));
257
- //only add to createdSelectedItems if it was previously created and not
258
- // from the initial items or a possible query response
259
- if (wasNewlyCreated) {
260
- setCreatedSelectedItems((prevItems) => [...prevItems, item]);
261
- }
262
- }
263
-
264
287
  if (clearQueryOnSelection) {
265
288
  requestState.current.query = "";
266
289
  inputRef.current?.focus();
@@ -281,12 +304,13 @@ export function MultiSelect<T>({
281
304
  }
282
305
  const fn = async () => {
283
306
  setShowSpinner(true);
284
- setFilteredItemList([]);
307
+ setFilteredItems([]);
285
308
  const resultFromQuery = runOnQueryChange && (await runOnQueryChange(removeExtraSpaces(query)));
286
309
  if (requestState.current.query === query) {
287
310
  // Only use most recent request
288
- setFilteredItemList(() =>
289
- [...(resultFromQuery ?? itemsCopy), ...createdItems].filter((item) =>
311
+ const outsideOptions = [...(resultFromQuery ?? externalItems)];
312
+ setFilteredItems(
313
+ [...outsideOptions, ...createdItems.current].filter((item) =>
290
314
  itemLabel(item).toLowerCase().includes(query.toLowerCase())
291
315
  )
292
316
  );
@@ -297,7 +321,7 @@ export function MultiSelect<T>({
297
321
  } else if (!query.length) {
298
322
  // if the query is empty we need to show all options and reset current query
299
323
  requestState.current.query = "";
300
- setFilteredItemList(() => [...itemsCopy, ...createdItems]);
324
+ setFilteredItems(() => [...externalItems, ...createdItems.current]);
301
325
  }
302
326
  };
303
327
 
@@ -314,7 +338,7 @@ export function MultiSelect<T>({
314
338
  return null;
315
339
  }
316
340
  let label = itemLabel(item);
317
- if (createdItems.find((created) => itemId(created) === itemId(item))) {
341
+ if (createdItems.current.find((created) => itemId(created) === itemId(item))) {
318
342
  label += newItemPostfix;
319
343
  }
320
344
  return (
@@ -334,8 +358,17 @@ export function MultiSelect<T>({
334
358
  */
335
359
  const handleClear = () => {
336
360
  requestState.current.query = "";
337
- setSelectedItems([]);
338
- setFilteredItemList(itemsCopy);
361
+
362
+ if (isControlled) {
363
+ onSelection({
364
+ selectedItems: [],
365
+ createdItems: createdItems.current,
366
+ });
367
+ } else {
368
+ setSelectedItems([]);
369
+ }
370
+
371
+ setFilteredItems([...externalItems, ...createdItems.current]);
339
372
  };
340
373
 
341
374
  /**
@@ -343,9 +376,8 @@ export function MultiSelect<T>({
343
376
  * @param label
344
377
  * @param index
345
378
  */
346
- const removeTagFromSelectionViaIndex = (label: React.ReactNode, index: number) => {
379
+ const removeTagFromSelectionViaIndex = (_label: React.ReactNode, index: number) => {
347
380
  setSelectedItems([...selectedItems.slice(0, index), ...selectedItems.slice(index + 1)]);
348
- setCreatedSelectedItems((items) => items.filter((item) => itemLabel(item) !== label));
349
381
  };
350
382
 
351
383
  /**
@@ -354,9 +386,8 @@ export function MultiSelect<T>({
354
386
  const createNewItem = (query: string): T => {
355
387
  const newItem = createNewItemFromQuery!(query);
356
388
  //set new items
357
- setCreatedItems((items) => [...items, newItem]);
358
- setCreatedSelectedItems((items) => [...items, newItem]);
359
- setFilteredItemList((items) => [...items, newItem]);
389
+ createdItems.current = [...createdItems.current, newItem];
390
+ setFilteredItems((items) => [...items, newItem]);
360
391
  requestState.current.query = "";
361
392
  return newItem;
362
393
  };
@@ -366,12 +397,7 @@ export function MultiSelect<T>({
366
397
  * @param event
367
398
  */
368
399
  const handleOnKeyUp = (event: React.KeyboardEvent<HTMLElement>) => {
369
- if (
370
- event.key === "Enter" &&
371
- !filteredItemList.length &&
372
- !!requestState.current.query &&
373
- createNewItemFromQuery
374
- ) {
400
+ if (event.key === "Enter" && !filteredItems.length && !!requestState.current.query && createNewItemFromQuery) {
375
401
  createNewItem(requestState.current.query);
376
402
  }
377
403
  inputRef.current?.focus();
@@ -438,7 +464,7 @@ export function MultiSelect<T>({
438
464
  {...otherMultiSelectProps}
439
465
  query={requestState.current.query}
440
466
  onQueryChange={onQueryChange}
441
- items={filteredItemList}
467
+ items={filteredItems}
442
468
  onItemSelect={onItemSelect}
443
469
  itemRenderer={onItemRenderer}
444
470
  itemsEqual={(a: T, b: T) => itemId(a) === itemId(b)}
@@ -449,6 +475,7 @@ export function MultiSelect<T>({
449
475
  onActiveItemChange={(activeItem) => setFocusedItem(activeItem)}
450
476
  fill={fullWidth}
451
477
  createNewItemFromQuery={createNewItemFromQuery}
478
+ disabled={disabled}
452
479
  tagInputProps={{
453
480
  inputProps: {
454
481
  id: "item",
@@ -4,22 +4,6 @@ import { Meta, StoryFn } from "@storybook/react";
4
4
 
5
5
  import { MultiSelectSelectionProps, MultiSuggestField } from "./../../../index";
6
6
 
7
- export default {
8
- title: "Forms/MultiSuggestField",
9
- component: MultiSuggestField,
10
- argTypes: {
11
- items: {
12
- control: "none",
13
- },
14
- },
15
- } as Meta<typeof MultiSuggestField>;
16
-
17
- const Template: StoryFn<typeof MultiSuggestField> = (args) => (
18
- <div>
19
- <MultiSuggestField {...args} />
20
- </div>
21
- );
22
-
23
7
  const testLabels = loremIpsum({
24
8
  p: 1,
25
9
  avgSentencesPerParagraph: 5,
@@ -28,13 +12,32 @@ const testLabels = loremIpsum({
28
12
  random: false,
29
13
  })
30
14
  .toString()
31
- .split(".");
15
+ .split(".")
16
+ .map((item) => item.trim());
32
17
 
33
18
  const items = new Array(5).fill(undefined).map((_, id) => {
34
19
  const testLabel = testLabels[id];
35
20
  return { testLabel, testId: `${testLabel}-id` };
36
21
  });
37
22
 
23
+ export default {
24
+ title: "Forms/MultiSuggestField",
25
+ component: MultiSuggestField,
26
+ argTypes: {
27
+ items: {
28
+ control: "none",
29
+ },
30
+ },
31
+ } as Meta<typeof MultiSuggestField>;
32
+
33
+ const Template: StoryFn<typeof MultiSuggestField> = (args) => {
34
+ return (
35
+ <div>
36
+ <MultiSuggestField {...args} />
37
+ </div>
38
+ );
39
+ };
40
+
38
41
  export const Default = Template.bind({});
39
42
  Default.args = {
40
43
  items,
@@ -64,20 +67,22 @@ const selectedItems = items.slice(1, 3);
64
67
  /**
65
68
  * Set the default selected values from the client code.
66
69
  */
67
- export const predefinedValues = Template.bind({});
68
- predefinedValues.args = {
70
+
71
+ export const predefinedNotControlledValues = Template.bind({});
72
+ predefinedNotControlledValues.args = {
69
73
  items,
70
74
  selectedItems,
71
75
  prePopulateWithItems: false,
76
+ onSelection: undefined,
72
77
  itemId: (item) => item.testId,
73
- itemLabel: (item) => <span data-testid={`${item.testLabel.trim()}`}>{item.testLabel}</span>,
78
+ itemLabel: (item) => item.testLabel,
74
79
  };
75
80
 
76
81
  /**
77
82
  * New item creation, add to a existing list
78
83
  */
79
- export const newItemCreation = Template.bind({});
80
- newItemCreation.args = {
84
+ export const uncontrolledNewItemCreation = Template.bind({});
85
+ uncontrolledNewItemCreation.args = {
81
86
  items,
82
87
  createNewItemFromQuery: (query) => ({ testId: `${query}-id`, testLabel: query }),
83
88
  prePopulateWithItems: false,
@@ -88,7 +93,7 @@ newItemCreation.args = {
88
93
  const CreationTemplate: StoryFn = () => {
89
94
  const [selectedValues, setSelectedValues] = useState<string[]>([]);
90
95
 
91
- const empty = useMemo<string[]>(() => [], []);
96
+ const items = useMemo<string[]>(() => ["foo", "bar", "baz"], []);
92
97
 
93
98
  const identity = useCallback((item: string): string => item, []);
94
99
 
@@ -100,7 +105,7 @@ const CreationTemplate: StoryFn = () => {
100
105
 
101
106
  return (
102
107
  <MultiSuggestField<string>
103
- items={empty}
108
+ items={items}
104
109
  selectedItems={selectedValues}
105
110
  onSelection={handleOnSelect}
106
111
  itemId={identity}
@@ -114,4 +119,44 @@ const CreationTemplate: StoryFn = () => {
114
119
  /**
115
120
  * Completely create all items from quieries
116
121
  */
117
- export const buildItemsFromQuery = CreationTemplate.bind({});
122
+ export const conrolledNewItemCreation = CreationTemplate.bind({});
123
+
124
+ const WithResetButtonComponent = (): JSX.Element => {
125
+ const copy: Array<{ testLabel: string; testId: string }> = [items[2]];
126
+
127
+ const [selected, setSelected] = useState(copy);
128
+
129
+ const handleOnSelect = useCallback((params) => {
130
+ const items = params.selectedItems;
131
+ setSelected(items);
132
+ }, []);
133
+
134
+ const handleReset = (): void => {
135
+ setSelected(copy);
136
+ };
137
+
138
+ return (
139
+ <div>
140
+ <button onClick={handleReset}>Reset</button>
141
+ <br />
142
+ <br />
143
+ <MultiSuggestField<{ testLabel: string; testId: string }>
144
+ items={items}
145
+ selectedItems={selected}
146
+ onSelection={handleOnSelect}
147
+ itemId={({ testId }) => testId}
148
+ itemLabel={({ testLabel }) => testLabel}
149
+ createNewItemFromQuery={(query) => ({ testId: `${query}-id`, testLabel: query })}
150
+ />
151
+ </div>
152
+ );
153
+ };
154
+
155
+ const WithResetButton: StoryFn = () => {
156
+ return <WithResetButtonComponent />;
157
+ };
158
+
159
+ /**
160
+ * Reset values
161
+ */
162
+ export const withResetItemAndCreation = WithResetButton.bind({});
@@ -1,10 +1,50 @@
1
- import React from "react";
1
+ import React, { useCallback, useState } from "react";
2
2
  import { fireEvent, render, screen, waitFor } from "@testing-library/react";
3
3
 
4
4
  import "@testing-library/jest-dom";
5
5
 
6
6
  import { MultiSuggestField } from "../MultiSuggestField";
7
- import { Default, dropdownOnFocus, predefinedValues } from "../MultiSuggestField.stories";
7
+ import { Default, dropdownOnFocus, predefinedNotControlledValues } from "../MultiSuggestField.stories";
8
+
9
+ const testLabels = ["label1", "label2", "label3", "label4", "label5"];
10
+
11
+ const items = new Array(5).fill(undefined).map((_, id) => {
12
+ const testLabel = testLabels[id];
13
+ return { testLabel, testId: `${testLabel}-id` };
14
+ });
15
+
16
+ export const TestComponent = (): JSX.Element => {
17
+ const copy: Array<{ testLabel: string; testId: string }> = [items[2]];
18
+
19
+ const [selected, setSelected] = useState(copy);
20
+
21
+ const handleOnSelect = useCallback((params) => {
22
+ const items = params.selectedItems;
23
+ setSelected(items);
24
+ }, []);
25
+
26
+ const handleReset = (): void => {
27
+ setSelected(copy);
28
+ };
29
+
30
+ return (
31
+ <div>
32
+ <button data-testid="reset-button" onClick={handleReset}>
33
+ Reset
34
+ </button>
35
+ <br />
36
+ <br />
37
+ <MultiSuggestField<{ testLabel: string; testId: string }>
38
+ items={items}
39
+ createNewItemFromQuery={(query) => ({ testId: `${query}-id`, testLabel: query })}
40
+ onSelection={handleOnSelect}
41
+ itemId={({ testId }) => testId}
42
+ itemLabel={({ testLabel }) => testLabel}
43
+ selectedItems={selected}
44
+ />
45
+ </div>
46
+ );
47
+ };
8
48
 
9
49
  describe("MultiSuggestField", () => {
10
50
  it("should render default input", () => {
@@ -15,25 +55,27 @@ describe("MultiSuggestField", () => {
15
55
  });
16
56
 
17
57
  it("should render default selected items", async () => {
18
- const { getByTestId } = render(<MultiSuggestField {...predefinedValues.args} />);
58
+ const { getByText } = render(<MultiSuggestField {...predefinedNotControlledValues.args} />);
19
59
 
20
- const [firstSelected, secondSelected]: Array<string> = predefinedValues.args.selectedItems.map(
21
- ({ testLabel }) => testLabel.trim()
60
+ const [firstSelected, secondSelected]: Array<string> = predefinedNotControlledValues.args.selectedItems.map(
61
+ ({ testLabel }) => testLabel
22
62
  );
23
63
 
24
64
  await waitFor(() => {
25
- expect(getByTestId(firstSelected)).toBeInTheDocument();
26
- expect(getByTestId(secondSelected)).toBeInTheDocument();
65
+ expect(getByText(firstSelected)).toBeInTheDocument();
66
+ expect(getByText(secondSelected)).toBeInTheDocument();
27
67
  });
28
68
  });
29
69
 
30
- it("should clear all selected items on clear button click", async () => {
31
- const { queryByTestId, container } = render(<MultiSuggestField {...predefinedValues.args} />);
32
-
33
- const [firstSelected, secondSelected]: Array<string> = predefinedValues.args.selectedItems.map(
34
- ({ testLabel }) => testLabel.trim()
70
+ it("should clear all selected items on clear button click for uncontrolled field", async () => {
71
+ const { container } = render(
72
+ <MultiSuggestField {...predefinedNotControlledValues.args} onSelection={undefined} />
35
73
  );
36
74
 
75
+ const selectedLength = predefinedNotControlledValues.args.selectedItems.length;
76
+
77
+ expect(container.querySelectorAll("[data-tag-index]").length).toBe(selectedLength);
78
+
37
79
  const clearButton = container.querySelector('[data-test-id="clear-all-items"');
38
80
 
39
81
  expect(clearButton).toBeInTheDocument();
@@ -41,8 +83,7 @@ describe("MultiSuggestField", () => {
41
83
  fireEvent.click(clearButton!);
42
84
 
43
85
  await waitFor(() => {
44
- expect(queryByTestId(firstSelected)).not.toBeInTheDocument();
45
- expect(queryByTestId(secondSelected)).not.toBeInTheDocument();
86
+ expect(container.querySelectorAll("[data-tag-index]").length).toBe(0);
46
87
  });
47
88
  });
48
89
 
@@ -94,4 +135,85 @@ describe("MultiSuggestField", () => {
94
135
  expect(menuItems.length).toBe(dropdownOnFocus.args.items.length);
95
136
  });
96
137
  });
138
+
139
+ it("should render disable field with selected items", async () => {
140
+ const { container } = render(<MultiSuggestField {...predefinedNotControlledValues.args} disabled />);
141
+
142
+ const [inputTargetContainer] = container.getElementsByClassName("eccgui-multiselect__target");
143
+
144
+ expect(inputTargetContainer.getAttribute("aria-disabled")).toBe("true");
145
+ });
146
+
147
+ it("should call onSelection function with the selected items for the contolled field", async () => {
148
+ const onSelection = jest.fn();
149
+
150
+ const { container } = render(
151
+ <MultiSuggestField {...dropdownOnFocus.args} items={items} onSelection={onSelection} />
152
+ );
153
+
154
+ const [inputContainer] = container.getElementsByClassName("eccgui-multiselect");
155
+ const [input] = inputContainer.getElementsByTagName("input");
156
+
157
+ fireEvent.click(input);
158
+
159
+ await waitFor(() => {
160
+ const listbox = screen.getByRole("listbox");
161
+ expect(listbox).toBeInTheDocument();
162
+
163
+ const menuItems = listbox.getElementsByClassName("eccgui-menu__item");
164
+ expect(menuItems.length).toBe(dropdownOnFocus.args.items.length);
165
+
166
+ const item = menuItems[0];
167
+ fireEvent.click(item);
168
+ });
169
+
170
+ await waitFor(() => {
171
+ const expectedObject = {
172
+ createdItems: [],
173
+ newlySelected: items[0],
174
+ selectedItems: [items[0]],
175
+ };
176
+ expect(onSelection).toHaveBeenCalledWith(expectedObject);
177
+ });
178
+ });
179
+
180
+ it("should reset values correctly with the pre-defined values for the contolled field", async () => {
181
+ const { container, getByTestId } = render(<TestComponent />);
182
+
183
+ const [inputContainer] = container.getElementsByClassName("eccgui-multiselect");
184
+ const [input] = inputContainer.getElementsByTagName("input");
185
+
186
+ fireEvent.click(input);
187
+
188
+ await waitFor(() => {
189
+ const listbox = screen.getByRole("listbox");
190
+ expect(listbox).toBeInTheDocument();
191
+
192
+ const menuItems = listbox.getElementsByClassName("eccgui-menu__item");
193
+ expect(menuItems.length).toBe(dropdownOnFocus.args.items.length);
194
+
195
+ const selectedItems = inputContainer.querySelectorAll("[data-tag-index]");
196
+ expect(selectedItems.length).toBe(1);
197
+
198
+ const item = menuItems[0];
199
+ fireEvent.click(item);
200
+
201
+ const otherItem = menuItems[menuItems.length - 1];
202
+ fireEvent.click(otherItem);
203
+
204
+ const selectedItemsAfterSelection = inputContainer.querySelectorAll("[data-tag-index]");
205
+
206
+ expect(selectedItemsAfterSelection.length).toBe(3);
207
+
208
+ const resetButton = getByTestId("reset-button");
209
+ expect(resetButton).toBeInTheDocument();
210
+
211
+ fireEvent.click(resetButton);
212
+ });
213
+
214
+ await waitFor(() => {
215
+ const selectedItemsAfterReset = inputContainer.querySelectorAll("[data-tag-index]");
216
+ expect(selectedItemsAfterReset.length).toBe(1);
217
+ });
218
+ });
97
219
  });
@@ -114,7 +114,7 @@ export const CodeEditor = ({
114
114
  id,
115
115
  mode = "undefined",
116
116
  preventLineNumbers = false,
117
- defaultValue,
117
+ defaultValue = "",
118
118
  readOnly = false,
119
119
  height,
120
120
  wrapLines = false,