@rancher/shell 3.0.12-rc.4 → 3.0.12-rc.5

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 (81) hide show
  1. package/assets/styles/global/_button.scss +1 -1
  2. package/assets/translations/en-us.yaml +39 -10
  3. package/components/ActionDropdownShell.vue +5 -3
  4. package/components/ButtonGroup.vue +26 -1
  5. package/components/CruResource.vue +51 -2
  6. package/components/PromptRestore.vue +93 -32
  7. package/components/Questions/index.vue +1 -0
  8. package/components/ResourceTable.vue +1 -0
  9. package/components/SortableTable/index.vue +4 -3
  10. package/components/Wizard.vue +14 -1
  11. package/components/__tests__/ButtonGroup.test.ts +56 -0
  12. package/components/__tests__/PromptRestore.test.ts +169 -19
  13. package/components/fleet/GitRepoAdvancedTab.vue +1 -0
  14. package/components/fleet/GitRepoMetadataTab.vue +5 -0
  15. package/components/fleet/HelmOpAppCoConfigTab.vue +4 -0
  16. package/components/fleet/HelmOpMetadataTab.vue +5 -0
  17. package/components/form/FileSelector.vue +39 -1
  18. package/components/form/PrivateRegistry.constants.ts +7 -0
  19. package/components/form/PrivateRegistry.vue +253 -18
  20. package/components/form/SelectOrCreateAuthSecret.vue +140 -17
  21. package/components/form/__tests__/FileSelector.test.ts +23 -0
  22. package/components/form/__tests__/PrivateRegistry.test.ts +463 -73
  23. package/components/form/__tests__/SelectOrCreateAuthSecret.test.ts +122 -0
  24. package/components/formatter/EtcdSnapshotName.vue +73 -0
  25. package/components/nav/Header.vue +8 -1
  26. package/components/templates/default.vue +7 -0
  27. package/config/features.js +1 -0
  28. package/config/labels-annotations.js +2 -0
  29. package/config/product/manager.js +6 -0
  30. package/config/secret.ts +10 -0
  31. package/config/settings.ts +6 -2
  32. package/config/types.js +7 -0
  33. package/detail/provisioning.cattle.io.cluster.vue +79 -3
  34. package/dialog/RotateEncryptionKeyDialog.vue +33 -9
  35. package/dialog/__tests__/RotateEncryptionKeyDialog.test.ts +78 -0
  36. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +92 -0
  37. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +101 -0
  38. package/edit/__tests__/management.cattle.io.setting.test.ts +2 -1
  39. package/edit/compliance.cattle.io.clusterscanprofile.vue +39 -41
  40. package/edit/fleet.cattle.io.gitrepo.vue +70 -16
  41. package/edit/fleet.cattle.io.helmop.vue +51 -5
  42. package/edit/helm.cattle.io.projecthelmchart.vue +1 -0
  43. package/edit/{management.cattle.io.setting.vue → management.cattle.io.setting/index.vue} +32 -9
  44. package/edit/management.cattle.io.setting/system-default-registry-pull-secrets.vue +81 -0
  45. package/edit/provisioning.cattle.io.cluster/SelectCredential.vue +3 -12
  46. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +18 -0
  47. package/edit/provisioning.cattle.io.cluster/rke2.vue +5 -1
  48. package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +0 -1
  49. package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +14 -55
  50. package/models/__tests__/provisioning.cattle.io.cluster.test.ts +156 -0
  51. package/models/__tests__/secret.test.ts +68 -1
  52. package/models/management.cattle.io.cluster.js +21 -3
  53. package/models/pod.js +13 -2
  54. package/models/provisioning.cattle.io.cluster.js +59 -9
  55. package/models/rke.cattle.io.etcdsnapshot.js +17 -9
  56. package/models/secret.js +19 -0
  57. package/models/workload.js +12 -7
  58. package/package.json +1 -1
  59. package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +485 -107
  60. package/pages/c/_cluster/apps/charts/install.vue +114 -28
  61. package/pkg/require-asset.lib.js +25 -0
  62. package/pkg/vue.config.js +7 -0
  63. package/plugins/dashboard-store/__tests__/resource-class.test.ts +84 -0
  64. package/plugins/dashboard-store/getters.js +0 -1
  65. package/plugins/dashboard-store/resource-class.js +52 -12
  66. package/rancher-components/Form/TextArea/TextAreaAutoGrow.vue +30 -0
  67. package/rancher-components/Form/TextArea/__tests__/TextAreaAutoGrow.test.ts +95 -0
  68. package/rancher-components/RcButton/index.ts +1 -1
  69. package/rancher-components/RcDropdown/RcDropdownTrigger.vue +6 -1
  70. package/store/__tests__/features.test.ts +131 -0
  71. package/store/__tests__/growl.test.ts +374 -0
  72. package/store/__tests__/modal.test.ts +131 -0
  73. package/store/__tests__/slideInPanel.test.ts +88 -0
  74. package/store/__tests__/type-map.utils.test.ts +433 -0
  75. package/store/features.js +4 -0
  76. package/types/shell/index.d.ts +62 -0
  77. package/utils/__tests__/operation-cr.test.ts +34 -0
  78. package/utils/operation-cr.js +19 -0
  79. package/utils/require-asset.ts +7 -0
  80. package/utils/validators/__tests__/private-registry.test.ts +27 -15
  81. package/utils/validators/private-registry.ts +15 -4
