@reltio/sources 1.4.1585 → 1.4.1586-mui5

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 (94) hide show
  1. package/index.ts +1 -0
  2. package/package.json +38 -21
  3. package/public/bundle.js +205 -0
  4. package/public/bundle.js.LICENSE.txt +79 -0
  5. package/public/package.json +22 -0
  6. package/scripts/build/index.js +20 -0
  7. package/src/components/CrosswalkTreeView/CrosswalkTreeView.tsx +47 -0
  8. package/src/components/CrosswalkTreeView/__tests__/CrosswalkTreeView.spec.tsx +72 -0
  9. package/src/components/CrosswalkTreeView/__tests__/helpers.spec.ts +961 -0
  10. package/src/components/CrosswalkTreeView/helpers.ts +91 -0
  11. package/src/components/CrosswalkTreeView/styles.ts +10 -0
  12. package/src/components/EmptyState/EmptyState.tsx +20 -0
  13. package/src/components/EmptyState/__tests__/EmptyState.test.tsx +13 -0
  14. package/src/components/EmptyState/icons/EmptySources.svg +43 -0
  15. package/src/components/EmptyState/styles.ts +19 -0
  16. package/src/components/LegendTable/LegendTable.tsx +124 -0
  17. package/src/components/LegendTable/LegendTableRow/ContributorRow/ContributorRow.tsx +46 -0
  18. package/src/components/LegendTable/LegendTableRow/ContributorRow/__tests__/ContributorRow.test.tsx +103 -0
  19. package/src/components/LegendTable/LegendTableRow/ContributorRow/styles.ts +31 -0
  20. package/src/components/LegendTable/LegendTableRow/LegendTableRow.tsx +179 -0
  21. package/src/components/LegendTable/LegendTableRow/__tests__/LegendTableRow.test.tsx +371 -0
  22. package/src/components/LegendTable/LegendTableRow/styles.ts +31 -0
  23. package/src/components/LegendTable/__tests__/LegendTable.test.tsx +273 -0
  24. package/src/components/LegendTable/__tests__/helpers.test.ts +541 -0
  25. package/src/components/LegendTable/helpers.ts +64 -0
  26. package/src/components/LegendTable/styles.ts +17 -0
  27. package/src/components/LegendView/LegendView.tsx +201 -0
  28. package/src/components/LegendView/__tests__/LegendView.test.tsx +307 -0
  29. package/src/components/LegendView/styles.ts +51 -0
  30. package/src/components/RightSidePanel/RightSidePanel.tsx +25 -0
  31. package/src/components/RightSidePanel/__tests__/RightSidePanel.test.tsx +28 -0
  32. package/src/components/RightSidePanel/styles.ts +14 -0
  33. package/src/components/RulesestsSelector/RulesestsSelector.tsx +31 -0
  34. package/src/components/RulesestsSelector/RulesetOption/RulesetOption.tsx +25 -0
  35. package/src/components/RulesestsSelector/RulesetOption/__tests__/RulesetOption.test.tsx +25 -0
  36. package/src/components/RulesestsSelector/RulesetOption/styles.ts +27 -0
  37. package/src/components/RulesestsSelector/RulesetsMenuList/RulesetsMenuList.tsx +14 -0
  38. package/src/components/RulesestsSelector/__tests__/RulesetsSelector.test.tsx +91 -0
  39. package/src/components/RulesestsSelector/styles.ts +7 -0
  40. package/src/components/SankeyChart/Link.tsx +31 -0
  41. package/src/components/SankeyChart/MergeTooltip.tsx +37 -0
  42. package/src/components/SankeyChart/Node.tsx +137 -0
  43. package/src/components/SankeyChart/SankeyChart.tsx +103 -0
  44. package/src/components/SankeyChart/__tests__/Link.spec.tsx +127 -0
  45. package/src/components/SankeyChart/__tests__/MergeTooltip.spec.tsx +22 -0
  46. package/src/components/SankeyChart/__tests__/Node.spec.tsx +248 -0
  47. package/src/components/SankeyChart/__tests__/SankeyChart.spec.tsx +161 -0
  48. package/src/components/SankeyChart/__tests__/__snapshots__/SankeyChart.spec.tsx.snap +183 -0
  49. package/src/components/SankeyChart/sankey.ts +322 -0
  50. package/src/components/SankeyChart/styles.ts +41 -0
  51. package/src/components/SankeyChart/useSankey.tsx +44 -0
  52. package/src/components/SelectableTree/SelectableTree.tsx +46 -0
  53. package/src/components/SelectableTree/__tests__/SelectableTree.test.tsx +28 -0
  54. package/src/components/SelectableTree/__tests__/__snapshots__/SelectableTree.test.tsx.snap +59 -0
  55. package/src/components/SelectableTree/styles.ts +42 -0
  56. package/src/components/SourcesView/SourcesView.tsx +318 -0
  57. package/src/components/SourcesView/__tests__/IntegrationSourcesView.spec.tsx +199 -0
  58. package/src/components/SourcesView/__tests__/SourcesView.spec.tsx +903 -0
  59. package/src/components/SourcesView/__tests__/data/metadata.data.ts +27 -0
  60. package/src/components/SourcesView/__tests__/data/store.data.ts +65 -0
  61. package/src/components/SourcesView/icons/LegendIcon.svg +8 -0
  62. package/src/components/SourcesView/index.tsx +57 -0
  63. package/src/components/SourcesView/styles.ts +44 -0
  64. package/src/components/TreeActionSnackbar/TreeActionSnackbar.tsx +38 -0
  65. package/src/components/TreeActionSnackbar/__tests__/TreeActionSnackbar.test.tsx +38 -0
  66. package/src/components/TreeActionSnackbar/styles.ts +11 -0
  67. package/src/components/UnmergeDialog/UnmergeDialog.tsx +78 -0
  68. package/src/components/UnmergeDialog/__tests__/UnmergeDialog.test.tsx +124 -0
  69. package/src/components/UnmergeDialog/__tests__/testData.data.ts +271 -0
  70. package/src/components/UnmergeDialog/styles.ts +34 -0
  71. package/src/components/UnsubscribeDialog/UnsubscribeDialog.tsx +64 -0
  72. package/src/components/UnsubscribeDialog/__tests__/UnsubscribeDialog.test.tsx +59 -0
  73. package/src/components/UnsubscribeDialog/styles.ts +31 -0
  74. package/src/hooks/__tests__/useCrosswalkTree.specs.tsx +68 -0
  75. package/src/hooks/__tests__/useCrosswalkTreeActions.specs.ts +110 -0
  76. package/src/hooks/__tests__/useCrosswalksDisplay.specs.tsx +213 -0
  77. package/src/hooks/__tests__/useEntityWithSurvivorshipGroups.specs.ts +160 -0
  78. package/src/hooks/useCrosswalkTree.ts +38 -0
  79. package/src/hooks/useCrosswalkTreeActions.ts +83 -0
  80. package/src/hooks/useCrosswalksDisplay.ts +110 -0
  81. package/src/hooks/useEntityWithSurvivorshipGroups.tsx +51 -0
  82. package/src/icons/notMatch.svg +14 -0
  83. package/src/index.ts +1 -0
  84. package/src/services/errors.ts +11 -0
  85. package/src/types/LegendMode.ts +5 -0
  86. package/src/types/SankeyChartData.ts +31 -0
  87. package/src/types/SourcesSavedState.ts +3 -0
  88. package/src/types/UnmergeContributorEvent.ts +7 -0
  89. package/src/types/UnsubscribeContributorEvent.ts +6 -0
  90. package/src/types/index.ts +4 -0
  91. package/tsconfig.json +4 -0
  92. package/webpack.config.js +10 -0
  93. package/bundle.js +0 -2
  94. package/bundle.js.LICENSE.txt +0 -42
