@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,145 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue';
3
+ import { useStore } from 'vuex';
4
+
5
+ import Select from '@shell/components/form/Select';
6
+ import UnitInput from '@shell/components/form/UnitInput';
7
+ import { LabeledInput } from '@components/Form/LabeledInput';
8
+ import { RcButton } from '@components/RcButton';
9
+
10
+ import { TYPES } from './shared';
11
+ import { useI18n } from '@shell/composables/useI18n';
12
+
13
+ const props = defineProps<{
14
+ id: string,
15
+ index: number,
16
+ mode: string,
17
+ types: any[],
18
+ }>();
19
+
20
+ const emit = defineEmits(['remove']);
21
+
22
+ const store = useStore();
23
+ const { t } = useI18n(store);
24
+
25
+ const resourceType = defineModel<string>('resourceType');
26
+ const resourceIdentifier = defineModel<string>('resourceIdentifier');
27
+ const projectLimit = defineModel<string>('projectLimit');
28
+ const namespaceDefaultLimit = defineModel<string>('namespaceDefaultLimit');
29
+
30
+ const typeOption = computed(() => {
31
+ return props.types.find((type) => type.value === resourceType.value) || {};
32
+ });
33
+
34
+ const isCustom = computed(() => {
35
+ return resourceType.value === TYPES.EXTENDED;
36
+ });
37
+
38
+ const removeAriaLabel = computed(() => {
39
+ let identifier;
40
+
41
+ if (isCustom.value) {
42
+ identifier = resourceIdentifier.value || t('generic.ariaLabel.genericRow', { index: String(props.index) });
43
+ } else {
44
+ identifier = typeOption.value?.label || t('generic.ariaLabel.genericRow', { index: String(props.index) });
45
+ }
46
+
47
+ return t('resourceQuota.ariaLabel.remove', { identifier });
48
+ });
49
+
50
+ const customTypeRules = computed(() => {
51
+ // Return a validation rule that makes the field required when isCustom is true
52
+ if (isCustom.value) {
53
+ return [
54
+ (value: string) => {
55
+ if (!value) {
56
+ return t('resourceQuota.errors.customTypeRequired');
57
+ }
58
+
59
+ return undefined;
60
+ }
61
+ ];
62
+ }
63
+
64
+ return [];
65
+ });
66
+
67
+ const remove = (id: string) => {
68
+ emit('remove', id);
69
+ };
70
+
71
+ const updateResourceIdentifier = (resourceType: string) => {
72
+ if (resourceType === TYPES.EXTENDED) {
73
+ return;
74
+ }
75
+
76
+ resourceIdentifier.value = resourceType;
77
+ };
78
+ </script>
79
+
80
+ <template>
81
+ <div
82
+ role="row"
83
+ class="row mb-10"
84
+ >
85
+ <Select
86
+ v-model:value="resourceType"
87
+ class="mr-10"
88
+ :mode="mode"
89
+ :options="types"
90
+ :aria-label="t('resourceQuota.ariaLabel.resourceType', { row: index })"
91
+ data-testid="projectrow-type-input"
92
+ @update:value="updateResourceIdentifier"
93
+ />
94
+ <LabeledInput
95
+ v-model:value="resourceIdentifier"
96
+ :disabled="!isCustom"
97
+ :required="isCustom"
98
+ :mode="mode"
99
+ :placeholder="t('resourceQuota.resourceIdentifier.placeholder')"
100
+ :rules="customTypeRules"
101
+ :require-dirty="false"
102
+ :aria-label="t('resourceQuota.ariaLabel.resourceIdentifier', { row: index })"
103
+ class="mr-10"
104
+ data-testid="projectrow-custom-type-input"
105
+ />
106
+ <UnitInput
107
+ v-model:value="projectLimit"
108
+ class="mr-10"
109
+ :mode="mode"
110
+ :placeholder="typeOption.placeholder"
111
+ :increment="typeOption.increment"
112
+ :input-exponent="typeOption.inputExponent"
113
+ :base-unit="typeOption.baseUnit"
114
+ :output-modifier="true"
115
+ :aria-label="t('resourceQuota.ariaLabel.projectLimit', { row: index })"
116
+ data-testid="projectrow-project-quota-input"
117
+ />
118
+ <UnitInput
119
+ v-model:value="namespaceDefaultLimit"
120
+ :mode="mode"
121
+ :placeholder="typeOption.placeholder"
122
+ :increment="typeOption.increment"
123
+ :input-exponent="typeOption.inputExponent"
124
+ :base-unit="typeOption.baseUnit"
125
+ :output-modifier="true"
126
+ :aria-label="t('resourceQuota.ariaLabel.namespaceDefaultLimit', { row: index })"
127
+ data-testid="projectrow-namespace-quota-input"
128
+ />
129
+ <RcButton
130
+ variant="tertiary"
131
+ :aria-label="removeAriaLabel"
132
+ @click="remove(id)"
133
+ >
134
+ {{ t('generic.remove') }}
135
+ </RcButton>
136
+ </div>
137
+ </template>
138
+
139
+ <style lang='scss' scoped>
140
+ .row {
141
+ display: flex;
142
+ flex-direction: row;
143
+ justify-content: space-evenly;
144
+ }
145
+ </style>
@@ -0,0 +1,307 @@
1
+ import { shallowMount } from '@vue/test-utils';
2
+ import Namespace from '@shell/components/form/ResourceQuota/Namespace.vue';
3
+
4
+ describe('namespace', () => {
5
+ const makeProject = (overrides: Record<string, unknown> = {}) => ({
6
+ spec: {
7
+ resourceQuota: { limit: {} },
8
+ namespaceDefaultResourceQuota: { limit: {} }
9
+ },
10
+ namespaces: [],
11
+ ...overrides
12
+ });
13
+
14
+ const defaultProps = {
15
+ mode: 'edit',
16
+ types: [],
17
+ value: { resourceQuota: { limit: {} } },
18
+ project: makeProject()
19
+ };
20
+
21
+ const createWrapper = (propsOverrides: Record<string, unknown> = {}) => {
22
+ return shallowMount(Namespace, { props: { ...defaultProps, ...propsOverrides } });
23
+ };
24
+
25
+ describe('computed: projectResourceQuotaLimits', () => {
26
+ it('passes standard quota keys through unchanged', () => {
27
+ const wrapper: any = createWrapper({
28
+ project: makeProject({
29
+ spec: {
30
+ resourceQuota: { limit: { limitsCpu: '2000m', configMaps: '10' } },
31
+ namespaceDefaultResourceQuota: { limit: {} }
32
+ }
33
+ })
34
+ });
35
+
36
+ expect(wrapper.vm.projectResourceQuotaLimits).toStrictEqual({
37
+ limitsCpu: '2000m',
38
+ configMaps: '10'
39
+ });
40
+ });
41
+
42
+ it('flattens extended resources to dotted keys', () => {
43
+ const wrapper: any = createWrapper({
44
+ project: makeProject({
45
+ spec: {
46
+ resourceQuota: { limit: { extended: { 'requests.nvidia.com/gpu': '4' } } },
47
+ namespaceDefaultResourceQuota: { limit: {} }
48
+ }
49
+ })
50
+ });
51
+
52
+ expect(wrapper.vm.projectResourceQuotaLimits).toStrictEqual({ 'extended.requests.nvidia.com/gpu': '4' });
53
+ });
54
+
55
+ it('flattens multiple extended entries alongside standard keys', () => {
56
+ const wrapper: any = createWrapper({
57
+ project: makeProject({
58
+ spec: {
59
+ resourceQuota: {
60
+ limit: {
61
+ limitsCpu: '2000m',
62
+ extended: {
63
+ 'requests.nvidia.com/gpu': '4',
64
+ 'example.com/fpga': '2'
65
+ }
66
+ }
67
+ },
68
+ namespaceDefaultResourceQuota: { limit: {} }
69
+ }
70
+ })
71
+ });
72
+
73
+ expect(wrapper.vm.projectResourceQuotaLimits).toStrictEqual({
74
+ limitsCpu: '2000m',
75
+ 'extended.requests.nvidia.com/gpu': '4',
76
+ 'extended.example.com/fpga': '2'
77
+ });
78
+ });
79
+
80
+ it('returns an empty object when limit is empty', () => {
81
+ const wrapper: any = createWrapper();
82
+
83
+ expect(wrapper.vm.projectResourceQuotaLimits).toStrictEqual({});
84
+ });
85
+
86
+ it('flattens an extended resource whose identifier itself starts with "extended."', () => {
87
+ const wrapper: any = createWrapper({
88
+ project: makeProject({
89
+ spec: {
90
+ resourceQuota: { limit: { extended: { 'extended.nvidia.com/gpu': '4' } } },
91
+ namespaceDefaultResourceQuota: { limit: {} }
92
+ }
93
+ })
94
+ });
95
+
96
+ expect(wrapper.vm.projectResourceQuotaLimits).toStrictEqual({ 'extended.extended.nvidia.com/gpu': '4' });
97
+ });
98
+
99
+ it('returns an empty object when resourceQuota is not set on the project', () => {
100
+ const wrapper: any = createWrapper({
101
+ project: makeProject({
102
+ spec: {
103
+ resourceQuota: undefined,
104
+ namespaceDefaultResourceQuota: { limit: {} }
105
+ }
106
+ })
107
+ });
108
+
109
+ expect(wrapper.vm.projectResourceQuotaLimits).toStrictEqual({});
110
+ });
111
+ });
112
+
113
+ describe('computed: namespaceResourceQuotaLimits', () => {
114
+ it('passes standard quota keys through and preserves namespace id', () => {
115
+ const wrapper: any = createWrapper({
116
+ project: makeProject({
117
+ namespaces: [
118
+ { id: 'ns1', resourceQuota: { limit: { limitsCpu: '500m' } } }
119
+ ]
120
+ })
121
+ });
122
+
123
+ expect(wrapper.vm.namespaceResourceQuotaLimits).toStrictEqual([
124
+ { id: 'ns1', limitsCpu: '500m' }
125
+ ]);
126
+ });
127
+
128
+ it('flattens extended resources for each namespace', () => {
129
+ const wrapper: any = createWrapper({
130
+ project: makeProject({
131
+ namespaces: [
132
+ { id: 'ns1', resourceQuota: { limit: { extended: { 'requests.nvidia.com/gpu': '2' } } } }
133
+ ]
134
+ })
135
+ });
136
+
137
+ expect(wrapper.vm.namespaceResourceQuotaLimits).toStrictEqual([
138
+ { id: 'ns1', 'extended.requests.nvidia.com/gpu': '2' }
139
+ ]);
140
+ });
141
+
142
+ it('handles multiple namespaces with a mix of standard and extended keys', () => {
143
+ const wrapper: any = createWrapper({
144
+ project: makeProject({
145
+ namespaces: [
146
+ { id: 'ns1', resourceQuota: { limit: { limitsCpu: '500m' } } },
147
+ { id: 'ns2', resourceQuota: { limit: { extended: { 'requests.nvidia.com/gpu': '1' } } } }
148
+ ]
149
+ })
150
+ });
151
+
152
+ expect(wrapper.vm.namespaceResourceQuotaLimits).toHaveLength(2);
153
+ expect(wrapper.vm.namespaceResourceQuotaLimits[0]).toStrictEqual({ id: 'ns1', limitsCpu: '500m' });
154
+ expect(wrapper.vm.namespaceResourceQuotaLimits[1]).toStrictEqual({ id: 'ns2', 'extended.requests.nvidia.com/gpu': '1' });
155
+ });
156
+
157
+ it('returns an empty array when the project has no namespaces', () => {
158
+ const wrapper: any = createWrapper();
159
+
160
+ expect(wrapper.vm.namespaceResourceQuotaLimits).toStrictEqual([]);
161
+ });
162
+ });
163
+
164
+ describe('computed: defaultResourceQuotaLimits', () => {
165
+ it('passes standard quota keys through unchanged', () => {
166
+ const wrapper: any = createWrapper({
167
+ project: makeProject({
168
+ spec: {
169
+ resourceQuota: { limit: {} },
170
+ namespaceDefaultResourceQuota: { limit: { limitsCpu: '1000m' } }
171
+ }
172
+ })
173
+ });
174
+
175
+ expect(wrapper.vm.defaultResourceQuotaLimits).toStrictEqual({ limitsCpu: '1000m' });
176
+ });
177
+
178
+ it('flattens extended quota keys', () => {
179
+ const wrapper: any = createWrapper({
180
+ project: makeProject({
181
+ spec: {
182
+ resourceQuota: { limit: {} },
183
+ namespaceDefaultResourceQuota: { limit: { extended: { 'requests.nvidia.com/gpu': '2' } } }
184
+ }
185
+ })
186
+ });
187
+
188
+ expect(wrapper.vm.defaultResourceQuotaLimits).toStrictEqual({ 'extended.requests.nvidia.com/gpu': '2' });
189
+ });
190
+
191
+ it('returns an empty object when the namespaceDefaultResourceQuota limit is missing', () => {
192
+ const wrapper: any = createWrapper({
193
+ project: makeProject({
194
+ spec: {
195
+ resourceQuota: { limit: {} },
196
+ namespaceDefaultResourceQuota: {}
197
+ }
198
+ })
199
+ });
200
+
201
+ expect(wrapper.vm.defaultResourceQuotaLimits).toStrictEqual({});
202
+ });
203
+ });
204
+
205
+ describe('computed: editableLimits', () => {
206
+ it('returns standard keys for standard quota types', () => {
207
+ const wrapper: any = createWrapper({
208
+ project: makeProject({
209
+ spec: {
210
+ resourceQuota: { limit: { limitsCpu: '2000m', configMaps: '10' } },
211
+ namespaceDefaultResourceQuota: { limit: {} }
212
+ }
213
+ })
214
+ });
215
+
216
+ expect(wrapper.vm.editableLimits).toStrictEqual(['limitsCpu', 'configMaps']);
217
+ });
218
+
219
+ it('returns dotted keys for extended quota types', () => {
220
+ const wrapper: any = createWrapper({
221
+ project: makeProject({
222
+ spec: {
223
+ resourceQuota: { limit: { extended: { 'requests.nvidia.com/gpu': '4' } } },
224
+ namespaceDefaultResourceQuota: { limit: {} }
225
+ }
226
+ })
227
+ });
228
+
229
+ expect(wrapper.vm.editableLimits).toStrictEqual(['extended.requests.nvidia.com/gpu']);
230
+ });
231
+
232
+ it('returns an empty array when there are no project quota limits', () => {
233
+ const wrapper: any = createWrapper();
234
+
235
+ expect(wrapper.vm.editableLimits).toStrictEqual([]);
236
+ });
237
+ });
238
+
239
+ describe('method: update', () => {
240
+ it('sets standard keys directly on the limit object', () => {
241
+ const value = { resourceQuota: { limit: { limitsCpu: '1000m' } } };
242
+ const wrapper: any = createWrapper({ value });
243
+
244
+ wrapper.vm.update('configMaps', '20');
245
+
246
+ expect(value.resourceQuota.limit).toMatchObject({ configMaps: '20', limitsCpu: '1000m' });
247
+ });
248
+
249
+ it('writes an extended key into the nested limit.extended object', () => {
250
+ const value = { resourceQuota: { limit: {} } };
251
+ const wrapper: any = createWrapper({ value });
252
+
253
+ wrapper.vm.update('extended.requests.nvidia.com/gpu', '4');
254
+
255
+ expect(value.resourceQuota).toStrictEqual({ limit: { extended: { 'requests.nvidia.com/gpu': '4' } } });
256
+ });
257
+
258
+ it('merges a new extended key with existing extended entries', () => {
259
+ const value = { resourceQuota: { limit: { extended: { 'requests.nvidia.com/gpu': '2' } } } };
260
+ const wrapper: any = createWrapper({ value });
261
+
262
+ wrapper.vm.update('extended.example.com/fpga', '1');
263
+
264
+ expect(value.resourceQuota.limit.extended).toStrictEqual({
265
+ 'requests.nvidia.com/gpu': '2',
266
+ 'example.com/fpga': '1'
267
+ });
268
+ });
269
+
270
+ it('overwrites an existing extended key with the new value', () => {
271
+ const value = { resourceQuota: { limit: { extended: { 'requests.nvidia.com/gpu': '2' } } } };
272
+ const wrapper: any = createWrapper({ value });
273
+
274
+ wrapper.vm.update('extended.requests.nvidia.com/gpu', '8');
275
+
276
+ expect(value.resourceQuota.limit.extended).toStrictEqual({ 'requests.nvidia.com/gpu': '8' });
277
+ });
278
+
279
+ it('does not affect existing standard keys when setting an extended key', () => {
280
+ const value = { resourceQuota: { limit: { limitsCpu: '2000m' } } };
281
+ const wrapper: any = createWrapper({ value });
282
+
283
+ wrapper.vm.update('extended.requests.nvidia.com/gpu', '4');
284
+
285
+ expect((value.resourceQuota.limit as any).limitsCpu).toBe('2000m');
286
+ });
287
+
288
+ it('correctly round-trips an extended resource whose identifier starts with "extended."', () => {
289
+ const value = { resourceQuota: { limit: {} } };
290
+ const wrapper: any = createWrapper({ value });
291
+
292
+ wrapper.vm.update('extended.extended.nvidia.com/gpu', '4');
293
+
294
+ expect(value.resourceQuota.limit).toStrictEqual({ extended: { 'extended.nvidia.com/gpu': '4' } });
295
+ });
296
+
297
+ it('does not write an extended entry into the standard keys when updating a standard key', () => {
298
+ const value = { resourceQuota: { limit: {} } };
299
+ const wrapper: any = createWrapper({ value });
300
+
301
+ wrapper.vm.update('limitsCpu', '500m');
302
+
303
+ expect((value.resourceQuota.limit as any).extended).toBeUndefined();
304
+ expect((value.resourceQuota.limit as any).limitsCpu).toBe('500m');
305
+ });
306
+ });
307
+ });