@rancher/shell 3.0.9-rc.5 → 3.0.9-rc.6

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 (142) hide show
  1. package/assets/images/providers/oci-open-containers.svg +22 -0
  2. package/assets/images/providers/traefik.png +0 -0
  3. package/assets/styles/themes/_dark.scss +2 -0
  4. package/assets/styles/themes/_light.scss +2 -0
  5. package/assets/styles/themes/_modern.scss +6 -0
  6. package/assets/translations/en-us.yaml +129 -25
  7. package/components/CruResource.vue +3 -1
  8. package/components/ExplorerProjectsNamespaces.vue +12 -12
  9. package/components/Resource/Detail/Card/StatusCard/__tests__/StatusCard.test.ts +109 -0
  10. package/components/Resource/Detail/Card/StatusCard/index.vue +21 -4
  11. package/components/Resource/Detail/Metadata/IdentifyingInformation/__tests__/identifying-fields.test.ts +19 -2
  12. package/components/Resource/Detail/Metadata/IdentifyingInformation/identifying-fields.ts +19 -11
  13. package/components/Resource/Detail/ResourcePopover/__tests__/index.test.ts +12 -0
  14. package/components/Resource/Detail/ResourcePopover/index.vue +2 -0
  15. package/components/Resource/Detail/ResourceRow.vue +2 -2
  16. package/components/ResourceList/index.vue +7 -4
  17. package/components/Window/ContainerLogs.vue +48 -37
  18. package/components/fleet/FleetClusterTargets/TargetsList.vue +2 -2
  19. package/components/fleet/FleetClusterTargets/index.vue +6 -1
  20. package/components/fleet/GitRepoAdvancedTab.vue +333 -0
  21. package/components/fleet/GitRepoMetadataTab.vue +43 -0
  22. package/components/fleet/GitRepoRepositoryTab.vue +101 -0
  23. package/components/fleet/GitRepoTargetTab.vue +77 -0
  24. package/components/fleet/HelmOpAdvancedTab.vue +247 -0
  25. package/components/fleet/HelmOpChartTab.vue +158 -0
  26. package/components/fleet/HelmOpMetadataTab.vue +46 -0
  27. package/components/fleet/HelmOpTargetTab.vue +84 -0
  28. package/components/fleet/HelmOpValuesTab.vue +147 -0
  29. package/components/fleet/__tests__/FleetClusterTargets.test.ts +119 -70
  30. package/components/form/NodeScheduling.vue +81 -7
  31. package/components/form/PodAffinity.vue +1 -36
  32. package/components/form/ResourceLabeledSelect.vue +8 -4
  33. package/components/form/ResourceQuota/Namespace.vue +30 -9
  34. package/components/form/ResourceQuota/NamespaceRow.vue +25 -7
  35. package/components/form/ResourceQuota/Project.vue +140 -82
  36. package/components/form/ResourceQuota/ResourceQuotaEntry.vue +145 -0
  37. package/components/form/ResourceQuota/__tests__/Namespace.test.ts +307 -0
  38. package/components/form/ResourceQuota/__tests__/NamespaceRow.test.ts +281 -0
  39. package/components/form/ResourceQuota/__tests__/Project.test.ts +274 -27
  40. package/components/form/ResourceQuota/__tests__/ResourceQuotaEntry.test.ts +215 -0
  41. package/components/form/SchedulingCustomization.vue +14 -6
  42. package/components/form/SelectOrCreateAuthSecret.vue +107 -18
  43. package/components/form/__tests__/NodeScheduling.test.ts +12 -9
  44. package/components/form/__tests__/PodAffinity.test.ts +21 -2
  45. package/components/form/__tests__/SchedulingCustomization.test.ts +240 -0
  46. package/components/formatter/ClusterLink.vue +8 -0
  47. package/components/formatter/SecretOrigin.vue +79 -0
  48. package/config/labels-annotations.js +7 -6
  49. package/config/pagination-table-headers.js +6 -4
  50. package/config/product/explorer.js +1 -11
  51. package/config/query-params.js +3 -0
  52. package/config/settings.ts +15 -2
  53. package/config/table-headers.js +21 -17
  54. package/config/types.js +23 -8
  55. package/detail/workload/index.vue +11 -16
  56. package/dialog/DeactivateDriverDialog.vue +1 -1
  57. package/dialog/Ipv6NetworkingDialog.vue +156 -0
  58. package/dialog/ScalePoolDownDialog.vue +2 -2
  59. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +1 -1
  60. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +1 -0
  61. package/edit/__tests__/management.cattle.io.project.test.js +56 -128
  62. package/edit/auth/oidc.vue +1 -1
  63. package/edit/catalog.cattle.io.clusterrepo.vue +155 -25
  64. package/edit/fleet.cattle.io.gitrepo.vue +153 -283
  65. package/edit/fleet.cattle.io.helmop.vue +190 -332
  66. package/edit/management.cattle.io.project.vue +5 -42
  67. package/edit/management.cattle.io.setting.vue +6 -0
  68. package/edit/provisioning.cattle.io.cluster/__tests__/Basics.test.ts +55 -24
  69. package/edit/provisioning.cattle.io.cluster/__tests__/Networking.test.ts +1 -103
  70. package/edit/provisioning.cattle.io.cluster/__tests__/index.test.ts +13 -1
  71. package/edit/provisioning.cattle.io.cluster/__tests__/rke2-fleet-cluster-agent.test.ts +283 -0
  72. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +65 -49
  73. package/edit/provisioning.cattle.io.cluster/ingress/IngressCards.vue +112 -0
  74. package/edit/provisioning.cattle.io.cluster/ingress/IngressConfiguration.vue +158 -0
  75. package/edit/provisioning.cattle.io.cluster/rke2.vue +171 -72
  76. package/edit/provisioning.cattle.io.cluster/shared.ts +36 -1
  77. package/edit/provisioning.cattle.io.cluster/tabs/AgentConfiguration.vue +2 -1
  78. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +55 -7
  79. package/edit/provisioning.cattle.io.cluster/tabs/Ingress.vue +319 -0
  80. package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +2 -1
  81. package/edit/provisioning.cattle.io.cluster/tabs/etcd/__tests__/S3Config.test.ts +13 -1
  82. package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +10 -44
  83. package/edit/secret/index.vue +1 -1
  84. package/edit/token.vue +68 -29
  85. package/edit/workload/__tests__/index.test.ts +2 -37
  86. package/edit/workload/index.vue +6 -2
  87. package/edit/workload/mixins/workload.js +0 -32
  88. package/list/__tests__/management.cattle.io.setting.test.ts +198 -0
  89. package/list/management.cattle.io.setting.vue +13 -0
  90. package/list/provisioning.cattle.io.cluster.vue +50 -1
  91. package/list/secret.vue +4 -9
  92. package/list/service.vue +6 -8
  93. package/machine-config/amazonec2.vue +11 -4
  94. package/machine-config/components/EC2Networking.vue +46 -30
  95. package/machine-config/components/__tests__/EC2Networking.test.ts +7 -7
  96. package/machine-config/components/__tests__/utils/vpcSubnetMockData.js +0 -9
  97. package/machine-config/digitalocean.vue +3 -3
  98. package/models/__tests__/namespace.test.ts +11 -0
  99. package/models/__tests__/provisioning.cattle.io.cluster.test.ts +96 -0
  100. package/models/__tests__/workload.test.ts +42 -1
  101. package/models/catalog.cattle.io.clusterrepo.js +30 -4
  102. package/models/ext.cattle.io.token.js +48 -0
  103. package/models/kontainerdriver.js +2 -2
  104. package/models/namespace.js +7 -1
  105. package/models/nodedriver.js +2 -2
  106. package/models/provisioning.cattle.io.cluster.js +28 -7
  107. package/models/secret.js +0 -17
  108. package/models/service.js +44 -1
  109. package/models/token.js +4 -0
  110. package/models/workload.js +12 -6
  111. package/package.json +1 -1
  112. package/pages/account/index.vue +96 -67
  113. package/pages/auth/setup.vue +5 -14
  114. package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +4 -1
  115. package/pages/c/_cluster/apps/charts/index.vue +93 -4
  116. package/pages/c/_cluster/apps/charts/install.vue +317 -42
  117. package/pages/c/_cluster/manager/drivers/kontainerDriver/index.vue +5 -4
  118. package/pages/c/_cluster/settings/index.vue +3 -1
  119. package/plugins/dashboard-store/__tests__/getters.test.ts +108 -0
  120. package/plugins/dashboard-store/__tests__/resource-class.test.ts +27 -0
  121. package/plugins/dashboard-store/actions.js +3 -8
  122. package/plugins/dashboard-store/getters.js +7 -5
  123. package/plugins/dashboard-store/mutations.js +4 -1
  124. package/plugins/dashboard-store/resource-class.js +3 -3
  125. package/plugins/steve/__tests__/steve-class.test.ts +102 -141
  126. package/plugins/steve/steve-class.js +12 -3
  127. package/plugins/steve/steve-pagination-utils.ts +6 -2
  128. package/rancher-components/RcIcon/types.ts +2 -0
  129. package/rancher-components/RcItemCard/RcItemCard.vue +64 -19
  130. package/store/prefs.js +3 -0
  131. package/types/aws-sdk.d.ts +121 -0
  132. package/types/resources/node.ts +15 -0
  133. package/types/shell/index.d.ts +536 -506
  134. package/types/store/pagination.types.ts +5 -5
  135. package/utils/__tests__/array.test.ts +1 -29
  136. package/utils/__tests__/cluster-agent-configuration.test.ts +203 -0
  137. package/utils/array.ts +0 -11
  138. package/utils/aws.ts +21 -0
  139. package/utils/cluster.js +22 -2
  140. package/utils/selector-typed.ts +1 -1
  141. package/components/__tests__/ProjectRow.test.ts +0 -206
  142. package/components/form/ResourceQuota/ProjectRow.vue +0 -277
