@axinom/mosaic-ui 0.61.0 → 0.62.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 (71) hide show
  1. package/dist/components/Accordion/Accordion.d.ts.map +1 -1
  2. package/dist/components/Explorer/BulkEdit/BulkEdit.model.d.ts +22 -0
  3. package/dist/components/Explorer/BulkEdit/BulkEdit.model.d.ts.map +1 -0
  4. package/dist/components/Explorer/BulkEdit/BulkEditContext.d.ts +7 -0
  5. package/dist/components/Explorer/BulkEdit/BulkEditContext.d.ts.map +1 -0
  6. package/dist/components/Explorer/BulkEdit/FormFieldsConfigConverter.d.ts +10 -0
  7. package/dist/components/Explorer/BulkEdit/FormFieldsConfigConverter.d.ts.map +1 -0
  8. package/dist/components/Explorer/BulkEdit/GenerateMutation.d.ts +4 -0
  9. package/dist/components/Explorer/BulkEdit/GenerateMutation.d.ts.map +1 -0
  10. package/dist/components/Explorer/BulkEdit/index.d.ts +6 -0
  11. package/dist/components/Explorer/BulkEdit/index.d.ts.map +1 -0
  12. package/dist/components/Explorer/BulkEdit/useBulkEdit.d.ts +16 -0
  13. package/dist/components/Explorer/BulkEdit/useBulkEdit.d.ts.map +1 -0
  14. package/dist/components/Explorer/Explorer.d.ts +4 -1
  15. package/dist/components/Explorer/Explorer.d.ts.map +1 -1
  16. package/dist/components/Explorer/Explorer.model.d.ts +13 -0
  17. package/dist/components/Explorer/Explorer.model.d.ts.map +1 -1
  18. package/dist/components/Explorer/helpers/useActions.d.ts +3 -1
  19. package/dist/components/Explorer/helpers/useActions.d.ts.map +1 -1
  20. package/dist/components/Explorer/helpers/useSubtitle.d.ts +13 -0
  21. package/dist/components/Explorer/helpers/useSubtitle.d.ts.map +1 -0
  22. package/dist/components/Explorer/index.d.ts +1 -0
  23. package/dist/components/Explorer/index.d.ts.map +1 -1
  24. package/dist/components/FieldSelection/FieldSelection.d.ts +7 -0
  25. package/dist/components/FieldSelection/FieldSelection.d.ts.map +1 -0
  26. package/dist/components/FieldSelection/index.d.ts +2 -0
  27. package/dist/components/FieldSelection/index.d.ts.map +1 -0
  28. package/dist/components/FormStation/FormStation.d.ts.map +1 -1
  29. package/dist/components/FormStation/FormStationHeader/FormStationHeader.d.ts.map +1 -1
  30. package/dist/components/Icons/Icons.d.ts.map +1 -1
  31. package/dist/components/Icons/Icons.models.d.ts +47 -46
  32. package/dist/components/Icons/Icons.models.d.ts.map +1 -1
  33. package/dist/components/List/List.d.ts.map +1 -1
  34. package/dist/components/index.d.ts +1 -0
  35. package/dist/components/index.d.ts.map +1 -1
  36. package/dist/helpers/testing.d.ts +3 -1
  37. package/dist/helpers/testing.d.ts.map +1 -1
  38. package/dist/index.es.js +4 -4
  39. package/dist/index.es.js.map +1 -1
  40. package/dist/index.js +4 -4
  41. package/dist/index.js.map +1 -1
  42. package/package.json +6 -2
  43. package/src/components/Accordion/Accordion.tsx +13 -11
  44. package/src/components/Explorer/BulkEdit/BulkEdit.model.ts +21 -0
  45. package/src/components/Explorer/BulkEdit/BulkEditContext.tsx +11 -0
  46. package/src/components/Explorer/BulkEdit/FormFieldsConfigConverter.spec.tsx +162 -0
  47. package/src/components/Explorer/BulkEdit/FormFieldsConfigConverter.tsx +45 -0
  48. package/src/components/Explorer/BulkEdit/GenerateMutation.spec.tsx +141 -0
  49. package/src/components/Explorer/BulkEdit/GenerateMutation.tsx +90 -0
  50. package/src/components/Explorer/BulkEdit/index.ts +8 -0
  51. package/src/components/Explorer/BulkEdit/useBulkEdit.tsx +132 -0
  52. package/src/components/Explorer/Explorer.model.ts +14 -0
  53. package/src/components/Explorer/Explorer.stories.tsx +82 -0
  54. package/src/components/Explorer/Explorer.tsx +41 -57
  55. package/src/components/Explorer/helpers/useActions.ts +21 -5
  56. package/src/components/Explorer/helpers/useFilters.spec.tsx +140 -0
  57. package/src/components/Explorer/helpers/useStationMessage.spec.tsx +91 -0
  58. package/src/components/Explorer/helpers/useSubtitle.spec.tsx +115 -0
  59. package/src/components/Explorer/helpers/useSubtitle.tsx +52 -0
  60. package/src/components/Explorer/index.ts +10 -0
  61. package/src/components/FieldSelection/FieldSelection.scss +18 -0
  62. package/src/components/FieldSelection/FieldSelection.spec.tsx +62 -0
  63. package/src/components/FieldSelection/FieldSelection.stories.tsx +30 -0
  64. package/src/components/FieldSelection/FieldSelection.tsx +154 -0
  65. package/src/components/FieldSelection/index.ts +1 -0
  66. package/src/components/FormStation/FormStation.tsx +8 -4
  67. package/src/components/FormStation/FormStationHeader/FormStationHeader.tsx +22 -3
  68. package/src/components/Icons/Icons.models.ts +1 -0
  69. package/src/components/Icons/Icons.tsx +17 -0
  70. package/src/components/List/List.tsx +11 -0
  71. package/src/components/index.ts +1 -0
