@rancher/shell 3.0.12-rc.5 → 3.0.12-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.
- package/apis/intf/resources-api/resource-base.ts +42 -2
- package/apis/intf/resources-api/resource-instance.ts +101 -0
- package/apis/intf/resources-api/resources-api.ts +177 -23
- package/apis/intf/resources.ts +8 -6
- package/apis/resources/__tests__/resources-api-class.test.ts +309 -28
- package/apis/resources/resources-api-class.ts +232 -43
- package/assets/images/providers/traefik.png +0 -0
- package/assets/translations/en-us.yaml +10 -4
- package/chart/__tests__/rancher-monitoring-dashboards-index.test.ts +269 -0
- package/chart/rancher-monitoring-dashboards/index.vue +155 -0
- package/cloud-credential/__tests__/generic.test.ts +132 -0
- package/cloud-credential/generic.vue +33 -3
- package/components/EtcdInfoBanner.vue +2 -12
- package/components/GrafanaDashboard.vue +2 -8
- package/components/Resource/Detail/Masthead/__tests__/index.test.ts +68 -14
- package/components/Resource/Detail/Masthead/index.vue +8 -3
- package/components/form/Security.vue +1 -1
- package/components/formatter/Translate.vue +22 -1
- package/components/nav/Group.vue +5 -10
- package/components/nav/TopLevelMenu.vue +1 -30
- package/components/nav/__tests__/Group.test.ts +61 -0
- package/config/labels-annotations.js +1 -0
- package/config/table-headers.js +5 -2
- package/core/__tests__/plugin-products-apply.test.ts +787 -0
- package/core/__tests__/plugin-products-extend.test.ts +189 -0
- package/core/__tests__/plugin-products-helpers.test.ts +81 -78
- package/core/__tests__/plugin-products-new.test.ts +389 -0
- package/core/__tests__/plugin-products-scenarios.test.ts +980 -0
- package/core/__tests__/plugin-products-side-menu.test.ts +1935 -0
- package/core/plugin-products-base.ts +201 -93
- package/core/plugin-products-extending.ts +1 -1
- package/core/plugin-products-external.ts +380 -0
- package/core/plugin-products-helpers.ts +28 -24
- package/core/plugin-products-internal.ts +207 -0
- package/core/plugin-products-top-level.ts +10 -5
- package/core/plugin-products-type-guards.ts +17 -6
- package/core/plugin-products.ts +5 -7
- package/core/plugin-types.ts +1 -389
- package/core/plugin.ts +10 -11
- package/core/types.ts +6 -202
- package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +52 -0
- package/detail/pod.vue +1 -1
- package/detail/provisioning.cattle.io.cluster.vue +0 -2
- package/edit/auth/AuthProviderWarningBanners.vue +12 -0
- package/edit/auth/__tests__/AuthProviderWarningBanners.test.ts +10 -1
- package/edit/auth/__tests__/azuread.test.ts +1 -0
- package/edit/auth/__tests__/github.test.ts +1 -0
- package/edit/auth/__tests__/oidc.test.ts +1 -0
- package/edit/auth/__tests__/saml.test.ts +1 -0
- package/edit/auth/ldap/__tests__/index.test.ts +1 -0
- package/edit/provisioning.cattle.io.cluster/index.vue +1 -1
- package/edit/provisioning.cattle.io.cluster/rke2.vue +1 -1
- package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +2 -2
- package/list/management.cattle.io.user.vue +24 -5
- package/list/utils/management.cattle.io.cluster.utils.ts +1 -1
- package/machine-config/__tests__/generic.test.ts +163 -0
- package/machine-config/components/EC2Networking.vue +31 -7
- package/machine-config/components/__tests__/EC2Networking.test.ts +38 -15
- package/machine-config/generic.vue +32 -5
- package/mixins/resource-fetch.js +1 -1
- package/models/__tests__/rke-machine.cattle.io.vmwarevspheremachinetemplate.test.ts +22 -0
- package/models/pod.js +12 -10
- package/models/rke-machine.cattle.io.vmwarevspheremachinetemplate.js +9 -0
- package/package.json +1 -1
- package/pages/auth/login.vue +1 -1
- package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +110 -0
- package/pages/c/_cluster/apps/charts/install.vue +29 -0
- package/pages/c/_cluster/auth/config/index.vue +11 -4
- package/pages/c/_cluster/monitoring/index.vue +6 -1
- package/plugins/dashboard-store/resource-class.js +10 -3
- package/plugins/steve/__tests__/steve-class-resource-api.test.ts +147 -0
- package/plugins/steve/steve-class.js +43 -0
- package/plugins/steve/subscribe.js +11 -0
- package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +115 -0
- package/rancher-components/Form/LabeledInput/LabeledInput.vue +83 -3
- package/store/__tests__/auth.test.ts +18 -0
- package/store/__tests__/notifications.test.ts +466 -1
- package/store/__tests__/ui-context.test.ts +255 -0
- package/store/auth.js +11 -1
- package/store/features.js +1 -0
- package/store/plugins.js +6 -0
- package/types/rancher/steve.api.ts +2 -2
- package/types/shell/index.d.ts +58 -0
- package/types/store/dashboard-store.types.ts +2 -2
- package/types/store/type-map.ts +262 -1
- package/utils/async.ts +2 -0
- package/utils/grafana.js +2 -17
- package/utils/monitoring.js +38 -1
- package/core/__tests__/plugin-products.test.ts +0 -4694
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
import { PluginProduct } from '@shell/core/plugin-products';
|
|
2
|
+
import { Plugin } from '@shell/core/plugin';
|
|
3
|
+
import { IExtension } from '@shell/core/types';
|
|
4
|
+
import { ProductChildPage, ProductMetadata, ProductMetadataSinglePage, StandardProductNames } from '@shell/core/plugin-products-external';
|
|
5
|
+
|
|
6
|
+
// Mock the helper functions
|
|
7
|
+
jest.mock('@shell/core/plugin-products-helpers', () => ({
|
|
8
|
+
gatherChildrenOrdering: jest.fn((config) => config),
|
|
9
|
+
generateTopLevelExtensionSimpleBaseRoute: jest.fn((name, opts) => ({
|
|
10
|
+
name: `${ name }-simple`,
|
|
11
|
+
path: opts?.omitPath ? '' : `/${ name }`,
|
|
12
|
+
component: opts?.component,
|
|
13
|
+
})),
|
|
14
|
+
generateVirtualTypeRoute: jest.fn((parentName, childName, opts) => ({
|
|
15
|
+
name: childName ? `${ parentName }-${ childName }` : `${ parentName }-group`,
|
|
16
|
+
path: opts?.omitPath ? '' : `/${ parentName }/${ childName || 'group' }`,
|
|
17
|
+
component: opts?.component,
|
|
18
|
+
})),
|
|
19
|
+
generateConfigureTypeRoute: jest.fn((parentName, page, opts) => {
|
|
20
|
+
const routeName = opts?.extendProduct ? `c-cluster-${ parentName }-resource` : `${ parentName }-c-cluster-resource`;
|
|
21
|
+
const routePath = opts?.extendProduct ? `c/:cluster/${ parentName }/:resource` : `${ parentName }/c/:cluster/:resource`;
|
|
22
|
+
const cluster = opts?.extendProduct ? undefined : '__BLANK_CLUSTER__';
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
name: routeName,
|
|
26
|
+
path: opts?.omitPath ? '' : routePath,
|
|
27
|
+
params: cluster ? {
|
|
28
|
+
product: parentName.replace(/-/g, ''),
|
|
29
|
+
cluster,
|
|
30
|
+
resource: page?.type,
|
|
31
|
+
} : {
|
|
32
|
+
product: parentName,
|
|
33
|
+
resource: page?.type,
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}),
|
|
37
|
+
generateResourceRoutes: jest.fn((parentName, child) => [
|
|
38
|
+
{
|
|
39
|
+
name: `${ parentName }-${ child.type }-list`,
|
|
40
|
+
path: `/${ parentName }/${ child.type }`,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: `${ parentName }-${ child.type }-detail`,
|
|
44
|
+
path: `/${ parentName }/${ child.type }/:id`,
|
|
45
|
+
},
|
|
46
|
+
]),
|
|
47
|
+
}));
|
|
48
|
+
|
|
49
|
+
jest.mock('@shell/core/productDebugger', () => ({
|
|
50
|
+
DSLRegistrationsPerProduct: jest.fn(),
|
|
51
|
+
registeredRoutes: jest.fn(),
|
|
52
|
+
}));
|
|
53
|
+
|
|
54
|
+
// Create mock factories
|
|
55
|
+
function createMockPlugin(): IExtension {
|
|
56
|
+
return {
|
|
57
|
+
_registerTopLevelProduct: jest.fn(),
|
|
58
|
+
addRoute: jest.fn(),
|
|
59
|
+
enableServerSidePagination: jest.fn(),
|
|
60
|
+
DSL: jest.fn(),
|
|
61
|
+
} as any as IExtension;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function createMockStore(extendableProducts: string[] = Object.values(StandardProductNames), managementSchemas: string[] = []): any {
|
|
65
|
+
return {
|
|
66
|
+
getters: {
|
|
67
|
+
'type-map/productByName': (productName: string) => (extendableProducts.includes(productName) ? { name: productName, extendable: true } : undefined),
|
|
68
|
+
'management/schemaFor': (type: string) => (managementSchemas.includes(type) ? { id: type } : undefined),
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
describe('pluginProduct', () => {
|
|
74
|
+
describe('new product scenarios', () => {
|
|
75
|
+
it('should create a new product with config items', () => {
|
|
76
|
+
const mockPlugin = createMockPlugin();
|
|
77
|
+
const productMetadata: ProductMetadata = {
|
|
78
|
+
name: 'test-product',
|
|
79
|
+
label: 'Test Product',
|
|
80
|
+
sideBar: { icon: { name: 'icon-test' } }
|
|
81
|
+
};
|
|
82
|
+
const config: ProductChildPage[] = [
|
|
83
|
+
{
|
|
84
|
+
name: 'overview',
|
|
85
|
+
label: 'Overview',
|
|
86
|
+
component: { name: 'OverviewPage' },
|
|
87
|
+
sideMenu: { weight: 1 },
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: 'details',
|
|
91
|
+
label: 'Details',
|
|
92
|
+
component: { name: 'DetailsPage' },
|
|
93
|
+
sideMenu: { weight: 2 },
|
|
94
|
+
},
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
const pluginProduct = new PluginProduct(mockPlugin, productMetadata, config);
|
|
98
|
+
|
|
99
|
+
expect(pluginProduct.newProduct).toBe(true);
|
|
100
|
+
expect(mockPlugin._registerTopLevelProduct).toHaveBeenCalledTimes(1);
|
|
101
|
+
expect(mockPlugin.addRoute).toHaveBeenCalledTimes(2);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should create a single page product', () => {
|
|
105
|
+
const mockPlugin = createMockPlugin();
|
|
106
|
+
const productSinglePage: ProductMetadataSinglePage = {
|
|
107
|
+
name: 'single-page-product',
|
|
108
|
+
label: 'Single Page',
|
|
109
|
+
component: { name: 'SinglePageComponent' },
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const pluginProduct = new PluginProduct(mockPlugin, productSinglePage, []);
|
|
113
|
+
|
|
114
|
+
expect(pluginProduct.newProduct).toBe(true);
|
|
115
|
+
expect(mockPlugin._registerTopLevelProduct).toHaveBeenCalledTimes(1);
|
|
116
|
+
expect(mockPlugin.addRoute).toHaveBeenCalledTimes(1);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should handle product names with dashes by removing them', () => {
|
|
120
|
+
const mockPlugin = createMockPlugin();
|
|
121
|
+
const productMetadata: ProductMetadata = {
|
|
122
|
+
name: 'test-product-name',
|
|
123
|
+
label: 'Test',
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
new PluginProduct(mockPlugin, productMetadata, []);
|
|
127
|
+
|
|
128
|
+
expect(mockPlugin._registerTopLevelProduct).toHaveBeenCalledTimes(1);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('should create default empty page config when no config provided', () => {
|
|
132
|
+
const mockPlugin = createMockPlugin();
|
|
133
|
+
const productMetadata: ProductMetadata = {
|
|
134
|
+
name: 'empty-product',
|
|
135
|
+
label: 'Empty',
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
new PluginProduct(mockPlugin, productMetadata, []);
|
|
139
|
+
|
|
140
|
+
expect(mockPlugin.addRoute).toHaveBeenCalledTimes(1);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should throw error when product object lacks name property', () => {
|
|
144
|
+
const mockPlugin = createMockPlugin();
|
|
145
|
+
const invalidProduct: Partial<ProductMetadata> = { label: 'No Name' };
|
|
146
|
+
|
|
147
|
+
expect(() => {
|
|
148
|
+
new PluginProduct(mockPlugin, invalidProduct as ProductMetadata, []);
|
|
149
|
+
}).toThrow('Invalid product');
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('should throw error when product type is invalid', () => {
|
|
153
|
+
const mockPlugin = createMockPlugin();
|
|
154
|
+
|
|
155
|
+
expect(() => {
|
|
156
|
+
new PluginProduct(mockPlugin, {} as ProductMetadata, []);
|
|
157
|
+
}).toThrow('Invalid product');
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
describe('fromName convenience method', () => {
|
|
162
|
+
it('should create a new top-level product from a string name', () => {
|
|
163
|
+
const mockPlugin = createMockPlugin();
|
|
164
|
+
const pluginProduct = PluginProduct.fromName(mockPlugin, 'my-first-product');
|
|
165
|
+
|
|
166
|
+
expect(pluginProduct.newProduct).toBe(true);
|
|
167
|
+
expect(mockPlugin._registerTopLevelProduct).toHaveBeenCalledTimes(1);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('should register a route with EmptyProductPage when created from a string name', () => {
|
|
171
|
+
const mockPlugin = createMockPlugin();
|
|
172
|
+
|
|
173
|
+
PluginProduct.fromName(mockPlugin, 'my-first-product');
|
|
174
|
+
|
|
175
|
+
expect(mockPlugin.addRoute).toHaveBeenCalledTimes(1);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('should use the string as both name and label for the product', () => {
|
|
179
|
+
const mockPlugin = createMockPlugin();
|
|
180
|
+
const mockStore = createMockStore();
|
|
181
|
+
const productCalls: any[] = [];
|
|
182
|
+
const mockDSL = {
|
|
183
|
+
product: jest.fn((...args: any[]) => productCalls.push(args)),
|
|
184
|
+
basicType: jest.fn(),
|
|
185
|
+
labelGroup: jest.fn(),
|
|
186
|
+
setGroupDefaultType: jest.fn(),
|
|
187
|
+
weightGroup: jest.fn(),
|
|
188
|
+
moveType: jest.fn(),
|
|
189
|
+
virtualType: jest.fn(),
|
|
190
|
+
configureType: jest.fn(),
|
|
191
|
+
weightType: jest.fn(),
|
|
192
|
+
headers: jest.fn(),
|
|
193
|
+
hideBulkActions: jest.fn(),
|
|
194
|
+
|
|
195
|
+
mapGroup: jest.fn(),
|
|
196
|
+
ignoreGroup: jest.fn(),
|
|
197
|
+
mapType: jest.fn(),
|
|
198
|
+
ignoreType: jest.fn(),
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
jest.spyOn(mockPlugin, 'DSL').mockReturnValue(mockDSL);
|
|
202
|
+
|
|
203
|
+
const pluginProduct = PluginProduct.fromName(mockPlugin, 'my-first-product');
|
|
204
|
+
|
|
205
|
+
pluginProduct.apply(mockPlugin, mockStore);
|
|
206
|
+
|
|
207
|
+
expect(productCalls).toHaveLength(1);
|
|
208
|
+
expect(productCalls[0][0]).toStrictEqual(expect.objectContaining({
|
|
209
|
+
name: 'myfirstproduct',
|
|
210
|
+
label: 'my-first-product',
|
|
211
|
+
}));
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('should handle product names with dashes by removing them for the internal name', () => {
|
|
215
|
+
const mockPlugin = createMockPlugin();
|
|
216
|
+
const mockStore = createMockStore();
|
|
217
|
+
const productCalls: any[] = [];
|
|
218
|
+
const mockDSL = {
|
|
219
|
+
product: jest.fn((...args: any[]) => productCalls.push(args)),
|
|
220
|
+
basicType: jest.fn(),
|
|
221
|
+
labelGroup: jest.fn(),
|
|
222
|
+
setGroupDefaultType: jest.fn(),
|
|
223
|
+
weightGroup: jest.fn(),
|
|
224
|
+
moveType: jest.fn(),
|
|
225
|
+
virtualType: jest.fn(),
|
|
226
|
+
configureType: jest.fn(),
|
|
227
|
+
weightType: jest.fn(),
|
|
228
|
+
headers: jest.fn(),
|
|
229
|
+
hideBulkActions: jest.fn(),
|
|
230
|
+
|
|
231
|
+
mapGroup: jest.fn(),
|
|
232
|
+
ignoreGroup: jest.fn(),
|
|
233
|
+
mapType: jest.fn(),
|
|
234
|
+
ignoreType: jest.fn(),
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
jest.spyOn(mockPlugin, 'DSL').mockReturnValue(mockDSL);
|
|
238
|
+
|
|
239
|
+
const pluginProduct = PluginProduct.fromName(mockPlugin, 'test-product-name');
|
|
240
|
+
|
|
241
|
+
pluginProduct.apply(mockPlugin, mockStore);
|
|
242
|
+
|
|
243
|
+
expect(productCalls[0][0]).toStrictEqual(expect.objectContaining({ name: 'testproductname' }));
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('should handle product names without dashes', () => {
|
|
247
|
+
const mockPlugin = createMockPlugin();
|
|
248
|
+
const mockStore = createMockStore();
|
|
249
|
+
const productCalls: any[] = [];
|
|
250
|
+
const mockDSL = {
|
|
251
|
+
product: jest.fn((...args: any[]) => productCalls.push(args)),
|
|
252
|
+
basicType: jest.fn(),
|
|
253
|
+
labelGroup: jest.fn(),
|
|
254
|
+
setGroupDefaultType: jest.fn(),
|
|
255
|
+
weightGroup: jest.fn(),
|
|
256
|
+
moveType: jest.fn(),
|
|
257
|
+
virtualType: jest.fn(),
|
|
258
|
+
configureType: jest.fn(),
|
|
259
|
+
weightType: jest.fn(),
|
|
260
|
+
headers: jest.fn(),
|
|
261
|
+
hideBulkActions: jest.fn(),
|
|
262
|
+
mapGroup: jest.fn(),
|
|
263
|
+
ignoreGroup: jest.fn(),
|
|
264
|
+
mapType: jest.fn(),
|
|
265
|
+
ignoreType: jest.fn(),
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
jest.spyOn(mockPlugin, 'DSL').mockReturnValue(mockDSL);
|
|
269
|
+
|
|
270
|
+
const pluginProduct = PluginProduct.fromName(mockPlugin, 'myproduct');
|
|
271
|
+
|
|
272
|
+
pluginProduct.apply(mockPlugin, mockStore);
|
|
273
|
+
|
|
274
|
+
expect(productCalls[0][0]).toStrictEqual(expect.objectContaining({ name: 'myproduct' }));
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
describe('addProduct duplicate guard', () => {
|
|
279
|
+
it('should throw when addProduct is called twice with the same product name (object form)', () => {
|
|
280
|
+
const plugin = new Plugin('test-extension');
|
|
281
|
+
|
|
282
|
+
const product: ProductMetadata = {
|
|
283
|
+
name: 'my-product',
|
|
284
|
+
label: 'My Product',
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
const config: ProductChildPage[] = [
|
|
288
|
+
{
|
|
289
|
+
name: 'page-a', label: 'Page A', component: { name: 'PageA' }
|
|
290
|
+
},
|
|
291
|
+
];
|
|
292
|
+
|
|
293
|
+
plugin.addProduct(product, config);
|
|
294
|
+
|
|
295
|
+
expect(() => {
|
|
296
|
+
plugin.addProduct(product, [{
|
|
297
|
+
name: 'page-b', label: 'Page B', component: { name: 'PageB' }
|
|
298
|
+
}]);
|
|
299
|
+
}).toThrow('addProduct can only be called once per product');
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it('should throw when addProduct is called twice with the same product name (string form)', () => {
|
|
303
|
+
const plugin = new Plugin('test-extension');
|
|
304
|
+
|
|
305
|
+
plugin.addProduct('my-product');
|
|
306
|
+
|
|
307
|
+
expect(() => {
|
|
308
|
+
plugin.addProduct('my-product');
|
|
309
|
+
}).toThrow('addProduct can only be called once per product');
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it('should throw when addProduct is called twice mixing string and object form for the same name', () => {
|
|
313
|
+
const plugin = new Plugin('test-extension');
|
|
314
|
+
|
|
315
|
+
plugin.addProduct('my-product');
|
|
316
|
+
|
|
317
|
+
expect(() => {
|
|
318
|
+
plugin.addProduct({ name: 'my-product', label: 'My Product' }, []);
|
|
319
|
+
}).toThrow('addProduct can only be called once per product');
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
it('should allow addProduct for different product names', () => {
|
|
323
|
+
const plugin = new Plugin('test-extension');
|
|
324
|
+
|
|
325
|
+
plugin.addProduct('product-a');
|
|
326
|
+
|
|
327
|
+
expect(() => {
|
|
328
|
+
plugin.addProduct('product-b');
|
|
329
|
+
}).not.toThrow();
|
|
330
|
+
|
|
331
|
+
expect(plugin.productConfigs).toHaveLength(2);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
it('should allow addProduct and extendProduct for the same name (extending is separate)', () => {
|
|
335
|
+
const plugin = new Plugin('test-extension');
|
|
336
|
+
|
|
337
|
+
plugin.addProduct('my-product');
|
|
338
|
+
|
|
339
|
+
expect(() => {
|
|
340
|
+
plugin.extendProduct('explorer', [{
|
|
341
|
+
name: 'extra-page', label: 'Extra', component: { name: 'Extra' }
|
|
342
|
+
}]);
|
|
343
|
+
}).not.toThrow();
|
|
344
|
+
|
|
345
|
+
expect(plugin.productConfigs).toHaveLength(2);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it('should throw when addProduct is called twice with single page product form', () => {
|
|
349
|
+
const plugin = new Plugin('test-extension');
|
|
350
|
+
|
|
351
|
+
const singlePage: ProductMetadataSinglePage = {
|
|
352
|
+
name: 'my-dashboard',
|
|
353
|
+
label: 'My Dashboard',
|
|
354
|
+
component: { name: 'DashboardPage' },
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
plugin.addProduct(singlePage);
|
|
358
|
+
|
|
359
|
+
expect(() => {
|
|
360
|
+
plugin.addProduct(singlePage);
|
|
361
|
+
}).toThrow('addProduct can only be called once per product');
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
describe('error handling', () => {
|
|
366
|
+
it('should throw error when config children is not an array', () => {
|
|
367
|
+
const mockPlugin = createMockPlugin();
|
|
368
|
+
const productMetadata: ProductMetadata = {
|
|
369
|
+
name: 'bad-group',
|
|
370
|
+
label: 'Bad Group',
|
|
371
|
+
};
|
|
372
|
+
const badConfig: any[] = [
|
|
373
|
+
{
|
|
374
|
+
name: 'group',
|
|
375
|
+
label: 'Group',
|
|
376
|
+
sideMenu: {
|
|
377
|
+
children: 'not-an-array', // Invalid
|
|
378
|
+
weight: 1,
|
|
379
|
+
},
|
|
380
|
+
|
|
381
|
+
},
|
|
382
|
+
];
|
|
383
|
+
|
|
384
|
+
expect(() => {
|
|
385
|
+
new PluginProduct(mockPlugin, productMetadata, badConfig);
|
|
386
|
+
}).toThrow('forEach');
|
|
387
|
+
});
|
|
388
|
+
});
|
|
389
|
+
});
|