@rancher/shell 3.0.9-rc.3 → 3.0.9-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 (128) hide show
  1. package/assets/brand/suse/metadata.json +2 -1
  2. package/assets/translations/en-us.yaml +105 -5
  3. package/components/ActionMenuShell.vue +1 -1
  4. package/components/Inactivity.vue +2 -2
  5. package/components/Resource/Detail/Card/ExtrasCard.vue +49 -15
  6. package/components/Resource/Detail/Card/__tests__/ExtrasCard.test.ts +111 -0
  7. package/components/Resource/Detail/Masthead/__tests__/index.test.ts +0 -17
  8. package/components/Resource/Detail/Masthead/index.vue +11 -4
  9. package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +3 -1
  10. package/components/Resource/Detail/Metadata/index.vue +1 -1
  11. package/components/Resource/Detail/ResourceRow.vue +1 -1
  12. package/components/ResourceDetail/Masthead/latest.vue +12 -2
  13. package/components/ResourceList/index.vue +9 -0
  14. package/components/ResourceTable.vue +38 -4
  15. package/components/Tabbed/Tab.vue +4 -0
  16. package/components/Tabbed/index.vue +4 -1
  17. package/components/__tests__/ProjectRow.test.ts +60 -0
  18. package/components/form/ChangePassword.vue +41 -35
  19. package/components/form/ResourceQuota/Project.vue +42 -1
  20. package/components/form/ResourceQuota/ProjectRow.vue +71 -4
  21. package/components/form/ResourceQuota/__tests__/Project.test.ts +63 -0
  22. package/components/form/SelectOrCreateAuthSecret.vue +6 -1
  23. package/components/form/__tests__/SelectOrCreateAuthSecret.test.ts +35 -0
  24. package/components/formatter/KubeconfigClusters.vue +74 -0
  25. package/components/formatter/MachineSummaryGraph.vue +10 -2
  26. package/components/formatter/__tests__/KubeconfigClusters.test.ts +125 -0
  27. package/components/nav/TopLevelMenu.helper.ts +50 -2
  28. package/components/nav/TopLevelMenu.vue +14 -0
  29. package/components/nav/Type.vue +5 -0
  30. package/components/nav/__tests__/TopLevelMenu.test.ts +3 -3
  31. package/components/nav/__tests__/Type.test.ts +6 -4
  32. package/config/product/explorer.js +4 -3
  33. package/config/product/manager.js +47 -3
  34. package/config/router/navigation-guards/authentication.js +8 -9
  35. package/config/router/routes.js +4 -1
  36. package/config/types.js +10 -2
  37. package/detail/auditlog.cattle.io.auditpolicy.vue +19 -0
  38. package/detail/management.cattle.io.user.vue +1 -2
  39. package/detail/node.vue +0 -1
  40. package/detail/provisioning.cattle.io.cluster.vue +2 -1
  41. package/dialog/ChangePasswordDialog.vue +8 -0
  42. package/dialog/GenericPrompt.vue +20 -3
  43. package/dialog/ScaleMachineDownDialog.vue +65 -15
  44. package/dialog/SearchDialog.vue +10 -2
  45. package/dialog/__tests__/ScaleMachineDownDialog.test.ts +184 -0
  46. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +89 -0
  47. package/edit/__tests__/management.cattle.io.project.test.js +56 -1
  48. package/edit/auditlog.cattle.io.auditpolicy/AdditionalRedactions.vue +114 -0
  49. package/edit/auditlog.cattle.io.auditpolicy/Filters.vue +119 -0
  50. package/edit/auditlog.cattle.io.auditpolicy/General.vue +180 -0
  51. package/edit/auditlog.cattle.io.auditpolicy/__tests__/AdditionalRedactions.test.ts +327 -0
  52. package/edit/auditlog.cattle.io.auditpolicy/__tests__/Filters.test.ts +449 -0
  53. package/edit/auditlog.cattle.io.auditpolicy/__tests__/General.test.ts +472 -0
  54. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/AdditionalRedactions.test.ts.snap +27 -0
  55. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/Filters.test.ts.snap +39 -0
  56. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/General.test.ts.snap +174 -0
  57. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/index.test.ts.snap +29 -0
  58. package/edit/auditlog.cattle.io.auditpolicy/__tests__/index.test.ts +215 -0
  59. package/edit/auditlog.cattle.io.auditpolicy/index.vue +104 -0
  60. package/edit/auditlog.cattle.io.auditpolicy/types.ts +28 -0
  61. package/edit/fleet.cattle.io.gitrepo.vue +16 -1
  62. package/edit/management.cattle.io.project.vue +8 -2
  63. package/edit/management.cattle.io.user.vue +29 -34
  64. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +178 -0
  65. package/edit/provisioning.cattle.io.cluster/rke2.vue +22 -2
  66. package/edit/provisioning.cattle.io.cluster/shared.ts +4 -0
  67. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +1 -0
  68. package/edit/provisioning.cattle.io.cluster/tabs/etcd/S3Config.vue +57 -2
  69. package/edit/provisioning.cattle.io.cluster/tabs/etcd/__tests__/S3Config.test.ts +109 -0
  70. package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +1 -0
  71. package/list/auditlog.cattle.io.auditpolicy.vue +63 -0
  72. package/list/ext.cattle.io.kubeconfig.vue +118 -0
  73. package/list/group.principal.vue +11 -15
  74. package/list/management.cattle.io.user.vue +11 -21
  75. package/machine-config/azure.vue +14 -0
  76. package/mixins/__tests__/chart.test.ts +147 -0
  77. package/mixins/browser-tab-visibility.js +5 -4
  78. package/mixins/chart.js +10 -8
  79. package/mixins/fetch.client.js +6 -0
  80. package/models/__tests__/auditlog.cattle.io.auditpolicy.test.ts +117 -0
  81. package/models/__tests__/ext.cattle.io.kubeconfig.test.ts +364 -0
  82. package/models/__tests__/secret.test.ts +55 -0
  83. package/models/__tests__/workload.test.ts +49 -6
  84. package/models/auditlog.cattle.io.auditpolicy.js +46 -0
  85. package/models/cluster.x-k8s.io.machine.js +1 -1
  86. package/models/cluster.x-k8s.io.machinedeployment.js +5 -5
  87. package/models/event.js +5 -0
  88. package/models/ext.cattle.io.groupmembershiprefreshrequest.js +15 -0
  89. package/models/ext.cattle.io.kubeconfig.ts +97 -0
  90. package/models/ext.cattle.io.passwordchangerequest.js +15 -0
  91. package/models/ext.cattle.io.selfuser.js +15 -0
  92. package/models/fleet-application.js +17 -7
  93. package/models/management.cattle.io.user.js +28 -31
  94. package/models/schema.js +18 -0
  95. package/models/secret.js +28 -25
  96. package/models/steve-schema.ts +39 -2
  97. package/models/workload.js +3 -2
  98. package/package.json +2 -2
  99. package/pages/about.vue +3 -2
  100. package/pages/account/index.vue +23 -16
  101. package/pages/auth/login.vue +15 -8
  102. package/pages/auth/setup.vue +52 -15
  103. package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +38 -14
  104. package/pages/c/_cluster/apps/charts/index.vue +1 -0
  105. package/pages/home.vue +9 -3
  106. package/plugins/dashboard-store/__tests__/resource-class.test.ts +1 -3
  107. package/plugins/dashboard-store/actions.js +7 -0
  108. package/plugins/dashboard-store/getters.js +23 -1
  109. package/plugins/dashboard-store/index.js +3 -2
  110. package/plugins/dashboard-store/mutations.js +4 -0
  111. package/plugins/dashboard-store/resource-class.js +12 -5
  112. package/plugins/steve/__tests__/steve-class.test.ts +167 -0
  113. package/plugins/steve/schema.d.ts +5 -0
  114. package/plugins/steve/steve-class.js +19 -0
  115. package/plugins/steve/steve-pagination-utils.ts +2 -1
  116. package/rancher-components/RcItemCard/RcItemCard.test.ts +4 -2
  117. package/rancher-components/RcItemCard/RcItemCard.vue +27 -10
  118. package/store/auth.js +57 -19
  119. package/store/notifications.ts +1 -1
  120. package/store/type-map.js +12 -1
  121. package/types/shell/index.d.ts +24 -15
  122. package/types/store/dashboard-store.types.ts +7 -0
  123. package/utils/__tests__/chart.test.ts +96 -0
  124. package/utils/__tests__/version.test.ts +1 -19
  125. package/utils/chart.js +64 -0
  126. package/utils/pagination-wrapper.ts +11 -3
  127. package/utils/version.js +5 -17
  128. package/vue.config.js +26 -13
