@rancher/shell 3.0.8-rc.10 → 3.0.8-rc.13

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 (96) hide show
  1. package/apis/impl/apis.ts +61 -0
  2. package/apis/index.ts +40 -0
  3. package/apis/intf/modal.ts +90 -0
  4. package/apis/intf/shell.ts +36 -0
  5. package/apis/intf/slide-in.ts +98 -0
  6. package/apis/intf/system.ts +34 -0
  7. package/apis/shell/__tests__/modal.test.ts +80 -0
  8. package/apis/shell/__tests__/notifications.test.ts +71 -0
  9. package/apis/shell/__tests__/slide-in.test.ts +54 -0
  10. package/apis/shell/__tests__/system.test.ts +129 -0
  11. package/apis/shell/index.ts +38 -0
  12. package/apis/shell/modal.ts +41 -0
  13. package/apis/shell/notifications.ts +65 -0
  14. package/apis/shell/slide-in.ts +33 -0
  15. package/apis/shell/system.ts +65 -0
  16. package/apis/vue-shim.d.ts +11 -0
  17. package/components/CruResource.vue +8 -1
  18. package/components/Drawer/ResourceDetailDrawer/__tests__/composables.test.ts +50 -1
  19. package/components/Drawer/ResourceDetailDrawer/composables.ts +19 -0
  20. package/components/Drawer/ResourceDetailDrawer/index.vue +3 -1
  21. package/components/ModalManager.vue +11 -1
  22. package/components/ResourceDetail/index.vue +3 -0
  23. package/components/ResourceTable.vue +54 -21
  24. package/components/SlideInPanelManager.vue +16 -11
  25. package/components/SortableTable/index.vue +20 -2
  26. package/components/Tabbed/index.vue +37 -2
  27. package/components/auth/login/ldap.vue +3 -3
  28. package/components/form/NodeScheduling.vue +2 -2
  29. package/components/form/ResourceTabs/composable.ts +2 -2
  30. package/components/nav/Group.vue +9 -2
  31. package/components/nav/Header.vue +1 -1
  32. package/components/nav/Type.vue +8 -3
  33. package/components/nav/__tests__/Type.test.ts +59 -0
  34. package/composables/cruResource.ts +27 -0
  35. package/composables/focusTrap.ts +3 -1
  36. package/composables/resourceDetail.ts +15 -0
  37. package/config/router/navigation-guards/clusters.js +3 -3
  38. package/config/router/navigation-guards/products.js +1 -1
  39. package/core/__tests__/extension-manager-impl.test.js +437 -0
  40. package/core/extension-manager-impl.js +6 -27
  41. package/core/plugin-helpers.ts +2 -2
  42. package/core/plugin.ts +9 -1
  43. package/core/plugins-loader.js +2 -2
  44. package/core/types-provisioning.ts +4 -0
  45. package/core/types.ts +35 -0
  46. package/detail/provisioning.cattle.io.cluster.vue +8 -6
  47. package/dialog/DeveloperLoadExtensionDialog.vue +1 -1
  48. package/dialog/SearchDialog.vue +1 -0
  49. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +21 -21
  50. package/edit/provisioning.cattle.io.cluster/index.vue +5 -5
  51. package/edit/provisioning.cattle.io.cluster/rke2.vue +8 -8
  52. package/edit/workload/index.vue +1 -1
  53. package/initialize/install-plugins.js +4 -5
  54. package/models/management.cattle.io.cluster.js +1 -1
  55. package/models/provisioning.cattle.io.cluster.js +1 -1
  56. package/package.json +3 -3
  57. package/pages/auth/login.vue +3 -3
  58. package/pages/auth/setup.vue +1 -1
  59. package/pages/auth/verify.vue +3 -3
  60. package/pages/c/_cluster/apps/charts/install.vue +33 -0
  61. package/pages/c/_cluster/fleet/index.vue +4 -7
  62. package/pkg/auto-import.js +3 -3
  63. package/pkg/dynamic-importer.lib.js +1 -1
  64. package/pkg/import.js +1 -1
  65. package/plugins/dashboard-store/getters.js +1 -1
  66. package/plugins/dashboard-store/model-loader.js +1 -1
  67. package/plugins/dashboard-store/resource-class.js +6 -2
  68. package/plugins/plugin.js +2 -2
  69. package/plugins/steve/__tests__/steve-pagination-utils.test.ts +301 -128
  70. package/plugins/steve/steve-class.js +1 -1
  71. package/plugins/steve/steve-pagination-utils.ts +108 -43
  72. package/scripts/publish-shell.sh +25 -0
  73. package/store/__tests__/catalog.test.ts +1 -1
  74. package/store/__tests__/type-map.test.ts +164 -2
  75. package/store/auth.js +1 -1
  76. package/store/i18n.js +3 -3
  77. package/store/index.js +5 -3
  78. package/store/notifications.ts +2 -0
  79. package/store/type-map.js +14 -5
  80. package/types/internal-api/shell/modal.d.ts +6 -6
  81. package/types/notifications/index.ts +126 -15
  82. package/types/rancher/index.d.ts +9 -0
  83. package/types/shell/index.d.ts +1 -0
  84. package/types/vue-shim.d.ts +5 -4
  85. package/utils/pagination-utils.ts +2 -2
  86. package/utils/pagination-wrapper.ts +1 -1
  87. package/utils/unit-tests/pagination-utils.spec.ts +8 -8
  88. package/vue.config.js +3 -3
  89. package/composables/useExtensionManager.ts +0 -17
  90. package/core/__test__/extension-manager-impl.test.js +0 -236
  91. package/core/plugins.js +0 -38
  92. package/plugins/internal-api/index.ts +0 -37
  93. package/plugins/internal-api/shared/base-api.ts +0 -13
  94. package/plugins/internal-api/shell/shell.api.ts +0 -108
  95. package/types/internal-api/shell/growl.d.ts +0 -25
  96. package/types/internal-api/shell/slideIn.d.ts +0 -15
