@rancher/shell 3.0.9-rc.6 → 3.0.10

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 (82) hide show
  1. package/assets/styles/base/_color.scss +4 -0
  2. package/assets/styles/themes/_light.scss +6 -6
  3. package/assets/styles/themes/_modern.scss +14 -6
  4. package/assets/translations/en-us.yaml +2 -5
  5. package/components/CopyToClipboard.vue +28 -0
  6. package/components/CopyToClipboardText.vue +4 -0
  7. package/components/CruResource.vue +1 -0
  8. package/components/GlobalRoleBindings.vue +1 -5
  9. package/components/IconOrSvg.vue +61 -42
  10. package/components/ResourceDetail/index.vue +0 -21
  11. package/components/SortableTable/index.vue +2 -2
  12. package/components/__tests__/CruResource.test.ts +35 -1
  13. package/components/form/BannerSettings.vue +2 -2
  14. package/components/form/NotificationSettings.vue +2 -2
  15. package/composables/useIsNewDetailPageEnabled.test.ts +98 -0
  16. package/composables/useIsNewDetailPageEnabled.ts +12 -0
  17. package/config/product/explorer.js +11 -1
  18. package/config/product/manager.js +0 -1
  19. package/config/table-headers.js +0 -9
  20. package/config/types.js +0 -1
  21. package/detail/fleet.cattle.io.cluster.vue +1 -1
  22. package/dialog/FeatureFlagListDialog.vue +1 -1
  23. package/edit/auth/github-app-steps.vue +2 -0
  24. package/edit/auth/github-steps.vue +2 -0
  25. package/edit/catalog.cattle.io.clusterrepo.vue +1 -1
  26. package/edit/management.cattle.io.user.vue +60 -35
  27. package/edit/monitoring.coreos.com.alertmanagerconfig/__tests__/auth.spec.ts +145 -0
  28. package/edit/monitoring.coreos.com.alertmanagerconfig/__tests__/index.test.ts +202 -0
  29. package/edit/monitoring.coreos.com.alertmanagerconfig/__tests__/tls.spec.ts +226 -0
  30. package/edit/monitoring.coreos.com.alertmanagerconfig/auth.vue +24 -21
  31. package/edit/monitoring.coreos.com.alertmanagerconfig/types/__tests__/opsgenie.spec.ts +157 -0
  32. package/edit/monitoring.coreos.com.alertmanagerconfig/types/__tests__/pagerduty.spec.ts +132 -0
  33. package/edit/monitoring.coreos.com.alertmanagerconfig/types/__tests__/slack.spec.ts +108 -0
  34. package/edit/monitoring.coreos.com.alertmanagerconfig/types/pagerduty.vue +2 -1
  35. package/edit/monitoring.coreos.com.receiver/__tests__/auth.spec.ts +165 -0
  36. package/edit/monitoring.coreos.com.receiver/__tests__/index.test.ts +153 -0
  37. package/edit/monitoring.coreos.com.receiver/__tests__/tls.spec.ts +115 -0
  38. package/edit/monitoring.coreos.com.receiver/types/__tests__/email.spec.ts +86 -0
  39. package/edit/monitoring.coreos.com.receiver/types/__tests__/opsgenie.spec.ts +209 -0
  40. package/edit/monitoring.coreos.com.receiver/types/__tests__/pagerduty.spec.ts +105 -0
  41. package/edit/monitoring.coreos.com.receiver/types/__tests__/slack.spec.ts +92 -0
  42. package/edit/monitoring.coreos.com.receiver/types/__tests__/webhook.spec.ts +131 -0
  43. package/edit/provisioning.cattle.io.cluster/ingress/IngressCards.vue +14 -12
  44. package/edit/provisioning.cattle.io.cluster/rke2.vue +4 -5
  45. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +18 -3
  46. package/edit/provisioning.cattle.io.cluster/tabs/Ingress.vue +100 -76
  47. package/edit/token.vue +29 -68
  48. package/list/provisioning.cattle.io.cluster.vue +2 -2
  49. package/models/__tests__/chart.test.ts +2 -2
  50. package/models/chart.js +3 -3
  51. package/models/token.js +0 -4
  52. package/package.json +8 -8
  53. package/pages/account/index.vue +67 -96
  54. package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +108 -24
  55. package/pages/c/_cluster/apps/charts/index.vue +1 -11
  56. package/pages/c/_cluster/explorer/index.vue +2 -19
  57. package/pages/c/_cluster/explorer/tools/index.vue +1 -1
  58. package/pages/c/_cluster/manager/cloudCredential/index.vue +1 -1
  59. package/pages/c/_cluster/uiplugins/index.vue +1 -1
  60. package/pkg/auto-import.js +41 -0
  61. package/plugins/dashboard-store/resource-class.js +2 -2
  62. package/plugins/steve/__tests__/steve-class.test.ts +1 -1
  63. package/plugins/steve/steve-class.js +3 -3
  64. package/plugins/steve/steve-pagination-utils.ts +2 -4
  65. package/rancher-components/Pill/RcCounterBadge/RcCounterBadge.vue +7 -7
  66. package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.vue +5 -2
  67. package/rancher-components/RcIcon/types.ts +2 -2
  68. package/rancher-components/RcItemCard/RcItemCard.vue +8 -1
  69. package/rancher-components/RcSection/RcSection.test.ts +323 -0
  70. package/rancher-components/RcSection/RcSection.vue +252 -0
  71. package/rancher-components/RcSection/RcSectionActions.test.ts +212 -0
  72. package/rancher-components/RcSection/RcSectionActions.vue +85 -0
  73. package/rancher-components/RcSection/RcSectionBadges.test.ts +149 -0
  74. package/rancher-components/RcSection/RcSectionBadges.vue +29 -0
  75. package/rancher-components/RcSection/index.ts +12 -0
  76. package/rancher-components/RcSection/types.ts +86 -0
  77. package/scripts/test-plugins-build.sh +5 -4
  78. package/types/shell/index.d.ts +93 -108
  79. package/utils/style.ts +17 -0
  80. package/utils/svg-filter.js +4 -3
  81. package/utils/units.js +14 -5
  82. package/models/ext.cattle.io.token.js +0 -48