@@ -5,6 +5,7 @@ import { IconName } from '../Icons';
5
5
  import { SortData } from '../List';
6
6
  import { PageHeaderJsActionProps } from '../PageHeader/PageHeaderAction/PageHeaderAction.model';
7
7
  import { ErrorType } from '../models';
8
+ import { BulkEditConfig } from './BulkEdit';
8
9
 
9
10
  /**
10
11
  * Item selection can have two modes:
@@ -123,3 +124,16 @@ export interface QuickEditRegistration<T> {
123
124
  */
124
125
  generateDetailsLink?: ((item: T) => LocationDescriptor<unknown>) | false;
125
126
  }
127
+
128
+ export interface BulkEditRegistration<T extends Data> {
129
+ /** The label of the action. */
130
+ label: string;
131
+ /** Optional built in icon. This prop also accepts an img src. */
132
+ icon?: IconName | string;
133
+ /** Component to render. This will override the component that is generated. */
134
+ component?: JSX.Element;
135
+ /** Bulk Edit Configuration */
136
+ config?: BulkEditConfig;
137
+ /** Function which will be called when save button is clicked */
138
+ saveData: (data: T, items: ItemSelection<T>) => Promise<void>;
139
+ }
@@ -21,6 +21,7 @@ import {
21
21
  } from '../FormStation';
22
22
  import { IconName } from '../Icons';
23
23
  import { ListSelectMode } from '../List';
24
+ import { generateBulkEditMutation } from './BulkEdit/GenerateMutation';
24
25
  import { Explorer } from './Explorer';
25
26
  import { QuickEditContext } from './QuickEdit/QuickEditContext';