@@ -0,0 +1,131 @@
1
+ import {
2
+ getters,
3
+ actions,
4
+ mapFeature,
5
+ create,
6
+ MULTI_CLUSTER,
7
+ LEGACY,
8
+ RKE2,
9
+ } from '@shell/store/features';
10
+ import { MANAGEMENT } from '@shell/config/types';
11
+
12
+ describe('features store', () => {
13
+ describe('create', () => {
14
+ it('returns the feature name', () => {
15
+ const name = 'test-feature-create-only';
16
+ const result = create(name, true);
17
+
18
+ expect(result).toStrictEqual(name);
19
+ });
20
+ });
21
+
22
+ describe('mapFeature', () => {
23
+ it('calls the features/get getter with the feature name', () => {
24
+ const mockGetFn = jest.fn(() => true);
25
+ const ctx = { $store: { getters: { 'features/get': mockGetFn } } };
26
+ const mapped = mapFeature('some-feature');
27
+
28
+ const result = mapped.get.call(ctx);
29
+
30
+ expect(mockGetFn).toHaveBeenCalledWith('some-feature');
31
+ expect(result).toBe(true);
32
+ });
33
+
34
+ it('set throws an error indicating the store is get-only', () => {
35
+ const mapped = mapFeature('some-feature');
36
+
37
+ expect(() => mapped.set(true)).toThrow('The feature store only supports getting');
38
+ });
39
+ });
40
+
41
+ describe('getters', () => {
42
+ describe('get', () => {
43
+ it('throws for an unknown feature name', () => {
44
+ const rootGetters = { 'management/byId': jest.fn(() => undefined) };
45
+
46
+ expect(() => getters.get({}, {}, {}, rootGetters)('unknown-feature-xyz')).toThrow('Unknown feature: unknown-feature-xyz');
47
+ });
48
+
49
+ it('returns entry.enabled from the server when an entry is found', () => {
50
+ const entry = { enabled: false };
51
+ const rootGetters = { 'management/byId': jest.fn(() => entry) };
52
+
53
+ const result = getters.get({}, {}, {}, rootGetters)(MULTI_CLUSTER);
54
+
55
+ expect(result).toBe(false);
56
+ });
57
+
58
+ it.each([
59
+ {
60
+ desc: 'multi-cluster-management (default true)',
61
+ feature: MULTI_CLUSTER,
62
+ expected: true,
63
+ },
64
+ {
65
+ desc: 'legacy (default false)',
66
+ feature: LEGACY,
67
+ expected: false,
68
+ },
69
+ {
70
+ desc: 'rke2 (default true)',
71
+ feature: RKE2,
72
+ expected: true,
73
+ },
74
+ ])('returns the registered default when no server entry exists for $desc', ({ feature, expected }) => {
75
+ const rootGetters = { 'management/byId': jest.fn(() => undefined) };
76
+
77
+ const result = getters.get({}, {}, {}, rootGetters)(feature);
78
+
79
+ expect(result).toBe(expected);
80
+ });
81
+
82
+ it('calls management/byId with the MANAGEMENT.FEATURE type and the feature name', () => {
83
+ const byId = jest.fn(() => undefined);
84
+ const rootGetters = { 'management/byId': byId };
85
+
86
+ getters.get({}, {}, {}, rootGetters)(RKE2);
87
+
88
+ expect(byId).toHaveBeenCalledWith(MANAGEMENT.FEATURE, RKE2);
89
+ });
90
+ });
91
+ });
92
+
93
+ describe('actions', () => {
94
+ describe('loadServer', () => {
95
+ it('dispatches management/findAll when canList returns true', async() => {
96
+ const findAllResult = [{ name: 'feature-1' }];
97
+ const dispatch = jest.fn().mockResolvedValue(findAllResult);
98
+ const rootGetters = { 'management/canList': jest.fn(() => true) };
99
+
100
+ const result = await actions.loadServer({ rootGetters, dispatch });
101
+
102
+ expect(dispatch).toHaveBeenCalledWith(
103
+ 'management/findAll',
104
+ { type: MANAGEMENT.FEATURE, opt: { watch: false } },
105
+ { root: true }
106
+ );
107
+ expect(result).toStrictEqual(findAllResult);
108
+ });
109
+
110
+ it('does not dispatch when canList returns false', async() => {
111
+ const dispatch = jest.fn();
112
+ const rootGetters = { 'management/canList': jest.fn(() => false) };
113
+
114
+ const result = await actions.loadServer({ rootGetters, dispatch });
115
+
116
+ expect(dispatch).not.toHaveBeenCalled();
117
+ expect(result).toBeUndefined();
118
+ });
119
+
120
+ it('calls management/canList with the MANAGEMENT.FEATURE type', async() => {
121
+ const canList = jest.fn(() => false);
122
+ const dispatch = jest.fn();
123
+ const rootGetters = { 'management/canList': canList };
124
+
125
+ await actions.loadServer({ rootGetters, dispatch });
126
+
127
+ expect(canList).toHaveBeenCalledWith(MANAGEMENT.FEATURE);
128
+ });
129
+ });
130
+ });
131
+ });
@@ -0,0 +1,374 @@
1
+ import { state, getters, mutations, actions } from '@shell/store/growl';
2
+ import { NotificationLevel } from '@shell/types/notifications';
3
+
4
+ describe('growl store', () => {
5
+ describe('state', () => {
6
+ it('returns initial state with nextId=1 and empty stack', () => {
7
+ const result = state();
8
+
9
+ expect(result.nextId).toStrictEqual(1);
10
+ expect(result.stack).toStrictEqual([]);
11
+ });
12
+ });
13
+
14
+ describe('getters', () => {
15
+ const item1 = {
16
+ id: 1, color: 'success', title: 'item 1'
17
+ };
18
+ const item2 = {
19
+ id: 2, color: 'error', title: 'item 2'
20
+ };
21
+ let mockState: ReturnType<typeof state>;
22
+
23
+ beforeEach(() => {
24
+ mockState = state();
25
+ mockState.stack = [item1, item2];
26
+ });
27
+
28
+ describe('find', () => {
29
+ it('finds an item by key and value', () => {
30
+ const result = getters.find(mockState)({ key: 'color', val: 'error' });
31
+
32
+ expect(result).toStrictEqual(item2);
33
+ });
34
+
35
+ it('returns undefined when no item matches', () => {
36
+ const result = getters.find(mockState)({ key: 'color', val: 'warning' });
37
+
38
+ expect(result).toBeUndefined();
39
+ });
40
+ });
41
+
42
+ describe('byId', () => {
43
+ it('finds an item by id', () => {
44
+ const result = getters.byId(mockState)(2);
45
+
46
+ expect(result).toStrictEqual(item2);
47
+ });
48
+
49
+ it('returns undefined when id not found', () => {
50
+ const result = getters.byId(mockState)(99);
51
+
52
+ expect(result).toBeUndefined();
53
+ });
54
+ });
55
+ });
56
+
57
+ describe('mutations', () => {
58
+ describe('add', () => {
59
+ it('adds an item to the front of the stack', () => {
60
+ const s = state();
61
+
62
+ mutations.add(s, { title: 'hello' });
63
+
64
+ expect(s.stack).toHaveLength(1);
65
+ expect(s.stack[0]).toMatchObject({ title: 'hello' });
66
+ });
67
+
68
+ it('assigns auto-incremented ids and updates nextId', () => {
69
+ const s = state();
70
+
71
+ mutations.add(s, { title: 'first' });
72
+ mutations.add(s, { title: 'second' });
73
+
74
+ expect(s.stack[1].id).toStrictEqual(1);
75
+ expect(s.stack[0].id).toStrictEqual(2);
76
+ expect(s.nextId).toStrictEqual(3);
77
+ });
78
+
79
+ it('sets the started timestamp on the new item', () => {
80
+ const s = state();
81
+ const before = Date.now();
82
+
83
+ mutations.add(s, { title: 'timed' });
84
+
85
+ const after = Date.now();
86
+
87
+ expect(s.stack[0].started).toBeGreaterThanOrEqual(before);
88
+ expect(s.stack[0].started).toBeLessThanOrEqual(after);
89
+ });
90
+
91
+ it('prepends new items so the most recent is first', () => {
92
+ const s = state();
93
+
94
+ mutations.add(s, { title: 'first' });
95
+ mutations.add(s, { title: 'second' });
96
+
97
+ expect(s.stack[0].title).toStrictEqual('second');
98
+ expect(s.stack[1].title).toStrictEqual('first');
99
+ });
100
+
101
+ it('removes the oldest item when the stack reaches MAX_GROWLS (5)', () => {
102
+ const s = state();
103
+
104
+ for (let i = 0; i < 5; i++) {
105
+ mutations.add(s, { title: `item-${ i }` });
106
+ }
107
+
108
+ mutations.add(s, { title: 'overflow' });
109
+
110
+ expect(s.stack).toHaveLength(5);
111
+ expect(s.stack[0].title).toStrictEqual('overflow');
112
+ });
113
+ });
114
+
115
+ describe('remove', () => {
116
+ it('removes an item from the stack by id', () => {
117
+ const s = state();
118
+
119
+ mutations.add(s, { title: 'to remove' });
120
+ const { id } = s.stack[0];
121
+
122
+ mutations.remove(s, id);
123
+
124
+ expect(s.stack).toHaveLength(0);
125
+ });
126
+
127
+ it('is a no-op when the id does not exist', () => {
128
+ const s = state();
129
+
130
+ mutations.add(s, { title: 'keep' });
131
+ mutations.remove(s, 999);
132
+
133
+ expect(s.stack).toHaveLength(1);
134
+ });
135
+ });
136
+
137
+ describe('clear', () => {
138
+ it('empties the stack', () => {
139
+ const s = state();
140
+
141
+ mutations.add(s, { title: 'a' });
142
+ mutations.add(s, { title: 'b' });
143
+ mutations.clear(s);
144
+
145
+ expect(s.stack).toHaveLength(0);
146
+ });
147
+ });
148
+ });
149
+
150
+ describe('actions', () => {
151
+ let commit: jest.Mock;
152
+ let dispatch: jest.Mock;
153
+
154
+ beforeEach(() => {
155
+ commit = jest.fn();
156
+ dispatch = jest.fn();
157
+ });
158
+
159
+ describe('clear', () => {
160
+ it('commits the clear mutation', async() => {
161
+ await actions.clear({ commit } as any);
162
+
163
+ expect(commit).toHaveBeenCalledWith('clear');
164
+ });
165
+ });
166
+
167
+ describe('remove', () => {
168
+ it('commits the remove mutation with the given id', async() => {
169
+ await actions.remove({ commit } as any, 42);
170
+
171
+ expect(commit).toHaveBeenCalledWith('remove', 42);
172
+ });
173
+ });
174
+
175
+ describe('close', () => {
176
+ it('removes the growl and dispatches notifications/markRead when the growl has a notification', async() => {
177
+ const notifId = 'notif-123';
178
+ const mockGetters = { byId: jest.fn().mockReturnValue({ notification: notifId }) };
179
+
180
+ dispatch.mockResolvedValue(undefined);
181
+
182
+ await actions.close({
183
+ commit, dispatch, getters: mockGetters
184
+ } as any, 7);
185
+
186
+ expect(commit).toHaveBeenCalledWith('remove', 7);
187
+ expect(dispatch).toHaveBeenCalledWith('notifications/markRead', notifId, { root: true });
188
+ });
189
+
190
+ it('removes the growl without dispatching when the growl has no notification', async() => {
191
+ const mockGetters = { byId: jest.fn().mockReturnValue({ notification: undefined }) };
192
+
193
+ await actions.close({
194
+ commit, dispatch, getters: mockGetters
195
+ } as any, 3);
196
+
197
+ expect(commit).toHaveBeenCalledWith('remove', 3);
198
+ expect(dispatch).not.toHaveBeenCalled();
199
+ });
200
+ });
201
+
202
+ describe('success', () => {
203
+ it('dispatches notifications/fromGrowl with Success level and commits add with success styling', async() => {
204
+ const mockNotification = 'notif-success';
205
+
206
+ dispatch.mockResolvedValue(mockNotification);
207
+
208
+ await actions.success({ commit, dispatch } as any, { title: 'Done', message: 'ok' });
209
+
210
+ expect(dispatch).toHaveBeenCalledWith('notifications/fromGrowl', {
211
+ title: 'Done',
212
+ message: 'ok',
213
+ level: NotificationLevel.Success,
214
+ }, { root: true });
215
+ expect(commit).toHaveBeenCalledWith('add', {
216
+ color: 'success',
217
+ icon: 'checkmark',
218
+ timeout: 5000,
219
+ notification: mockNotification,
220
+ title: 'Done',
221
+ message: 'ok',
222
+ });
223
+ });
224
+ });
225
+
226
+ describe('info', () => {
227
+ it('commits add with info styling without dispatching a notification', async() => {
228
+ await actions.info({ commit } as any, { title: 'FYI', message: 'just info' });
229
+
230
+ expect(dispatch).not.toHaveBeenCalled();
231
+ expect(commit).toHaveBeenCalledWith('add', {
232
+ color: 'info',
233
+ icon: 'info',
234
+ timeout: 5000,
235
+ title: 'FYI',
236
+ message: 'just info',
237
+ });
238
+ });
239
+ });
240
+
241
+ describe('warning', () => {
242
+ it('dispatches notifications/fromGrowl with Warning level and commits add with warning styling', async() => {
243
+ const mockNotification = 'notif-warning';
244
+
245
+ dispatch.mockResolvedValue(mockNotification);
246
+
247
+ await actions.warning({ commit, dispatch } as any, { title: 'Careful', message: 'watch out' });
248
+
249
+ expect(dispatch).toHaveBeenCalledWith('notifications/fromGrowl', {
250
+ title: 'Careful',
251
+ message: 'watch out',
252
+ level: NotificationLevel.Warning,
253
+ }, { root: true });
254
+ expect(commit).toHaveBeenCalledWith('add', {
255
+ color: 'warning',
256
+ icon: 'warning',
257
+ timeout: 5000,
258
+ notification: mockNotification,
259
+ title: 'Careful',
260
+ message: 'watch out',
261
+ });
262
+ });
263
+ });
264
+
265
+ describe('error', () => {
266
+ it('dispatches notifications/fromGrowl with Error level and commits add with error styling', async() => {
267
+ const mockNotification = 'notif-error';
268
+
269
+ dispatch.mockResolvedValue(mockNotification);
270
+
271
+ await actions.error({ commit, dispatch } as any, { title: 'Oops', message: 'failed' });
272
+
273
+ expect(dispatch).toHaveBeenCalledWith('notifications/fromGrowl', {
274
+ title: 'Oops',
275
+ message: 'failed',
276
+ level: NotificationLevel.Error,
277
+ }, { root: true });
278
+ expect(commit).toHaveBeenCalledWith('add', {
279
+ color: 'error',
280
+ icon: 'error',
281
+ timeout: 5000,
282
+ notification: mockNotification,
283
+ title: 'Oops',
284
+ message: 'failed',
285
+ });
286
+ });
287
+ });
288
+
289
+ describe('fromError', () => {
290
+ it('dispatches fromGrowl with the stringified error message and commits add with error styling', async() => {
291
+ const mockNotification = 'notif-from-error';
292
+
293
+ dispatch.mockResolvedValue(mockNotification);
294
+
295
+ const err = new Error('something broke');
296
+
297
+ await actions.fromError({ commit, dispatch } as any, { title: 'Error title', err });
298
+
299
+ expect(dispatch).toHaveBeenCalledWith('notifications/fromGrowl', {
300
+ title: 'Error title',
301
+ message: 'something broke',
302
+ level: NotificationLevel.Error,
303
+ }, { root: true });
304
+ expect(commit).toHaveBeenCalledWith('add', {
305
+ color: 'error',
306
+ icon: 'error',
307
+ timeout: 5000,
308
+ notification: mockNotification,
309
+ title: 'Error title',
310
+ message: 'something broke',
311
+ });
312
+ });
313
+ });
314
+
315
+ describe('notification', () => {
316
+ it.each([
317
+ {
318
+ desc: 'Success level',
319
+ level: NotificationLevel.Success,
320
+ color: 'success',
321
+ icon: 'checkmark',
322
+ },
323
+ {
324
+ desc: 'Warning level',
325
+ level: NotificationLevel.Warning,
326
+ color: 'warning',
327
+ icon: 'warning',
328
+ },
329
+ {
330
+ desc: 'Error level',
331
+ level: NotificationLevel.Error,
332
+ color: 'error',
333
+ icon: 'error',
334
+ },
335
+ ])('commits add for $desc', ({ level, color, icon }) => {
336
+ const notif = {
337
+ id: 'n1',
338
+ title: 'test',
339
+ message: 'msg',
340
+ level,
341
+ };
342
+
343
+ actions.notification({ commit } as any, notif);
344
+
345
+ expect(commit).toHaveBeenCalledWith('add', {
346
+ title: 'test',
347
+ message: 'msg',
348
+ notification: 'n1',
349
+ timeout: 5000,
350
+ color,
351
+ icon,
352
+ });
353
+ });
354
+
355
+ it.each([
356
+ { desc: 'Info level', level: NotificationLevel.Info },
357
+ { desc: 'Announcement level', level: NotificationLevel.Announcement },
358
+ { desc: 'Task level', level: NotificationLevel.Task },
359
+ { desc: 'Hidden level', level: NotificationLevel.Hidden },
360
+ ])('skips commit for $desc', ({ level }) => {
361
+ const notif = {
362
+ id: 'n1',
363
+ title: 'test',
364
+ message: 'msg',
365
+ level,
366
+ };
367
+
368
+ actions.notification({ commit } as any, notif);
369
+
370
+ expect(commit).not.toHaveBeenCalled();
371
+ });
372
+ });
373
+ });
374
+ });
@@ -0,0 +1,131 @@
1
+ import modalStore from '../modal';
2
+
3
+ describe('modal store', () => {
4
+ let s: ReturnType<typeof modalStore.state>;
5
+ const fakeComponent = { name: 'FakeComponent' } as any;
6
+
7
+ beforeEach(() => {
8
+ s = modalStore.state();
9
+ });
10
+
11
+ describe('state', () => {
12
+ it('returns initial default state', () => {
13
+ expect(s.isOpen).toBe(false);
14
+ expect(s.component).toBeNull();
15
+ expect(s.componentProps).toStrictEqual({});
16
+ expect(s.resources).toStrictEqual([]);
17
+ expect(s.closeOnClickOutside).toBe(false);
18
+ expect(s.modalWidth).toStrictEqual('600px');
19
+ expect(s.modalSticky).toBe(false);
20
+ });
21
+ });
22
+
23
+ describe('mutations', () => {
24
+ describe('openModal', () => {
25
+ it('sets isOpen to true', () => {
26
+ modalStore.mutations.openModal(s, { component: fakeComponent });
27
+
28
+ expect(s.isOpen).toBe(true);
29
+ });
30
+
31
+ it('stores the component reference', () => {
32
+ modalStore.mutations.openModal(s, { component: fakeComponent });
33
+
34
+ expect(s.component).toBe(fakeComponent);
35
+ });
36
+
37
+ it('sets componentProps from payload', () => {
38
+ modalStore.mutations.openModal(s, { component: fakeComponent, componentProps: { foo: 'bar' } });
39
+
40
+ expect(s.componentProps).toStrictEqual({ foo: 'bar' });
41
+ });
42
+
43
+ it('defaults componentProps to empty object when not provided', () => {
44
+ modalStore.mutations.openModal(s, { component: fakeComponent });
45
+
46
+ expect(s.componentProps).toStrictEqual({});
47
+ });
48
+
49
+ it('preserves resources when payload resources is an array', () => {
50
+ const res = [{ id: 1 }, { id: 2 }];
51
+
52
+ modalStore.mutations.openModal(s, { component: fakeComponent, resources: res });
53
+
54
+ expect(s.resources).toStrictEqual(res);
55
+ });
56
+
57
+ it('wraps a single non-array resource in an array', () => {
58
+ const res = { id: 1 };
59
+
60
+ modalStore.mutations.openModal(s, { component: fakeComponent, resources: res as any });
61
+
62
+ expect(s.resources).toStrictEqual([res]);
63
+ });
64
+
65
+ it('sets resources to empty array when not provided', () => {
66
+ modalStore.mutations.openModal(s, { component: fakeComponent });
67
+
68
+ expect(s.resources).toStrictEqual([]);
69
+ });
70
+
71
+ it('sets closeOnClickOutside to true when specified', () => {
72
+ modalStore.mutations.openModal(s, { component: fakeComponent, closeOnClickOutside: true });
73
+
74
+ expect(s.closeOnClickOutside).toBe(true);
75
+ });
76
+
77
+ it('defaults closeOnClickOutside to false when not provided', () => {
78
+ modalStore.mutations.openModal(s, { component: fakeComponent });
79
+
80
+ expect(s.closeOnClickOutside).toBe(false);
81
+ });
82
+
83
+ it('sets custom modalWidth from payload', () => {
84
+ modalStore.mutations.openModal(s, { component: fakeComponent, modalWidth: '800px' });
85
+
86
+ expect(s.modalWidth).toStrictEqual('800px');
87
+ });
88
+
89
+ it('defaults modalWidth to 600px when not provided', () => {
90
+ modalStore.mutations.openModal(s, { component: fakeComponent });
91
+
92
+ expect(s.modalWidth).toStrictEqual('600px');
93
+ });
94
+
95
+ it('sets modalSticky to true when specified', () => {
96
+ modalStore.mutations.openModal(s, { component: fakeComponent, modalSticky: true });
97
+
98
+ expect(s.modalSticky).toBe(true);
99
+ });
100
+
101
+ it('defaults modalSticky to false when not provided', () => {
102
+ modalStore.mutations.openModal(s, { component: fakeComponent });
103
+
104
+ expect(s.modalSticky).toBe(false);
105
+ });
106
+ });
107
+
108
+ describe('closeModal', () => {
109
+ it('resets all state to defaults', () => {
110
+ modalStore.mutations.openModal(s, {
111
+ component: fakeComponent,
112
+ componentProps: { foo: 'bar' },
113
+ resources: [{ id: 1 }],
114
+ closeOnClickOutside: true,
115
+ modalWidth: '800px',
116
+ modalSticky: true,
117
+ });
118
+
119
+ modalStore.mutations.closeModal(s);
120
+
121
+ expect(s.isOpen).toBe(false);
122
+ expect(s.component).toBeNull();
123
+ expect(s.componentProps).toStrictEqual({});
124
+ expect(s.resources).toStrictEqual([]);
125
+ expect(s.closeOnClickOutside).toBe(false);
126
+ expect(s.modalWidth).toStrictEqual('600px');
127
+ expect(s.modalSticky).toBe(false);
128
+ });
129
+ });
130
+ });
131
+ });