@@ -0,0 +1,903 @@
1
+ import React from 'react';
2
+ import {createStore} from 'redux';
3
+ import {Provider} from 'react-redux';
4
+ import {act} from 'react-dom/test-utils';
5
+ import {mount, ReactWrapper, shallow} from 'enzyme';
6
+ import Typography from '@mui/material/Typography';
7
+ import Divider from '@mui/material/Divider';
8
+
9
+ import * as components from '@reltio/components';
10
+ import {
11
+ AttributesTable,
12
+ CrosswalkDragLayer,
13
+ EmptyStub,
14
+ ErrorPopup,
15
+ ExpandedAttributesProvider,
16
+ HistoryActivityType,
17
+ HistoryFilter,
18
+ HistoryHeader,
19
+ HistoryPanelEmptyState,
20
+ HistoryView,
21
+ PerspectivesSettingsContext,
22
+ ResizablePanes,
23
+ ScreenProfileBand,
24
+ SideButtonsPanel
25
+ } from '@reltio/components';
26
+ import * as mdmSdk from '@reltio/mdm-sdk';
27
+ import {DateRangeTypes, UnmergeOperation} from '@reltio/mdm-sdk';
28
+ import SourcesView from '../SourcesView';
29
+ import {RulesetsSelector} from '../../RulesestsSelector/RulesestsSelector';
30
+ import * as RightSidePanelImport from '../../RightSidePanel/RightSidePanel';
31
+ import {RightSidePanel} from '../../RightSidePanel/RightSidePanel';
32
+ import {LegendMode, SourcesSavedState} from '../../../types';
33
+ import * as useCrosswalkTreeImport from '../../../hooks/useCrosswalkTree';
34
+ import * as useCrosswalksDisplayImport from '../../../hooks/useCrosswalksDisplay';
35
+ import * as useCrosswalkTreeActionsImport from '../../../hooks/useCrosswalkTreeActions';
36
+ import * as useEntityWithSurvivorshipGroupsImport from '../../../hooks/useEntityWithSurvivorshipGroups';
37
+ import {profile} from '@reltio/mdm-module';
38
+ import LegendView from '../../LegendView/LegendView';
39
+ import EmptyState from '../../EmptyState/EmptyState';
40
+ import TreeActionSnackbar from '../../TreeActionSnackbar/TreeActionSnackbar';
41
+
42
+ jest.mock('@reltio/mdm-sdk', () => ({
43
+ ...jest.requireActual<Record<string, unknown>>('@reltio/mdm-sdk'),
44
+ getCrosswalkTree: jest.fn(),
45
+ isDataTenantEntity: jest.fn(),
46
+ getHistoricUris: jest.fn(),
47
+ getHistoryWithTotal: jest.fn()
48
+ }));
49
+
50
+ jest.mock('@reltio/components', () => ({
51
+ ...jest.requireActual<Record<string, unknown>>('@reltio/components'),
52
+ ScreenProfileBand: () => null,
53
+ AttributesTable: () => null,
54
+ useModifiedEntity: jest.fn(),
55
+ CrosswalkDragLayer: () => null,
56
+ useHistory: jest.fn(),
57
+ HistoryHeader: () => null,
58
+ HistoryView: () => null,
59
+ useHistorySlice: jest.fn(),
60
+ useSnackbar: jest.fn()
61
+ }));
62
+
63
+ describe('SourcesView tests', () => {
64
+ let initialState;
65
+ let resolveSavedState;
66
+ const dispatch = jest.fn();
67
+ const createTestStore = (customInitialState?) => {
68
+ const reducer = (state = {...initialState, ...customInitialState}) => {
69
+ return state;
70
+ };
71
+ const store = createStore(reducer);
72
+ store.dispatch = dispatch;
73
+ return store;
74
+ };
75
+ const saveState = jest.fn();
76
+ const props = {
77
+ config: {
78
+ id: 'SourcesViewId'
79
+ },
80
+ saveState,
81
+ getSavedState: jest.fn(
82
+ () =>
83
+ new Promise((_resolve) => {
84
+ resolveSavedState = _resolve;
85
+ })
86
+ ) as () => Promise<SourcesSavedState>
87
+ };
88
+ const attributeActions = {
89
+ onPinAttribute: jest.fn(),
90
+ onDeleteAttribute: jest.fn(),
91
+ onIgnoreAttribute: jest.fn(),
92
+ onEditAttribute: jest.fn(),
93
+ onAddAttribute: jest.fn()
94
+ };
95
+ const modifiedEntity = {
96
+ uri: 'entities/1',
97
+ type: 'configuration/entityTypes/HCP'
98
+ };
99
+ const crosswalksMap = {
100
+ entity: {},
101
+ relation: {}
102
+ };
103
+ const crosswalkTree = {
104
+ crosswalks: [],
105
+ uri: 'test'
106
+ };
107
+ const crosswalksDisplayProps = {
108
+ getCrosswalkColor: jest.fn(),
109
+ focusedCrosswalks: [],
110
+ highlightedCrosswalks: [],
111
+ toggleCrosswalkFocus: jest.fn(),
112
+ setCrosswalkHighlighted: jest.fn()
113
+ };
114
+ const showSnackbarMessageMock = jest.fn();
115
+ const SnackbarRendererMock = () => <div />;
116
+ const useSnackbarMock = jest.spyOn(components, 'useSnackbar').mockReturnValue({
117
+ SnackbarRenderer: SnackbarRendererMock,
118
+ showSnackbarMessage: showSnackbarMessageMock
119
+ });
120
+
121
+ const newlyCreatedAttributes = [];
122
+ const useModifiedEntityMock = jest.spyOn(components, 'useModifiedEntity').mockReturnValue({
123
+ modifiedEntity,
124
+ crosswalksMap,
125
+ newlyCreatedAttributes,
126
+ ...attributeActions
127
+ });
128
+ const useCrosswalksDisplayMock = jest
129
+ .spyOn(useCrosswalksDisplayImport, 'useCrosswalksDisplay')
130
+ .mockReturnValue(crosswalksDisplayProps);
131
+ const showErrorMock = jest.spyOn(ErrorPopup, 'addError');
132
+ const isDataTenantEntityMock = jest.spyOn(mdmSdk, 'isDataTenantEntity').mockReturnValue(false);
133
+
134
+ const useCrosswalkTreeMock = jest.spyOn(useCrosswalkTreeImport, 'useCrosswalkTree');
135
+ const crosswalkTreeActions = {
136
+ addCrosswalk: jest.fn(),
137
+ unmergeContributor: jest.fn(),
138
+ editCrosswalk: jest.fn(),
139
+ deleteCrosswalk: jest.fn(),
140
+ unsubscribeContributor: jest.fn()
141
+ };
142
+ const useCrosswalkTreeActionsMock = jest
143
+ .spyOn(useCrosswalkTreeActionsImport, 'useCrosswalkTreeActions')
144
+ .mockReturnValue(crosswalkTreeActions);
145
+ const onReloadCrosswalkTree = jest.fn();
146
+
147
+ const onChangeSurvivorshipGroup = jest.fn();
148
+
149
+ const historicUris = ['entities/12345', 'entities/54321'];
150
+ const historyWithTotal = {
151
+ total: 3,
152
+ changes: [
153
+ {
154
+ timestamp: 1636456567409,
155
+ user: 'user1',
156
+ type: 'ENTITY_CHANGED'
157
+ },
158
+ {
159
+ timestamp: 1633089195406,
160
+ user: 'user2',
161
+ type: 'ENTITIES_SPLITTED'
162
+ },
163
+ {
164
+ timestamp: 1633089195000,
165
+ user: 'user3',
166
+ type: 'ENTITIES_SPLITTED'
167
+ }
168
+ ]
169
+ };
170
+
171
+ const onApplyFilterMock = jest.fn();
172
+ const onClearFilterMock = jest.fn();
173
+
174
+ const useHistoryResult = {
175
+ isLoading: false,
176
+ canLoadMore: false,
177
+ onLoadMore: jest.fn(),
178
+ historicUris,
179
+ historyFilter: {
180
+ users: ['username2'],
181
+ activities: [HistoryActivityType.UNMERGE]
182
+ },
183
+ historyWithTotal,
184
+ onApplyFilter: onApplyFilterMock,
185
+ onClearFilter: onClearFilterMock
186
+ };
187
+ const useHistoryMock = jest.spyOn(components, 'useHistory');
188
+ const useHistorySliceMock = jest
189
+ .spyOn(components, 'useHistorySlice')
190
+ .mockReturnValue({historySlice: null, isLoading: false});
191
+
192
+ beforeEach(() => {
193
+ initialState = {
194
+ tenant: {
195
+ id: 'tenant'
196
+ },
197
+ profile: {
198
+ entity: {
199
+ uri: 'entities/1',
200
+ type: 'configuration/entityTypes/HCP',
201
+ attributes: {
202
+ CountryCode: [
203
+ {
204
+ uri: 'entities/1/attributes/CountryCode/1',
205
+ type: 'configuration/entityTypes/HCP/attributes/CountryCode',
206
+ value: 'Italy',
207
+ lookupCode: 'IT'
208
+ }
209
+ ]
210
+ },
211
+ crosswalks: [
212
+ {
213
+ uri: 'entities/j8K1vc9/crosswalks/12jf9nL05',
214
+ type: 'configuration/sources/Reltio',
215
+ value: 'nQpZyQt',
216
+ reltioLoadDate: '2021-06-11T08:24:31.726Z',
217
+ createDate: '2018-11-19T12:14:00.735Z',
218
+ updateDate: '2018-11-19T12:14:00.735Z',
219
+ attributes: [
220
+ 'entities/j8K1vc9/attributes/CountryCode/12jf9nc37',
221
+ 'entities/j8K1vc9/attributes/Name/1dl1LhLwS'
222
+ ],
223
+ singleAttributeUpdateDates: {
224
+ 'entities/j8K1vc9/attributes/Name/1dl1LhLwS': '2018-11-29T05:39:56.445Z'
225
+ }
226
+ },
227
+ {
228
+ uri: 'entities/j8K1vc9/crosswalks/1CwTinUzm',
229
+ type: 'configuration/sources/AHA',
230
+ value: 'AHA_LEV',
231
+ reltioLoadDate: '2021-06-09T11:32:16.434Z',
232
+ sourcePublishDate: '1970-01-01T00:00:00.000Z',
233
+ createDate: '2018-11-28T13:16:18.540Z',
234
+ updateDate: '2018-11-28T13:16:18.540Z',
235
+ attributes: [
236
+ 'entities/j8K1vc9/attributes/Address/ZxOfYYo',
237
+ 'entities/j8K1vc9/attributes/Name/1CwTindWI'
238
+ ],
239
+ singleAttributeUpdateDates: {
240
+ 'entities/j8K1vc9/attributes/Name/1CwTindWI': '2021-06-09T11:32:16.434Z'
241
+ }
242
+ }
243
+ ]
244
+ },
245
+ history: {
246
+ event: {
247
+ aStamp: 1637128261696
248
+ }
249
+ }
250
+ },
251
+ metadata: {
252
+ tenant: {
253
+ entityTypes: [
254
+ {
255
+ uri: 'configuration/entityTypes/HCP',
256
+ label: 'HCP',
257
+ attributes: [
258
+ {
259
+ label: 'Country Code',
260
+ name: 'CountryCode',
261
+ type: 'String',
262
+ uri: 'configuration/entityTypes/HCP/attributes/CountryCode'
263
+ }
264
+ ]
265
+ }
266
+ ]
267
+ }
268
+ },
269
+ ui: {
270
+ config: {}
271
+ }
272
+ };
273
+ });
274
+
275
+ beforeAll(() => {
276
+ useCrosswalkTreeMock.mockReturnValue({
277
+ crosswalkTree: {
278
+ crosswalks: [],
279
+ uri: 'test'
280
+ },
281
+ reload: onReloadCrosswalkTree
282
+ });
283
+ jest.spyOn(RightSidePanelImport, 'RightSidePanel').mockImplementation(EmptyStub);
284
+ useHistoryMock.mockReturnValue(useHistoryResult);
285
+ jest.spyOn(useEntityWithSurvivorshipGroupsImport, 'useEntityWithSurvivorshipGroups').mockImplementation(
286
+ (entity) => ({
287
+ survivorshipGroups: [{uri: 'uri1', mapping: [], label: 'label'}],
288
+ currentSurvivorshipGroupUri: 'uri1',
289
+ onChangeSurvivorshipGroup,
290
+ entity,
291
+ isDefaultSurvivorshipGroupUri: true,
292
+ isLoading: false
293
+ })
294
+ );
295
+ });
296
+
297
+ it('should render correctly', () => {
298
+ const store = createTestStore();
299
+ const settingsContext = {settings: {source: {active: 0, width: 480}}, onChange: () => {}};
300
+ const wrapper = mount(
301
+ <Provider store={store}>
302
+ <PerspectivesSettingsContext.Provider value={settingsContext}>
303
+ <SourcesView {...props} />
304
+ </PerspectivesSettingsContext.Provider>
305
+ </Provider>
306
+ );
307
+ expect(wrapper.find(ResizablePanes).prop('size')).toBe(480);
308
+ expect(useCrosswalksDisplayMock).toBeCalledWith({
309
+ activeMode: 'crosswalks',
310
+ contributors: [crosswalkTree],
311
+ entity: initialState.profile.entity
312
+ });
313
+ expect(useModifiedEntityMock).toBeCalledWith(
314
+ expect.objectContaining({
315
+ entity: initialState.profile.entity
316
+ })
317
+ );
318
+ expect(useSnackbarMock).toBeCalledWith({
319
+ anchorOrigin: {vertical: 'top', horizontal: 'center'},
320
+ showDismiss: true
321
+ });
322
+ expect(useCrosswalkTreeMock).toBeCalledWith({
323
+ entityUri: initialState.profile.entity.uri,
324
+ onSuccess: expect.any(Function)
325
+ });
326
+ expect(wrapper.find(ExpandedAttributesProvider).prop('attributeUris')).toBe(newlyCreatedAttributes);
327
+ const attributeTable = wrapper.find(ExpandedAttributesProvider).find(AttributesTable);
328
+ expect(attributeTable.prop('entity')).toBe(modifiedEntity);
329
+ expect(attributeTable.prop('crosswalksMap')).toBe(crosswalksMap);
330
+ expect(attributeTable.prop('onDelete')).toBe(attributeActions.onDeleteAttribute);
331
+ expect(attributeTable.prop('onPin')).toBe(attributeActions.onPinAttribute);
332
+ expect(attributeTable.prop('onIgnore')).toBe(attributeActions.onIgnoreAttribute);
333
+ expect(attributeTable.prop('onEdit')).toBe(attributeActions.onEditAttribute);
334
+ expect(attributeTable.prop('onAdd')).toBe(attributeActions.onAddAttribute);
335
+ expect(attributeTable.prop('isLoading')).toBe(false);
336
+
337
+ const additionalHeaderItems = new ReactWrapper(attributeTable.prop('additionalHeaderItems') as any);
338
+ expect(additionalHeaderItems.find(RulesetsSelector).props()).toEqual({
339
+ survivorshipGroups: [{uri: 'uri1', mapping: [], label: 'label'}],
340
+ currentSurvivorshipGroupUri: 'uri1',
341
+ onChange: onChangeSurvivorshipGroup
342
+ });
343
+ expect(additionalHeaderItems.find(Divider)).toHaveLength(1);
344
+
345
+ const profileBand = wrapper.find(ScreenProfileBand);
346
+ expect(profileBand.prop('entity')).toBe(initialState.profile.entity);
347
+ expect(profileBand.prop('historySlice')).toBe(null);
348
+
349
+ expect(wrapper.find(SideButtonsPanel).prop('activeIndexId')).toBe(0);
350
+
351
+ expect(wrapper.find(CrosswalkDragLayer)).toHaveLength(1);
352
+ });
353
+
354
+ it('should pass correct isLoading prop to AttributesTable', () => {
355
+ jest.spyOn(useEntityWithSurvivorshipGroupsImport, 'useEntityWithSurvivorshipGroups').mockImplementation(
356
+ (entity) => ({
357
+ survivorshipGroups: [],
358
+ currentSurvivorshipGroupUri: null,
359
+ onChangeSurvivorshipGroup,
360
+ entity,
361
+ isDefaultSurvivorshipGroupUri: false,
362
+ isLoading: true
363
+ })
364
+ );
365
+
366
+ const store = createTestStore();
367
+ const wrapper = mount(
368
+ <Provider store={store}>
369
+ <SourcesView {...props} />
370
+ </Provider>
371
+ );
372
+
373
+ const attributeTable = wrapper.find(ExpandedAttributesProvider).find(AttributesTable);
374
+ expect(attributeTable.prop('isLoading')).toBe(true);
375
+ });
376
+
377
+ it('should pass readOnly prop equals true to AttributesTable if current survivorshipGroup is not default', () => {
378
+ jest.spyOn(useEntityWithSurvivorshipGroupsImport, 'useEntityWithSurvivorshipGroups').mockImplementation(
379
+ (entity) => ({
380
+ survivorshipGroups: [],
381
+ currentSurvivorshipGroupUri: null,
382
+ onChangeSurvivorshipGroup,
383
+ entity,
384
+ isDefaultSurvivorshipGroupUri: false,
385
+ isLoading: false
386
+ })
387
+ );
388
+
389
+ const store = createTestStore();
390
+ const wrapper = mount(
391
+ <Provider store={store}>
392
+ <SourcesView {...props} />
393
+ </Provider>
394
+ );
395
+
396
+ const attributeTable = wrapper.find(ExpandedAttributesProvider).find(AttributesTable);
397
+ expect(attributeTable.prop('readOnly')).toBe(true);
398
+ });
399
+
400
+ describe('Legend tab', () => {
401
+ it('should render LegendView in side panel and close it on tab click', async () => {
402
+ const store = createTestStore();
403
+ let wrapper;
404
+ const settingsContext = {settings: {source: {active: 0, width: 480}}, onChange: () => {}};
405
+ await act(async () => {
406
+ wrapper = mount(
407
+ <Provider store={store}>
408
+ <PerspectivesSettingsContext.Provider value={settingsContext}>
409
+ <SourcesView {...props} />
410
+ </PerspectivesSettingsContext.Provider>
411
+ </Provider>
412
+ );
413
+ });
414
+
415
+ const rightSidePanel = wrapper.find(RightSidePanel);
416
+ const titleBlock = shallow(<div>{rightSidePanel.prop('tabs')[0].title}</div>);
417
+ expect(titleBlock.find(Typography).text()).toBe('Legend');
418
+ const legendBlock = shallow(<div>{rightSidePanel.prop('tabs')[0].content}</div>).find(LegendView);
419
+ expect(legendBlock.props()).toMatchObject({
420
+ entity: initialState.profile.entity,
421
+ activeMode: 'crosswalks',
422
+ crosswalkTree,
423
+ contributors: [{uri: 'test', crosswalks: []}],
424
+ selectedAttributeTypes: undefined,
425
+ onAddCrosswalk: crosswalkTreeActions.addCrosswalk,
426
+ onUnmergeContributor: crosswalkTreeActions.unmergeContributor,
427
+ onUnsubscribeContributor: crosswalkTreeActions.unsubscribeContributor,
428
+ onEditCrosswalk: crosswalkTreeActions.editCrosswalk,
429
+ onDeleteCrosswalk: crosswalkTreeActions.deleteCrosswalk
430
+ });
431
+ });
432
+
433
+ it('should change legend mode correctly', () => {
434
+ const store = createTestStore();
435
+ const settingsContext = {settings: {source: {active: 0, width: 480}}, onChange: () => {}};
436
+ const wrapper = mount(
437
+ <Provider store={store}>
438
+ <PerspectivesSettingsContext.Provider value={settingsContext}>
439
+ <SourcesView {...props} />
440
+ </PerspectivesSettingsContext.Provider>
441
+ </Provider>
442
+ );
443
+
444
+ const rightSidePanel = wrapper.find(RightSidePanel);
445
+ const legendBlock = shallow(<div>{rightSidePanel.prop('tabs')[0].content}</div>).find(LegendView);
446
+ act(() => {
447
+ legendBlock.prop('onChangeMode')(LegendMode.Source);
448
+ });
449
+ wrapper.update();
450
+
451
+ const newRightSidePanel = wrapper.find(RightSidePanel);
452
+ const newLegendBlock = shallow(<div>{newRightSidePanel.prop('tabs')[0].content}</div>).find(LegendView);
453
+ expect(newLegendBlock.prop('activeMode')).toBe(LegendMode.Source);
454
+ });
455
+
456
+ it('should apply selected attributes from table to legend view', () => {
457
+ const store = createTestStore();
458
+ const settingsContext = {settings: {source: {active: 0, width: 480}}, onChange: () => {}};
459
+ const wrapper = mount(
460
+ <Provider store={store}>
461
+ <PerspectivesSettingsContext.Provider value={settingsContext}>
462
+ <SourcesView {...props} />
463
+ </PerspectivesSettingsContext.Provider>
464
+ </Provider>
465
+ );
466
+
467
+ const selectedAttributeTypes = [
468
+ {
469
+ label: 'Country Code',
470
+ name: 'CountryCode',
471
+ type: 'String',
472
+ uri: 'configuration/entityTypes/HCP/attributes/CountryCode'
473
+ }
474
+ ];
475
+ act(() => {
476
+ wrapper.find(AttributesTable).prop('onSelectAttributeTypes')(selectedAttributeTypes);
477
+ });
478
+ wrapper.update();
479
+
480
+ const rightSidePanel = wrapper.find(RightSidePanel);
481
+ const titleBlock = shallow(<div>{rightSidePanel.prop('tabs')[0].title}</div>);
482
+ expect(titleBlock.find(Typography).text()).toBe('Legend');
483
+ const legendBlock = shallow(<div>{rightSidePanel.prop('tabs')[0].content}</div>).find(LegendView);
484
+ expect(legendBlock.prop('selectedAttributeTypes')).toBe(selectedAttributeTypes);
485
+ });
486
+ });
487
+
488
+ it('should provide correct visibleColumns to AttributeTable and change it', async () => {
489
+ const store = createTestStore();
490
+ const wrapper = mount(
491
+ <Provider store={store}>
492
+ <SourcesView {...props} />
493
+ </Provider>
494
+ );
495
+ await act(async () => {
496
+ resolveSavedState({visibleColumns: ['name1', 'name2']});
497
+ });
498
+ wrapper.update();
499
+
500
+ const attributeTable = wrapper.find(AttributesTable);
501
+ expect(attributeTable.prop('visibleColumns')).toEqual(['name1', 'name2']);
502
+
503
+ act(() => {
504
+ attributeTable.prop('onChangeVisibleColumns')(['name3']);
505
+ });
506
+ wrapper.update();
507
+ expect(wrapper.find(AttributesTable).prop('visibleColumns')).toEqual(['name3']);
508
+ expect(saveState).toHaveBeenCalledWith({visibleColumns: ['name3']});
509
+ });
510
+
511
+ it('should refresh entity and reload crosswalk tree on successful attribute action', () => {
512
+ const store = createTestStore();
513
+ mount(
514
+ <Provider store={store}>
515
+ <SourcesView {...props} />
516
+ </Provider>
517
+ );
518
+ const {onModify} = useModifiedEntityMock.mock.calls[0][0];
519
+ onModify();
520
+ expect(dispatch).toBeCalledWith(
521
+ profile.entity.actions.loadEntity({
522
+ viewId: 'SourcesViewId',
523
+ uri: 'entities/1'
524
+ })
525
+ );
526
+ expect(onReloadCrosswalkTree).toHaveBeenCalled();
527
+ });
528
+
529
+ it('should show error on entity attribute action error', () => {
530
+ const store = createTestStore();
531
+ mount(
532
+ <Provider store={store}>
533
+ <SourcesView {...props} />
534
+ </Provider>
535
+ );
536
+ const {onError} = useModifiedEntityMock.mock.calls[0][0];
537
+ onError({errorMessage: 'Some error'});
538
+ expect(showErrorMock).toBeCalledWith({
539
+ title: 'Error',
540
+ message: 'Some error'
541
+ });
542
+ });
543
+
544
+ it('should refresh entity and reload crosswalk tree on successful crosswalk tree action', () => {
545
+ const store = createTestStore();
546
+ mount(
547
+ <Provider store={store}>
548
+ <SourcesView {...props} />
549
+ </Provider>
550
+ );
551
+ const {onSuccess} = useCrosswalkTreeActionsMock.mock.calls[0][0];
552
+ onSuccess();
553
+ expect(dispatch).toBeCalledWith(
554
+ profile.entity.actions.loadEntity({
555
+ viewId: 'SourcesViewId',
556
+ uri: 'entities/1'
557
+ })
558
+ );
559
+ expect(onReloadCrosswalkTree).toHaveBeenCalled();
560
+ });
561
+
562
+ it('should show correct snackbar when onShowMessage prop of useCrosswalkTreeActions calls', () => {
563
+ const store = createTestStore();
564
+ const options = {
565
+ message: 'some message',
566
+ entity: {
567
+ uri: 'entities/123',
568
+ type: 'configuration/entityTypes/HCP',
569
+ label: 'Entity label'
570
+ }
571
+ };
572
+ mount(
573
+ <Provider store={store}>
574
+ <SourcesView {...props} />
575
+ </Provider>
576
+ );
577
+ const {onShowMessage} = useCrosswalkTreeActionsMock.mock.calls[0][0];
578
+ onShowMessage(options.message, options.entity);
579
+ expect(showSnackbarMessageMock).toBeCalledWith(
580
+ <TreeActionSnackbar message={options.message} entity={options.entity} />
581
+ );
582
+
583
+ onShowMessage(options.message);
584
+ expect(showSnackbarMessageMock).toBeCalledWith(options.message);
585
+ });
586
+
587
+ it('should show error on crosswalk tree action error', () => {
588
+ const store = createTestStore();
589
+ mount(
590
+ <Provider store={store}>
591
+ <SourcesView {...props} />
592
+ </Provider>
593
+ );
594
+ const {onError} = useCrosswalkTreeActionsMock.mock.calls[0][0];
595
+ onError({errorMessage: 'Some error'});
596
+ expect(showErrorMock).toBeCalledWith({
597
+ title: 'Error',
598
+ message: 'Some error'
599
+ });
600
+ });
601
+
602
+ it('should show error and reload data on crosswalk not found error', () => {
603
+ const store = createTestStore();
604
+ mount(
605
+ <Provider store={store}>
606
+ <SourcesView {...props} />
607
+ </Provider>
608
+ );
609
+ const {onError} = useCrosswalkTreeActionsMock.mock.calls[0][0];
610
+ onError({errorMessage: 'Crosswalk not found', errorCode: 102});
611
+ expect(showErrorMock).toBeCalledWith({
612
+ title: 'Error',
613
+ message: 'Crosswalk not found'
614
+ });
615
+ expect(dispatch).toBeCalledWith(
616
+ profile.entity.actions.loadEntity({
617
+ viewId: 'SourcesViewId',
618
+ uri: 'entities/1'
619
+ })
620
+ );
621
+ expect(onReloadCrosswalkTree).toHaveBeenCalled();
622
+ });
623
+
624
+ it('should render correctly for data tenant entity', () => {
625
+ isDataTenantEntityMock.mockReturnValueOnce(true);
626
+ const store = createTestStore();
627
+ const settingsContext = {settings: {source: {active: 0, width: 480}}, onChange: () => {}};
628
+ const wrapper = mount(
629
+ <Provider store={store}>
630
+ <PerspectivesSettingsContext.Provider value={settingsContext}>
631
+ <SourcesView {...props} />
632
+ </PerspectivesSettingsContext.Provider>
633
+ </Provider>
634
+ );
635
+ expect(useCrosswalkTreeImport.useCrosswalkTree).toBeCalledWith({
636
+ entityUri: undefined,
637
+ onSuccess: expect.any(Function)
638
+ });
639
+ expect(wrapper.find(AttributesTable)).toHaveLength(0);
640
+ expect(wrapper.find(EmptyState)).toHaveLength(1);
641
+
642
+ isDataTenantEntityMock.mockReturnValueOnce(true);
643
+
644
+ const rightSidePanel = wrapper.find(RightSidePanel);
645
+
646
+ const legendBlock = shallow(<div>{rightSidePanel.prop('tabs')[0].content}</div>).find(LegendView);
647
+ expect(legendBlock.prop('entity')).toBeNull();
648
+ });
649
+
650
+ describe('History tab', () => {
651
+ it('should render History tab', () => {
652
+ const store = createTestStore();
653
+ const wrapper = mount(
654
+ <Provider store={store}>
655
+ <SourcesView {...props} config={{id: 'SourcesViewId'}} />
656
+ </Provider>
657
+ );
658
+ const sideButtonsPanelButtons = wrapper.find(SideButtonsPanel).prop('buttonsProps');
659
+ expect(sideButtonsPanelButtons.length).toBe(2);
660
+ expect(sideButtonsPanelButtons[0].tooltipTitle).toBe('Legend');
661
+ expect(sideButtonsPanelButtons[1].tooltipTitle).toBe('History');
662
+ });
663
+
664
+ it('should render HistoryView in side panel if click on History tab', () => {
665
+ const store = createTestStore();
666
+ const settingsContext = {settings: {source: {active: 1, width: 480}}, onChange: () => {}};
667
+ const wrapper = mount(
668
+ <Provider store={store}>
669
+ <PerspectivesSettingsContext.Provider value={settingsContext}>
670
+ <SourcesView {...props} config={{id: 'SourcesViewId'}} />
671
+ </PerspectivesSettingsContext.Provider>
672
+ </Provider>
673
+ );
674
+
675
+ const rightSidePanel = wrapper.find(RightSidePanel);
676
+ const titleBlock = shallow(<div>{rightSidePanel.prop('tabs')[1].title}</div>);
677
+ expect(titleBlock.find(HistoryHeader)).toHaveLength(1);
678
+ expect(titleBlock.find(HistoryHeader).props()).toMatchObject({
679
+ total: historicUris.length + 1,
680
+ entityType: initialState.metadata.tenant.entityTypes[0],
681
+ filter: useHistoryResult.historyFilter
682
+ });
683
+
684
+ const historyBlock = shallow(<div>{rightSidePanel.prop('tabs')[1].content}</div>).find(HistoryView);
685
+ expect(historyBlock).toHaveLength(1);
686
+ expect(historyBlock.props()).toMatchObject({
687
+ isLoading: useHistoryResult.isLoading,
688
+ historyWithTotal: useHistoryResult.historyWithTotal,
689
+ entityUri: initialState.profile.entity.uri,
690
+ historyEvent: initialState.profile.history.event,
691
+ contributorsUris: useHistoryResult.historicUris,
692
+ onLoadMore: useHistoryResult.onLoadMore,
693
+ canLoadMore: useHistoryResult.canLoadMore
694
+ });
695
+ });
696
+
697
+ it('should render HistoryPanelEmptyState in side panel for data tenant entity', () => {
698
+ const store = createTestStore();
699
+ isDataTenantEntityMock.mockReturnValue(true);
700
+ const settingsContext = {settings: {source: {active: 1, width: 480}}, onChange: () => {}};
701
+ const wrapper = mount(
702
+ <Provider store={store}>
703
+ <PerspectivesSettingsContext.Provider value={settingsContext}>
704
+ <SourcesView {...props} config={{id: 'SourcesViewId'}} />
705
+ </PerspectivesSettingsContext.Provider>
706
+ </Provider>
707
+ );
708
+ expect(useHistoryMock).toBeCalledWith({entity: null, enabled: false});
709
+
710
+ const rightSidePanel = wrapper.find(RightSidePanel);
711
+ const titleBlock = shallow(<div>{rightSidePanel.prop('tabs')[1].title}</div>);
712
+ expect(titleBlock.find(HistoryHeader)).toHaveLength(1);
713
+ const historyBlock = shallow(<div>{rightSidePanel.prop('tabs')[1].content}</div>);
714
+ expect(historyBlock.find(HistoryView).length).toBe(0);
715
+ expect(historyBlock.find(HistoryPanelEmptyState).length).toBe(1);
716
+ isDataTenantEntityMock.mockReset();
717
+ });
718
+
719
+ it('should render HistoryPanelEmptyState in side panel for temp entity', () => {
720
+ const store = createTestStore({
721
+ profile: {
722
+ entity: {
723
+ uri: 'entities/uri$$1644994321193',
724
+ type: 'configuration/entityTypes/HCP',
725
+ attributes: {},
726
+ crosswalks: []
727
+ },
728
+ history: {
729
+ event: {
730
+ aStamp: 1637128261696
731
+ }
732
+ }
733
+ }
734
+ });
735
+ const settingsContext = {settings: {source: {active: 1, width: 480}}, onChange: () => {}};
736
+ const wrapper = mount(
737
+ <Provider store={store}>
738
+ <PerspectivesSettingsContext.Provider value={settingsContext}>
739
+ <SourcesView {...props} config={{id: 'SourcesViewId'}} />
740
+ </PerspectivesSettingsContext.Provider>
741
+ </Provider>
742
+ );
743
+ expect(useHistoryMock).toBeCalledWith({entity: null, enabled: false});
744
+
745
+ act(() => {
746
+ wrapper.find(SideButtonsPanel).prop('onButtonClick')(1);
747
+ });
748
+ wrapper.update();
749
+
750
+ const rightSidePanel = wrapper.find(RightSidePanel);
751
+ const titleBlock = shallow(<div>{rightSidePanel.prop('tabs')[1].title}</div>);
752
+ expect(titleBlock.find(HistoryHeader)).toHaveLength(1);
753
+ const historyBlock = shallow(<div>{rightSidePanel.prop('tabs')[1].content}</div>);
754
+ expect(historyBlock.find(HistoryView).length).toBe(0);
755
+ expect(historyBlock.find(HistoryPanelEmptyState).length).toBe(1);
756
+ });
757
+
758
+ it('should set history filter in useHistory hook when call onApplyFilter of HistoryHeader', () => {
759
+ const store = createTestStore();
760
+ const settingsContext = {settings: {source: {active: 1, width: 480}}, onChange: () => {}};
761
+ const wrapper = mount(
762
+ <Provider store={store}>
763
+ <PerspectivesSettingsContext.Provider value={settingsContext}>
764
+ <SourcesView {...props} config={{id: 'SourcesViewId'}} />
765
+ </PerspectivesSettingsContext.Provider>
766
+ </Provider>
767
+ );
768
+
769
+ const rightSidePanel = wrapper.find(RightSidePanel);
770
+ const titleBlock = shallow(<div>{rightSidePanel.prop('tabs')[1].title}</div>);
771
+ expect(titleBlock.find(HistoryHeader).prop('filter')).toBe(useHistoryResult.historyFilter);
772
+ const newFilter: HistoryFilter = {
773
+ dateRange: {
774
+ type: DateRangeTypes.AGO,
775
+ period: [4, 'month']
776
+ },
777
+ users: ['username1', 'username3'],
778
+ activities: [HistoryActivityType.UPDATE, HistoryActivityType.UNMERGE],
779
+ attributes: []
780
+ };
781
+
782
+ act(() => {
783
+ titleBlock.find(HistoryHeader).prop('onApplyFilter')(newFilter);
784
+ });
785
+ wrapper.update();
786
+ expect(onApplyFilterMock).toBeCalledWith(newFilter);
787
+ });
788
+ });
789
+
790
+ it('should render correctly if history slice is opened', () => {
791
+ const historySlice = {
792
+ aEntity: {
793
+ uri: 'entities/sliceA',
794
+ type: 'configuration/entityTypes/HCP',
795
+ attributes: {}
796
+ },
797
+ bEntity: {
798
+ uri: 'entities/sliceB',
799
+ type: 'configuration/entityTypes/HCP',
800
+ attributes: {}
801
+ }
802
+ };
803
+ useHistorySliceMock.mockReturnValue({isLoading: false, historySlice});
804
+ const store = createTestStore();
805
+ const settingsContext = {settings: {source: {active: 0, width: 480}}, onChange: () => {}};
806
+ const wrapper = mount(
807
+ <Provider store={store}>
808
+ <PerspectivesSettingsContext.Provider value={settingsContext}>
809
+ <SourcesView {...props} />
810
+ </PerspectivesSettingsContext.Provider>
811
+ </Provider>
812
+ );
813
+ const profileBand = wrapper.find(ScreenProfileBand);
814
+ expect(profileBand.prop('entity')).toBe(initialState.profile.entity);
815
+ expect(profileBand.prop('historySlice')).toBe(historySlice);
816
+
817
+ expect(useModifiedEntityMock).toBeCalledWith(
818
+ expect.objectContaining({
819
+ entity: historySlice.aEntity
820
+ })
821
+ );
822
+ expect(wrapper.find(AttributesTable).prop('readOnly')).toBe(true);
823
+
824
+ expect(useCrosswalkTreeMock).toBeCalledWith({
825
+ entityUri: historySlice.aEntity.uri,
826
+ onSuccess: expect.any(Function)
827
+ });
828
+ const rightSidePanel = wrapper.find(RightSidePanel);
829
+ const legendView = shallow(<div>{rightSidePanel.prop('tabs')[0].content}</div>).find(LegendView);
830
+ expect(legendView.prop('entity')).toBe(historySlice.aEntity);
831
+ expect(legendView.prop('readOnly')).toBe(true);
832
+ useHistorySliceMock.mockClear();
833
+ });
834
+
835
+ it('should pass readOnly prop equals true to if unmerge or unsubscribe request is in process', async () => {
836
+ const getLegendBlock = (wrapper: ReactWrapper) => {
837
+ const rightSidePanel = wrapper.find(RightSidePanel);
838
+ return shallow(<div>{rightSidePanel.prop('tabs')[0].content}</div>).find(LegendView);
839
+ };
840
+ useHistorySliceMock.mockReturnValue({isLoading: false, historySlice: null});
841
+ useCrosswalkTreeActionsMock.mockImplementation((params) => {
842
+ const crosswalkTreeActions = {
843
+ addCrosswalk: jest.fn(),
844
+ unmergeContributor: (_event) => {
845
+ params.onLoad();
846
+ params.onSuccess();
847
+ },
848
+ editCrosswalk: jest.fn(),
849
+ deleteCrosswalk: jest.fn(),
850
+ unsubscribeContributor: jest.fn()
851
+ };
852
+ return crosswalkTreeActions;
853
+ });
854
+
855
+ const reloadData = () =>
856
+ new Promise<void>((resolve) => {
857
+ resolve();
858
+ });
859
+
860
+ useCrosswalkTreeMock.mockImplementation((params) => {
861
+ return {
862
+ crosswalkTree: {
863
+ crosswalks: [],
864
+ uri: 'test'
865
+ },
866
+ reload: () => {
867
+ reloadData().finally(() => {
868
+ params.onSuccess();
869
+ });
870
+ }
871
+ };
872
+ });
873
+
874
+ const store = createTestStore();
875
+ const settingsContext = {settings: {source: {active: 0, width: 480}}, onChange: () => {}};
876
+ const wrapper = mount(
877
+ <Provider store={store}>
878
+ <PerspectivesSettingsContext.Provider value={settingsContext}>
879
+ <SourcesView {...props} />
880
+ </PerspectivesSettingsContext.Provider>
881
+ </Provider>
882
+ );
883
+ const legendBlock = getLegendBlock(wrapper);
884
+ expect(legendBlock.prop('readOnly')).toBe(false);
885
+
886
+ legendBlock.prop('onUnmergeContributor')({
887
+ contributorUri: 'contributorUri',
888
+ entityUri: 'entityUri',
889
+ operationType: UnmergeOperation.Unmerge
890
+ });
891
+
892
+ wrapper.setProps({});
893
+
894
+ expect(getLegendBlock(wrapper).prop('readOnly')).toBe(true);
895
+
896
+ await act(async () => {
897
+ reloadData();
898
+ });
899
+ wrapper.setProps({});
900
+
901
+ expect(getLegendBlock(wrapper).prop('readOnly')).toBe(false);
902
+ });
903
+ });