@spinnaker/google 2026.3.2 → 2026.4.0

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 (108) hide show
  1. package/dist/autoscalingPolicy/GceAutoScalingFieldLayout.d.ts +2 -2
  2. package/dist/domain/loadBalancer.d.ts +6 -3
  3. package/dist/image/ImageSelect.d.ts +1 -1
  4. package/dist/index.js +159 -1
  5. package/dist/index.js.map +1 -1
  6. package/dist/loadBalancer/configure/common/gceLoadBalancerModels.d.ts +13 -2
  7. package/dist/loadBalancer/configure/http/GceHttpLoadBalancerEditor.d.ts +3 -0
  8. package/dist/loadBalancer/configure/http/GceHttpLoadBalancerModal.d.ts +1 -1
  9. package/dist/loadBalancer/configure/network/GceRegionalExternalNetworkLoadBalancerEditor.d.ts +30 -0
  10. package/dist/loadBalancer/configure/network/GceRegionalExternalNetworkLoadBalancerModal.d.ts +53 -0
  11. package/dist/loadBalancer/httpLoadBalancerUtils.service.d.ts +3 -1
  12. package/dist/serverGroup/configure/wizard/GceCloneServerGroupModal.d.ts +3 -0
  13. package/dist/serverGroup/configure/wizard/GceServerGroupWizard.types.d.ts +1 -0
  14. package/dist/serverGroup/configure/wizard/customInstance/CustomInstanceConfigurer.d.ts +1 -1
  15. package/dist/serverGroup/configure/wizard/customInstance/GceCustomInstanceBuilder.d.ts +2 -2
  16. package/dist/serverGroup/configure/wizard/pages/AdvancedSettings.d.ts +3 -0
  17. package/dist/serverGroup/configure/wizard/pages/GceServerGroupLoadBalancers.d.ts +1 -0
  18. package/dist/serverGroup/configure/wizard/pages/Policies.d.ts +21 -3
  19. package/dist/serverGroup/configure/wizard/pages/ServerGroupBasicSettings.d.ts +1 -0
  20. package/dist/serverGroup/configure/wizard/pages/ServerGroupCapacity.d.ts +1 -0
  21. package/dist/serverGroup/configure/wizard/pages/ServerGroupImageSettings.d.ts +1 -0
  22. package/dist/serverGroup/configure/wizard/pages/ServerGroupInstanceType.d.ts +1 -0
  23. package/dist/serverGroup/configure/wizard/zones/GceInstanceFlexibilityConfigurer.d.ts +2 -2
  24. package/dist/serverGroup/details/GceInstanceFlexibilityPolicyDetails.d.ts +2 -2
  25. package/dist/serverGroup/details/stateful/MarkDiskStatefulButton.d.ts +2 -2
  26. package/dist/serverGroup/details/stateful/UpdateBootImageButton.d.ts +2 -2
  27. package/package.json +5 -5
  28. package/src/autoHealingPolicy/GceAutoHealingPolicyEditor.spec.tsx +68 -52
  29. package/src/autoscalingPolicy/GceAutoscalingPolicyEditor.spec.tsx +43 -50
  30. package/src/autoscalingPolicy/GceAutoscalingPolicyEditor.tsx +1 -1
  31. package/src/autoscalingPolicy/GceAutoscalingPolicyWriter.spec.ts +1 -1
  32. package/src/domain/loadBalancer.ts +6 -3
  33. package/src/gce.module.spec.ts +132 -153
  34. package/src/instance/details/GceInstanceDetails.spec.tsx +86 -83
  35. package/src/loadBalancer/configure/choice/GceLoadBalancerChoiceModal.spec.tsx +162 -121
  36. package/src/loadBalancer/configure/choice/GceLoadBalancerChoiceModal.tsx +13 -1
  37. package/src/loadBalancer/configure/common/GceProxyLoadBalancerEditor.spec.tsx +47 -30
  38. package/src/loadBalancer/configure/common/GceProxyLoadBalancerEditor.tsx +2 -1
  39. package/src/loadBalancer/configure/common/GceProxyLoadBalancerModal.spec.tsx +85 -57
  40. package/src/loadBalancer/configure/common/gceLoadBalancerData.spec.ts +17 -16
  41. package/src/loadBalancer/configure/common/gceLoadBalancerModels.spec.ts +230 -5
  42. package/src/loadBalancer/configure/common/gceLoadBalancerModels.ts +186 -22
  43. package/src/loadBalancer/configure/common/gceLoadBalancerSubmission.spec.ts +298 -2
  44. package/src/loadBalancer/configure/common/gceLoadBalancerSubmission.ts +35 -5
  45. package/src/loadBalancer/configure/common/useGceLoadBalancerData.spec.tsx +37 -46
  46. package/src/loadBalancer/configure/http/GceHttpLoadBalancerEditor.spec.tsx +399 -58
  47. package/src/loadBalancer/configure/http/GceHttpLoadBalancerEditor.tsx +148 -41
  48. package/src/loadBalancer/configure/http/GceHttpLoadBalancerListenerEditor.spec.tsx +169 -36
  49. package/src/loadBalancer/configure/http/GceHttpLoadBalancerListenerEditor.tsx +80 -12
  50. package/src/loadBalancer/configure/http/GceHttpLoadBalancerModal.spec.tsx +224 -124
  51. package/src/loadBalancer/configure/http/GceHttpLoadBalancerModal.tsx +8 -2
  52. package/src/loadBalancer/configure/http/GceHttpLoadBalancerResourceEditors.spec.tsx +57 -22
  53. package/src/loadBalancer/configure/http/GceHttpLoadBalancerResourceEditors.tsx +21 -0
  54. package/src/loadBalancer/configure/http/GceHttpLoadBalancerRoutingEditor.spec.tsx +24 -20
  55. package/src/loadBalancer/configure/network/GceNetworkLoadBalancerEditor.spec.tsx +41 -32
  56. package/src/loadBalancer/configure/network/GceNetworkLoadBalancerModal.spec.tsx +35 -30
  57. package/src/loadBalancer/configure/network/GceRegionalExternalNetworkLoadBalancerEditor.spec.tsx +443 -0
  58. package/src/loadBalancer/configure/network/GceRegionalExternalNetworkLoadBalancerEditor.tsx +401 -0
  59. package/src/loadBalancer/configure/network/GceRegionalExternalNetworkLoadBalancerModal.spec.tsx +330 -0
  60. package/src/loadBalancer/configure/network/GceRegionalExternalNetworkLoadBalancerModal.tsx +348 -0
  61. package/src/loadBalancer/details/gceLoadBalancerDetails.spec.tsx +439 -35
  62. package/src/loadBalancer/details/gceLoadBalancerDetails.tsx +104 -21
  63. package/src/loadBalancer/httpLoadBalancerUtils.service.spec.ts +126 -0
  64. package/src/loadBalancer/httpLoadBalancerUtils.service.ts +27 -11
  65. package/src/loadBalancer/loadBalancer.setTransformer.spec.ts +165 -0
  66. package/src/loadBalancer/loadBalancer.setTransformer.ts +15 -2
  67. package/src/pipeline/stages/googleStageConfig.spec.js +22 -16
  68. package/src/securityGroup/configure/GceSecurityGroupModal.spec.tsx +177 -193
  69. package/src/securityGroup/configure/GceSecurityGroupModal.tsx +14 -2
  70. package/src/securityGroup/details/GceSecurityGroupDetails.spec.tsx +69 -78
  71. package/src/serverGroup/configure/serverGroupCommandBuilder.service.spec.js +35 -15
  72. package/src/serverGroup/configure/serverGroupConfiguration.service.js +7 -1
  73. package/src/serverGroup/configure/serverGroupConfiguration.service.spec.js +74 -4
  74. package/src/serverGroup/configure/wizard/GceCloneServerGroupModal.spec.tsx +593 -855
  75. package/src/serverGroup/configure/wizard/GceCloneServerGroupModal.tsx +120 -38
  76. package/src/serverGroup/configure/wizard/GceServerGroupWizard.types.ts +1 -0
  77. package/src/serverGroup/configure/wizard/GceServerGroupWizardPage.spec.tsx +53 -50
  78. package/src/serverGroup/configure/wizard/customInstance/GceCustomInstanceBuilder.spec.tsx +65 -49
  79. package/src/serverGroup/configure/wizard/hiddenMetadataKeys.value.js +1 -0
  80. package/src/serverGroup/configure/wizard/pages/AdvancedSettings.spec.tsx +101 -108
  81. package/src/serverGroup/configure/wizard/pages/AdvancedSettings.tsx +32 -28
  82. package/src/serverGroup/configure/wizard/pages/GceServerGroupFirewalls.spec.tsx +60 -59
  83. package/src/serverGroup/configure/wizard/pages/GceServerGroupLoadBalancers.spec.tsx +240 -114
  84. package/src/serverGroup/configure/wizard/pages/GceServerGroupLoadBalancers.tsx +152 -63
  85. package/src/serverGroup/configure/wizard/pages/Policies.spec.tsx +93 -48
  86. package/src/serverGroup/configure/wizard/pages/Policies.tsx +80 -55
  87. package/src/serverGroup/configure/wizard/pages/ServerGroupBasicSettings.spec.tsx +123 -94
  88. package/src/serverGroup/configure/wizard/pages/ServerGroupBasicSettings.tsx +24 -18
  89. package/src/serverGroup/configure/wizard/pages/ServerGroupCapacity.spec.tsx +152 -133
  90. package/src/serverGroup/configure/wizard/pages/ServerGroupCapacity.tsx +40 -37
  91. package/src/serverGroup/configure/wizard/pages/ServerGroupImageSettings.spec.tsx +66 -60
  92. package/src/serverGroup/configure/wizard/pages/ServerGroupImageSettings.tsx +8 -2
  93. package/src/serverGroup/configure/wizard/pages/ServerGroupInstanceType.spec.tsx +131 -101
  94. package/src/serverGroup/configure/wizard/pages/ServerGroupInstanceType.tsx +29 -28
  95. package/src/serverGroup/configure/wizard/zones/GceInstanceFlexibilityConfigurer.spec.tsx +104 -81
  96. package/src/serverGroup/details/GceInstanceFlexibilityPolicyDetails.spec.tsx +10 -11
  97. package/src/serverGroup/details/autoHealingPolicy/GceAutoHealingPolicyDetails.spec.tsx +51 -59
  98. package/src/serverGroup/details/autoHealingPolicy/modal/GceUpsertAutoHealingPolicyModal.spec.tsx +3 -3
  99. package/src/serverGroup/details/autoscalingPolicy/GceAutoscalingPolicyDetails.spec.tsx +49 -58
  100. package/src/serverGroup/details/autoscalingPolicy/modal/GceUpsertAutoscalingPolicyModal.spec.tsx +3 -3
  101. package/src/serverGroup/details/gceServerGroupDetails.spec.tsx +162 -141
  102. package/src/serverGroup/details/resize/GceResizeServerGroupModal.spec.tsx +102 -86
  103. package/src/serverGroup/details/resize/GceResizeServerGroupModal.tsx +4 -0
  104. package/src/serverGroup/details/rollback/GceRollbackServerGroupModal.spec.tsx +40 -31
  105. package/src/serverGroup/serverGroup.transformer.js +1 -0
  106. package/src/serverGroup/serverGroup.transformer.spec.js +48 -0
  107. package/src/validation/ApplicationNameValidator.spec.ts +1 -1
  108. /package/dist/{gce.logo2ae85d8704b1d6b8.png → gce.logoChI3w_A5.png} +0 -0