@@ -6,7 +6,7 @@ import ResourceTabs from '@shell/components/form/ResourceTabs';
6
6
  import Tab from '@shell/components/Tabbed/Tab';
7
7
  import { MANAGEMENT, FLEET, SCHEMA } from '@shell/config/types';
8
8
  import { FLEET as FLEET_LABELS } from '@shell/config/labels-annotations';
9
- import { allHash } from 'utils/promise';
9
+ import { allHash } from '@shell/utils/promise';
10
10
 
11
11
  export default {
12
12
  name: 'FleetDetailCluster',
@@ -134,7 +134,7 @@ export default {
134
134
  this.waiting = false;
135
135
  this.close();
136
136
 
137
- await this.$store.dispatch('management/findAll', { type: this.resource, opt: { force: true } });
137
+ await this.$store.dispatch('management/findAll', { type: this.update.type, opt: { force: true } });
138
138
  }
139
139
  } catch (e) {}
140
140
 
@@ -49,6 +49,7 @@ defineProps<{
49
49
  <CopyToClipboard
50
50
  label-as="tooltip"
51
51
  :text="tArgs.serverUrl"
52
+ :aria-label="t('generic.copyValueToClipboard', { value: tArgs.serverUrl })"
52
53
  class="icon-btn"
53
54
  action-color="bg-transparent"
54
55
  />
@@ -67,6 +68,7 @@ defineProps<{
67
68
  <CopyToClipboard
68
69
  :text="t(`authConfig.${name}.form.callback.value`, tArgs, true)"
69
70
  label-as="tooltip"
71
+ :aria-label="t('generic.copyValueToClipboard', { value: t(`authConfig.${name}.form.callback.value`, tArgs, true) })"
70
72
  class="icon-btn"
71
73
  action-color="bg-transparent"
72
74
  />
@@ -40,6 +40,7 @@ defineProps<{
40
40
  <b>{{ t(`authConfig.${name}.form.homepage.label`) }}</b>: {{ tArgs.serverUrl }} <CopyToClipboard
41
41
  label-as="tooltip"
42
42
  :text="tArgs.serverUrl"
43
+ :aria-label="t('generic.copyValueToClipboard', { value: tArgs.serverUrl })"
43
44
  class="icon-btn"
44
45
  action-color="bg-transparent"
45
46
  />
@@ -49,6 +50,7 @@ defineProps<{
49
50
  <b>{{ t(`authConfig.${name}.form.callback.label`) }}</b>: {{ tArgs.serverUrl }} <CopyToClipboard
50
51
  :text="tArgs.serverUrl"
51
52
  label-as="tooltip"
53
+ :aria-label="t('generic.copyValueToClipboard', { value: tArgs.serverUrl })"
52
54
  class="icon-btn"
53
55
  action-color="bg-transparent"
54
56
  />
@@ -228,7 +228,7 @@ export default {
228
228
  <template>
229
229
  <form>
230
230
  <h2>{{ t('catalog.repo.target.label') }}</h2>
231
- <div class="row mb-10">
231
+ <div class="row mb-20">
232
232
  <div class="col span-12 target-groups">
233
233
  <RcItemCard
234
234
  v-for="card in clusterRepoTargets"
@@ -16,8 +16,6 @@ export default {
16
16
  ChangePassword, GlobalRoleBindings, CruResource, LabeledInput, Loading
17
17
  },
18
18
 
19
- emits: ['update:mode'],
20
-
21
19
  mixins: [
22
20
  CreateEditView
23
21
  ],
@@ -42,8 +40,7 @@ export default {
42
40
  password: false,
43
41
  roles: !showGlobalRoles,
44
42
  rolesChanged: false,
45
- },
46
- watchOverride: false,
43
+ }
47
44
  };
48
45
  },
49
46
 
@@ -110,10 +107,20 @@ export default {
110
107
  if (this.isCreate) {
111
108
  const user = await this.createUser();
112
109
 
113
- await this.updateRoles(user.id);
110
+ await this.createSecret(user);
111
+ await this.updateRoles(user);
112
+
113
+ // Show success notification only after ALL operations complete
114
+ // this is a "clone" of steve-class "processSaveResponse" toast/growl
115
+ this.$store.dispatch('growl/success', {
116
+ title: this.t('generic.autogeneratedCreated.title', { resource: user.kind }),
117
+ message: this.t('generic.autogeneratedCreated.message', { id: user.username, resource: user.kind }),
118
+ timeout: 3000
119
+ }, { root: true });
114
120
  } else {
115
- await this.editUser();
116
- await this.updateRoles();
121
+ const user = await this.editUser();
122
+
123
+ await this.updateRoles(user);
117
124
  }
118
125
 
119
126
  this.$router.replace({ name: this.doneRoute });
@@ -139,21 +146,10 @@ export default {
139
146
  username: this.form.username
140
147
  });
141
148
 
142
- const userSaved = await user.save();
143
-
144
- if (this.form.password.password) {
145
- // create secret to hold user password
146
- const secret = await this.$store.dispatch('management/create', {
147
- type: SECRET,
148
- metadata: {
149
- namespace: 'cattle-local-user-passwords',
150
- name: userSaved.id
151
- },
152
- data: { password: base64Encode(this.form.password.password) }
153
- });
154
-
155
- await secret.save();
156
- }
149
+ const userSaved = await user.save({
150
+ // Don't show a success toast until the secret and GRB are also created
151
+ suppressSuccessToast: true,
152
+ });
157
153
 
158
154
  return userSaved;
159
155
  },
@@ -178,27 +174,57 @@ export default {
178
174
  await wait(5000);
179
175
  }
180
176
 
181
- this.value.save();
177
+ const user = this.value.save();
178
+
179
+ return user;
182
180
  },