@@ -0,0 +1,61 @@
1
+ import { throttle } from 'lodash';
2
+ import { createExtensionManager } from '@shell/core/extension-manager-impl';
3
+ import { ShellApiImpl } from '@shell/apis/shell';
4
+
5
+ /**
6
+ * Initialise the APIs that are available in the shell
7
+ *
8
+ * This is loaded during app startiup in `initialize/index.js`
9
+ */
10
+ export function initUiApis(context: any, inject: any, vueApp: any) {
11
+ // ======================================================================================================================
12
+ // Extension Manager
13
+ // ======================================================================================================================
14
+ const extensionManager = createExtensionManager(context);
15
+ const deprecationMessage = '[DEPRECATED] `this.$plugin` is deprecated and will be removed in a future version. Use `this.$extension` instead.';
16
+
17
+ registerApi('plugin', deprecationProxy(extensionManager, deprecationMessage), inject, vueApp);
18
+ registerApi('extension', extensionManager, inject, vueApp);
19
+
20
+ // ======================================================================================================================
21
+ // Shell API
22
+ // ======================================================================================================================
23
+ registerApi('shell', new ShellApiImpl(context.store), inject, vueApp);
24
+ }
25
+
26
+ // ======================================================================================================================
27
+ // Helpers
28
+ // ======================================================================================================================
29
+
30
+ function registerApi(name: string, api: any, inject: any, vueApp: any) {
31
+ inject(name, api);
32
+ vueApp.provide(`$${ name }`, api);
33
+ }
34
+
35
+ /**
36
+ * Proxy to log a deprecation warning when target is accessed. Only prints
37
+ * deprecation warnings in dev builds.
38
+ * @param {*} target the object to proxy
39
+ * @param {*} message the deprecation warning to print to the console
40
+ * @returns The proxied target that prints a deprecation warning when target is
41
+ * accessed
42
+ */
43
+ const deprecationProxy = (target: any, message: string) => {
44
+ const logWarning = throttle(() => {
45
+ // eslint-disable-next-line no-console
46
+ console.warn(message);
47
+ }, 150);
48
+
49
+ const deprecationHandler = {
50
+ get(target: any, prop: any) {
51
+ logWarning();
52
+
53
+ return Reflect.get(target, prop);
54
+ }
55
+ };
56
+
57
+ // an empty handler allows the proxy to behave just like the original target
58
+ const proxyHandler = !!process.env.dev ? deprecationHandler : {};
59
+
60
+ return new Proxy(target, proxyHandler);
61
+ };
package/apis/index.ts ADDED
@@ -0,0 +1,40 @@
1
+ // Main export for APIs, particularly for the composition API
2
+
3
+ import { inject } from 'vue';
4
+ import { ExtensionManager } from '@shell/types/extension-manager';
5
+ import { ShellApi as ShellApiImport } from '@shell/apis/intf/shell';
6
+
7
+ // Re-export the types for the APIs, so they appear in this module
8
+ export type ShellApi = ShellApiImport;
9
+ export type ExtensionManagerApi = ExtensionManager;
10
+
11
+ /**
12
+ * Returns an object that can be used to access the registered extension manager instance.
13
+ *
14
+ * @returns Returns an object that can be used to access the registered extension manager instance.
15
+ */
16
+ export const useExtensionManager = (): ExtensionManagerApi => {
17
+ return getApi<ExtensionManagerApi>('$extension', 'useExtensionManager');
18
+ };
19
+
20
+ /**
21
+ * Returns an object that implements the ShellApi interface
22
+ *
23
+ * @returns Returns an object that implements the ShellApi interface
24
+ */
25
+ export const useShell = (): ShellApi => {
26
+ return getApi<ShellApi>('$shell', 'useShell');
27
+ };
28
+
29
+ // =================================================================================================================
30
+ // Internal helper to get any API by key with error handling
31
+ // =================================================================================================================
32
+ function getApi<T>(key: string, name: string): T {
33
+ const api = inject<T>(key);
34
+
35
+ if (!api) {
36
+ throw new Error(`${ name } must only be called after ${ key } has been initialized`);
37
+ }
38
+
39
+ return api as T;
40
+ }
@@ -0,0 +1,90 @@
1
+ import { Component } from 'vue';
2
+
3
+ /**
4
+ * Configuration object for opening a modal.
5
+ */
6
+ export interface ModalConfig {
7
+ /**
8
+ * Props to pass directly to the component rendered inside the modal.
9
+ *
10
+ * Example:
11
+ * ```ts
12
+ * props: { title: 'Hello Modal', isVisible: true }
13
+ * ```
14
+ */
15
+ props?: Record<string, any>;
16
+
17
+ /**
18
+ * Array of resources that the modal component might need.
19
+ * These are passed directly into the modal's `resources` prop.
20
+ *
21
+ * Example:
22
+ * ```ts
23
+ * resources: [myResource, anotherResource]
24
+ * ```
25
+ */
26
+ resources?: any[];
27
+
28
+ /**
29
+ * Custom width for the modal. Defaults to `600px`.
30
+ * The width can be specified as a string with a valid unit (`px`, `%`, `rem`, etc.).
31
+ *
32
+ * Examples:
33
+ * ```ts
34
+ * width: '800px' // Width in pixels
35
+ * width: '75%' // Width as a percentage
36
+ * ```
37
+ */
38
+ width?: string;
39
+
40
+ /**
41
+ * Determines if clicking outside the modal will close it. Defaults to `true`.
42
+ * Set this to `false` to prevent closing via outside clicks.
43
+ *
44
+ * Example:
45
+ * ```ts
46
+ * closeOnClickOutside: false
47
+ * ```
48
+ */
49
+ closeOnClickOutside?: boolean;
50
+
51
+ /**
52
+ * If true, the modal is considered "sticky" and may not close automatically
53
+ * on certain user interactions. Defaults to `false`.
54
+ *
55
+ * Example:
56
+ * ```ts
57
+ * modalSticky: true
58
+ * ```
59
+ */
60
+ // modalSticky?: boolean; // Not implemented yet
61
+ }
62
+
63
+ /**
64
+ * API for displaying modals in Rancher UI. Here's what a Modal looks like in Rancher UI:
65
+ * * ![modal Example](/img/modal.png)
66
+ */
67
+ export interface ModalApi {
68
+ /**
69
+ * Opens a modal dialog in Rancher UI
70
+ *
71
+ * Example:
72
+ * ```ts
73
+ * import MyCustomModal from '@/components/MyCustomModal.vue';
74
+ *
75
+ * this.$shell.modal.show(MyCustomModal, {
76
+ * props: { title: 'Hello Modal' }
77
+ * });
78
+ * ```
79
+ * For usage with the Composition API check usage guide [here](../../shell-api#using-composition-api-in-vue).
80
+ *
81
+ * @param component
82
+ * The Vue component to be displayed inside the modal.
83
+ * This can be any SFC (Single-File Component) imported and passed in as a `Component`.
84
+ *
85
+ *
86
+ * @param config Modal configuration object
87
+ *
88
+ */
89
+ open(component: Component, config?: ModalConfig): void;
90
+ }
@@ -0,0 +1,36 @@
1
+
2
+ import { NotificationApi } from '@shell/types/notifications';
3
+ import { ModalApi } from '@shell/apis/intf/modal';
4
+ import { SlideInApi } from '@shell/apis/intf/slide-in';
5
+ import { SystemApi } from '@shell/apis/intf/system';
6
+
7
+ export * from '@shell/types/notifications';
8
+ export * from '@shell/apis/intf/modal';
9
+ export * from '@shell/apis/intf/slide-in';
10
+ export * from '@shell/apis/intf/system';
11
+
12
+ /**
13
+ * @internal
14
+ * Available "API's" inside Shell API
15
+ */
16
+ export interface ShellApi {
17
+ /**
18
+ * Provides access to the Modal API which can be used for displaying modals in Rancher UI
19
+ */
20
+ get modal(): ModalApi;
21
+
22
+ /**
23
+ * Provides access to the Slide-In API which can be used for displaying Slide-In panels in Rancher UI
24
+ */
25
+ get slideIn(): SlideInApi;
26
+
27
+ /**
28
+ * Provides access to the Notification Center API which can be used for notifications in the Rancher UI Notification Center
29
+ */
30
+ get notification(): NotificationApi;
31
+
32
+ /**
33
+ * Provides access to the system API which providers information about the current system
34
+ */
35
+ get system(): SystemApi;
36
+ }
@@ -0,0 +1,98 @@
1
+ import { Component } from 'vue';
2
+
3
+ /**
4
+ *
5
+ * Configuration object for opening a Slide-In panel. Here's what a Slide-In looks like in Rancher UI:
6
+ *
7
+ */
8
+ export interface SlideInConfig {
9
+ /**
10
+ *
11
+ * Width of the Slide-In panel in percentage, related to the window width. Defaults to `33%`
12
+ *
13
+ */
14
+ width?: string;
15
+ /**
16
+ *
17
+ * Height of the Slide-In panel. Can be percentage or vh. Defaults to (window - header) height.
18
+ * Can be set as `33%` or `80vh`
19
+ *
20
+ */
21
+ height?: string;
22
+ /**
23
+ *
24
+ * CSS Top position for the Slide-In panel, string using px, as `0px` or `20px`. Default is right below header height
25
+ *
26
+ */
27
+ top?: string;
28
+ /**
29
+ *
30
+ * title for the Slide-In panel
31
+ *
32
+ */
33
+ title?: string;
34
+ /**
35
+ *
36
+ * Wether Slide-In header is displayed or not
37
+ *
38
+ */
39
+ showHeader?: boolean;
40
+ /**
41
+ *
42
+ * Array of props to watch out for in route, when they change, closes Slide-In
43
+ * @ignore
44
+ *
45
+ */
46
+ closeOnRouteChange?: [string];
47
+ /**
48
+ *
49
+ * Return focus selector for focus trap
50
+ * @ignore
51
+ *
52
+ */
53
+ returnFocusSelector?: string;
54
+ /**
55
+ *
56
+ * We can pass variable (value) to "force" focus trap to initialize "on-demand"
57
+ * @ignore
58
+ *
59
+ */
60
+ focusTrapWatcherBasedVariable?: boolean;
61
+ /**
62
+ *
63
+ * Vue Props to pass directly to the component rendered inside the slide in panel in an object format as "props=..."
64
+ *
65
+ * Useful for passing additional information or context to the component rendered inside the Slide-In window
66
+ *
67
+ */
68
+ [key: string]: any;
69
+ }
70
+
71
+ /**
72
+ * API for displaying Slide-In panels in Rancher UI
73
+ * * ![slidein Example](/img/slidein.png)
74
+ */
75
+ export interface SlideInApi {
76
+ /**
77
+ * Opens a slide in panel in Rancher UI
78
+ *
79
+ * Example:
80
+ * ```ts
81
+ * import MyCustomSlideIn from '@/components/MyCustomSlideIn.vue';
82
+ *
83
+ * this.$shell.slideIn.open(MyCustomSlideIn, {
84
+ * title: 'Hello from SlideIn panel!'
85
+ * });
86
+ * ```
87
+ *
88
+ * For usage with the Composition API check usage guide [here](../../shell-api#using-composition-api-in-vue).
89
+ *
90
+ * @param component
91
+ * The Vue component to be displayed inside the slide in panel.
92
+ * This can be any SFC (Single-File Component) imported and passed in as a `Component`.
93
+ *
94
+ * @param config Slide-In configuration object
95
+ *
96
+ */
97
+ open(component: Component, config?: SlideInConfig): void;
98
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * system API which providers information about the current system
3
+ * * ![system Example](/img/system.png)
4
+ */
5
+ export interface SystemApi {
6
+ /**
7
+ * Rancher version
8
+ */
9
+ rancherVersion: string;
10
+ /**
11
+ * Rancher UI version
12
+ */
13
+ uiVersion: string;
14
+ /**
15
+ * If Rancher system running is Prime
16
+ */
17
+ isRancherPrime: boolean;
18
+ /**
19
+ * Git Commit for Rancher system running
20
+ */
21
+ gitCommit: string;
22
+ /**
23
+ * Rancher Kubernetes version
24
+ */
25
+ kubernetesVersion: string;
26
+ /**
27
+ * If Rancher system is a Dev build
28
+ */
29
+ isDevBuild: boolean;
30
+ /**
31
+ * If Rancher system is a Pre-Release build/version
32
+ */
33
+ isPrereleaseVersion: boolean;
34
+ }
@@ -0,0 +1,80 @@
1
+ // modal.test.ts
2
+
3
+ import {
4
+ describe, it, expect, jest, beforeEach
5
+ } from '@jest/globals';
6
+ import { ModalApiImpl } from '../modal'; // Adjust path as needed
7
+ import { Store } from 'vuex';
8
+
9
+ // A mock component to use in tests
10
+ const MockComponent = { template: '<div>Mock</div>' };
11
+
12
+ describe('modalApiImpl', () => {
13
+ let mockStore: Store<any>;
14
+ let modalApi: ModalApiImpl;
15
+ let mockCommit: jest.Mock;
16
+
17
+ beforeEach(() => {
18
+ // 1. Arrange: Create a mock commit function
19
+ mockCommit = jest.fn() as any;
20
+
21
+ // Create a mock store object
22
+ mockStore = { commit: mockCommit } as any;
23
+
24
+ // 2. Arrange: Instantiate the class with the mock store
25
+ modalApi = new ModalApiImpl(mockStore);
26
+ });
27
+
28
+ it('should open a modal with all default values', () => {
29
+ // 3. Act: Call the method with only the required component
30
+ modalApi.open(MockComponent);
31
+
32
+ // 4. Assert: Check if the store's commit was called correctly
33
+ expect(mockCommit).toHaveBeenCalledTimes(1);
34
+ expect(mockCommit).toHaveBeenCalledWith('modal/openModal', {
35
+ component: MockComponent,
36
+ componentProps: {},
37
+ resources: [],
38
+ modalWidth: '600px',
39
+ closeOnClickOutside: true,
40
+ });
41
+ });
42
+
43
+ it('should open a modal with custom configuration', () => {
44
+ const config = {
45
+ props: { title: 'Hello' },
46
+ resources: [{ id: 1, type: 'node' }],
47
+ width: '800px',
48
+ closeOnClickOutside: false,
49
+ };
50
+
51
+ // 3. Act: Call the method with a config
52
+ modalApi.open(MockComponent, config);
53
+
54
+ // 4. Assert: Check if the config values override the defaults
55
+ expect(mockCommit).toHaveBeenCalledTimes(1);
56
+ expect(mockCommit).toHaveBeenCalledWith('modal/openModal', {
57
+ component: MockComponent,
58
+ componentProps: config.props,
59
+ resources: config.resources,
60
+ modalWidth: config.width,
61
+ closeOnClickOutside: config.closeOnClickOutside,
62
+ });
63
+ });
64
+
65
+ it('should correctly merge a partial config with defaults', () => {
66
+ const config = { props: { id: '123' } };
67
+
68
+ // 3. Act
69
+ modalApi.open(MockComponent, config);
70
+
71
+ // 4. Assert
72
+ expect(mockCommit).toHaveBeenCalledWith('modal/openModal', {
73
+ component: MockComponent,
74
+ componentProps: config.props, // Custom
75
+ resources: [], // Default
76
+ modalWidth: '600px', // Default
77
+ closeOnClickOutside: true, // Default
78
+ });
79
+ });
80
+ });
@@ -0,0 +1,71 @@
1
+ // notifications.test.ts
2
+
3
+ import {
4
+ describe, it, expect, jest, beforeEach
5
+ } from '@jest/globals';
6
+ import { NotificationApiImpl } from '../notifications';
7
+ import { Store } from 'vuex';
8
+ import { NotificationLevel } from '@shell/types/notifications'; // Assuming this path
9
+
10
+ describe('notificationApiImpl', () => {
11
+ let mockStore: Store<any>;
12
+ let notificationApi: NotificationApiImpl;
13
+ let mockDispatch: jest.Mock;
14
+
15
+ beforeEach(() => {
16
+ // 1. Arrange
17
+ mockDispatch = jest.fn() as any;
18
+ mockStore = { dispatch: mockDispatch } as any;
19
+
20
+ // 2. Arrange
21
+ notificationApi = new NotificationApiImpl(mockStore);
22
+ });
23
+
24
+ it('should send a notification and return its ID', async() => {
25
+ const mockNotificationId = 'notif-12345';
26
+ const config = { progress: 80 };
27
+
28
+ // Arrange: Mock the dispatch to return a promise resolving to the ID
29
+ mockDispatch.mockResolvedValue(mockNotificationId);
30
+
31
+ // 3. Act: Call the async method
32
+ const result = await notificationApi.send(
33
+ NotificationLevel.Success,
34
+ 'Test Title',
35
+ 'Test Message',
36
+ config
37
+ );
38
+
39
+ // 4. Assert
40
+ expect(mockDispatch).toHaveBeenCalledTimes(1);
41
+ expect(mockDispatch).toHaveBeenCalledWith(
42
+ 'notifications/add',
43
+ {
44
+ level: NotificationLevel.Success,
45
+ title: 'Test Title',
46
+ message: 'Test Message',
47
+ progress: 80,
48
+ },
49
+ { root: true }
50
+ );
51
+
52
+ // Assert the return value
53
+ expect(result).toBe(mockNotificationId);
54
+ });
55
+
56
+ it('should update notification progress', () => {
57
+ // 3. Act
58
+ notificationApi.updateProgress('notif-abc', 75);
59
+
60
+ // 4. Assert
61
+ expect(mockDispatch).toHaveBeenCalledTimes(1);
62
+ expect(mockDispatch).toHaveBeenCalledWith(
63
+ 'notifications/update',
64
+ {
65
+ id: 'notif-abc',
66
+ progress: 75,
67
+ },
68
+ { root: true }
69
+ );
70
+ });
71
+ });
@@ -0,0 +1,54 @@
1
+ // slide-in.test.ts
2
+
3
+ import {
4
+ describe, it, expect, jest, beforeEach
5
+ } from '@jest/globals';
6
+ import { SlideInApiImpl } from '../slide-in'; // Adjust path as needed
7
+ import { Store } from 'vuex';
8
+
9
+ // A mock component to use in tests
10
+ const MockComponent = { template: '<div>Mock</div>' };
11
+
12
+ describe('slideInApiImpl', () => {
13
+ let mockStore: Store<any>;
14
+ let slideInApi: SlideInApiImpl;
15
+ let mockCommit: jest.Mock;
16
+
17
+ beforeEach(() => {
18
+ // 1. Arrange
19
+ mockCommit = jest.fn() as any;
20
+ mockStore = { commit: mockCommit } as any;
21
+
22
+ // 2. Arrange
23
+ slideInApi = new SlideInApiImpl(mockStore);
24
+ });
25
+
26
+ it('should open a slide-in panel with no config', () => {
27
+ // 3. Act
28
+ slideInApi.open(MockComponent);
29
+
30
+ // 4. Assert
31
+ expect(mockCommit).toHaveBeenCalledTimes(1);
32
+ expect(mockCommit).toHaveBeenCalledWith('slideInPanel/open', {
33
+ component: MockComponent,
34
+ componentProps: {},
35
+ });
36
+ });
37
+
38
+ it('should open a slide-in panel with a config', () => {
39
+ const config = {
40
+ title: 'Test Panel',
41
+ width: '50%',
42
+ };
43
+
44
+ // 3. Act
45
+ slideInApi.open(MockComponent, config);
46
+
47
+ // 4. Assert
48
+ expect(mockCommit).toHaveBeenCalledTimes(1);
49
+ expect(mockCommit).toHaveBeenCalledWith('slideInPanel/open', {
50
+ component: MockComponent,
51
+ componentProps: { ...config }, // The implementation spreads the config
52
+ });
53
+ });
54
+ });
@@ -0,0 +1,129 @@
1
+ // system.test.ts
2
+
3
+ import {
4
+ describe, it, expect, jest, beforeEach
5
+ } from '@jest/globals';
6
+ import { SystemApiImpl } from '../system';
7
+ import { Store } from 'vuex';
8
+
9
+ // --- Mock the external modules ---
10
+
11
+ // 1. Import the functions we need to mock
12
+ import { getVersionData, getKubeVersionData, isRancherPrime } from '@shell/config/version';
13
+ import { isDevBuild, isPrerelease } from '@shell/utils/version';
14
+
15
+ // 2. Tell Jest to mock the entire module
16
+ // We provide a factory function () => ({ ... }) to define the mock implementation.
17
+ jest.mock('@shell/config/version', () => ({
18
+ getVersionData: jest.fn(),
19
+ getKubeVersionData: jest.fn(),
20
+ isRancherPrime: jest.fn(),
21
+ }));
22
+
23
+ jest.mock('@shell/utils/version', () => ({
24
+ isDevBuild: jest.fn(),
25
+ isPrerelease: jest.fn(),
26
+ }));
27
+
28
+ // 3. Cast the imported functions to Jest Mocks for type-safety
29
+ const mockGetVersionData = getVersionData as jest.Mock;
30
+ const mockGetKubeVersionData = getKubeVersionData as jest.Mock;
31
+ const mockIsRancherPrime = isRancherPrime as jest.Mock;
32
+ const mockIsDevBuild = isDevBuild as jest.Mock;
33
+ const mockIsPrerelease = isPrerelease as jest.Mock;
34
+
35
+ // --- End of Mocking Setup ---
36
+
37
+ describe('systemApiImpl', () => {
38
+ let mockStore: Store<any>;
39
+ let systemApi: SystemApiImpl;
40
+ let mockGetter: jest.Mock;
41
+
42
+ beforeEach(() => {
43
+ // Reset all mocks before each test
44
+ mockGetVersionData.mockClear();
45
+ mockGetKubeVersionData.mockClear();
46
+ mockIsRancherPrime.mockClear();
47
+ mockIsDevBuild.mockClear();
48
+ mockIsPrerelease.mockClear();
49
+
50
+ // Arrange: Mock the store
51
+ mockGetter = jest.fn() as any;
52
+ mockStore = {
53
+ getters: { 'management/byId': mockGetter },
54
+ $config: { dashboardVersion: 'v2.9-test' }
55
+ } as any;
56
+
57
+ // Arrange: Instantiate the class
58
+ systemApi = new SystemApiImpl(mockStore);
59
+ });
60
+
61
+ // --- Tests for Store-based Getters ---
62
+
63
+ it('should get uiVersion from store.$config', () => {
64
+ expect(systemApi.uiVersion).toBe('v2.9-test');
65
+ });
66
+ // --- Tests for Mocked Module Getters ---
67
+
68
+ it('should get rancherVersion and gitCommit from getVersionData', () => {
69
+ // Arrange: Define the return value for this test
70
+ mockGetVersionData.mockReturnValue({ Version: 'v2.8.0', GitCommit: 'abc1234' });
71
+
72
+ // Act & Assert
73
+ expect(systemApi.rancherVersion).toBe('v2.8.0');
74
+ expect(systemApi.gitCommit).toBe('abc1234');
75
+ expect(mockGetVersionData).toHaveBeenCalledTimes(2); // Called for both getters
76
+ });
77
+
78
+ it('should get kubernetesVersion and buildPlatform from getKubeVersionData', () => {
79
+ // Arrange
80
+ mockGetKubeVersionData.mockReturnValue({ gitVersion: 'v1.25.0', platform: 'linux/amd64' });
81
+
82
+ // Act & Assert
83
+ expect(systemApi.kubernetesVersion).toBe('v1.25.0');
84
+ });
85
+
86
+ it('should handle undefined kubeData gracefully', () => {
87
+ // Arrange: Simulate getKubeVersionData returning nothing
88
+ mockGetKubeVersionData.mockReturnValue(undefined);
89
+
90
+ // Act & Assert (The code's `|| {}` handles this)
91
+ expect(systemApi.kubernetesVersion).toBeUndefined();
92
+ });
93
+
94
+ it('should get isRancherPrime from isRancherPrime', () => {
95
+ // Arrange
96
+ mockIsRancherPrime.mockReturnValue(true);
97
+
98
+ // Act & Assert
99
+ expect(systemApi.isRancherPrime).toBe(true);
100
+ expect(mockIsRancherPrime).toHaveBeenCalledTimes(1);
101
+ });
102
+
103
+ it('should call isDevBuild with the correct rancherVersion', () => {
104
+ // Arrange
105
+ mockGetVersionData.mockReturnValue({ Version: 'v2.8.0-dev', GitCommit: '...' });
106
+ mockIsDevBuild.mockReturnValue(true); // The logic we are testing
107
+
108
+ // Act
109
+ const result = systemApi.isDevBuild;
110
+
111
+ // Assert
112
+ expect(result).toBe(true);
113
+ // This is the key check: verify it passed its OWN version to the util
114
+ expect(mockIsDevBuild).toHaveBeenCalledWith('v2.8.0-dev');
115
+ });
116
+
117
+ it('should call isPrereleaseVersion with the correct rancherVersion', () => {
118
+ // Arrange
119
+ mockGetVersionData.mockReturnValue({ Version: 'v2.8.0-rc1', GitCommit: '...' });
120
+ mockIsPrerelease.mockReturnValue(true);
121
+
122
+ // Act
123
+ const result = systemApi.isPrereleaseVersion;
124
+
125
+ // Assert
126
+ expect(result).toBe(true);
127
+ expect(mockIsPrerelease).toHaveBeenCalledWith('v2.8.0-rc1');
128
+ });
129
+ });