@rancher/shell 3.0.11 → 3.0.12-rc.1
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.
- package/assets/styles/base/_mixins.scss +31 -0
- package/assets/styles/base/_variables.scss +2 -0
- package/assets/styles/themes/_modern.scss +6 -5
- package/assets/translations/en-us.yaml +5 -4
- package/assets/translations/zh-hans.yaml +0 -3
- package/components/EmptyProductPage.vue +76 -0
- package/components/Resource/Detail/CopyToClipboard.vue +1 -2
- package/components/Resource/Detail/Metadata/KeyValueRow.vue +9 -3
- package/components/Resource/Detail/TitleBar/__tests__/__snapshots__/index.test.ts.snap +31 -0
- package/components/Resource/Detail/TitleBar/__tests__/index.test.ts +45 -1
- package/components/Resource/Detail/TitleBar/index.vue +1 -1
- package/components/Resource/Detail/ViewOptions/__tests__/__snapshots__/index.test.ts.snap +9 -0
- package/components/Resource/Detail/ViewOptions/__tests__/index.test.ts +62 -0
- package/components/Resource/Detail/ViewOptions/index.vue +2 -1
- package/components/ResourceList/Masthead.vue +25 -2
- package/components/SideNav.vue +13 -0
- package/components/__tests__/PromptModal.test.ts +2 -0
- package/components/fleet/FleetClusters.vue +1 -0
- package/components/fleet/__tests__/FleetClusters.test.ts +71 -0
- package/components/form/NodeScheduling.vue +17 -3
- package/components/form/PrivateRegistry.vue +69 -0
- package/components/form/__tests__/PrivateRegistry.test.ts +133 -0
- package/components/formatter/WorkloadHealthScale.vue +3 -1
- package/components/nav/Group.vue +26 -3
- package/components/nav/Header.vue +32 -7
- package/components/nav/TopLevelMenu.vue +15 -1
- package/config/pagination-table-headers.js +8 -1
- package/config/product/apps.js +2 -1
- package/config/product/auth.js +1 -0
- package/config/product/backup.js +1 -0
- package/config/product/compliance.js +1 -1
- package/config/product/explorer.js +25 -6
- package/config/product/fleet.js +1 -0
- package/config/product/gatekeeper.js +1 -0
- package/config/product/istio.js +1 -0
- package/config/product/logging.js +1 -0
- package/config/product/longhorn.js +2 -1
- package/config/product/manager.js +1 -0
- package/config/product/monitoring.js +1 -0
- package/config/product/navlinks.js +1 -0
- package/config/product/neuvector.js +2 -1
- package/config/product/settings.js +1 -0
- package/config/product/uiplugins.js +1 -0
- package/core/__tests__/plugin-products-helpers.test.ts +454 -0
- package/core/__tests__/plugin-products.test.ts +3219 -0
- package/core/extension-manager-impl.js +30 -1
- package/core/plugin-products-base.ts +375 -0
- package/core/plugin-products-extending.ts +44 -0
- package/core/plugin-products-helpers.ts +262 -0
- package/core/plugin-products-top-level.ts +66 -0
- package/core/plugin-products-type-guards.ts +33 -0
- package/core/plugin-products.ts +50 -0
- package/core/plugin-types.ts +222 -0
- package/core/plugin.ts +45 -10
- package/core/productDebugger.js +48 -0
- package/core/types.ts +95 -11
- package/detail/__tests__/__snapshots__/fleet.cattle.io.bundle.test.ts.snap +52 -0
- package/detail/__tests__/fleet.cattle.io.bundle.test.ts +171 -0
- package/detail/fleet.cattle.io.bundle.vue +21 -34
- package/dialog/ExtensionCatalogInstallDialog.vue +1 -1
- package/dialog/InstallExtensionDialog.vue +6 -27
- package/dialog/UninstallExistingExtensionDialog.vue +141 -0
- package/dialog/UninstallExtensionDialog.vue +4 -26
- package/dialog/__tests__/UninstallExistingExtensionDialog.test.ts +114 -0
- package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +1 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/Ingress.test.ts +176 -0
- package/edit/provisioning.cattle.io.cluster/rke2.vue +4 -1
- package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +6 -0
- package/edit/provisioning.cattle.io.cluster/tabs/Ingress.vue +7 -2
- package/list/provisioning.cattle.io.cluster.vue +0 -1
- package/list/workload.vue +11 -4
- package/mixins/resource-fetch.js +12 -3
- package/models/pod.js +18 -0
- package/models/workload.js +20 -2
- package/package.json +1 -2
- package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +0 -1
- package/pages/c/_cluster/settings/brand.vue +4 -4
- package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +231 -13
- package/pages/c/_cluster/uiplugins/index.vue +143 -37
- package/plugins/dashboard-store/__tests__/resource-class.test.ts +1 -0
- package/plugins/dashboard-store/actions.js +3 -2
- package/plugins/dashboard-store/resource-class.js +62 -6
- package/plugins/plugin.js +16 -0
- package/plugins/steve/steve-pagination-utils.ts +7 -0
- package/scripts/typegen.sh +13 -1
- package/store/__tests__/type-map.test.ts +84 -24
- package/store/type-map.js +42 -3
- package/tsconfig.paths.json +1 -0
- package/types/resources/pod.ts +18 -0
- package/types/shell/index.d.ts +8506 -2909
- package/types/store/dashboard-store.types.ts +5 -0
- package/types/store/pagination.types.ts +6 -0
- package/utils/axios.js +1 -4
- package/utils/dynamic-importer.js +3 -2
- package/utils/pagination-utils.ts +1 -1
- package/utils/uiplugins.ts +12 -16
- package/utils/validators/__tests__/private-registry.test.ts +76 -0
- package/utils/validators/private-registry.ts +28 -0
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
import pluginProductsHelpers from '@shell/core/plugin-products-helpers';
|
|
2
|
+
import { BLANK_CLUSTER } from '@shell/store/store-types';
|
|
3
|
+
import { ProductChildPage, ProductChildGroup } from '@shell/core/plugin-types';
|
|
4
|
+
|
|
5
|
+
const gatherChildrenOrdering = pluginProductsHelpers.gatherChildrenOrdering.bind(pluginProductsHelpers);
|
|
6
|
+
const generateTopLevelExtensionSimpleBaseRoute = pluginProductsHelpers.generateTopLevelExtensionSimpleBaseRoute.bind(pluginProductsHelpers);
|
|
7
|
+
const generateVirtualTypeRoute = pluginProductsHelpers.generateVirtualTypeRoute.bind(pluginProductsHelpers);
|
|
8
|
+
const generateConfigureTypeRoute = pluginProductsHelpers.generateConfigureTypeRoute.bind(pluginProductsHelpers);
|
|
9
|
+
const generateResourceRoutes = pluginProductsHelpers.generateResourceRoutes.bind(pluginProductsHelpers);
|
|
10
|
+
|
|
11
|
+
describe('plugin-products-helpers', () => {
|
|
12
|
+
// ============= gatherChildrenOrdering tests =============
|
|
13
|
+
describe('gatherChildrenOrdering', () => {
|
|
14
|
+
it('should sort children by weight descending', () => {
|
|
15
|
+
const children = [
|
|
16
|
+
{
|
|
17
|
+
name: 'a', label: 'A', weight: 10, children: []
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
name: 'b', label: 'B', weight: 30, children: []
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: 'c', label: 'C', weight: 20, children: []
|
|
24
|
+
},
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
const result = gatherChildrenOrdering(children);
|
|
28
|
+
|
|
29
|
+
expect(result[0].name).toBe('b'); // 30
|
|
30
|
+
expect(result[1].name).toBe('c'); // 20
|
|
31
|
+
expect(result[2].name).toBe('a'); // 10
|
|
32
|
+
// Verify original array is not mutated
|
|
33
|
+
expect(children[0].name).toBe('a');
|
|
34
|
+
expect(children[1].name).toBe('b');
|
|
35
|
+
expect(children[2].name).toBe('c');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should assign weights to items missing them', () => {
|
|
39
|
+
const children = [
|
|
40
|
+
{
|
|
41
|
+
name: 'a', label: 'A', weight: 50, children: []
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: 'b', label: 'B', children: []
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'c', label: 'C', children: []
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
const result = gatherChildrenOrdering(children);
|
|
52
|
+
|
|
53
|
+
expect(result[0].weight).toBe(50);
|
|
54
|
+
expect(result[1].weight!).toBeLessThan(50); // 49
|
|
55
|
+
expect(result[2].weight!).toBeLessThan(result[1].weight!); // 48
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should use 999 as minWeight when no explicit weights are provided', () => {
|
|
59
|
+
const children = [
|
|
60
|
+
{
|
|
61
|
+
name: 'a', label: 'A', children: []
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: 'b', label: 'B', children: []
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: 'c', label: 'C', children: []
|
|
68
|
+
},
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
const result = gatherChildrenOrdering(children);
|
|
72
|
+
|
|
73
|
+
// minWeight starts at 999, then each item gets minWeight - (index + 1)
|
|
74
|
+
// so: 999 - 1 = 998, 999 - 2 = 997, 999 - 3 = 996
|
|
75
|
+
expect(result[0].weight).toBe(998);
|
|
76
|
+
expect(result[1].weight).toBe(997);
|
|
77
|
+
expect(result[2].weight).toBe(996);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should recursively apply ordering to nested children', () => {
|
|
81
|
+
const children = [
|
|
82
|
+
{
|
|
83
|
+
name: 'group',
|
|
84
|
+
label: 'Group',
|
|
85
|
+
weight: 50,
|
|
86
|
+
children: [
|
|
87
|
+
{
|
|
88
|
+
name: 'nested-a', label: 'Nested A', weight: 20, children: []
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: 'nested-b', label: 'Nested B', weight: 10, children: []
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
name: 'nested-c', label: 'Nested C', children: []
|
|
95
|
+
}, // no weight
|
|
96
|
+
],
|
|
97
|
+
},
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
const result = gatherChildrenOrdering(children);
|
|
101
|
+
|
|
102
|
+
expect(result[0].children[0].name).toBe('nested-a'); // 20
|
|
103
|
+
expect(result[0].children[1].name).toBe('nested-b'); // 10
|
|
104
|
+
expect(result[0].children[2].name).toBe('nested-c'); // auto-assigned 9
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should handle empty children array', () => {
|
|
108
|
+
const children: any[] = [];
|
|
109
|
+
const result = gatherChildrenOrdering(children);
|
|
110
|
+
|
|
111
|
+
expect(result).toStrictEqual([]);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should not mutate order when weights are not provided for all items', () => {
|
|
115
|
+
const children = [
|
|
116
|
+
{
|
|
117
|
+
name: 'first', label: 'First', children: []
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
name: 'second', label: 'Second', weight: 100, children: []
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: 'third', label: 'Third', children: []
|
|
124
|
+
},
|
|
125
|
+
];
|
|
126
|
+
|
|
127
|
+
const result = gatherChildrenOrdering(children);
|
|
128
|
+
|
|
129
|
+
// second should be first due to highest weight
|
|
130
|
+
expect(result[0].name).toBe('second');
|
|
131
|
+
expect(result[1].name).toBe('first');
|
|
132
|
+
expect(result[2].name).toBe('third');
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// ============= generateTopLevelExtensionSimpleBaseRoute tests =============
|
|
137
|
+
describe('generateTopLevelExtensionSimpleBaseRoute', () => {
|
|
138
|
+
it('should generate base route with path and params', () => {
|
|
139
|
+
const route = generateTopLevelExtensionSimpleBaseRoute('my-product');
|
|
140
|
+
|
|
141
|
+
expect(route.name).toBe('my-product');
|
|
142
|
+
expect(route.path).toBe('my-product');
|
|
143
|
+
expect(route.params).toStrictEqual({ product: 'my-product' });
|
|
144
|
+
expect(route.meta).toStrictEqual({ product: 'my-product' });
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('should include component when provided', () => {
|
|
148
|
+
const MockComponent = { template: '<div>test</div>' };
|
|
149
|
+
const route = generateTopLevelExtensionSimpleBaseRoute('my-product', { component: MockComponent });
|
|
150
|
+
|
|
151
|
+
expect(route.component).toBe(MockComponent);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('should omit path when omitPath option is true', () => {
|
|
155
|
+
const route = generateTopLevelExtensionSimpleBaseRoute('my-product', { omitPath: true });
|
|
156
|
+
|
|
157
|
+
expect(route.path).toBeUndefined();
|
|
158
|
+
expect(route.name).toBe('my-product');
|
|
159
|
+
expect(route.params).toStrictEqual({ product: 'my-product' });
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('should include component and omit path when both options are set', () => {
|
|
163
|
+
const MockComponent = { template: '<div>test</div>' };
|
|
164
|
+
const route = generateTopLevelExtensionSimpleBaseRoute('my-product', {
|
|
165
|
+
component: MockComponent,
|
|
166
|
+
omitPath: true,
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
expect(route.component).toBe(MockComponent);
|
|
170
|
+
expect(route.path).toBeUndefined();
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// ============= generateVirtualTypeRoute tests =============
|
|
175
|
+
describe('generateVirtualTypeRoute', () => {
|
|
176
|
+
it('should generate top-level extension route when extendProduct is false/undefined', () => {
|
|
177
|
+
const page: ProductChildPage = {
|
|
178
|
+
name: 'overview',
|
|
179
|
+
label: 'Overview',
|
|
180
|
+
component: () => Promise.resolve({ default: {} }),
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const route = generateVirtualTypeRoute('my-product', page);
|
|
184
|
+
|
|
185
|
+
expect(route.name).toBe('my-product-c-cluster-overview');
|
|
186
|
+
expect(route.path).toBe('my-product/c/:cluster/overview');
|
|
187
|
+
expect(route.params).toStrictEqual({
|
|
188
|
+
product: 'my-product',
|
|
189
|
+
cluster: BLANK_CLUSTER,
|
|
190
|
+
});
|
|
191
|
+
expect(route.meta).toStrictEqual({
|
|
192
|
+
product: 'my-product',
|
|
193
|
+
cluster: BLANK_CLUSTER,
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('should generate cluster-level extension route when extendProduct is true', () => {
|
|
198
|
+
const page: ProductChildPage = {
|
|
199
|
+
name: 'overview',
|
|
200
|
+
label: 'Overview',
|
|
201
|
+
component: () => Promise.resolve({ default: {} }),
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const route = generateVirtualTypeRoute('my-product', page, { extendProduct: true });
|
|
205
|
+
|
|
206
|
+
expect(route.name).toBe('c-cluster-my-product-overview');
|
|
207
|
+
expect(route.path).toBe('c/:cluster/my-product/overview');
|
|
208
|
+
expect(route.params).toStrictEqual({ product: 'my-product' });
|
|
209
|
+
expect(route.meta).toStrictEqual({ product: 'my-product' });
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('should handle group routes without page child', () => {
|
|
213
|
+
const route = generateVirtualTypeRoute('my-product', undefined);
|
|
214
|
+
|
|
215
|
+
expect(route.name).toBe('my-product-c-cluster');
|
|
216
|
+
expect(route.path).toBe('my-product/c/:cluster');
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('should omit path when omitPath option is true', () => {
|
|
220
|
+
const page: ProductChildPage = {
|
|
221
|
+
name: 'settings',
|
|
222
|
+
label: 'Settings',
|
|
223
|
+
component: () => Promise.resolve({ default: {} }),
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const route = generateVirtualTypeRoute('my-product', page, { omitPath: true });
|
|
227
|
+
|
|
228
|
+
expect(route.path).toBeUndefined();
|
|
229
|
+
expect(route.name).toBe('my-product-c-cluster-settings');
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('should use provided component', () => {
|
|
233
|
+
const MockComponent = { template: '<div>test</div>' };
|
|
234
|
+
const route = generateVirtualTypeRoute('my-product', undefined, { component: MockComponent });
|
|
235
|
+
|
|
236
|
+
expect(route.component).toBe(MockComponent);
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// ============= generateConfigureTypeRoute tests =============
|
|
241
|
+
describe('generateConfigureTypeRoute', () => {
|
|
242
|
+
it('should generate top-level extension resource route', () => {
|
|
243
|
+
const page: ProductChildPage = { type: 'provisioning.cattle.io.cluster' };
|
|
244
|
+
|
|
245
|
+
const route = generateConfigureTypeRoute('my-product', page);
|
|
246
|
+
|
|
247
|
+
expect(route.name).toBe('my-product-c-cluster-resource');
|
|
248
|
+
expect(route.path).toBe('my-product/c/:cluster/:resource');
|
|
249
|
+
expect(route.params).toStrictEqual({
|
|
250
|
+
product: 'my-product',
|
|
251
|
+
cluster: BLANK_CLUSTER,
|
|
252
|
+
resource: 'provisioning.cattle.io.cluster',
|
|
253
|
+
});
|
|
254
|
+
expect(route.meta).toStrictEqual({
|
|
255
|
+
product: 'my-product',
|
|
256
|
+
cluster: BLANK_CLUSTER,
|
|
257
|
+
resource: 'provisioning.cattle.io.cluster',
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('should generate cluster-level extension resource route when extendProduct is true', () => {
|
|
262
|
+
const page: ProductChildPage = { type: 'provisioning.cattle.io.cluster' };
|
|
263
|
+
|
|
264
|
+
const route = generateConfigureTypeRoute('my-product', page, { extendProduct: true });
|
|
265
|
+
|
|
266
|
+
expect(route.name).toBe('c-cluster-my-product-resource');
|
|
267
|
+
expect(route.path).toBe('c/:cluster/my-product/:resource');
|
|
268
|
+
expect(route.params).toStrictEqual({
|
|
269
|
+
product: 'my-product',
|
|
270
|
+
resource: 'provisioning.cattle.io.cluster',
|
|
271
|
+
});
|
|
272
|
+
expect(route.meta).toStrictEqual({
|
|
273
|
+
product: 'my-product',
|
|
274
|
+
resource: 'provisioning.cattle.io.cluster',
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('should handle pages without a type gracefully', () => {
|
|
279
|
+
const page: Partial<ProductChildPage> = { name: 'clusters' };
|
|
280
|
+
|
|
281
|
+
const route = generateConfigureTypeRoute('my-product', page as ProductChildPage);
|
|
282
|
+
|
|
283
|
+
expect(route.name).toBe('my-product-c-cluster-resource');
|
|
284
|
+
expect(route.params?.resource).toBeUndefined();
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it('should omit path when omitPath option is true', () => {
|
|
288
|
+
const page: ProductChildPage = { type: 'provisioning.cattle.io.cluster' };
|
|
289
|
+
|
|
290
|
+
const route = generateConfigureTypeRoute('my-product', page, { omitPath: true });
|
|
291
|
+
|
|
292
|
+
expect(route.path).toBeUndefined();
|
|
293
|
+
expect(route.name).toBe('my-product-c-cluster-resource');
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it('should include component when provided', () => {
|
|
297
|
+
const MockComponent = { template: '<div>test</div>' };
|
|
298
|
+
const page: ProductChildPage = { type: 'provisioning.cattle.io.cluster' };
|
|
299
|
+
|
|
300
|
+
const route = generateConfigureTypeRoute('my-product', page, { component: MockComponent });
|
|
301
|
+
|
|
302
|
+
expect(route.component).toBe(MockComponent);
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
// ============= generateResourceRoutes tests =============
|
|
307
|
+
describe('generateResourceRoutes', () => {
|
|
308
|
+
it('should generate all resource routes for top-level extension', () => {
|
|
309
|
+
const page: ProductChildPage = { type: 'provisioning.cattle.io.cluster' };
|
|
310
|
+
|
|
311
|
+
const routes = generateResourceRoutes('my-product', page);
|
|
312
|
+
|
|
313
|
+
expect(routes).toHaveLength(4);
|
|
314
|
+
expect(routes[0].name).toBe('my-product-c-cluster-resource');
|
|
315
|
+
expect(routes[0].path).toBe('my-product/c/:cluster/:resource');
|
|
316
|
+
|
|
317
|
+
expect(routes[1].name).toBe('my-product-c-cluster-resource-create');
|
|
318
|
+
expect(routes[1].path).toBe('my-product/c/:cluster/:resource/create');
|
|
319
|
+
|
|
320
|
+
expect(routes[2].name).toBe('my-product-c-cluster-resource-id');
|
|
321
|
+
expect(routes[2].path).toBe('my-product/c/:cluster/:resource/:id');
|
|
322
|
+
|
|
323
|
+
expect(routes[3].name).toBe('my-product-c-cluster-resource-namespace-id');
|
|
324
|
+
expect(routes[3].path).toBe('my-product/c/:cluster/:resource/:namespace/:id');
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
it('should include meta data with asyncSetup for detail and edit routes', () => {
|
|
328
|
+
const page: ProductChildPage = { type: 'provisioning.cattle.io.cluster' };
|
|
329
|
+
|
|
330
|
+
const routes = generateResourceRoutes('my-product', page);
|
|
331
|
+
|
|
332
|
+
expect(routes[2].meta.asyncSetup).toBe(true); // detail route
|
|
333
|
+
expect(routes[3].meta.asyncSetup).toBe(true); // edit route
|
|
334
|
+
expect(routes[0].meta.asyncSetup).toBeUndefined(); // list route
|
|
335
|
+
expect(routes[1].meta.asyncSetup).toBeUndefined(); // create route
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it('should generate cluster-level extension resource routes when extendProduct is true', () => {
|
|
339
|
+
const page: ProductChildPage = { type: 'provisioning.cattle.io.cluster' };
|
|
340
|
+
|
|
341
|
+
const routes = generateResourceRoutes('my-product', page, { extendProduct: true });
|
|
342
|
+
|
|
343
|
+
expect(routes[0].name).toBe('c-cluster-my-product-resource');
|
|
344
|
+
expect(routes[0].path).toBe('c/:cluster/my-product/:resource');
|
|
345
|
+
|
|
346
|
+
expect(routes[1].name).toBe('c-cluster-my-product-resource-create');
|
|
347
|
+
expect(routes[1].path).toBe('c/:cluster/my-product/:resource/create');
|
|
348
|
+
|
|
349
|
+
expect(routes[2].name).toBe('c-cluster-my-product-resource-id');
|
|
350
|
+
expect(routes[2].path).toBe('c/:cluster/my-product/:resource/:id');
|
|
351
|
+
|
|
352
|
+
expect(routes[3].name).toBe('c-cluster-my-product-resource-namespace-id');
|
|
353
|
+
expect(routes[3].path).toBe('c/:cluster/my-product/:resource/:namespace/:id');
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
it('should include BLANK_CLUSTER in meta for top-level extensions', () => {
|
|
357
|
+
const page: ProductChildPage = { type: 'provisioning.cattle.io.cluster' };
|
|
358
|
+
|
|
359
|
+
const routes = generateResourceRoutes('my-product', page);
|
|
360
|
+
|
|
361
|
+
routes.forEach((route) => {
|
|
362
|
+
expect((route.meta as any).cluster).toBe(BLANK_CLUSTER);
|
|
363
|
+
expect(route.meta.product).toBe('my-product');
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
it('should not include BLANK_CLUSTER in meta for cluster-level extensions', () => {
|
|
368
|
+
const page: ProductChildPage = { type: 'provisioning.cattle.io.cluster' };
|
|
369
|
+
|
|
370
|
+
const routes = generateResourceRoutes('my-product', page, { extendProduct: true });
|
|
371
|
+
|
|
372
|
+
routes.forEach((route) => {
|
|
373
|
+
expect((route.meta as any).cluster).toBeUndefined();
|
|
374
|
+
expect(route.meta.product).toBe('my-product');
|
|
375
|
+
});
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
it('should have component as async functions by default', () => {
|
|
379
|
+
const page: ProductChildPage = { type: 'provisioning.cattle.io.cluster' };
|
|
380
|
+
|
|
381
|
+
const routes = generateResourceRoutes('my-product', page);
|
|
382
|
+
|
|
383
|
+
routes.forEach((route) => {
|
|
384
|
+
expect(typeof route.component).toBe('function');
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
// ============= Integration-like tests =============
|
|
390
|
+
describe('integration: complex product structure', () => {
|
|
391
|
+
it('should handle a complete product config ordering and route generation', () => {
|
|
392
|
+
const config = [
|
|
393
|
+
{
|
|
394
|
+
name: 'overview', label: 'Overview', weight: 10, children: []
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
name: 'settings', label: 'Settings', weight: 5, type: 'core.v1.configmap'
|
|
398
|
+
},
|
|
399
|
+
{
|
|
400
|
+
name: 'resources',
|
|
401
|
+
label: 'Resources',
|
|
402
|
+
type: 'core.v1.pod',
|
|
403
|
+
weight: 15,
|
|
404
|
+
},
|
|
405
|
+
];
|
|
406
|
+
|
|
407
|
+
const ordered = gatherChildrenOrdering(config as ProductChildGroup[]);
|
|
408
|
+
|
|
409
|
+
// Should be sorted by weight descending: 15, 10, 5
|
|
410
|
+
expect(ordered[0].weight).toBe(15);
|
|
411
|
+
expect(ordered[0].name).toBe('resources');
|
|
412
|
+
expect(ordered[1].weight).toBe(10);
|
|
413
|
+
expect(ordered[1].name).toBe('overview');
|
|
414
|
+
expect(ordered[2].weight).toBe(5);
|
|
415
|
+
expect(ordered[2].name).toBe('settings');
|
|
416
|
+
|
|
417
|
+
// Test route generation for each
|
|
418
|
+
const overviewRoute = generateVirtualTypeRoute('my-product', ordered[1] as ProductChildPage);
|
|
419
|
+
|
|
420
|
+
expect(overviewRoute.name).toContain('overview');
|
|
421
|
+
|
|
422
|
+
const resourceRoute = generateConfigureTypeRoute('my-product', ordered[0] as ProductChildPage);
|
|
423
|
+
|
|
424
|
+
expect(resourceRoute.path).toContain('resource');
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
it('should handle nested group ordering', () => {
|
|
428
|
+
const config = [
|
|
429
|
+
{
|
|
430
|
+
name: 'main',
|
|
431
|
+
label: 'Main',
|
|
432
|
+
weight: 50,
|
|
433
|
+
children: [
|
|
434
|
+
{
|
|
435
|
+
name: 'page1', label: 'Page 1', weight: 100, children: []
|
|
436
|
+
},
|
|
437
|
+
{
|
|
438
|
+
name: 'page2', label: 'Page 2', children: []
|
|
439
|
+
},
|
|
440
|
+
{
|
|
441
|
+
name: 'page3', label: 'Page 3', weight: 50, children: []
|
|
442
|
+
},
|
|
443
|
+
],
|
|
444
|
+
},
|
|
445
|
+
];
|
|
446
|
+
|
|
447
|
+
const ordered = gatherChildrenOrdering(config);
|
|
448
|
+
|
|
449
|
+
expect(ordered[0].children[0].name).toBe('page1'); // 100
|
|
450
|
+
expect(ordered[0].children[1].name).toBe('page3'); // 50
|
|
451
|
+
expect(ordered[0].children[2].name).toBe('page2'); // auto 49
|
|
452
|
+
});
|
|
453
|
+
});
|
|
454
|
+
});
|