@@ -0,0 +1,449 @@
1
+ import { shallowMount, VueWrapper } from '@vue/test-utils';
2
+ import Filters from '../Filters.vue';
3
+ import { ComponentPublicInstance } from 'vue';
4
+ import { AuditPolicy, FilterRule } from '@shell/edit/auditlog.cattle.io.auditpolicy/types';
5
+
6
+ // Mock the ID generation to have consistent snapshots
7
+ jest.mock('@shell/utils/string', () => ({ generateRandomAlphaString: () => 'test-id-123' }));
8
+
9
+ interface FiltersComponent extends ComponentPublicInstance {
10
+ spec: AuditPolicy;
11
+ addRow: (key: 'action' | 'requestURI', filters: FilterRule[]) => void;
12
+ updateRow: (key: 'action' | 'requestURI', index: number, value: string) => void;
13
+ defaultAddValue: FilterRule;
14
+ }
15
+
16
+ const defaultProps = {
17
+ value: { filters: [] },
18
+ mode: 'create'
19
+ };
20
+
21
+ const globalMocks = {
22
+ global: {
23
+ mocks: {
24
+ $t: (key: string) => key,
25
+ t: (key: string) => key,
26
+ $store: {
27
+ getters: { 'i18n/t': (key: string) => key },
28
+ dispatch: jest.fn()
29
+ },
30
+ $route: {
31
+ params: {},
32
+ query: {}
33
+ },
34
+ $router: {
35
+ push: jest.fn(),
36
+ replace: jest.fn()
37
+ }
38
+ },
39
+ stubs: {
40
+ ArrayList: true,
41
+ LabeledInput: true,
42
+ LabeledSelect: true
43
+ }
44
+ }
45
+ };
46
+
47
+ function factory(props: Record<string, any> = {}, options: Record<string, any> = {}): VueWrapper<FiltersComponent> {
48
+ return shallowMount(Filters, {
49
+ props: { ...defaultProps, ...props },
50
+ ...globalMocks,
51
+ ...options
52
+ }) as unknown as VueWrapper<FiltersComponent>;
53
+ }
54
+
55
+ describe('component: Filters', () => {
56
+ describe('rendering & initial state', () => {
57
+ it('should render with default props (snapshot)', () => {
58
+ const wrapper = factory();
59
+
60
+ expect(wrapper.element).toMatchSnapshot();
61
+ });
62
+
63
+ it('should render with create mode', () => {
64
+ const wrapper = factory({ mode: 'create' });
65
+
66
+ expect(wrapper.exists()).toBe(true);
67
+ expect(wrapper.find('.row.mb-40').exists()).toBe(true);
68
+ });
69
+
70
+ it('should render with edit mode', () => {
71
+ const wrapper = factory({ mode: 'edit' });
72
+
73
+ expect(wrapper.exists()).toBe(true);
74
+ expect(wrapper.find('.row.mb-40').exists()).toBe(true);
75
+ });
76
+
77
+ it('should render with view mode', () => {
78
+ const wrapper = factory({ mode: 'view' });
79
+
80
+ expect(wrapper.exists()).toBe(true);
81
+ expect(wrapper.find('.row.mb-40').exists()).toBe(true);
82
+ });
83
+
84
+ it('should render with initial filters data', () => {
85
+ const value = {
86
+ filters: [
87
+ { action: 'allow', requestURI: '/api/v1/pods' },
88
+ { action: 'deny', requestURI: '/api/v1/secrets' }
89
+ ]
90
+ };
91
+ const wrapper = factory({ value });
92
+
93
+ expect(wrapper.vm.spec.filters).toHaveLength(2);
94
+ });
95
+ });
96
+
97
+ describe('props & state changes', () => {
98
+ it('should handle empty value prop gracefully', () => {
99
+ const wrapper = factory({ value: undefined });
100
+
101
+ expect(wrapper.exists()).toBe(true);
102
+ expect(wrapper.vm.spec.filters).toStrictEqual([]);
103
+ });
104
+
105
+ it('should handle null value prop gracefully', () => {
106
+ const wrapper = factory({ value: null });
107
+
108
+ expect(wrapper.exists()).toBe(true);
109
+ expect(wrapper.vm.spec.filters).toStrictEqual([]);
110
+ });
111
+
112
+ it('should update when mode prop changes', async() => {
113
+ const wrapper = factory({ mode: 'create' });
114
+
115
+ expect((wrapper.props() as any).mode).toBe('create');
116
+ await wrapper.setProps({ mode: 'view' });
117
+ expect((wrapper.props() as any).mode).toBe('view');
118
+ });
119
+
120
+ it('should merge defaults with provided value', () => {
121
+ const value = {
122
+ filters: [{ action: 'allow', requestURI: '/custom' }],
123
+ customProp: 'test'
124
+ };
125
+ const wrapper = factory({ value });
126
+
127
+ expect((wrapper.vm.spec.filters ?? [])).toHaveLength(1);
128
+ expect((wrapper.vm.spec.filters ?? [])[0]).toStrictEqual({ action: 'allow', requestURI: '/custom' });
129
+ expect((wrapper.vm.spec as any).customProp).toBe('test');
130
+ });
131
+ });
132
+
133
+ describe('user interaction', () => {
134
+ it('should emit update:value when addRow is called', () => {
135
+ const wrapper = factory();
136
+ const newFilters = [{ action: 'allow', requestURI: '' }];
137
+
138
+ wrapper.vm.addRow('action', newFilters);
139
+
140
+ expect(wrapper.emitted('update:value')).toBeTruthy();
141
+ const events = wrapper.emitted('update:value');
142
+
143
+ expect(events && events[0]).toBeTruthy();
144
+
145
+ const emitted = events && events[0] && events[0][0] as { filters: FilterRule[] };
146
+
147
+ expect(emitted && emitted.filters).toHaveLength(1);
148
+ expect(emitted && emitted.filters[0]).toStrictEqual({
149
+ action: 'allow',
150
+ requestURI: ''
151
+ });
152
+ });
153
+
154
+ it('should emit update:value when updateRow is called', () => {
155
+ const value = {
156
+ filters: [
157
+ { action: 'allow', requestURI: '/api/v1/pods' }
158
+ ]
159
+ };
160
+ const wrapper = factory({ value });
161
+
162
+ wrapper.vm.updateRow('action', 0, 'deny');
163
+
164
+ expect(wrapper.emitted('update:value')).toBeTruthy();
165
+ const events = wrapper.emitted('update:value');
166
+
167
+ expect(events && events[0]).toBeTruthy();
168
+
169
+ const emitted = events && events[0] && events[0][0] as { filters: FilterRule[] };
170
+
171
+ expect(emitted && emitted.filters).toHaveLength(1);
172
+ expect(emitted && emitted.filters[0]).toStrictEqual({
173
+ action: 'deny',
174
+ requestURI: '/api/v1/pods'
175
+ });
176
+ });
177
+
178
+ it('should preserve existing prop values when emitting updates', () => {
179
+ const existingValue = { someOtherProp: 'existing' };
180
+ const wrapper = factory({ value: existingValue });
181
+
182
+ wrapper.vm.addRow('action', [{ action: 'allow', requestURI: '' }]);
183
+
184
+ const events = wrapper.emitted('update:value');
185
+
186
+ expect(events && events[0]).toBeTruthy();
187
+
188
+ const emitted = events && events[0] && events[0][0] as { someOtherProp?: string; filters: FilterRule[] };
189
+
190
+ expect(emitted && emitted.someOtherProp).toBe('existing');
191
+ expect(emitted && emitted.filters).toHaveLength(1);
192
+ });
193
+
194
+ it('should update correct filter by index', () => {
195
+ const value = {
196
+ filters: [
197
+ { action: 'allow', requestURI: '/first' },
198
+ { action: 'deny', requestURI: '/second' },
199
+ { action: 'allow', requestURI: '/third' }
200
+ ]
201
+ };
202
+ const wrapper = factory({ value });
203
+
204
+ wrapper.vm.updateRow('requestURI', 1, '/updated');
205
+
206
+ const events = wrapper.emitted('update:value');
207
+
208
+ expect(events && events[0]).toBeTruthy();
209
+
210
+ const emitted = events && events[0] && events[0][0] as { filters: FilterRule[] };
211
+
212
+ expect(emitted && emitted.filters).toHaveLength(3);
213
+ expect(emitted && emitted.filters[0]).toStrictEqual({ action: 'allow', requestURI: '/first' });
214
+ expect(emitted && emitted.filters[1]).toStrictEqual({ action: 'deny', requestURI: '/updated' });
215
+ expect(emitted && emitted.filters[2]).toStrictEqual({ action: 'allow', requestURI: '/third' });
216
+ });
217
+
218
+ it('should add filters and emit events correctly', () => {
219
+ const wrapper = factory();
220
+ const expectedDefault = { action: 'allow', requestURI: '' };
221
+
222
+ // Add first filter
223
+ wrapper.vm.addRow('action', [expectedDefault]);
224
+ expect(wrapper.emitted('update:value')).toHaveLength(1);
225
+
226
+ // Add second filter
227
+ wrapper.vm.addRow('action', [expectedDefault, expectedDefault]);
228
+ expect(wrapper.emitted('update:value')).toHaveLength(2);
229
+
230
+ // Add third filter
231
+ wrapper.vm.addRow('action', [expectedDefault, expectedDefault, expectedDefault]);
232
+ expect(wrapper.emitted('update:value')).toHaveLength(3);
233
+
234
+ // Check that each emission contains the correct filters
235
+ const emissions = wrapper.emitted('update:value');
236
+
237
+ expect(emissions && emissions[0] && (emissions[0][0] as any).filters).toHaveLength(1);
238
+
239
+ expect(emissions && emissions[1] && (emissions[1][0] as any).filters).toHaveLength(2);
240
+
241
+ expect(emissions && emissions[2] && (emissions[2][0] as any).filters).toHaveLength(3);
242
+
243
+ expect(emissions && emissions[2] && (emissions[2][0] as any).filters[2]).toStrictEqual(expectedDefault);
244
+ });
245
+ });
246
+
247
+ describe('computed properties & logic', () => {
248
+ it('should have defaultAddValue configured correctly', () => {
249
+ const wrapper = factory();
250
+
251
+ expect(wrapper.vm.defaultAddValue).toStrictEqual({
252
+ action: 'allow',
253
+ requestURI: ''
254
+ });
255
+ });
256
+
257
+ it('should initialize spec reactive ref correctly', () => {
258
+ const wrapper = factory();
259
+
260
+ expect(wrapper.vm.spec).toBeDefined();
261
+ expect(Array.isArray(wrapper.vm.spec.filters ?? [])).toBe(true);
262
+ });
263
+
264
+ it('should merge defaults with props correctly in spec', () => {
265
+ const value = { filters: [{ action: 'deny', requestURI: '/test' }] };
266
+ const wrapper = factory({ value });
267
+
268
+ expect((wrapper.vm.spec.filters ?? [])).toHaveLength(1);
269
+ expect((wrapper.vm.spec.filters ?? [])[0]).toStrictEqual({
270
+ action: 'deny',
271
+ requestURI: '/test'
272
+ });
273
+ });
274
+ });
275
+
276
+ describe('component configuration', () => {
277
+ it('should configure ArrayList component with correct props', () => {
278
+ const wrapper = factory();
279
+ const arrayListComponent = wrapper.findComponent({ name: 'ArrayList' });
280
+
281
+ expect(arrayListComponent.exists()).toBe(true);
282
+ expect((arrayListComponent.props() as any).mode).toBe('create');
283
+ expect((arrayListComponent.props() as any).protip).toBe(false);
284
+ });
285
+
286
+ it('should pass correct mode to ArrayList component', () => {
287
+ const createWrapper = factory({ mode: 'create' });
288
+ const editWrapper = factory({ mode: 'edit' });
289
+ const viewWrapper = factory({ mode: 'view' });
290
+
291
+ expect((createWrapper.findComponent({ name: 'ArrayList' }).props() as any).mode).toBe('create');
292
+ expect((editWrapper.findComponent({ name: 'ArrayList' }).props() as any).mode).toBe('edit');
293
+ expect((viewWrapper.findComponent({ name: 'ArrayList' }).props() as any).mode).toBe('view');
294
+ });
295
+
296
+ it('should bind correct event handlers to ArrayList component', () => {
297
+ const wrapper = factory();
298
+ const arrayListComponent = wrapper.findComponent({ name: 'ArrayList' });
299
+
300
+ expect(arrayListComponent.exists()).toBe(true);
301
+ // ArrayList component handles add/remove internally
302
+ expect((arrayListComponent.props() as any).defaultAddValue).toStrictEqual({
303
+ action: 'allow',
304
+ requestURI: ''
305
+ });
306
+ });
307
+
308
+ it('should handle filter data correctly', () => {
309
+ const value = { filters: [{ action: 'allow', requestURI: '/test' }] };
310
+ const wrapper = factory({ value });
311
+
312
+ // Check that the component initializes with the provided filters
313
+ expect(wrapper.vm.spec.filters).toHaveLength(1);
314
+ expect((wrapper.vm.spec.filters ?? [])[0]).toStrictEqual({ action: 'allow', requestURI: '/test' });
315
+
316
+ // Check that the component structure exists
317
+ expect(wrapper.find('.row.mb-40').exists()).toBe(true);
318
+ expect(wrapper.findComponent({ name: 'ArrayList' }).exists()).toBe(true);
319
+ });
320
+
321
+ it('should not render input components when no filters exist', () => {
322
+ const wrapper = factory({ value: { filters: [] } });
323
+
324
+ const labeledSelectStubs = wrapper.findAll('labeled-select-stub');
325
+ const labeledInputStubs = wrapper.findAll('labeled-input-stub');
326
+
327
+ expect(labeledSelectStubs).toHaveLength(0);
328
+ expect(labeledInputStubs).toHaveLength(0);
329
+ });
330
+ });
331
+
332
+ describe('filter data structure', () => {
333
+ it('should handle filter with action property', () => {
334
+ const value = { filters: [{ action: 'allow' }] };
335
+ const wrapper = factory({ value });
336
+
337
+ expect((wrapper.vm.spec.filters ?? [])[0]?.action).toBe('allow');
338
+ });
339
+
340
+ it('should handle filter with requestURI property', () => {
341
+ const value = { filters: [{ requestURI: '/api/v1/namespaces' }] };
342
+ const wrapper = factory({ value });
343
+
344
+ expect((wrapper.vm.spec.filters ?? [])[0]?.requestURI).toBe('/api/v1/namespaces');
345
+ });
346
+
347
+ it('should handle filter with both action and requestURI', () => {
348
+ const value = { filters: [{ action: 'deny', requestURI: '/api/v1/secrets' }] };
349
+ const wrapper = factory({ value });
350
+
351
+ expect((wrapper.vm.spec.filters ?? [])[0]).toStrictEqual({
352
+ action: 'deny',
353
+ requestURI: '/api/v1/secrets'
354
+ });
355
+ });
356
+
357
+ it('should handle multiple filters with different configurations', () => {
358
+ const value = {
359
+ filters: [
360
+ { action: 'allow', requestURI: '/api/v1/pods' },
361
+ { action: 'deny' },
362
+ { requestURI: '/api/v1/configmaps' },
363
+ {}
364
+ ]
365
+ };
366
+ const wrapper = factory({ value });
367
+
368
+ expect((wrapper.vm.spec.filters ?? [])).toHaveLength(4);
369
+ expect((wrapper.vm.spec.filters ?? [])[0]).toStrictEqual({ action: 'allow', requestURI: '/api/v1/pods' });
370
+ expect((wrapper.vm.spec.filters ?? [])[1]).toStrictEqual({ action: 'deny' });
371
+ expect((wrapper.vm.spec.filters ?? [])[2]).toStrictEqual({ requestURI: '/api/v1/configmaps' });
372
+ expect((wrapper.vm.spec.filters ?? [])[3]).toStrictEqual({});
373
+ });
374
+ });
375
+
376
+ describe('edge cases', () => {
377
+ it('should handle empty filters array', () => {
378
+ const wrapper = factory({ value: { filters: [] } });
379
+
380
+ expect((wrapper.vm.spec.filters ?? [])).toStrictEqual([]);
381
+ expect(wrapper.findAllComponents({ name: 'Tab' })).toHaveLength(0);
382
+ });
383
+
384
+ it('should handle missing filters property', () => {
385
+ const wrapper = factory({ value: {} });
386
+
387
+ expect((wrapper.vm.spec.filters ?? [])).toStrictEqual([]);
388
+ });
389
+
390
+ it('should handle updateRow with empty filters array', () => {
391
+ const wrapper = factory({ value: { filters: [] } });
392
+
393
+ expect(() => {
394
+ wrapper.vm.updateRow('action', 0, 'allow');
395
+ }).not.toThrow();
396
+
397
+ const events = wrapper.emitted('update:value');
398
+
399
+ expect(events && events[0]).toBeTruthy();
400
+
401
+ const emitted = events && events[0] && events[0][0] as { filters: FilterRule[] };
402
+
403
+ expect(emitted && emitted.filters).toStrictEqual([{ action: 'allow', requestURI: '' }]);
404
+ });
405
+
406
+ it('should handle updateRow with out of bounds index gracefully', () => {
407
+ const value = { filters: [{ action: 'allow', requestURI: '/test' }] };
408
+ const wrapper = factory({ value });
409
+
410
+ expect(() => {
411
+ wrapper.vm.updateRow('action', 5, 'deny'); // Index out of bounds
412
+ }).not.toThrow();
413
+ });
414
+
415
+ it('should handle invalid filter data gracefully', () => {
416
+ const value = {
417
+ filters: [
418
+ null,
419
+ undefined,
420
+ { action: 'invalid' },
421
+ { requestURI: '' },
422
+ { extraProp: 'ignored' }
423
+ ]
424
+ };
425
+ const wrapper = factory({ value });
426
+
427
+ expect(wrapper.vm.spec.filters).toHaveLength(5);
428
+ expect(() => wrapper.vm.addRow('action', [{ action: 'allow', requestURI: '' }])).not.toThrow();
429
+ });
430
+
431
+ it('should handle filter modifications through reactive refs', () => {
432
+ const originalFilter = { action: 'allow', requestURI: '/test' };
433
+ const value = { filters: [originalFilter] };
434
+ const wrapper = factory({ value });
435
+
436
+ // Verify the filter is accessible in the component
437
+ expect((wrapper.vm.spec.filters ?? [])[0]).toBeDefined();
438
+ expect((wrapper.vm.spec.filters ?? [])[0]?.action).toBe('allow');
439
+
440
+ // Modify the filter through the component
441
+ if ((wrapper.vm.spec.filters ?? [])[0]) {
442
+ (wrapper.vm.spec.filters ?? [])[0].action = 'deny';
443
+ }
444
+
445
+ // Verify the component state reflects the change
446
+ expect((wrapper.vm.spec.filters ?? [])[0]?.action).toBe('deny');
447
+ });
448
+ });
449
+ });