183
181
 
184
- async updateRoles(userId) {
182
+ async createSecret(user) {
183
+ if (this.form.password.password) {
184
+ try {
185
+ // create secret to hold user password
186
+ const secret = await this.$store.dispatch('management/create', {
187
+ type: SECRET,
188
+ metadata: {
189
+ namespace: 'cattle-local-user-passwords',
190
+ name: user.id
191
+ },
192
+ data: { password: base64Encode(this.form.password.password) }
193
+ });
194
+
195
+ await secret.save();
196
+ } catch (err) {
197
+ if (this.isCreate) {
198
+ try {
199
+ // If secret creation fails, attempt to clean up the user to maintain consistency
200
+ await user.remove();
201
+ } catch (cleanupErr) {
202
+ // Log cleanup error but prioritize original error for user feedback
203
+ console.error('Failed to clean up user after secret creation failure:', cleanupErr); // eslint-disable-line no-console
204
+ }
205
+ }
206
+
207
+ throw err;
208
+ }
209
+ }
210
+ },
211
+
212
+ async updateRoles(user) {
185
213
  if (!this.$refs.grb) {
186
214
  return;
187
215
  }
188
216
 
189
217
  try {
190
- await this.$refs.grb.save(userId);
218
+ await this.$refs.grb.save(user.id);
191
219
  } catch (err) {
192
220
  if (this.isCreate) {
193
- this.watchOverride = true;
194
- this.$emit(
195
- 'update:mode',
196
- {
197
- userId,
198
- mode: _EDIT,
199
- resource: 'management.cattle.io.user',
200
- }
201
- );
221
+ try {
222
+ // If GRB creation fails, clean up the user to maintain consistency
223
+ await user.remove();
224
+ } catch (cleanupErr) {
225
+ // Log cleanup error but prioritize original error for user feedback
226
+ console.error('Failed to clean up user after GRB creation failure:', cleanupErr); // eslint-disable-line no-console
227
+ }
202
228
  }
203
229
  throw err;
204
230
  }
@@ -274,7 +300,6 @@ export default {
274
300
  :user-id="value.id || liveValue.id"
275
301
  :mode="mode"
276
302
  :real-mode="realMode"
277
- :watch-override="watchOverride"
278
303
  type="user"
279
304
  @hasChanges="validation.rolesChanged = $event"
280
305
  @canLogIn="validation.roles = $event"
@@ -0,0 +1,145 @@
1
+ import { shallowMount } from '@vue/test-utils';
2
+ import Auth from '@shell/edit/monitoring.coreos.com.alertmanagerconfig/auth.vue';
3
+ import LabeledSelect from '@shell/components/form/LabeledSelect.vue';
4
+ import SimpleSecretSelector from '@shell/components/form/SimpleSecretSelector';
5
+
6
+ describe('component: Auth.vue', () => {
7
+ const defaultProps = {
8
+ mode: 'edit',
9
+ value: {},
10
+ namespace: 'test-namespace'
11
+ };
12
+
13
+ it('should render correctly with initial props', () => {
14
+ const wrapper = shallowMount(Auth, {
15
+ props: defaultProps,
16
+ global: { mocks: { $fetchState: { pending: false, error: null } } }
17
+ });
18
+
19
+ expect(wrapper.find('h3').text()).toBe('%monitoringReceiver.auth.label%');
20
+ expect(wrapper.findComponent(LabeledSelect).exists()).toBe(true);
21
+ });
22
+
23
+ it('should initialize with basic auth type if present', () => {
24
+ const wrapper = shallowMount(Auth, {
25
+ props: { ...defaultProps, value: { basicAuth: { username: { name: 'test' } } } },
26
+ global: { mocks: { $fetchState: { pending: false, error: null } } }
27
+ });
28
+
29
+ expect(wrapper.vm.authType).toBe('basicAuth');
30
+ expect(wrapper.findAllComponents(SimpleSecretSelector)).toHaveLength(2);
31
+ });
32
+
33
+ it('should initialize with bearer token auth type if present', () => {
34
+ const wrapper = shallowMount(Auth, {
35
+ props: { ...defaultProps, value: { bearerTokenSecret: { name: 'test' } } },
36
+ global: { mocks: { $fetchState: { pending: false, error: null } } }
37
+ });
38
+
39
+ expect(wrapper.vm.authType).toBe('bearerTokenSecret');
40
+ expect(wrapper.findAllComponents(SimpleSecretSelector)).toHaveLength(1);
41
+ });
42
+
43
+ it('should switch to basic auth and clear other types', async() => {
44
+ const value = { bearerTokenSecret: { name: 'b', key: 'k' } };
45
+ const wrapper = shallowMount(Auth, {
46
+ props: { ...defaultProps, value },
47
+ global: { mocks: { $fetchState: { pending: false, error: null } } }
48
+ });
49
+
50
+ await wrapper.findComponent(LabeledSelect).vm.$emit('update:value', 'basicAuth');
51
+ expect(wrapper.vm.authType).toBe('basicAuth');
52
+ expect(wrapper.props('value').bearerTokenSecret).toBeUndefined();
53
+ expect(wrapper.props('value').basicAuth).toBeDefined();
54
+ });
55
+
56
+ it('should switch to bearer token and clear other types', async() => {
57
+ const value = { basicAuth: { username: { name: 'u' } } };
58
+ const wrapper = shallowMount(Auth, {
59
+ props: { ...defaultProps, value },
60
+ global: { mocks: { $fetchState: { pending: false, error: null } } }
61
+ });
62
+
63
+ await wrapper.findComponent(LabeledSelect).vm.$emit('update:value', 'bearerTokenSecret');
64
+ expect(wrapper.vm.authType).toBe('bearerTokenSecret');
65
+ expect(wrapper.props('value').basicAuth).toBeUndefined();
66
+ expect(wrapper.props('value').bearerTokenSecret).toBeDefined();
67
+ });
68
+
69
+ it('should switch to none and clear other types', async() => {
70
+ const value = { basicAuth: { username: { name: 'u' } } };
71
+ const wrapper = shallowMount(Auth, {
72
+ props: { ...defaultProps, value },
73
+ global: { mocks: { $fetchState: { pending: false, error: null } } }
74
+ });
75
+
76
+ await wrapper.findComponent(LabeledSelect).vm.$emit('update:value', 'none');
77
+ expect(wrapper.vm.authType).toBe('none');
78
+ expect(wrapper.props('value').basicAuth).toBeUndefined();
79
+ });
80
+
81
+ it('should update basic auth username and password', async() => {
82
+ const wrapper = shallowMount(Auth, {
83
+ props: { ...defaultProps, value: { basicAuth: { username: { name: 'test' } } } },
84
+ global: { mocks: { $fetchState: { pending: false, error: null } } }
85
+ });
86
+
87
+ await wrapper.vm.$nextTick();
88
+
89
+ const selectors = wrapper.findAllComponents(SimpleSecretSelector);
90
+
91
+ expect(selectors).toHaveLength(2);
92
+
93
+ const usernameSelector = selectors[0];
94
+ const passwordSelector = selectors[1];
95
+
96
+ await usernameSelector.vm.$emit('updateSecretName', 'user-secret');
97
+ await usernameSelector.vm.$emit('updateSecretKey', 'user-key');
98
+ await passwordSelector.vm.$emit('updateSecretName', 'pass-secret');
99
+ await passwordSelector.vm.$emit('updateSecretKey', 'pass-key');
100
+
101
+ const basicAuth = wrapper.props('value').basicAuth;
102
+
103
+ expect(basicAuth.username.name).toBe('user-secret');
104
+ expect(basicAuth.username.key).toBe('user-key');
105
+ expect(basicAuth.password.name).toBe('pass-secret');
106
+ expect(basicAuth.password.key).toBe('pass-key');
107
+ });
108
+
109
+ it('should update bearer token secret', async() => {
110
+ const wrapper = shallowMount(Auth, {
111
+ props: { ...defaultProps, value: { bearerTokenSecret: { name: 'test' } } },
112
+ global: { mocks: { $fetchState: { pending: false, error: null } } }
113
+ });
114
+
115
+ await wrapper.vm.$nextTick();
116
+
117
+ const selector = wrapper.findComponent(SimpleSecretSelector);
118
+
119
+ expect(selector.exists()).toBe(true);
120
+
121
+ await selector.vm.$emit('updateSecretName', 'bearer-name');
122
+ await selector.vm.$emit('updateSecretKey', 'bearer-key');
123
+
124
+ const bearerToken = wrapper.props('value').bearerTokenSecret;
125
+
126
+ expect(bearerToken.name).toBe('bearer-name');
127
+ expect(bearerToken.key).toBe('bearer-key');
128
+ });
129
+
130
+ it('should render in view mode', () => {
131
+ const wrapper = shallowMount(Auth, {
132
+ props: {
133
+ mode: 'view', value: { basicAuth: {} }, namespace: 'ns'
134
+ },
135
+ global: { mocks: { $fetchState: { pending: false, error: null } } }
136
+ });
137
+
138
+ expect(wrapper.findComponent(LabeledSelect).attributes('disabled')).toBe('true');
139
+ const selectors = wrapper.findAllComponents(SimpleSecretSelector);
140
+
141
+ selectors.forEach((selector) => {
142
+ expect(selector.props('disabled')).toBe(true);
143
+ });
144
+ });
145
+ });
@@ -0,0 +1,202 @@
1
+ /* eslint-disable import/first */
2
+ global.PointerEvent = class PointerEvent extends Event {};
3
+
4
+ import { shallowMount } from '@vue/test-utils';
5
+ import { EDITOR_MODES } from '@shell/components/YamlEditor.vue';
6
+ import { _EDIT, _VIEW } from '@shell/config/query-params';
7
+ import Index from '../index.vue';
8
+
9
+ describe('monitoring.coreos.com.alertmanagerconfig/index.vue', () => {
10
+ it('should render correctly', () => {
11
+ const valueMock = {
12
+ applyDefaults: jest.fn(),
13
+ spec: {
14
+ receivers: [
15
+ { name: 'receiver1', type: 'webhook' },
16
+ { name: 'receiver2', type: 'email' },
17
+ ],
18
+ },
19
+ getCreateReceiverRoute: jest.fn(() => ({ name: 'create-receiver' })),
20
+ getReceiverDetailLink: jest.fn((name) => `detail-${ name }`),
21
+ };
22
+
23
+ const wrapper = shallowMount(Index, {
24
+ propsData: {
25
+ value: valueMock,
26
+ mode: 'create',
27
+ },
28
+ global: {
29
+ mocks: {
30
+ $store: {
31
+ getters: { currentProduct: { inStore: 'test' } },
32
+ dispatch: jest.fn(() => Promise.resolve({ receiverSchema: {} })),
33
+ },
34
+ $router: { push: jest.fn() },
35
+ $route: { name: 'test-route' },
36
+ $fetchState: { pending: false },
37
+ }
38
+ },
39
+ stubs: {
40
+ CruResource: true,
41
+ NameNsDescription: true,
42
+ Tabbed: true,
43
+ Tab: true,
44
+ RouteConfig: true,
45
+ ResourceTable: true,
46
+ ActionMenu: true,
47
+ Loading: true,
48
+ }
49
+ });
50
+
51
+ // Ensure the component is rendered
52
+ expect(wrapper.exists()).toBe(true);
53
+
54
+ // Assert that applyDefaults is called
55
+ expect(valueMock.applyDefaults).toHaveBeenCalledTimes(1);
56
+
57
+ // Assert receiverOptions are correctly populated from value.spec.receivers
58
+ expect(wrapper.vm.receiverOptions).toStrictEqual(['receiver1', 'receiver2']);
59
+
60
+ // Assert createReceiverLink is correctly set
61
+ expect(wrapper.vm.createReceiverLink).toStrictEqual({ name: 'create-receiver' });
62
+
63
+ // Assert that receiverTableHeaders has the expected structure (basic check)
64
+ expect(wrapper.vm.receiverTableHeaders.length).toBeGreaterThan(0);
65
+ expect(wrapper.vm.receiverTableHeaders[0].name).toBe('name');
66
+ expect(wrapper.vm.receiverTableHeaders[1].name).toBe('type');
67
+ });
68
+
69
+ // Additional test for editorMode computed property
70
+ it('editorMode should return VIEW_CODE when mode is _VIEW', () => {
71
+ const valueMock = {
72
+ applyDefaults: jest.fn(),
73
+ spec: { receivers: [] },
74
+ getCreateReceiverRoute: jest.fn(),
75
+ getReceiverDetailLink: jest.fn(),
76
+ };
77
+
78
+ const wrapper = shallowMount(Index, {
79
+ propsData: {
80
+ value: valueMock,
81
+ mode: _VIEW, // Set mode to _VIEW
82
+ },
83
+ global: {
84
+ mocks: {
85
+ $store: {
86
+ getters: { currentProduct: { inStore: 'test' } },
87
+ dispatch: jest.fn(() => Promise.resolve({ receiverSchema: {} })),
88
+ },
89
+ $router: { push: jest.fn() },
90
+ $route: { name: 'test-route' },
91
+ $fetchState: { pending: false },
92
+ },
93
+ },
94
+ stubs: {
95
+ CruResource: true,
96
+ NameNsDescription: true,
97
+ Tabbed: true,
98
+ Tab: true,
99
+ RouteConfig: true,
100
+ ResourceTable: true,
101
+ ActionMenu: true,
102
+ Loading: true,
103
+ }
104
+ });
105
+
106
+ expect(wrapper.vm.editorMode).toBe(EDITOR_MODES.VIEW_CODE);
107
+ });
108
+
109
+ it('editorMode should return EDIT_CODE when mode is not _VIEW', () => {
110
+ const valueMock = {
111
+ applyDefaults: jest.fn(),
112
+ spec: { receivers: [] },
113
+ getCreateReceiverRoute: jest.fn(),
114
+ getReceiverDetailLink: jest.fn(),
115
+ };
116
+
117
+ const wrapper = shallowMount(Index, {
118
+ propsData: {
119
+ value: valueMock,
120
+ mode: _EDIT, // Set mode to _EDIT
121
+ },
122
+ global: {
123
+ mocks: {
124
+ $store: {
125
+ getters: { currentProduct: { inStore: 'test' } },
126
+ dispatch: jest.fn(() => Promise.resolve({ receiverSchema: {} })),
127
+ },
128
+ $router: { push: jest.fn() },
129
+ $route: { name: 'test-route' },
130
+ $fetchState: { pending: false },
131
+ },
132
+ },
133
+ stubs: {
134
+ CruResource: true,
135
+ NameNsDescription: true,
136
+ Tabbed: true,
137
+ Tab: true,
138
+ RouteConfig: true,
139
+ ResourceTable: true,
140
+ ActionMenu: true,
141
+ Loading: true,
142
+ }
143
+ });
144
+
145
+ expect(wrapper.vm.editorMode).toBe(EDITOR_MODES.EDIT_CODE);
146
+ });
147
+
148
+ it('translateReceiverTypes method translates receiver types', () => {
149
+ const valueMock = {
150
+ applyDefaults: jest.fn(),
151
+ spec: { receivers: [] },
152
+ getCreateReceiverRoute: jest.fn(),
153
+ getReceiverDetailLink: jest.fn(),
154
+ };
155
+
156
+ const wrapper = shallowMount(Index, {
157
+ propsData: {
158
+ value: valueMock,
159
+ mode: 'create',
160
+ },
161
+ global: {
162
+ mocks: {
163
+ $store: {
164
+ getters: { currentProduct: { inStore: 'test' } },
165
+ dispatch: jest.fn(() => Promise.resolve({ receiverSchema: {} })),
166
+ },
167
+ $router: { push: jest.fn() },
168
+ $route: { name: 'test-route' },
169
+ $fetchState: { pending: false },
170
+ },
171
+ },
172
+ stubs: {
173
+ CruResource: true,
174
+ NameNsDescription: true,
175
+ Tabbed: true,
176
+ Tab: true,
177
+ RouteConfig: true,
178
+ ResourceTable: true,
179
+ ActionMenu: true,
180
+ Loading: true,
181
+ }
182
+ });
183
+
184
+ // Ensure receiverTypes is initialized, then call the method
185
+ const originalReceiverTypes = [
186
+ { label: 'alertmanagerConfigReceiver.alerta', value: 'alerta' },
187
+ { label: 'alertmanagerConfigReceiver.dingtalk', value: 'dingtalk' }
188
+ ];
189
+
190
+ // Manually set receiverTypes for the test (it's normally populated from an import)
191
+ // In a real scenario, you'd mock the import or ensure it's loaded before the test.
192
+ // For this test, we'll directly manipulate the vm.
193
+ wrapper.setData({ receiverTypes: originalReceiverTypes });
194
+
195
+ const translatedTypes = wrapper.vm.translateReceiverTypes();
196
+
197
+ expect(translatedTypes).toStrictEqual([
198
+ { label: '%alertmanagerConfigReceiver.alerta%', value: 'alerta' },
199
+ { label: '%alertmanagerConfigReceiver.dingtalk%', value: 'dingtalk' }
200
+ ]);
201
+ });
202
+ });