@@ -0,0 +1,281 @@
1
+ import { shallowMount } from '@vue/test-utils';
2
+ import NamespaceRow from '@shell/components/form/ResourceQuota/NamespaceRow.vue';
3
+
4
+ describe('namespaceRow', () => {
5
+ const storeMock = { getters: { 'i18n/t': (key: string) => key } };
6
+
7
+ const standardType = {
8
+ value: 'limitsCpu',
9
+ inputExponent: -1,
10
+ baseUnit: '',
11
+ placeholder: 'Enter CPU',
12
+ increment: 1,
13
+ };
14
+
15
+ const extendedType = {
16
+ value: 'extended',
17
+ inputExponent: 0,
18
+ baseUnit: '',
19
+ placeholder: 'Enter value',
20
+ increment: 1,
21
+ };
22
+
23
+ const defaultProps = {
24
+ mode: 'edit',
25
+ type: 'limitsCpu',
26
+ types: [standardType, extendedType],
27
+ value: { limit: { limitsCpu: '1000m' } },
28
+ namespace: { id: 'test-ns' },
29
+ projectResourceQuotaLimits: { limitsCpu: '2000m' },
30
+ namespaceResourceQuotaLimits: [],
31
+ defaultResourceQuotaLimits: { limitsCpu: '500m' },
32
+ };
33
+
34
+ const createWrapper = (propsOverrides: Record<string, unknown> = {}) => {
35
+ return shallowMount(NamespaceRow, {
36
+ props: { ...defaultProps, ...propsOverrides },
37
+ global: { mocks: { $store: storeMock } },
38
+ });
39
+ };
40
+
41
+ describe('computed: displayType', () => {
42
+ it('returns the type unchanged for standard quota types', () => {
43
+ const wrapper: any = createWrapper({ type: 'limitsCpu' });
44
+
45
+ expect(wrapper.vm.displayType).toBe('limitsCpu');
46
+ });
47
+
48
+ it('strips the "extended." prefix for extended quota types', () => {
49
+ const wrapper: any = createWrapper({
50
+ type: 'extended.requests.nvidia.com/gpu',
51
+ value: { limit: { extended: { 'requests.nvidia.com/gpu': '4' } } },
52
+ projectResourceQuotaLimits: { 'extended.requests.nvidia.com/gpu': '8' },
53
+ defaultResourceQuotaLimits: { 'extended.requests.nvidia.com/gpu': '2' },
54
+ namespaceResourceQuotaLimits: [],
55
+ });
56
+
57
+ expect(wrapper.vm.displayType).toBe('requests.nvidia.com/gpu');
58
+ });
59
+
60
+ it('strips only the leading "extended." when the resource identifier itself starts with "extended."', () => {
61
+ const wrapper: any = createWrapper({
62
+ type: 'extended.extended.nvidia.com/gpu',
63
+ value: { limit: { extended: { 'extended.nvidia.com/gpu': '4' } } },
64
+ projectResourceQuotaLimits: { 'extended.extended.nvidia.com/gpu': '8' },
65
+ defaultResourceQuotaLimits: { 'extended.extended.nvidia.com/gpu': '2' },
66
+ namespaceResourceQuotaLimits: [],
67
+ });
68
+
69
+ expect(wrapper.vm.displayType).toBe('extended.nvidia.com/gpu');
70
+ });
71
+ });
72
+
73
+ describe('computed: currentLimit', () => {
74
+ it('returns the limit value for a standard quota type', () => {
75
+ const wrapper: any = createWrapper({
76
+ type: 'limitsCpu',
77
+ value: { limit: { limitsCpu: '1000m' } },
78
+ });
79
+
80
+ expect(wrapper.vm.currentLimit).toBe('1000m');
81
+ });
82
+
83
+ it('returns undefined when the standard type key is absent from limit', () => {
84
+ const wrapper: any = createWrapper({
85
+ type: 'limitsCpu',
86
+ value: { limit: {} },
87
+ });
88
+
89
+ expect(wrapper.vm.currentLimit).toBeUndefined();
90
+ });
91
+
92
+ it('reads the value from limit.extended for an extended quota type', () => {
93
+ const wrapper: any = createWrapper({
94
+ type: 'extended.requests.nvidia.com/gpu',
95
+ value: { limit: { extended: { 'requests.nvidia.com/gpu': '4' } } },
96
+ projectResourceQuotaLimits: { 'extended.requests.nvidia.com/gpu': '8' },
97
+ defaultResourceQuotaLimits: { 'extended.requests.nvidia.com/gpu': '2' },
98
+ namespaceResourceQuotaLimits: [],
99
+ });
100
+
101
+ expect(wrapper.vm.currentLimit).toBe('4');
102
+ });
103
+
104
+ it('returns undefined when limit.extended is absent for an extended quota type', () => {
105
+ const wrapper: any = createWrapper({
106
+ type: 'extended.requests.nvidia.com/gpu',
107
+ value: { limit: {} },
108
+ projectResourceQuotaLimits: { 'extended.requests.nvidia.com/gpu': '8' },
109
+ defaultResourceQuotaLimits: { 'extended.requests.nvidia.com/gpu': '2' },
110
+ namespaceResourceQuotaLimits: [],
111
+ });
112
+
113
+ expect(wrapper.vm.currentLimit).toBeUndefined();
114
+ });
115
+
116
+ it('reads the correct value when the resource identifier itself starts with "extended."', () => {
117
+ const wrapper: any = createWrapper({
118
+ type: 'extended.extended.nvidia.com/gpu',
119
+ value: { limit: { extended: { 'extended.nvidia.com/gpu': '4' } } },
120
+ projectResourceQuotaLimits: { 'extended.extended.nvidia.com/gpu': '8' },
121
+ defaultResourceQuotaLimits: { 'extended.extended.nvidia.com/gpu': '2' },
122
+ namespaceResourceQuotaLimits: [],
123
+ });
124
+
125
+ expect(wrapper.vm.currentLimit).toBe('4');
126
+ });
127
+
128
+ it('returns undefined when the extended resource identifier is not in limit.extended', () => {
129
+ const wrapper: any = createWrapper({
130
+ type: 'extended.requests.nvidia.com/gpu',
131
+ value: { limit: { extended: { 'example.com/fpga': '1' } } },
132
+ projectResourceQuotaLimits: { 'extended.requests.nvidia.com/gpu': '8' },
133
+ defaultResourceQuotaLimits: { 'extended.requests.nvidia.com/gpu': '2' },
134
+ namespaceResourceQuotaLimits: [],
135
+ });
136
+
137
+ expect(wrapper.vm.currentLimit).toBeUndefined();
138
+ });
139
+
140
+ it.each([
141
+ ['limitsCpu', { limitsCpu: '500m' }, '500m'],
142
+ ['configMaps', { configMaps: '10' }, '10'],
143
+ ['pods', { pods: '20' }, '20'],
144
+ ])('returns the correct value for standard type "%s"', (type, limit, expected) => {
145
+ const wrapper: any = createWrapper({
146
+ type,
147
+ types: [
148
+ {
149
+ value: type, inputExponent: 0, baseUnit: '', placeholder: '', increment: 1
150
+ },
151
+ extendedType,
152
+ ],
153
+ value: { limit },
154
+ projectResourceQuotaLimits: { [type]: '100' },
155
+ defaultResourceQuotaLimits: { [type]: '0' },
156
+ });
157
+
158
+ expect(wrapper.vm.currentLimit).toBe(expected);
159
+ });
160
+ });
161
+
162
+ describe('mounted(): initial value emission', () => {
163
+ it('emits update:value on mount with the type as the first argument', () => {
164
+ const wrapper: any = createWrapper({
165
+ type: 'limitsCpu',
166
+ value: { limit: { limitsCpu: '1000m' } },
167
+ });
168
+
169
+ const emitted = wrapper.emitted('update:value');
170
+
171
+ expect(emitted).toBeTruthy();
172
+ expect(emitted![0][0]).toBe('limitsCpu');
173
+ });
174
+
175
+ it('emits update:value on mount when currentLimit is not set, using defaultResourceQuotaLimits', () => {
176
+ const wrapper: any = createWrapper({
177
+ type: 'limitsCpu',
178
+ value: { limit: {} },
179
+ projectResourceQuotaLimits: { limitsCpu: '2000m' },
180
+ defaultResourceQuotaLimits: { limitsCpu: '500m' },
181
+ });
182
+
183
+ const emitted = wrapper.emitted('update:value');
184
+
185
+ expect(emitted).toBeTruthy();
186
+ expect(emitted![0][0]).toBe('limitsCpu');
187
+ });
188
+
189
+ it('emits update:value on mount for an extended quota type', () => {
190
+ const wrapper: any = createWrapper({
191
+ type: 'extended.requests.nvidia.com/gpu',
192
+ value: { limit: { extended: { 'requests.nvidia.com/gpu': '4' } } },
193
+ projectResourceQuotaLimits: { 'extended.requests.nvidia.com/gpu': '8' },
194
+ defaultResourceQuotaLimits: { 'extended.requests.nvidia.com/gpu': '2' },
195
+ namespaceResourceQuotaLimits: [],
196
+ });
197
+
198
+ const emitted = wrapper.emitted('update:value');
199
+
200
+ expect(emitted).toBeTruthy();
201
+ expect(emitted![0][0]).toBe('extended.requests.nvidia.com/gpu');
202
+ });
203
+ });
204
+
205
+ describe('computed: totalContribution', () => {
206
+ it('equals zero when currentLimit is 0 and no other namespaces contribute', () => {
207
+ const wrapper: any = createWrapper({
208
+ type: 'limitsCpu',
209
+ value: { limit: { limitsCpu: '0' } },
210
+ projectResourceQuotaLimits: { limitsCpu: '2000m' },
211
+ namespaceResourceQuotaLimits: [],
212
+ defaultResourceQuotaLimits: { limitsCpu: '0' },
213
+ });
214
+
215
+ expect(wrapper.vm.totalContribution).toBe(0);
216
+ });
217
+
218
+ it('includes contributions from other namespaces alongside the current limit', () => {
219
+ // ns2 contributes 500m, current namespace has 0
220
+ const wrapper: any = createWrapper({
221
+ type: 'limitsCpu',
222
+ value: { limit: { limitsCpu: '0' } },
223
+ projectResourceQuotaLimits: { limitsCpu: '2000m' },
224
+ namespaceResourceQuotaLimits: [
225
+ { id: 'ns2', limitsCpu: '500m' },
226
+ ],
227
+ defaultResourceQuotaLimits: { limitsCpu: '0' },
228
+ namespace: { id: 'test-ns' },
229
+ });
230
+
231
+ // ns2's 500m contribution should be included
232
+ expect(wrapper.vm.totalContribution).toBeGreaterThan(0);
233
+ });
234
+
235
+ it('uses currentLimit for the extended type in the total', () => {
236
+ const wrapper: any = createWrapper({
237
+ type: 'extended.requests.nvidia.com/gpu',
238
+ value: { limit: { extended: { 'requests.nvidia.com/gpu': '0' } } },
239
+ projectResourceQuotaLimits: { 'extended.requests.nvidia.com/gpu': '8' },
240
+ namespaceResourceQuotaLimits: [],
241
+ defaultResourceQuotaLimits: { 'extended.requests.nvidia.com/gpu': '0' },
242
+ });
243
+
244
+ expect(wrapper.vm.totalContribution).toBe(0);
245
+ });
246
+ });
247
+
248
+ describe('computed: namespaceLimits', () => {
249
+ it('excludes the current namespace from the list', () => {
250
+ const wrapper: any = createWrapper({
251
+ type: 'limitsCpu',
252
+ value: { limit: { limitsCpu: '500m' } },
253
+ projectResourceQuotaLimits: { limitsCpu: '2000m' },
254
+ namespaceResourceQuotaLimits: [
255
+ { id: 'test-ns', limitsCpu: '500m' },
256
+ { id: 'other-ns', limitsCpu: '300m' },
257
+ ],
258
+ namespace: { id: 'test-ns' },
259
+ defaultResourceQuotaLimits: { limitsCpu: '0' },
260
+ });
261
+
262
+ // Only the other namespace should contribute
263
+ expect(wrapper.vm.namespaceLimits).toHaveLength(1);
264
+ });
265
+
266
+ it('handles flattened extended keys from namespaceResourceQuotaLimits', () => {
267
+ const wrapper: any = createWrapper({
268
+ type: 'extended.requests.nvidia.com/gpu',
269
+ value: { limit: { extended: { 'requests.nvidia.com/gpu': '1' } } },
270
+ projectResourceQuotaLimits: { 'extended.requests.nvidia.com/gpu': '8' },
271
+ namespaceResourceQuotaLimits: [
272
+ { id: 'other-ns', 'extended.requests.nvidia.com/gpu': '2' },
273
+ ],
274
+ namespace: { id: 'test-ns' },
275
+ defaultResourceQuotaLimits: { 'extended.requests.nvidia.com/gpu': '0' },
276
+ });
277
+
278
+ expect(wrapper.vm.namespaceLimits).toHaveLength(1);
279
+ });
280
+ });
281
+ });
@@ -14,50 +14,297 @@ describe('project', () => {
14
14
  types: []
15
15
  };
16
16
 
17
- it('should emit validationChanged with false when validateTypes(false) is called (e.g., when adding a row)', () => {
18
- const wrapper: any = shallowMount(Project, { props: defaultProps });
17
+ describe('created() spec parser', () => {
18
+ it('should initialise resourceQuotas as an empty array when spec limits are empty', () => {
19
+ const wrapper: any = shallowMount(Project, { props: defaultProps });
19
20
 
20
- wrapper.vm.validateTypes(false);
21
+ expect(wrapper.vm.resourceQuotas).toStrictEqual([]);
22
+ });
23
+
24
+ it('should parse a standard resource type from the spec into resourceQuotas', () => {
25
+ const wrapper: any = shallowMount(Project, {
26
+ props: {
27
+ ...defaultProps,
28
+ value: {
29
+ spec: {
30
+ resourceQuota: { limit: { configMaps: '20' } },
31
+ namespaceDefaultResourceQuota: { limit: { configMaps: '10' } }
32
+ }
33
+ }
34
+ }
35
+ });
36
+
37
+ expect(wrapper.vm.resourceQuotas).toHaveLength(1);
38
+ expect(wrapper.vm.resourceQuotas[0]).toMatchObject({
39
+ resourceType: 'configMaps',
40
+ resourceIdentifier: 'configMaps',
41
+ projectLimit: '20',
42
+ namespaceDefaultLimit: '10',
43
+ });
44
+ });
45
+
46
+ it('should parse an extended resource type from the spec into resourceQuotas', () => {
47
+ const wrapper: any = shallowMount(Project, {
48
+ props: {
49
+ ...defaultProps,
50
+ value: {
51
+ spec: {
52
+ resourceQuota: { limit: { extended: { test1: '10' } } },
53
+ namespaceDefaultResourceQuota: { limit: { extended: { test1: '5' } } }
54
+ }
55
+ }
56
+ }
57
+ });
58
+
59
+ expect(wrapper.vm.resourceQuotas).toHaveLength(1);
60
+ expect(wrapper.vm.resourceQuotas[0]).toMatchObject({
61
+ resourceType: TYPES.EXTENDED,
62
+ resourceIdentifier: 'test1',
63
+ projectLimit: '10',
64
+ namespaceDefaultLimit: '5',
65
+ });
66
+ });
67
+
68
+ it('should initialise resourceQuotas as an empty array when resourceQuota exists but has no limit property', () => {
69
+ const wrapper: any = shallowMount(Project, {
70
+ props: {
71
+ ...defaultProps,
72
+ value: {
73
+ spec: {
74
+ resourceQuota: {},
75
+ namespaceDefaultResourceQuota: { limit: {} }
76
+ }
77
+ }
78
+ }
79
+ });
80
+
81
+ expect(wrapper.vm.resourceQuotas).toStrictEqual([]);
82
+ });
83
+
84
+ it('should parse project quotas and fall back to empty string for missing ns limits when namespaceDefaultResourceQuota has no limit property', () => {
85
+ const wrapper: any = shallowMount(Project, {
86
+ props: {
87
+ ...defaultProps,
88
+ value: {
89
+ spec: {
90
+ resourceQuota: { limit: { configMaps: '20' } },
91
+ namespaceDefaultResourceQuota: {}
92
+ }
93
+ }
94
+ }
95
+ });
21
96
 
22
- expect(wrapper.emitted('validationChanged')).toBeTruthy();
23
- expect(wrapper.emitted('validationChanged')[0]).toStrictEqual([false]);
97
+ expect(wrapper.vm.resourceQuotas).toHaveLength(1);
98
+ expect(wrapper.vm.resourceQuotas[0]).toMatchObject({
99
+ resourceType: 'configMaps',
100
+ projectLimit: '20',
101
+ namespaceDefaultLimit: '',
102
+ });
103
+ });
104
+
105
+ it('should parse both standard and extended resource types into resourceQuotas', () => {
106
+ const wrapper: any = shallowMount(Project, {
107
+ props: {
108
+ ...defaultProps,
109
+ value: {
110
+ spec: {
111
+ resourceQuota: { limit: { configMaps: '20', extended: { test1: '10' } } },
112
+ namespaceDefaultResourceQuota: { limit: { configMaps: '15', extended: { test1: '8' } } }
113
+ }
114
+ }
115
+ }
116
+ });
117
+
118
+ expect(wrapper.vm.resourceQuotas).toHaveLength(2);
119
+ });
24
120
  });
25
121
 
26
- it('should emit validationChanged with false if an extended type has no resource identifier', () => {
27
- const wrapper: any = shallowMount(Project, { props: defaultProps });
122
+ describe('specFromQuotas()', () => {
123
+ it('should convert a standard resourceQuota entry to projectLimit and nsLimit', () => {
124
+ const wrapper: any = shallowMount(Project, { props: defaultProps });
125
+
126
+ wrapper.setData({
127
+ resourceQuotas: [{
128
+ id: '1',
129
+ resourceType: 'configMaps',
130
+ resourceIdentifier: 'configMaps',
131
+ projectLimit: '20',
132
+ namespaceDefaultLimit: '10',
133
+ }]
134
+ });
135
+
136
+ expect(wrapper.vm.specFromQuotas()).toStrictEqual({
137
+ projectLimit: { configMaps: '20' },
138
+ nsLimit: { configMaps: '10' },
139
+ });
140
+ });
141
+
142
+ it('should convert an extended resourceQuota entry to a nested extended object', () => {
143
+ const wrapper: any = shallowMount(Project, { props: defaultProps });
144
+
145
+ wrapper.setData({
146
+ resourceQuotas: [{
147
+ id: '1',
148
+ resourceType: TYPES.EXTENDED,
149
+ resourceIdentifier: 'my-resource',
150
+ projectLimit: '5',
151
+ namespaceDefaultLimit: '3',
152
+ }]
153
+ });
154
+
155
+ expect(wrapper.vm.specFromQuotas()).toStrictEqual({
156
+ projectLimit: { extended: { 'my-resource': '5' } },
157
+ nsLimit: { extended: { 'my-resource': '3' } },
158
+ });
159
+ });
160
+
161
+ it('should omit an extended entry from the spec when resourceIdentifier is empty', () => {
162
+ const wrapper: any = shallowMount(Project, { props: defaultProps });
28
163
 
29
- wrapper.setData({ typeValues: [TYPES.EXTENDED] });
164
+ wrapper.setData({
165
+ resourceQuotas: [{
166
+ id: '1',
167
+ resourceType: TYPES.EXTENDED,
168
+ resourceIdentifier: '',
169
+ projectLimit: '5',
170
+ namespaceDefaultLimit: '3',
171
+ }]
172
+ });
30
173
 
31
- wrapper.vm.validateTypes(true);
174
+ const { projectLimit } = wrapper.vm.specFromQuotas();
32
175
 
33
- expect(wrapper.emitted('validationChanged')).toBeTruthy();
34
- expect(wrapper.emitted('validationChanged')[0]).toStrictEqual([false]);
176
+ expect(projectLimit.extended).toBeUndefined();
177
+ });
35
178
  });
36
179
 
37
- it('should emit validationChanged with true if an extended type has resource identifier', () => {
38
- const wrapper: any = shallowMount(Project, { props: defaultProps });
180
+ describe('remainingTypes()', () => {
181
+ const typesProps = [
182
+ {
183
+ value: TYPES.EXTENDED,
184
+ inputExponent: 0,
185
+ baseUnit: '',
186
+ labelKey: 'resourceQuota.custom',
187
+ placeholderKey: 'resourceQuota.projectLimit.unitlessPlaceholder'
188
+ },
189
+ {
190
+ value: 'configMaps',
191
+ inputExponent: 0,
192
+ baseUnit: '',
193
+ labelKey: 'resourceQuota.configMaps',
194
+ placeholderKey: 'resourceQuota.projectLimit.unitlessPlaceholder'
195
+ },
196
+ {
197
+ value: 'pods',
198
+ inputExponent: 0,
199
+ baseUnit: '',
200
+ labelKey: 'resourceQuota.pods',
201
+ placeholderKey: 'resourceQuota.projectLimit.unitlessPlaceholder'
202
+ }
203
+ ];
204
+
205
+ it('should exclude already-used non-extended types', () => {
206
+ const wrapper: any = shallowMount(Project, { props: { ...defaultProps, types: typesProps } });
207
+
208
+ wrapper.setData({
209
+ resourceQuotas: [{
210
+ id: '1',
211
+ resourceType: 'configMaps',
212
+ resourceIdentifier: 'configMaps',
213
+ projectLimit: '20',
214
+ namespaceDefaultLimit: '10',
215
+ }]
216
+ });
217
+
218
+ const values = wrapper.vm.remainingTypes('pods').map((t: any) => t.value);
219
+
220
+ expect(values).not.toContain('configMaps');
221
+ expect(values).toContain('pods');
222
+ expect(values).toContain(TYPES.EXTENDED);
223
+ });
224
+
225
+ it('should include the currentType even if it is already used', () => {
226
+ const wrapper: any = shallowMount(Project, { props: { ...defaultProps, types: typesProps } });
227
+
228
+ wrapper.setData({
229
+ resourceQuotas: [{
230
+ id: '1',
231
+ resourceType: 'configMaps',
232
+ resourceIdentifier: 'configMaps',
233
+ projectLimit: '20',
234
+ namespaceDefaultLimit: '10',
235
+ }]
236
+ });
237
+
238
+ const values = wrapper.vm.remainingTypes('configMaps').map((t: any) => t.value);
239
+
240
+ expect(values).toContain('configMaps');
241
+ });
39
242
 
40
- wrapper.setData({ typeValues: ['extended.my-resource'] });
243
+ it('should always include extended type', () => {
244
+ const wrapper: any = shallowMount(Project, { props: { ...defaultProps, types: typesProps } });
41
245
 
42
- wrapper.vm.validateTypes(true);
246
+ const values = wrapper.vm.remainingTypes('pods').map((t: any) => t.value);
43
247
 
44
- expect(wrapper.emitted('validationChanged')).toBeTruthy();
45
- expect(wrapper.emitted('validationChanged')[0]).toStrictEqual([true]);
248
+ expect(values).toContain(TYPES.EXTENDED);
249
+ });
46
250
  });
47
251
 
48
- it('should update typeValues and validate when updateResourceIdentifier is called', () => {
49
- const wrapper: any = shallowMount(Project, { props: defaultProps });
252
+ describe('validationChanged watcher', () => {
253
+ it('should emit validationChanged with false when an extended quota has no resource identifier', async() => {
254
+ const wrapper: any = shallowMount(Project, { props: defaultProps });
255
+
256
+ await wrapper.setData({
257
+ resourceQuotas: [{
258
+ id: '1',
259
+ resourceType: TYPES.EXTENDED,
260
+ resourceIdentifier: '',
261
+ projectLimit: '5',
262
+ namespaceDefaultLimit: '3',
263
+ }]
264
+ });
50
265
 
51
- wrapper.setData({ typeValues: [TYPES.EXTENDED] });
266
+ const emitted = wrapper.emitted('validationChanged');
52
267
 
53
- wrapper.vm.updateResourceIdentifier({
54
- type: TYPES.EXTENDED,
55
- customType: 'my-resource',
56
- index: 0
268
+ expect(emitted).toBeTruthy();
269
+ expect(emitted[emitted.length - 1]).toStrictEqual([false]);
57
270
  });
58
271
 
59
- expect(wrapper.vm.typeValues[0]).toStrictEqual('extended.my-resource');
60
- expect(wrapper.emitted('validationChanged')).toBeTruthy();
61
- expect(wrapper.emitted('validationChanged')[0]).toStrictEqual([true]);
272
+ it('should emit validationChanged with true when all extended quotas have resource identifiers', async() => {
273
+ const wrapper: any = shallowMount(Project, { props: defaultProps });
274
+
275
+ await wrapper.setData({
276
+ resourceQuotas: [{
277
+ id: '1',
278
+ resourceType: TYPES.EXTENDED,
279
+ resourceIdentifier: 'my-resource',
280
+ projectLimit: '5',
281
+ namespaceDefaultLimit: '3',
282
+ }]
283
+ });
284
+
285
+ const emitted = wrapper.emitted('validationChanged');
286
+
287
+ expect(emitted).toBeTruthy();
288
+ expect(emitted[emitted.length - 1]).toStrictEqual([true]);
289
+ });
290
+
291
+ it('should emit validationChanged with true when resourceQuotas has no extended entries', async() => {
292
+ const wrapper: any = shallowMount(Project, { props: defaultProps });
293
+
294
+ await wrapper.setData({
295
+ resourceQuotas: [{
296
+ id: '1',
297
+ resourceType: 'configMaps',
298
+ resourceIdentifier: 'configMaps',
299
+ projectLimit: '20',
300
+ namespaceDefaultLimit: '10',
301
+ }]
302
+ });
303
+
304
+ const emitted = wrapper.emitted('validationChanged');
305
+
306
+ expect(emitted).toBeTruthy();
307
+ expect(emitted[emitted.length - 1]).toStrictEqual([true]);
308
+ });
62
309
  });
63
310
  });