26
27
  import {
@@ -471,6 +472,7 @@ const QuickEditComponent2: React.FC = () => {
471
472
  export const QuickEdit: StoryObj<ExplorerStoryType> = {
472
473
  args: {
473
474
  ...Default.args,
475
+ generateItemLink: (item) => `/details/${item.id}`,
474
476
  stationKey: 'StoryBookExplorer_QuickEdit',
475
477
  quickEditRegistrations: [
476
478
  {
@@ -484,3 +486,83 @@ export const QuickEdit: StoryObj<ExplorerStoryType> = {
484
486
  ],
485
487
  },
486
488
  };
489
+
490
+ const BulkEditImagesAsyncFormFieldsConfig = {
491
+ mutation: 'bulkEditImagesAsync',
492
+ keys: {
493
+ add: 'relatedEntitiesToAdd',
494
+ remove: 'relatedEntitiesToRemove',
495
+ set: 'set',
496
+ filter: 'filter',
497
+ },
498
+ fields: {
499
+ imagesTagsAdd: {
500
+ type: [
501
+ {
502
+ name: 'String!',
503
+ },
504
+ ],
505
+ label: 'Images Tags ( Add )',
506
+ originalFieldName: 'imagesTags',
507
+ action: 'relatedEntitiesToAdd',
508
+ },
509
+ imagesTagsRemove: {
510
+ type: [
511
+ {
512
+ name: 'String!',
513
+ },
514
+ ],
515
+ label: 'Images Tags ( Remove )',
516
+ originalFieldName: 'imagesTags',
517
+ action: 'relatedEntitiesToRemove',
518
+ },
519
+ altText: {
520
+ type: 'String',
521
+ label: 'Alt Text',
522
+ originalFieldName: 'altText',
523
+ action: 'set',
524
+ },
525
+ focalX: {
526
+ type: 'BigFloat',
527
+ label: 'Focal X',
528
+ originalFieldName: 'focalX',
529
+ action: 'set',
530
+ },
531
+ focalY: {
532
+ type: 'BigFloat',
533
+ label: 'Focal Y',
534
+ originalFieldName: 'focalY',
535
+ action: 'set',
536
+ },
537
+ isArchived: {
538
+ type: 'Boolean',
539
+ label: 'Is Archived',
540
+ originalFieldName: 'isArchived',
541
+ action: 'set',
542
+ },
543
+ title: {
544
+ type: 'String',
545
+ label: 'Title',
546
+ originalFieldName: 'title',
547
+ action: 'set',
548
+ },
549
+ },
550
+ };
551
+
552
+ export const BulkEdit: StoryObj<ExplorerStoryType> = {
553
+ args: {
554
+ ...Default.args,
555
+ stationKey: 'StoryBookExplorer_QuickEdit',
556
+ bulkEditRegistration: {
557
+ label: 'Bulk Edit',
558
+ config: BulkEditImagesAsyncFormFieldsConfig,
559
+ saveData: async (data, items) => {
560
+ // eslint-disable-next-line no-console
561
+ console.log(
562
+ generateBulkEditMutation(BulkEditImagesAsyncFormFieldsConfig, data),
563
+ );
564
+ action('Save')(data, items);
565
+ },
566
+ },
567
+ },
568
+ };
@@ -24,8 +24,10 @@ import {
24
24
  import { PageHeader, PageHeaderActionProps } from '../PageHeader';
25
25
  import { getState, storeState } from '../Utils/State/GlobalState';
26
26
  import { ErrorType } from '../models';
27
+ import { useBulkEdit } from './BulkEdit/';
27
28
  import { ConditionalSplit } from './ConditionalSplit/ConditionalSplit';
28
29
  import {
30
+ BulkEditRegistration,
29
31
  ExplorerBulkAction,
30
32
  ExplorerDataProvider,
31
33
  ExplorerDataProviderConnection,
@@ -36,9 +38,10 @@ import {
36
38
  import classes from './Explorer.scss';
37
39
  import { useQuickEdit } from './QuickEdit/useQuickEdit';
38
40
  import { useActions } from './helpers/useActions';
39
- import { ResultCounts, useDataProvider } from './helpers/useDataProvider';
41
+ import { useDataProvider } from './helpers/useDataProvider';
40
42
  import { useFilters } from './helpers/useFilters';
41
43
  import { StationMessage, useStationMessage } from './helpers/useStationMessage';
44
+ import { useSubtitle } from './helpers/useSubtitle';
42
45
 
43
46
  export interface ExplorerProps<T extends Data> {
44
47
  /** Title shown in page header */
@@ -135,6 +138,10 @@ export interface ExplorerProps<T extends Data> {
135
138
  /** Quick Edit Registrations */
136
139
  quickEditRegistrations?: QuickEditRegistration<T>[];
137
140
 
141
+ /** Bulk Edit Registration
142
+ * @experimental The feature Bulk Edit and it's underlying implementation is still in beta. */
143
+ bulkEditRegistration?: BulkEditRegistration<T>;
144
+
138
145
  /**
139
146
  * When set, this function is used to generate the link that the user should be navigated to for each item.
140
147
  *
@@ -211,6 +218,7 @@ export const Explorer = React.forwardRef(function Explorer<T extends Data>(
211
218
  setTabTitle = true,
212
219
 
213
220
  quickEditRegistrations,
221
+ bulkEditRegistration,
214
222
 
215
223
  onItemClicked,
216
224
  onBulkActionsToggled = noop,
@@ -292,7 +300,7 @@ export const Explorer = React.forwardRef(function Explorer<T extends Data>(
292
300
  generateItemLink,
293
301
  });
294
302
 
295
- const { mode, setIsBulkOpen } = useListSelectionMode(
303
+ const { mode: listSelectionMode, setIsBulkOpen } = useListSelectionMode(
296
304
  selectionMode,
297
305
  openBulkActionsOnStart,
298
306
  isQuickEditMode,
@@ -342,14 +350,31 @@ export const Explorer = React.forwardRef(function Explorer<T extends Data>(
342
350
  async (item: T) => {
343
351
  if (isQuickEditMode) {
344
352
  await changeSelectedItem(item);
345
- } else if (onItemClicked !== undefined) {
346
- onItemClicked(item, mode);
353
+ }
354
+
355
+ if (onItemClicked !== undefined) {
356
+ onItemClicked(item, listSelectionMode);
347
357
  }
348
358
  },
349
- [isQuickEditMode, onItemClicked, changeSelectedItem, mode],
359
+ [isQuickEditMode, onItemClicked, changeSelectedItem, listSelectionMode],
350
360
  );
351
361
 
352
- const { resultsTitle } = useSubtitle<T>(mode, itemSelection, resultCount);
362
+ const {
363
+ BulkEditContextProvider,
364
+ BulkEditComponent,
365
+ isBulkEditMode,
366
+ bulkEditAction,
367
+ closeBulkEdit,
368
+ } = useBulkEdit({
369
+ bulkEditRegistration,
370
+ getBulkActionSelection,
371
+ });
372
+
373
+ const { resultsTitle } = useSubtitle<T>(
374
+ listSelectionMode,
375
+ itemSelection,
376
+ resultCount,
377
+ );
353
378
 
354
379
  const errAction = 'An error occurred when trying to execute the operation.';
355
380
 
@@ -379,6 +404,7 @@ export const Explorer = React.forwardRef(function Explorer<T extends Data>(
379
404
  actions,
380
405
  bulkActions,
381
406
  quickEditAction,
407
+ bulkEditAction,
382
408
  openBulkActionsOnStart,
383
409
  filtersVisible,
384
410
  hasFilters: filterOptions !== undefined,
@@ -391,10 +417,14 @@ export const Explorer = React.forwardRef(function Explorer<T extends Data>(
391
417
  onBulkActionsToggled,
392
418
  setIsBulkOpen,
393
419
  toggleFiltersVisible,
420
+ closeBulkEdit,
394
421
  });
395
422
 
396
423
  return (
397
- <ConditionalSplit shouldSplit={isQuickEditMode} minSecondarySize="500px">
424
+ <ConditionalSplit
425
+ shouldSplit={isQuickEditMode || isBulkEditMode}
426
+ minSecondarySize="500px"
427
+ >
398
428
  <div
399
429
  className={clsx(
400
430
  classes.container,
@@ -440,7 +470,7 @@ export const Explorer = React.forwardRef(function Explorer<T extends Data>(
440
470
  showActionButton={
441
471
  Boolean(generateItemLink) || Boolean(onItemClicked)
442
472
  } // or hard code to `true`?
443
- selectionMode={mode}
473
+ selectionMode={listSelectionMode}
444
474
  enableSelectAll={enableSelectAll}
445
475
  enableSelectAllDeselect={enableSelectAll && !hasMoreData}
446
476
  loadingTriggerOffset={loadingTriggerOffset}
@@ -460,6 +490,9 @@ export const Explorer = React.forwardRef(function Explorer<T extends Data>(
460
490
  {isQuickEditMode && (
461
491
  <QuickEditContextProvider>{quickEditStation}</QuickEditContextProvider>
462
492
  )}
493
+ {isBulkEditMode && (
494
+ <BulkEditContextProvider>{BulkEditComponent}</BulkEditContextProvider>
495
+ )}
463
496
  </ConditionalSplit>
464
497
  );
465
498
  });
@@ -495,55 +528,6 @@ const useListSelectionMode = (
495
528
  return { mode, setIsBulkOpen } as const;
496
529
  };
497
530
 
498
- /**
499
- * Sets PageHeader subtitle
500
- * @param mode current ListSelectMode
501
- * @param itemSelection current item selection
502
- * @param results total results
503
- */
504
- function useSubtitle<T extends Data>(
505
- mode: ListSelectMode,
506
- itemSelection: ItemSelectEventArgs<T>,
507
- results?: ResultCounts,
508
- ): {
509
- readonly resultsTitle: string;
510
- } {
511
- let resultsTitle = ''; // default to an empty string while results is being fetched
512
-
513
- if (results !== undefined) {
514
- switch (true) {
515
- case results.total === 1 && results.filtered === 1:
516
- resultsTitle = `Showing 1 Element`;
517
- break;
518
- case results.total === 1 && results.filtered === 0:
519
- resultsTitle = `Showing 0 of 1 Element`;
520
- break;
521
- case results.filtered === results.total:
522
- resultsTitle = `Showing all of ${results.total} Elements`;
523
- break;
524
- case results.filtered === undefined:
525
- resultsTitle = `Showing ${results.total} Elements`;
526
- break;
527
- default:
528
- resultsTitle = `Showing ${results.filtered} of ${results.total} Elements`;
529
- }
530
-
531
- // Append Selected items if list selection is in Multi mode
532
- if (mode === ListSelectMode.Multi) {
533
- if (itemSelection.mode === 'SINGLE_ITEMS') {
534
- resultsTitle = `${resultsTitle}, Selected: ${
535
- itemSelection.items?.length ?? 0
536
- }`;
537
- } else {
538
- // Show filtered results as selected results if 'SELECT_ALL' is active
539
- resultsTitle = `${resultsTitle}, Selected: ${results.filtered}`;
540
- }
541
- }
542
- }
543
-
544
- return { resultsTitle } as const;
545
- }
546
-
547
531
  const errMsg = (err: unknown | ErrorType, msg?: string): StationMessage => {
548
532
  return {
549
533
  ...ErrorTypeToStationError(err as ErrorType, msg),
@@ -20,6 +20,7 @@ interface UseActionsProps<T extends Data> {
20
20
  actions?: PageHeaderActionProps[];
21
21
  bulkActions?: ExplorerBulkAction<T>[];
22
22
  quickEditAction?: PageHeaderActionItemProps;
23
+ bulkEditAction?: ExplorerBulkAction<T>;
23
24
  openBulkActionsOnStart: boolean | undefined;
24
25
  filtersVisible: boolean;
25
26
  hasFilters: boolean;
@@ -34,6 +35,7 @@ interface UseActionsProps<T extends Data> {
34
35
  onBulkActionsToggled: (expanded: boolean) => void;
35
36
  setIsBulkOpen: (value: React.SetStateAction<boolean>) => void;
36
37
  toggleFiltersVisible: () => void;
38
+ closeBulkEdit: () => void;
37
39
  }
38
40
 
39
41
  interface UseActionsReturnType {
@@ -56,10 +58,12 @@ export const useActions = <T extends Data>({
56
58
  toggleFiltersVisible,
57
59
  quickEditAction,
58
60
  bulkActions,
61
+ closeBulkEdit,
62
+ bulkEditAction,
59
63
  }: UseActionsProps<T>): UseActionsReturnType => {
60
64
  const bulkActionItems: PageHeaderJsActionProps[] = useMemo(
61
- () =>
62
- (bulkActions ?? []).map((action) => ({
65
+ () => [
66
+ ...(bulkActions ?? []).map((action) => ({
63
67
  ...action,
64
68
  onClick: async () => {
65
69
  if (action.showStartedNotification !== false) {
@@ -97,7 +101,15 @@ export const useActions = <T extends Data>({
97
101
  }
98
102
  },
99
103
  })),
100
- [bulkActions, getBulkActionSelection, onReloadData, setStationMessage],
104
+ ...(bulkEditAction ? [bulkEditAction] : []),
105
+ ],
106
+ [
107
+ bulkActions,
108
+ bulkEditAction,
109
+ getBulkActionSelection,
110
+ onReloadData,
111
+ setStationMessage,
112
+ ],
101
113
  );
102
114
 
103
115
  const pageHeaderActions: PageHeaderActionItemProps[] = useMemo(() => {
@@ -120,7 +132,7 @@ export const useActions = <T extends Data>({
120
132
  });
121
133
  }
122
134
 
123
- if (bulkActions && bulkActions.length > 0) {
135
+ if (bulkActionItems.length > 0) {
124
136
  headerActions.push({ kind: 'spacer' });
125
137
 
126
138
  headerActions.push({
@@ -132,6 +144,10 @@ export const useActions = <T extends Data>({
132
144
  onActionsGroupToggled: async (isOpen) => {
133
145
  setIsBulkOpen(isOpen);
134
146
  onBulkActionsToggled(isOpen);
147
+
148
+ if (!isOpen) {
149
+ closeBulkEdit();
150
+ }
135
151
  },
136
152
  groupActionsDisabled:
137
153
  itemSelection.items?.length === 0 || resultCount?.filtered === 0,
@@ -179,7 +195,7 @@ export const useActions = <T extends Data>({
179
195
  actions,
180
196
  activeFilterCount,
181
197
  bulkActionItems,
182
- bulkActions,
198
+ closeBulkEdit,
183
199
  filtersVisible,
184
200
  hasFilters,
185
201
  itemSelection.items?.length,
@@ -0,0 +1,140 @@
1
+ import { act, renderHook } from '@testing-library/react-hooks';
2
+ import { Data } from '../../../types';
3
+ import { FilterType, FilterValues } from '../../Filters';
4
+ import { getState, storeState } from '../../Utils/State/GlobalState';
5
+ import { useFilters } from './useFilters';
6
+
7
+ // jest.mock('../../../hooks', () => ({
8
+ // useExpand: jest.fn(),
9
+ // }));
10
+
11
+ jest.mock('../../Utils/State/GlobalState', () => ({
12
+ getState: jest.fn(),
13
+ storeState: jest.fn(),
14
+ }));
15
+
16
+ describe('useFilters', () => {
17
+ const stationKey = 'testStation';
18
+ const globalStateOptions = { filters: true };
19
+ const filterOptions: FilterType<Data>[] = [];
20
+ const defaultFilterValues = { filter1: 'value1' };
21
+ const onFiltersChange = jest.fn();
22
+
23
+ beforeEach(() => {
24
+ // (useExpand as jest.Mock).mockReturnValue({
25
+ // isExpanded: true,
26
+ // toggleExpanded: jest.fn(),
27
+ // collapse: jest.fn(),
28
+ // });
29
+ (getState as jest.Mock).mockReturnValue(undefined);
30
+ jest.clearAllMocks();
31
+ });
32
+
33
+ it('should initialize with default filter values', () => {
34
+ const { result } = renderHook(() =>
35
+ useFilters({
36
+ stationKey,
37
+ globalStateOptions,
38
+ filterOptions,
39
+ defaultFilterValues,
40
+ onFiltersChange,
41
+ }),
42
+ );
43
+
44
+ expect(result.current.activeFilters).toEqual(defaultFilterValues);
45
+ });
46
+
47
+ it('should toggle filter visibility', () => {
48
+ const { result } = renderHook(() =>
49
+ useFilters({
50
+ stationKey,
51
+ globalStateOptions,
52
+ filterOptions,
53
+ defaultFilterValues,
54
+ onFiltersChange,
55
+ }),
56
+ );
57
+
58
+ act(() => {
59
+ result.current.toggleFiltersVisible();
60
+ });
61
+
62
+ expect(result.current.filtersVisible).toBe(false);
63
+ });
64
+
65
+ it('should collapse filters', () => {
66
+ const { result } = renderHook(() =>
67
+ useFilters({
68
+ stationKey,
69
+ globalStateOptions,
70
+ filterOptions,
71
+ defaultFilterValues,
72
+ onFiltersChange,
73
+ }),
74
+ );
75
+
76
+ act(() => {
77
+ result.current.collapseFilters();
78
+ });
79
+
80
+ expect(result.current.filtersVisible).toBe(false);
81
+ });
82
+
83
+ it('should update filters and call onFiltersChange', () => {
84
+ const newFilters: FilterValues<any> = { filter1: 'newValue' };
85
+ const { result } = renderHook(() =>
86
+ useFilters({
87
+ stationKey,
88
+ globalStateOptions,
89
+ filterOptions,
90
+ defaultFilterValues,
91
+ onFiltersChange,
92
+ }),
93
+ );
94
+
95
+ act(() => {
96
+ result.current.Filters.props.children.props.onFiltersChange(newFilters);
97
+ });
98
+
99
+ expect(result.current.activeFilters).toEqual(newFilters);
100
+ expect(onFiltersChange).toHaveBeenCalledWith(newFilters);
101
+ });
102
+
103
+ it('should store state when filters change', () => {
104
+ const newFilters: FilterValues<any> = { filter1: 'newValue' };
105
+ const { result } = renderHook(() =>
106
+ useFilters({
107
+ stationKey,
108
+ globalStateOptions,
109
+ filterOptions,
110
+ defaultFilterValues,
111
+ onFiltersChange,
112
+ }),
113
+ );
114
+
115
+ act(() => {
116
+ result.current.Filters.props.children.props.onFiltersChange(newFilters);
117
+ });
118
+
119
+ expect(storeState).toHaveBeenCalledWith(stationKey, 'filters', newFilters);
120
+ });
121
+
122
+ it('should clear state when filters are empty', () => {
123
+ const emptyFilters: FilterValues<any> = {};
124
+ const { result } = renderHook(() =>
125
+ useFilters({
126
+ stationKey,
127
+ globalStateOptions,
128
+ filterOptions,
129
+ defaultFilterValues,
130
+ onFiltersChange,
131
+ }),
132
+ );
133
+
134
+ act(() => {
135
+ result.current.Filters.props.children.props.onFiltersChange(emptyFilters);
136
+ });
137
+
138
+ expect(storeState).toHaveBeenCalledWith(stationKey, 'filters', undefined);
139
+ });
140
+ });
@@ -0,0 +1,91 @@
1
+ import { render } from '@testing-library/react';
2
+ import { act, renderHook } from '@testing-library/react-hooks';
3
+ import { MessageBar } from '../../MessageBar/MessageBar';
4
+ import { StationMessage, useStationMessage } from './useStationMessage';
5
+
6
+ jest.mock('../../MessageBar/MessageBar', () => ({
7
+ MessageBar: jest.fn(() => null),
8
+ }));
9
+
10
+ describe('useStationMessage', () => {
11
+ beforeEach(() => {
12
+ jest.clearAllMocks();
13
+ });
14
+
15
+ it('should initialize with undefined station message', () => {
16
+ const { result } = renderHook(() => useStationMessage());
17
+
18
+ expect(result.current.stationMessage).toBeUndefined();
19
+ });
20
+
21
+ it('should set a station message', () => {
22
+ const { result } = renderHook(() => useStationMessage());
23
+ const message: StationMessage = {
24
+ type: 'error',
25
+ title: 'Error',
26
+ body: 'An error occurred',
27
+ canClose: true,
28
+ };
29
+
30
+ act(() => {
31
+ result.current.setStationMessage(message);
32
+ });
33
+
34
+ expect(result.current.stationMessage).toEqual(message);
35
+ });
36
+
37
+ it('should clear the station message when onClose is called', () => {
38
+ const { result } = renderHook(() => useStationMessage());
39
+ const message: StationMessage = {
40
+ type: 'error',
41
+ title: 'Error',
42
+ body: 'An error occurred',
43
+ canClose: true,
44
+ };
45
+
46
+ act(() => {
47
+ result.current.setStationMessage(message);
48
+ });
49
+
50
+ act(() => {
51
+ result.current.StationMessage.props.children.props.onClose();
52
+ });
53
+
54
+ expect(result.current.stationMessage).toBeUndefined();
55
+ });
56
+
57
+ it('should render MessageBar with correct props', () => {
58
+ const { result } = renderHook(() => useStationMessage());
59
+ const message: StationMessage = {
60
+ type: 'error',
61
+ title: 'Error',
62
+ body: 'An error occurred',
63
+ canClose: true,
64
+ };
65
+
66
+ act(() => {
67
+ result.current.setStationMessage(message);
68
+ });
69
+
70
+ render(result.current.StationMessage);
71
+
72
+ expect(MessageBar).toHaveBeenCalledWith(
73
+ expect.objectContaining({
74
+ type: message.type,
75
+ title: message.title,
76
+ onClose: expect.any(Function),
77
+ onRetry: message.onRetry,
78
+ children: message.body,
79
+ }),
80
+ {},
81
+ );
82
+ });
83
+
84
+ it('should not render MessageBar when stationMessage is undefined', () => {
85
+ const { result } = renderHook(() => useStationMessage());
86
+
87
+ render(result.current.StationMessage);
88
+
89
+ expect(MessageBar).not.toHaveBeenCalled();
90
+ });
91
+ });