@@ -1,13 +1,15 @@
1
- import { shallow } from 'enzyme';
1
+ import { fireEvent, render, screen } from '@testing-library/react';
2
2
  import React from 'react';
3
3
 
4
- import { HelpField } from '@spinnaker/core';
4
+ import { HelpTextExpandedContext } from '@spinnaker/core';
5
5
 
6
6
  import {
7
7
  GceInstanceFlexibilityConfigurer,
8
8
  hasValidFlexibilityPolicy,
9
9
  nextSelectionName,
10
10
  } from './GceInstanceFlexibilityConfigurer';
11
+ import { setupUser } from '../../../../../../core/src/utils/testUtils/userEvent';
12
+ import '../../../../help/gce.help';
11
13
 
12
14
  describe('GceInstanceFlexibilityConfigurer', () => {
13
15
  const policy = {
@@ -17,15 +19,19 @@ describe('GceInstanceFlexibilityConfigurer', () => {
17
19
  };
18
20
 
19
21
  it('links the flexibility editor to its help content', () => {
20
- const wrapper = shallow(
21
- <GceInstanceFlexibilityConfigurer
22
- regional={true}
23
- targetShape="BALANCED"
24
- setInstanceFlexibilityPolicy={jasmine.createSpy('setPolicy')}
25
- />,
22
+ render(
23
+ <HelpTextExpandedContext.Provider value={true}>
24
+ <GceInstanceFlexibilityConfigurer
25
+ regional={true}
26
+ targetShape="BALANCED"
27
+ setInstanceFlexibilityPolicy={vi.fn()}
28
+ />
29
+ </HelpTextExpandedContext.Provider>,
26
30
  );
27
31
 
28
- expect(wrapper.find(HelpField).prop('id')).toBe('gce.serverGroup.instanceFlexibilityPolicy');
32
+ expect(
33
+ screen.getByText(/Defines named sets of acceptable machine types for a regional managed instance group/),
34
+ ).toBeInTheDocument();
29
35
  });
30
36
 
31
37
  it('allows EVEN when flexibility is absent', () => {
@@ -124,16 +130,15 @@ describe('GceInstanceFlexibilityConfigurer', () => {
124
130
  }),
125
131
  ).toBe(false);
126
132
 
127
- const wrapper = shallow(
133
+ render(
128
134
  <GceInstanceFlexibilityConfigurer
129
135
  instanceFlexibilityPolicy={duplicatePolicy}
130
136
  regional={true}
131
137
  targetShape="BALANCED"
132
- setInstanceFlexibilityPolicy={jasmine.createSpy('setPolicy')}
138
+ setInstanceFlexibilityPolicy={vi.fn()}
133
139
  />,
134
140
  );
135
- expect(wrapper.text()).toContain('Machine types must be unique across instance selections.');
136
- expect(wrapper.find('[role="alert"]').text()).toBe('Machine types must be unique across instance selections.');
141
+ expect(screen.getByRole('alert')).toHaveTextContent(/^Machine types must be unique across instance selections\.$/);
137
142
  });
138
143
 
139
144
  it('accepts distinct machine types across selections', () => {
@@ -168,7 +173,7 @@ describe('GceInstanceFlexibilityConfigurer', () => {
168
173
  });
169
174
 
170
175
  it('announces submission errors and associates malformed persisted controls', () => {
171
- const wrapper = shallow(
176
+ render(
172
177
  <GceInstanceFlexibilityConfigurer
173
178
  instanceFlexibilityPolicy={{
174
179
  instanceSelections: {
@@ -178,22 +183,21 @@ describe('GceInstanceFlexibilityConfigurer', () => {
178
183
  regional={true}
179
184
  targetShape="BALANCED"
180
185
  validationError="Instance flexibility policy is invalid."
181
- setInstanceFlexibilityPolicy={jasmine.createSpy('setPolicy')}
186
+ setInstanceFlexibilityPolicy={vi.fn()}
182
187
  />,
183
188
  );
184
- const errorId = 'gce-instance-flexibility-error';
185
- const rankInput = wrapper.find('#instance-flexibility-selection-preferred-rank');
186
- const blankMachineType = wrapper.find('input[aria-label="Machine type 1 for selection preferred"]');
187
- const validMachineType = wrapper.find('input[aria-label="Machine type 2 for selection preferred"]');
188
-
189
- expect(wrapper.find(`#${errorId}`).prop('role')).toBe('alert');
190
- expect(wrapper.find(`#${errorId}`).text()).toBe('Instance flexibility policy is invalid.');
191
- expect(rankInput.prop('aria-invalid')).toBe(true);
192
- expect(rankInput.prop('aria-describedby')).toBe(errorId);
193
- expect(blankMachineType.prop('aria-invalid')).toBe(true);
194
- expect(blankMachineType.prop('aria-describedby')).toBe(errorId);
195
- expect(validMachineType.prop('aria-invalid')).toBe(false);
196
- expect(validMachineType.prop('aria-describedby')).toBeUndefined();
189
+ const alert = screen.getByRole('alert');
190
+ const rankInput = screen.getByLabelText('Rank (optional)');
191
+ const blankMachineType = screen.getByRole('textbox', { name: 'Machine type 1 for selection preferred' });
192
+ const validMachineType = screen.getByRole('textbox', { name: 'Machine type 2 for selection preferred' });
193
+
194
+ expect(alert).toHaveTextContent(/^Instance flexibility policy is invalid\.$/);
195
+ expect(rankInput).toBeInvalid();
196
+ expect(rankInput).toHaveAccessibleDescription('Instance flexibility policy is invalid.');
197
+ expect(blankMachineType).toBeInvalid();
198
+ expect(blankMachineType).toHaveAccessibleDescription('Instance flexibility policy is invalid.');
199
+ expect(validMachineType).toBeValid();
200
+ expect(validMachineType).not.toHaveAttribute('aria-describedby');
197
201
  });
198
202
 
199
203
  it('accepts regional BALANCED/ANY/ANY_SINGLE_ZONE with rankless selections', () => {
@@ -221,9 +225,10 @@ describe('GceInstanceFlexibilityConfigurer', () => {
221
225
  expect(nextSelectionName(['selection-2'])).toBe('selection-1');
222
226
  });
223
227
 
224
- it('adds a named selection through the configurer', () => {
225
- const setPolicy = jasmine.createSpy('setPolicy');
226
- const wrapper = shallow(
228
+ it('adds a named selection through the configurer', async () => {
229
+ const user = setupUser();
230
+ const setPolicy = vi.fn();
231
+ render(
227
232
  <GceInstanceFlexibilityConfigurer
228
233
  instanceFlexibilityPolicy={undefined}
229
234
  regional={true}
@@ -232,7 +237,7 @@ describe('GceInstanceFlexibilityConfigurer', () => {
232
237
  />,
233
238
  );
234
239
 
235
- wrapper.find('button').simulate('click');
240
+ await user.click(screen.getByRole('button', { name: 'Add flexibility policy' }));
236
241
 
237
242
  expect(setPolicy).toHaveBeenCalledWith({
238
243
  instanceSelections: {
@@ -241,9 +246,10 @@ describe('GceInstanceFlexibilityConfigurer', () => {
241
246
  });
242
247
  });
243
248
 
244
- it('does not overwrite an existing renamed selection when adding another', () => {
245
- const setPolicy = jasmine.createSpy('setPolicy');
246
- const wrapper = shallow(
249
+ it('does not overwrite an existing renamed selection when adding another', async () => {
250
+ const user = setupUser();
251
+ const setPolicy = vi.fn();
252
+ render(
247
253
  <GceInstanceFlexibilityConfigurer
248
254
  instanceFlexibilityPolicy={{
249
255
  instanceSelections: {
@@ -256,7 +262,7 @@ describe('GceInstanceFlexibilityConfigurer', () => {
256
262
  />,
257
263
  );
258
264
 
259
- wrapper.find('button').last().simulate('click');
265
+ await user.click(screen.getByRole('button', { name: 'Add selection' }));
260
266
 
261
267
  expect(setPolicy).toHaveBeenCalledWith({
262
268
  instanceSelections: {
@@ -266,9 +272,10 @@ describe('GceInstanceFlexibilityConfigurer', () => {
266
272
  });
267
273
  });
268
274
 
269
- it('sends an explicit empty policy when the final selection is removed', () => {
270
- const setPolicy = jasmine.createSpy('setPolicy');
271
- const wrapper = shallow(
275
+ it('sends an explicit empty policy when the final selection is removed', async () => {
276
+ const user = setupUser();
277
+ const setPolicy = vi.fn();
278
+ render(
272
279
  <GceInstanceFlexibilityConfigurer
273
280
  instanceFlexibilityPolicy={policy}
274
281
  regional={true}
@@ -277,17 +284,14 @@ describe('GceInstanceFlexibilityConfigurer', () => {
277
284
  />,
278
285
  );
279
286
 
280
- wrapper
281
- .find('button')
282
- .filterWhere((button) => button.text() === 'Remove')
283
- .simulate('click');
287
+ await user.click(screen.getByRole('button', { name: 'Remove selection preferred' }));
284
288
 
285
289
  expect(setPolicy).toHaveBeenCalledWith({ instanceSelections: {} });
286
290
  });
287
291
 
288
292
  it('only persists finite non-negative integer ranks and supports rank zero and clearing', () => {
289
- const setPolicy = jasmine.createSpy('setPolicy');
290
- const wrapper = shallow(
293
+ const setPolicy = vi.fn();
294
+ render(
291
295
  <GceInstanceFlexibilityConfigurer
292
296
  instanceFlexibilityPolicy={policy}
293
297
  regional={true}
@@ -295,24 +299,25 @@ describe('GceInstanceFlexibilityConfigurer', () => {
295
299
  setInstanceFlexibilityPolicy={setPolicy}
296
300
  />,
297
301
  );
298
- const rankInput = wrapper.find('input[type="number"]');
302
+ const rankInput = screen.getByRole('spinbutton', { name: 'Rank (optional)' });
299
303
 
300
- expect(rankInput.prop('step')).toBe(1);
304
+ expect(rankInput).toHaveAttribute('step', '1');
301
305
 
302
- rankInput.simulate('change', { target: { value: '1.5' } });
303
- rankInput.simulate('change', { target: { value: '-1' } });
304
- rankInput.simulate('change', { target: { value: 'not-a-number' } });
306
+ // Non-numeric text cannot be entered into a number input (the DOM sanitizes it to ''), so only
307
+ // fractional and negative values are exercised as rejected ranks.
308
+ fireEvent.change(rankInput, { target: { value: '1.5' } });
309
+ fireEvent.change(rankInput, { target: { value: '-1' } });
305
310
  expect(setPolicy).not.toHaveBeenCalled();
306
311
 
307
- rankInput.simulate('change', { target: { value: '0' } });
312
+ fireEvent.change(rankInput, { target: { value: '0' } });
308
313
  expect(setPolicy).toHaveBeenCalledWith({
309
314
  instanceSelections: {
310
315
  preferred: { rank: 0, machineTypes: ['n2-standard-8'] },
311
316
  },
312
317
  });
313
318
 
314
- setPolicy.calls.reset();
315
- rankInput.simulate('change', { target: { value: '' } });
319
+ setPolicy.mockClear();
320
+ fireEvent.change(rankInput, { target: { value: '' } });
316
321
  expect(setPolicy).toHaveBeenCalledWith({
317
322
  instanceSelections: {
318
323
  preferred: { machineTypes: ['n2-standard-8'] },
@@ -320,9 +325,10 @@ describe('GceInstanceFlexibilityConfigurer', () => {
320
325
  });
321
326
  });
322
327
 
323
- it('resets blank and duplicate rename drafts to the current valid selection name', () => {
324
- const setPolicy = jasmine.createSpy('setPolicy');
325
- const wrapper = shallow(
328
+ it('resets blank and duplicate rename drafts to the current valid selection name', async () => {
329
+ const user = setupUser();
330
+ const setPolicy = vi.fn();
331
+ render(
326
332
  <GceInstanceFlexibilityConfigurer
327
333
  instanceFlexibilityPolicy={{
328
334
  instanceSelections: {
@@ -335,56 +341,73 @@ describe('GceInstanceFlexibilityConfigurer', () => {
335
341
  setInstanceFlexibilityPolicy={setPolicy}
336
342
  />,
337
343
  );
338
- const preferredNameInput = wrapper.find('input').filterWhere((input) => input.prop('defaultValue') === 'preferred');
339
- const blankDraft = { value: ' ' };
340
- const duplicateDraft = { value: 'fallback' };
341
-
342
- preferredNameInput.simulate('blur', { currentTarget: blankDraft, target: blankDraft });
343
- expect(blankDraft.value).toBe('preferred');
344
- preferredNameInput.simulate('blur', { currentTarget: duplicateDraft, target: duplicateDraft });
345
- expect(duplicateDraft.value).toBe('preferred');
344
+ const preferredNameInput = screen.getByDisplayValue('preferred');
345
+
346
+ await user.clear(preferredNameInput);
347
+ await user.type(preferredNameInput, ' ');
348
+ await user.tab();
349
+ expect(preferredNameInput).toHaveValue('preferred');
350
+
351
+ await user.clear(preferredNameInput);
352
+ await user.type(preferredNameInput, 'fallback');
353
+ await user.tab();
354
+ expect(preferredNameInput).toHaveValue('preferred');
346
355
  expect(setPolicy).not.toHaveBeenCalled();
347
356
  });
348
357
 
349
358
  it('associates labels and contextual accessible names with selection controls', () => {
350
- const wrapper = shallow(
359
+ render(
351
360
  <GceInstanceFlexibilityConfigurer
352
361
  instanceFlexibilityPolicy={policy}
353
362
  regional={true}
354
363
  targetShape="BALANCED"
355
- setInstanceFlexibilityPolicy={jasmine.createSpy('setPolicy')}
364
+ setInstanceFlexibilityPolicy={vi.fn()}
356
365
  />,
357
366
  );
358
367
  const selectionId = 'instance-flexibility-selection-preferred';
359
368
 
360
- expect(wrapper.find(`label[htmlFor="${selectionId}-name"]`).text()).toBe('Selection name');
361
- expect(wrapper.find(`input#${selectionId}-name`).exists()).toBe(true);
362
- expect(wrapper.find(`label[htmlFor="${selectionId}-rank"]`).text()).toBe('Rank (optional)');
363
- expect(wrapper.find(`input#${selectionId}-rank`).exists()).toBe(true);
364
- expect(wrapper.find(`input[aria-label="Machine type 1 for selection preferred"]`).prop('id')).toBe(
369
+ expect(screen.getByRole('textbox', { name: 'Selection name' })).toHaveAttribute('id', `${selectionId}-name`);
370
+ expect(screen.getByRole('spinbutton', { name: 'Rank (optional)' })).toHaveAttribute('id', `${selectionId}-rank`);
371
+ expect(screen.getByRole('textbox', { name: 'Machine type 1 for selection preferred' })).toHaveAttribute(
372
+ 'id',
365
373
  `${selectionId}-machine-type-0`,
366
374
  );
367
- expect(wrapper.find('button[aria-label="Remove selection preferred"]').exists()).toBe(true);
368
- expect(wrapper.find('button[aria-label="Remove machine type 1 from selection preferred"]').exists()).toBe(true);
375
+ expect(screen.getByRole('button', { name: 'Remove selection preferred' })).toBeInTheDocument();
376
+ expect(screen.getByRole('button', { name: 'Remove machine type 1 from selection preferred' })).toBeInTheDocument();
369
377
  });
370
378
 
371
379
  it('rerenders when policy and sibling primitive props change identity', () => {
372
- const wrapper = shallow(
380
+ const setPolicy = vi.fn();
381
+ const { rerender } = render(
373
382
  <GceInstanceFlexibilityConfigurer
374
383
  instanceFlexibilityPolicy={undefined}
375
384
  regional={true}
376
385
  targetShape="BALANCED"
377
- setInstanceFlexibilityPolicy={jasmine.createSpy('setPolicy')}
386
+ setInstanceFlexibilityPolicy={setPolicy}
378
387
  />,
379
388
  );
380
389
 
381
- expect(wrapper.text()).toContain('Add flexibility policy');
390
+ expect(screen.getByRole('button', { name: 'Add flexibility policy' })).toBeInTheDocument();
382
391
 
383
- wrapper.setProps({ instanceFlexibilityPolicy: policy });
384
- expect(wrapper.find('button[aria-label="Remove selection preferred"]').exists()).toBe(true);
392
+ rerender(
393
+ <GceInstanceFlexibilityConfigurer
394
+ instanceFlexibilityPolicy={policy}
395
+ regional={true}
396
+ targetShape="BALANCED"
397
+ setInstanceFlexibilityPolicy={setPolicy}
398
+ />,
399
+ );
400
+ expect(screen.getByRole('button', { name: 'Remove selection preferred' })).toBeInTheDocument();
385
401
 
386
- wrapper.setProps({ regional: false, targetShape: 'EVEN' });
387
- expect(wrapper.text()).toContain('Flexibility requires a regional server group.');
388
- expect(wrapper.text()).toContain('not EVEN');
402
+ rerender(
403
+ <GceInstanceFlexibilityConfigurer
404
+ instanceFlexibilityPolicy={policy}
405
+ regional={false}
406
+ targetShape="EVEN"
407
+ setInstanceFlexibilityPolicy={setPolicy}
408
+ />,
409
+ );
410
+ expect(screen.getByText('Flexibility requires a regional server group.')).toBeInTheDocument();
411
+ expect(screen.getByText(/not EVEN/)).toBeInTheDocument();
389
412
  });
390
413
  });
@@ -1,11 +1,11 @@
1
- import { shallow } from 'enzyme';
1
+ import { render } from '@testing-library/react';
2
2
  import React from 'react';
3
3
 
4
4
  import { GceInstanceFlexibilityPolicyDetails } from './GceInstanceFlexibilityPolicyDetails';
5
5
 
6
6
  describe('GceInstanceFlexibilityPolicyDetails', () => {
7
7
  it('renders selection names, optional ranks, and machine types without duplicating target shape', () => {
8
- const wrapper = shallow(
8
+ const { container } = render(
9
9
  <GceInstanceFlexibilityPolicyDetails
10
10
  instanceFlexibilityPolicy={{
11
11
  instanceSelections: {
@@ -16,17 +16,16 @@ describe('GceInstanceFlexibilityPolicyDetails', () => {
16
16
  />,
17
17
  );
18
18
 
19
- const text = wrapper.text();
20
- expect(text).toContain('preferred');
21
- expect(text).toContain('n2-standard-8');
22
- expect(text).toContain('fallback');
23
- expect(text).toContain('1');
24
- expect(text).toContain('e2-standard-8, c3-standard-8');
25
- expect(text).not.toContain('Target Shape');
19
+ expect(container).toHaveTextContent('preferred');
20
+ expect(container).toHaveTextContent('n2-standard-8');
21
+ expect(container).toHaveTextContent('fallback');
22
+ expect(container).toHaveTextContent('1');
23
+ expect(container).toHaveTextContent('e2-standard-8, c3-standard-8');
24
+ expect(container).not.toHaveTextContent('Target Shape');
26
25
  });
27
26
 
28
27
  it('renders nothing when the policy is absent', () => {
29
- const wrapper = shallow(<GceInstanceFlexibilityPolicyDetails />);
30
- expect(wrapper.type()).toBeNull();
28
+ const { container } = render(<GceInstanceFlexibilityPolicyDetails />);
29
+ expect(container).toBeEmptyDOMElement();
31
30
  });
32
31
  });
@@ -1,10 +1,10 @@
1
+ import { fireEvent, render, screen, waitFor } from '@testing-library/react';
1
2
  import React from 'react';
2
- import { shallow } from 'enzyme';
3
3
 
4
4
  import { ConfirmationModalService } from '@spinnaker/core';
5
5
 
6
- import { GceAutoscalingPolicyWriter } from '../../../autoscalingPolicy';
7
6
  import { GceAutoHealingPolicyDetails } from './GceAutoHealingPolicyDetails';
7
+ import { GceAutoscalingPolicyWriter } from '../../../autoscalingPolicy';
8
8
  import { GceUpsertAutoHealingPolicyModal } from './modal/GceUpsertAutoHealingPolicyModal';
9
9
 
10
10
  describe('GceAutoHealingPolicyDetails', () => {
@@ -22,7 +22,7 @@ describe('GceAutoHealingPolicyDetails', () => {
22
22
  const policy = { healthCheck: 'web', initialDelaySec: 0, maxUnavailable: { fixed: 0 } } as any;
23
23
 
24
24
  it('summarizes zero-valued delay without rendering legacy max unavailable data', () => {
25
- const wrapper = shallow(
25
+ const { container } = render(
26
26
  <GceAutoHealingPolicyDetails
27
27
  application={application}
28
28
  mutationsEnabled={true}
@@ -31,16 +31,16 @@ describe('GceAutoHealingPolicyDetails', () => {
31
31
  />,
32
32
  );
33
33
 
34
- expect(wrapper.text()).toContain('web');
35
- expect(wrapper.text()).toContain('0 seconds');
36
- expect(wrapper.text()).not.toContain('Max Unavailable');
37
- expect(wrapper.text()).not.toContain('0 fixed');
34
+ expect(screen.getByText('web')).toBeInTheDocument();
35
+ expect(screen.getByText('0 seconds')).toBeInTheDocument();
36
+ expect(container).not.toHaveTextContent('Max Unavailable');
37
+ expect(container).not.toHaveTextContent('0 fixed');
38
38
  });
39
39
 
40
40
  it('opens edit after managed-resource confirmation proceeds', async () => {
41
- const show = spyOn(GceUpsertAutoHealingPolicyModal, 'show');
42
- const confirm = spyOn(ConfirmationModalService, 'confirm').and.returnValue(Promise.resolve() as any);
43
- const wrapper = shallow(
41
+ const show = vi.spyOn(GceUpsertAutoHealingPolicyModal, 'show').mockReturnValue(undefined);
42
+ const confirm = vi.spyOn(ConfirmationModalService, 'confirm').mockReturnValue(Promise.resolve() as any);
43
+ render(
44
44
  <GceAutoHealingPolicyDetails
45
45
  application={application}
46
46
  mutationsEnabled={true}
@@ -49,19 +49,17 @@ describe('GceAutoHealingPolicyDetails', () => {
49
49
  />,
50
50
  );
51
51
 
52
- wrapper.find('[data-testid="edit-auto-healing-policy"]').simulate('click');
52
+ fireEvent.click(screen.getByTestId('edit-auto-healing-policy'));
53
53
  expect(show).not.toHaveBeenCalled();
54
- await Promise.resolve();
55
- await Promise.resolve();
56
54
 
57
- expect(confirm).toHaveBeenCalledWith(jasmine.objectContaining({ header: 'Pause Management?' }));
58
- expect(show).toHaveBeenCalledWith({ application, serverGroup: managedServerGroup, policy });
55
+ expect(confirm).toHaveBeenCalledWith(expect.objectContaining({ header: 'Pause Management?' }));
56
+ await waitFor(() => expect(show).toHaveBeenCalledWith({ application, serverGroup: managedServerGroup, policy }));
59
57
  });
60
58
 
61
59
  it('does not open edit when managed-resource confirmation is cancelled', async () => {
62
- const show = spyOn(GceUpsertAutoHealingPolicyModal, 'show');
63
- spyOn(ConfirmationModalService, 'confirm').and.returnValue(Promise.reject() as any);
64
- const wrapper = shallow(
60
+ const show = vi.spyOn(GceUpsertAutoHealingPolicyModal, 'show').mockReturnValue(undefined);
61
+ vi.spyOn(ConfirmationModalService, 'confirm').mockReturnValue(Promise.reject() as any);
62
+ render(
65
63
  <GceAutoHealingPolicyDetails
66
64
  application={application}
67
65
  mutationsEnabled={true}
@@ -70,17 +68,16 @@ describe('GceAutoHealingPolicyDetails', () => {
70
68
  />,
71
69
  );
72
70
 
73
- wrapper.find('[data-testid="edit-auto-healing-policy"]').simulate('click');
74
- await Promise.resolve();
75
- await Promise.resolve();
71
+ fireEvent.click(screen.getByTestId('edit-auto-healing-policy'));
72
+ await waitFor(() => expect(ConfirmationModalService.confirm).toHaveBeenCalled());
76
73
 
77
74
  expect(show).not.toHaveBeenCalled();
78
75
  });
79
76
 
80
77
  it('opens add after managed-resource confirmation proceeds', async () => {
81
- const show = spyOn(GceUpsertAutoHealingPolicyModal, 'show');
82
- const confirm = spyOn(ConfirmationModalService, 'confirm').and.returnValue(Promise.resolve() as any);
83
- const wrapper = shallow(
78
+ const show = vi.spyOn(GceUpsertAutoHealingPolicyModal, 'show').mockReturnValue(undefined);
79
+ const confirm = vi.spyOn(ConfirmationModalService, 'confirm').mockReturnValue(Promise.resolve() as any);
80
+ render(
84
81
  <GceAutoHealingPolicyDetails
85
82
  application={application}
86
83
  mutationsEnabled={true}
@@ -89,19 +86,17 @@ describe('GceAutoHealingPolicyDetails', () => {
89
86
  />,
90
87
  );
91
88
 
92
- wrapper.find('[data-testid="add-auto-healing-policy"]').simulate('click');
89
+ fireEvent.click(screen.getByTestId('add-auto-healing-policy'));
93
90
  expect(show).not.toHaveBeenCalled();
94
- await Promise.resolve();
95
- await Promise.resolve();
96
91
 
97
- expect(confirm).toHaveBeenCalledWith(jasmine.objectContaining({ header: 'Pause Management?' }));
98
- expect(show).toHaveBeenCalledWith({ application, serverGroup: managedServerGroup });
92
+ expect(confirm).toHaveBeenCalledWith(expect.objectContaining({ header: 'Pause Management?' }));
93
+ await waitFor(() => expect(show).toHaveBeenCalledWith({ application, serverGroup: managedServerGroup }));
99
94
  });
100
95
 
101
96
  it('does not open add when managed-resource confirmation is cancelled', async () => {
102
- const show = spyOn(GceUpsertAutoHealingPolicyModal, 'show');
103
- spyOn(ConfirmationModalService, 'confirm').and.returnValue(Promise.reject() as any);
104
- const wrapper = shallow(
97
+ const show = vi.spyOn(GceUpsertAutoHealingPolicyModal, 'show').mockReturnValue(undefined);
98
+ vi.spyOn(ConfirmationModalService, 'confirm').mockReturnValue(Promise.reject() as any);
99
+ render(
105
100
  <GceAutoHealingPolicyDetails
106
101
  application={application}
107
102
  mutationsEnabled={true}
@@ -110,20 +105,19 @@ describe('GceAutoHealingPolicyDetails', () => {
110
105
  />,
111
106
  );
112
107
 
113
- wrapper.find('[data-testid="add-auto-healing-policy"]').simulate('click');
114
- await Promise.resolve();
115
- await Promise.resolve();
108
+ fireEvent.click(screen.getByTestId('add-auto-healing-policy'));
109
+ await waitFor(() => expect(ConfirmationModalService.confirm).toHaveBeenCalled());
116
110
 
117
111
  expect(show).not.toHaveBeenCalled();
118
112
  });
119
113
 
120
114
  it('offers delete confirmation after managed-resource confirmation proceeds', async () => {
121
- const confirm = spyOn(ConfirmationModalService, 'confirm').and.returnValues(
122
- Promise.resolve() as any,
123
- Promise.resolve() as any,
124
- );
125
- const deletePolicy = spyOn(GceAutoscalingPolicyWriter, 'deleteAutoHealingPolicy');
126
- const wrapper = shallow(
115
+ const confirm = vi
116
+ .spyOn(ConfirmationModalService, 'confirm')
117
+ .mockReturnValueOnce(Promise.resolve() as any)
118
+ .mockReturnValueOnce(Promise.resolve() as any);
119
+ const deletePolicy = vi.spyOn(GceAutoscalingPolicyWriter, 'deleteAutoHealingPolicy').mockReturnValue(undefined);
120
+ render(
127
121
  <GceAutoHealingPolicyDetails
128
122
  application={application}
129
123
  mutationsEnabled={true}
@@ -132,12 +126,10 @@ describe('GceAutoHealingPolicyDetails', () => {
132
126
  />,
133
127
  );
134
128
 
135
- wrapper.find('[data-testid="delete-auto-healing-policy"]').simulate('click');
136
- await Promise.resolve();
137
- await Promise.resolve();
129
+ fireEvent.click(screen.getByTestId('delete-auto-healing-policy'));
130
+ await waitFor(() => expect(confirm.mock.calls.length).toBe(2));
138
131
 
139
- expect(confirm.calls.count()).toBe(2);
140
- const deleteConfirmation = confirm.calls.mostRecent().args[0];
132
+ const deleteConfirmation = confirm.mock.lastCall[0];
141
133
  deleteConfirmation.submitMethod();
142
134
 
143
135
  expect(deletePolicy).toHaveBeenCalledWith(application, managedServerGroup);
@@ -147,9 +139,9 @@ describe('GceAutoHealingPolicyDetails', () => {
147
139
  const cancelledConfirmation = {
148
140
  then: (_onProceed: () => void, onCancel: () => void) => Promise.resolve(onCancel()),
149
141
  } as any;
150
- const confirm = spyOn(ConfirmationModalService, 'confirm').and.returnValue(cancelledConfirmation);
151
- const deletePolicy = spyOn(GceAutoscalingPolicyWriter, 'deleteAutoHealingPolicy');
152
- const wrapper = shallow(
142
+ const confirm = vi.spyOn(ConfirmationModalService, 'confirm').mockReturnValue(cancelledConfirmation);
143
+ const deletePolicy = vi.spyOn(GceAutoscalingPolicyWriter, 'deleteAutoHealingPolicy').mockReturnValue(undefined);
144
+ render(
153
145
  <GceAutoHealingPolicyDetails
154
146
  application={application}
155
147
  mutationsEnabled={true}
@@ -158,17 +150,16 @@ describe('GceAutoHealingPolicyDetails', () => {
158
150
  />,
159
151
  );
160
152
 
161
- wrapper.find('[data-testid="delete-auto-healing-policy"]').simulate('click');
162
- await Promise.resolve();
163
- await Promise.resolve();
153
+ fireEvent.click(screen.getByTestId('delete-auto-healing-policy'));
154
+ await waitFor(() => expect(confirm).toHaveBeenCalled());
164
155
 
165
- expect(confirm.calls.count()).toBe(1);
166
- expect(confirm).toHaveBeenCalledWith(jasmine.objectContaining({ header: 'Pause Management?' }));
156
+ expect(confirm.mock.calls.length).toBe(1);
157
+ expect(confirm).toHaveBeenCalledWith(expect.objectContaining({ header: 'Pause Management?' }));
167
158
  expect(deletePolicy).not.toHaveBeenCalled();
168
159
  });
169
160
 
170
161
  it('hides all mutation actions when mutations are disabled', () => {
171
- const details = shallow(
162
+ const details = render(
172
163
  <GceAutoHealingPolicyDetails
173
164
  application={application}
174
165
  mutationsEnabled={false}
@@ -176,7 +167,10 @@ describe('GceAutoHealingPolicyDetails', () => {
176
167
  policy={policy}
177
168
  />,
178
169
  );
179
- const emptyDetails = shallow(
170
+ expect(details.queryByTestId('edit-auto-healing-policy')).not.toBeInTheDocument();
171
+ expect(details.queryByTestId('delete-auto-healing-policy')).not.toBeInTheDocument();
172
+ details.unmount();
173
+ const emptyDetails = render(
180
174
  <GceAutoHealingPolicyDetails
181
175
  application={application}
182
176
  mutationsEnabled={false}
@@ -185,8 +179,6 @@ describe('GceAutoHealingPolicyDetails', () => {
185
179
  />,
186
180
  );
187
181
 
188
- expect(details.find('[data-testid="edit-auto-healing-policy"]').exists()).toBe(false);
189
- expect(details.find('[data-testid="delete-auto-healing-policy"]').exists()).toBe(false);
190
- expect(emptyDetails.isEmptyRender()).toBe(true);
182
+ expect(emptyDetails.container).toBeEmptyDOMElement();
191
183
  });
192
184
  });
@@ -23,15 +23,15 @@ describe('GceUpsertAutoHealingPolicyModal', () => {
23
23
  }
24
24
 
25
25
  it('retains edited policy state when task creation is rejected', async () => {
26
- spyOn(GceAutoscalingPolicyWriter, 'upsertAutoHealingPolicy').and.returnValue(
26
+ vi.spyOn(GceAutoscalingPolicyWriter, 'upsertAutoHealingPolicy').mockReturnValue(
27
27
  Promise.reject({ failureMessage: 'No permission' }),
28
28
  );
29
29
  const modal = new GceUpsertAutoHealingPolicyModal({
30
30
  application: { name: 'my-app' },
31
31
  serverGroup: { account: 'my-account', name: 'my-app-main-v001', region: 'us-central1' },
32
32
  policy: { healthCheck: 'web', initialDelaySec: 300 },
33
- closeModal: jasmine.createSpy('closeModal'),
34
- dismissModal: jasmine.createSpy('dismissModal'),
33
+ closeModal: vi.fn(),
34
+ dismissModal: vi.fn(),
35
35
  } as any);
36
36
  const editedPolicy = { healthCheck: 'web', initialDelaySec: 0 };
37
37
  modal.state.policy = editedPolicy;