@spinnaker/google 2026.3.2 → 2026.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,5 +1,5 @@
1
+ import { fireEvent, render, within } from '@testing-library/react';
1
2
  import React from 'react';
2
- import { shallow } from 'enzyme';
3
3
 
4
4
  import {
5
5
  GceHttpLoadBalancerBackendServiceEditor,
@@ -8,8 +8,8 @@ import {
8
8
 
9
9
  describe('GceHttpLoadBalancerResourceEditors', () => {
10
10
  it('edits complete health-check objects without dropping unknown fields', () => {
11
- const onChange = jasmine.createSpy('onChange');
12
- const wrapper = shallow(
11
+ const onChange = vi.fn();
12
+ const { getByTestId, getAllByRole } = render(
13
13
  <GceHttpLoadBalancerHealthCheckEditor
14
14
  healthCheck={{
15
15
  checkIntervalSec: 10,
@@ -22,40 +22,69 @@ describe('GceHttpLoadBalancerResourceEditors', () => {
22
22
  }}
23
23
  healthChecks={[{ name: 'check-a' }]}
24
24
  onChange={onChange}
25
- onRemove={jasmine.createSpy('onRemove')}
25
+ onRemove={vi.fn()}
26
26
  />,
27
27
  );
28
28
 
29
- wrapper.find('[data-testid="health-check-port"]').simulate('change', { target: { value: '8080' } });
29
+ fireEvent.change(getByTestId('health-check-port'), { target: { value: '8080' } });
30
30
 
31
31
  expect(onChange).toHaveBeenCalledWith(
32
- jasmine.objectContaining({ name: 'check-a', port: 8080, requestPath: '/health', unknownField: 'keep' }),
32
+ expect.objectContaining({ name: 'check-a', port: 8080, requestPath: '/health', unknownField: 'keep' }),
33
33
  );
34
- expect(wrapper.find('button').everyWhere((button) => button.prop('type') === 'button')).toBe(true);
34
+ expect(getAllByRole('button').every((button) => button.getAttribute('type') === 'button')).toBe(true);
35
35
  });
36
36
 
37
37
  it('supports HTTP2 request paths and GRPC service names', () => {
38
- const onChange = jasmine.createSpy('onChange');
39
- const wrapper = shallow(
38
+ const onChange = vi.fn();
39
+ const { getByTestId, queryByTestId, rerender } = render(
40
40
  <GceHttpLoadBalancerHealthCheckEditor
41
41
  healthCheck={{ healthCheckType: 'GRPC', name: 'grpc-check', port: 443 }}
42
42
  healthChecks={[]}
43
43
  onChange={onChange}
44
- onRemove={jasmine.createSpy('onRemove')}
44
+ onRemove={vi.fn()}
45
45
  />,
46
46
  );
47
47
 
48
- expect(
49
- wrapper.find('[data-testid="health-check-protocol"] option').map((option) => option.prop('value')),
50
- ).toContain('HTTP2');
51
- expect(
52
- wrapper.find('[data-testid="health-check-protocol"] option').map((option) => option.prop('value')),
53
- ).toContain('GRPC');
54
- expect(wrapper.find('[data-testid="health-check-grpc-service-name"]').length).toBe(1);
48
+ expect(optionValues(getByTestId('health-check-protocol'))).toContain('HTTP2');
49
+ expect(optionValues(getByTestId('health-check-protocol'))).toContain('GRPC');
50
+ expect(getByTestId('health-check-grpc-service-name')).toBeInTheDocument();
51
+ expect(queryByTestId('health-check-path')).not.toBeInTheDocument();
52
+
53
+ fireEvent.change(getByTestId('health-check-grpc-service-name'), { target: { value: 'new.Service' } });
54
+ expect(onChange).toHaveBeenLastCalledWith(
55
+ expect.objectContaining({ grpcServiceName: 'new.Service', healthCheckType: 'GRPC' }),
56
+ );
57
+
58
+ rerender(
59
+ <GceHttpLoadBalancerHealthCheckEditor
60
+ healthCheck={{ healthCheckType: 'HTTP2', name: 'http2-check', port: 443, requestPath: '/ready' }}
61
+ healthChecks={[]}
62
+ onChange={onChange}
63
+ onRemove={vi.fn()}
64
+ />,
65
+ );
66
+
67
+ expect(getByTestId('health-check-path')).toHaveValue('/ready');
68
+ expect(queryByTestId('health-check-grpc-service-name')).not.toBeInTheDocument();
69
+ });
70
+
71
+ it('offers only HTTP and HTTPS backend protocols for EXTERNAL_MANAGED', () => {
72
+ const { getByTestId } = render(
73
+ <GceHttpLoadBalancerBackendServiceEditor
74
+ backendService={{ name: 'backend-a', protocol: 'HTTP' }}
75
+ backendServices={[]}
76
+ healthChecks={[]}
77
+ loadBalancerType="EXTERNAL_MANAGED"
78
+ onChange={vi.fn()}
79
+ onRemove={vi.fn()}
80
+ />,
81
+ );
82
+
83
+ expect(optionValues(getByTestId('backend-service-protocol'))).toEqual(['HTTP', 'HTTPS']);
55
84
  });
56
85
 
57
86
  it('selects complete backend-service and health-check references', () => {
58
- const onChange = jasmine.createSpy('onChange');
87
+ const onChange = vi.fn();
59
88
  const backendServices = [
60
89
  {
61
90
  healthCheck: { name: 'check-a', selfLink: 'https://compute/healthChecks/check-a' },
@@ -65,22 +94,28 @@ describe('GceHttpLoadBalancerResourceEditors', () => {
65
94
  unknownField: 'keep',
66
95
  },
67
96
  ];
68
- const wrapper = shallow(
97
+ const { getByTestId, getAllByRole } = render(
69
98
  <GceHttpLoadBalancerBackendServiceEditor
70
99
  backendService={{ name: '' }}
71
100
  backendServices={backendServices}
72
101
  healthChecks={[{ name: 'check-a', selfLink: 'https://compute/healthChecks/check-a' }]}
73
102
  loadBalancerType="HTTP"
74
103
  onChange={onChange}
75
- onRemove={jasmine.createSpy('onRemove')}
104
+ onRemove={vi.fn()}
76
105
  />,
77
106
  );
78
107
 
79
- wrapper.find('[data-testid="backend-service-reference"]').simulate('change', {
108
+ fireEvent.change(getByTestId('backend-service-reference'), {
80
109
  target: { value: 'backend-a' },
81
110
  });
82
111
 
83
112
  expect(onChange).toHaveBeenCalledWith(backendServices[0]);
84
- expect(wrapper.find('button').everyWhere((button) => button.prop('type') === 'button')).toBe(true);
113
+ expect(getAllByRole('button').every((button) => button.getAttribute('type') === 'button')).toBe(true);
85
114
  });
86
115
  });
116
+
117
+ function optionValues(select: HTMLElement): string[] {
118
+ return within(select)
119
+ .getAllByRole('option')
120
+ .map((option) => (option as HTMLOptionElement).value);
121
+ }
@@ -239,6 +239,27 @@ export function GceHttpLoadBalancerBackendServiceEditor({
239
239
  onChange={(event) => onChange({ ...backendService, portName: event.target.value })}
240
240
  />
241
241
  </label>
242
+ {loadBalancerType === 'EXTERNAL_MANAGED' && (
243
+ <label>
244
+ Protocol
245
+ <select
246
+ data-testid="backend-service-protocol"
247
+ value={backendService.protocol || 'HTTP'}
248
+ onChange={(event) =>
249
+ onChange({
250
+ ...backendService,
251
+ protocol: event.target.value as IGceLoadBalancerBackendService['protocol'],
252
+ })
253
+ }
254
+ >
255
+ {['HTTP', 'HTTPS'].map((protocol) => (
256
+ <option key={protocol} value={protocol}>
257
+ {protocol}
258
+ </option>
259
+ ))}
260
+ </select>
261
+ </label>
262
+ )}
242
263
  <label>
243
264
  Connection draining timeout
244
265
  <input
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { shallow } from 'enzyme';
2
+ import { fireEvent, render, within } from '@testing-library/react';
3
3
 
4
4
  import {
5
5
  GceHttpLoadBalancerHostRuleEditor,
@@ -13,8 +13,8 @@ describe('GceHttpLoadBalancerRoutingEditor', () => {
13
13
  ];
14
14
 
15
15
  it('adds and removes exact nested path-rule rows with non-submit buttons', () => {
16
- const onChange = jasmine.createSpy('onChange');
17
- const wrapper = shallow(
16
+ const onChange = vi.fn();
17
+ const { getByTestId, getAllByRole } = render(
18
18
  <GceHttpLoadBalancerHostRuleEditor
19
19
  backendServices={backendServices}
20
20
  hostRule={{
@@ -25,11 +25,11 @@ describe('GceHttpLoadBalancerRoutingEditor', () => {
25
25
  },
26
26
  }}
27
27
  onChange={onChange}
28
- onRemove={jasmine.createSpy('onRemove')}
28
+ onRemove={vi.fn()}
29
29
  />,
30
30
  );
31
31
 
32
- wrapper.find('[data-testid="add-path-rule"]').simulate('click');
32
+ fireEvent.click(getByTestId('add-path-rule'));
33
33
 
34
34
  expect(onChange).toHaveBeenCalledWith({
35
35
  hostPatterns: ['api.example.com'],
@@ -38,45 +38,49 @@ describe('GceHttpLoadBalancerRoutingEditor', () => {
38
38
  pathRules: [{ backendService: backendServices[1], paths: ['/v1'] }, { paths: [] }],
39
39
  },
40
40
  });
41
- expect(wrapper.find('button').everyWhere((button) => button.prop('type') === 'button')).toBe(true);
41
+ expect(getAllByRole('button').every((button) => button.getAttribute('type') === 'button')).toBe(true);
42
42
  });
43
43
 
44
44
  it('edits path lists and preserves complete unresolved backend references', () => {
45
- const onChange = jasmine.createSpy('onChange');
46
- const wrapper = shallow(
45
+ const onChange = vi.fn();
46
+ const { getByTestId, getAllByRole } = render(
47
47
  <GceHttpLoadBalancerPathRuleEditor
48
48
  backendServices={backendServices}
49
49
  onChange={onChange}
50
- onRemove={jasmine.createSpy('onRemove')}
50
+ onRemove={vi.fn()}
51
51
  pathRule={{ backendService: backendServices[1], paths: ['/v1'] }}
52
52
  />,
53
53
  );
54
54
 
55
- wrapper.find('[data-testid="path-rule-paths"]').simulate('change', { target: { value: '/v1, /v2' } });
56
- wrapper.find('[data-testid="path-rule-backend"]').simulate('change', {
55
+ fireEvent.change(getByTestId('path-rule-paths'), { target: { value: '/v1, /v2' } });
56
+ fireEvent.change(getByTestId('path-rule-backend'), {
57
57
  target: { value: 'default-backend' },
58
58
  });
59
59
 
60
- expect(onChange.calls.argsFor(0)[0]).toEqual({ backendService: backendServices[1], paths: ['/v1', '/v2'] });
61
- expect(onChange.calls.argsFor(1)[0]).toEqual({ backendService: backendServices[0], paths: ['/v1'] });
62
- expect(wrapper.find('button').everyWhere((button) => button.prop('type') === 'button')).toBe(true);
60
+ expect(onChange.mock.calls[0][0]).toEqual({ backendService: backendServices[1], paths: ['/v1', '/v2'] });
61
+ expect(onChange.mock.calls[1][0]).toEqual({ backendService: backendServices[0], paths: ['/v1'] });
62
+ expect(getAllByRole('button').every((button) => button.getAttribute('type') === 'button')).toBe(true);
63
63
  });
64
64
 
65
65
  it('requires every path matcher to select an explicit default backend', () => {
66
- const wrapper = shallow(
66
+ const { getByTestId } = render(
67
67
  <GceHttpLoadBalancerHostRuleEditor
68
68
  backendServices={backendServices}
69
69
  hostRule={{
70
70
  hostPatterns: ['api.example.com'],
71
71
  pathMatcher: { defaultService: backendServices[0], pathRules: [] },
72
72
  }}
73
- onChange={jasmine.createSpy('onChange')}
74
- onRemove={jasmine.createSpy('onRemove')}
73
+ onChange={vi.fn()}
74
+ onRemove={vi.fn()}
75
75
  />,
76
76
  );
77
- const select = wrapper.find('[data-testid="host-rule-default-backend"]');
77
+ const select = getByTestId('host-rule-default-backend');
78
78
 
79
- expect(select.prop('required')).toBe(true);
80
- expect(select.find('option').map((option) => option.prop('value'))).toEqual(['default-backend', 'api-backend']);
79
+ expect(select).toBeRequired();
80
+ expect(
81
+ within(select)
82
+ .getAllByRole('option')
83
+ .map((option) => (option as HTMLOptionElement).value),
84
+ ).toEqual(['default-backend', 'api-backend']);
81
85
  });
82
86
  });
@@ -1,7 +1,5 @@
1
+ import { fireEvent, render, within } from '@testing-library/react';
1
2
  import React from 'react';
2
- import { shallow } from 'enzyme';
3
-
4
- import type { IGceLoadBalancerData } from '../common';
5
3
 
6
4
  import {
7
5
  buildGceNetworkLoadBalancerOptions,
@@ -9,6 +7,7 @@ import {
9
7
  validateGceNetworkLoadBalancerCommand,
10
8
  } from './GceNetworkLoadBalancerEditor';
11
9
  import { normalizeGceNetworkLoadBalancerCommand } from './GceNetworkLoadBalancerModal';
10
+ import type { IGceLoadBalancerData } from '../common';
12
11
 
13
12
  describe('GceNetworkLoadBalancerEditor', () => {
14
13
  it('keeps unavailable persisted address and network references in scoped options', () => {
@@ -57,8 +56,8 @@ describe('GceNetworkLoadBalancerEditor', () => {
57
56
  },
58
57
  'edit',
59
58
  );
60
- const wrapper = shallow(
61
- <GceNetworkLoadBalancerEditor command={command} data={emptyData()} onChange={jasmine.createSpy('onChange')} />,
59
+ const { container } = render(
60
+ <GceNetworkLoadBalancerEditor command={command} data={emptyData()} onChange={vi.fn()} />,
62
61
  );
63
62
 
64
63
  [
@@ -78,25 +77,15 @@ describe('GceNetworkLoadBalancerEditor', () => {
78
77
  'checkIntervalSec',
79
78
  'healthyThreshold',
80
79
  'unhealthyThreshold',
81
- ].forEach((field) => expect(wrapper.find(`[data-field="${field}"]`).exists()).toBe(true));
82
- ['name', 'credentials', 'region', 'network', 'targetPool', 'sessionAffinity'].forEach((field) => {
83
- const control = wrapper.find(`[data-field="${field}"]`);
84
- expect((control.find('input').exists() ? control.find('input') : control.find('select')).prop('disabled')).toBe(
85
- true,
86
- );
80
+ ].forEach((name) => expect(field(container, name)).toBeInTheDocument());
81
+ ['name', 'credentials', 'region', 'network', 'targetPool', 'sessionAffinity'].forEach((name) => {
82
+ expect(fieldControl(container, name)).toBeDisabled();
87
83
  });
88
- ['address', 'protocol', 'portRange', 'healthCheckPort'].forEach((field) => {
89
- const control = wrapper.find(`[data-field="${field}"]`);
90
- expect(
91
- (control.find('input').exists() ? control.find('input') : control.find('select')).prop('disabled'),
92
- ).not.toBe(true);
84
+ ['address', 'protocol', 'portRange', 'healthCheckPort'].forEach((name) => {
85
+ expect(fieldControl(container, name)).not.toBeDisabled();
93
86
  });
94
- expect(wrapper.find('[data-field="protocol"] option').map((option) => option.prop('value'))).toEqual([
95
- '',
96
- 'TCP',
97
- 'UDP',
98
- ]);
99
- expect(wrapper.find('[data-field="sessionAffinity"] option').map((option) => option.prop('value'))).toEqual([
87
+ expect(optionValues(fieldControl(container, 'protocol'))).toEqual(['', 'TCP', 'UDP']);
88
+ expect(optionValues(fieldControl(container, 'sessionAffinity'))).toEqual([
100
89
  '',
101
90
  'NONE',
102
91
  'CLIENT_IP',
@@ -109,22 +98,24 @@ describe('GceNetworkLoadBalancerEditor', () => {
109
98
  { account: 'account-a', loadBalancerName: 'app-main', region: 'europe-west1' },
110
99
  'create',
111
100
  );
112
- const onChange = jasmine.createSpy('onChange');
113
- const wrapper = shallow(<GceNetworkLoadBalancerEditor command={command} data={emptyData()} onChange={onChange} />);
101
+ const onChange = vi.fn();
102
+ const { container } = render(
103
+ <GceNetworkLoadBalancerEditor command={command} data={emptyData()} onChange={onChange} />,
104
+ );
114
105
 
115
- wrapper.find('[data-field="protocol"] select').simulate('change', { target: { value: 'UDP' } });
116
- expect(onChange.calls.mostRecent().args[0].listeners[0].protocol).toBe('UDP');
106
+ fireEvent.change(fieldControl(container, 'protocol'), { target: { value: 'UDP' } });
107
+ expect(onChange.mock.lastCall[0].listeners[0].protocol).toBe('UDP');
117
108
 
118
- wrapper.find('[data-field="sessionAffinity"] select').simulate('change', {
109
+ fireEvent.change(fieldControl(container, 'sessionAffinity'), {
119
110
  target: { value: 'CLIENT_IP_PROTO' },
120
111
  });
121
- expect(onChange.calls.mostRecent().args[0].sessionAffinity).toBe('CLIENT_IP_PROTO');
112
+ expect(onChange.mock.lastCall[0].sessionAffinity).toBe('CLIENT_IP_PROTO');
122
113
 
123
- wrapper.find('[data-field="requestPath"] input').simulate('change', { target: { value: 'status' } });
124
- expect(onChange.calls.mostRecent().args[0].healthChecks[0].requestPath).toBe('/status');
114
+ fireEvent.change(fieldControl(container, 'requestPath'), { target: { value: 'status' } });
115
+ expect(onChange.mock.lastCall[0].healthChecks[0].requestPath).toBe('/status');
125
116
 
126
- wrapper.find('[data-field="healthCheckEnabled"] input').simulate('change', { target: { checked: false } });
127
- expect(onChange.calls.mostRecent().args[0].healthChecks).toEqual([]);
117
+ fireEvent.click(fieldControl(container, 'healthCheckEnabled'));
118
+ expect(onChange.mock.lastCall[0].healthChecks).toEqual([]);
128
119
  });
129
120
 
130
121
  it('validates the exact required listener and enabled health-check fields', () => {
@@ -173,3 +164,21 @@ function emptyData(): IGceLoadBalancerData {
173
164
  subnets: [],
174
165
  };
175
166
  }
167
+
168
+ function field(container: HTMLElement, name: string): HTMLElement | null {
169
+ return container.querySelector(`[data-field="${name}"]`);
170
+ }
171
+
172
+ function fieldControl(container: HTMLElement, name: string): HTMLInputElement | HTMLSelectElement {
173
+ const control = field(container, name)?.querySelector('input, select');
174
+ if (!(control instanceof HTMLInputElement || control instanceof HTMLSelectElement)) {
175
+ throw new Error(`No control found for ${name}`);
176
+ }
177
+ return control;
178
+ }
179
+
180
+ function optionValues(select: HTMLElement): string[] {
181
+ return within(select)
182
+ .getAllByRole('option')
183
+ .map((option) => (option as HTMLOptionElement).value);
184
+ }
@@ -1,7 +1,6 @@
1
1
  import React from 'react';
2
- import { shallow } from 'enzyme';
2
+ import { render, screen } from '@testing-library/react';
3
3
 
4
- import { GceNetworkLoadBalancerEditor } from './GceNetworkLoadBalancerEditor';
5
4
  import {
6
5
  GceNetworkLoadBalancerModal,
7
6
  normalizeGceNetworkLoadBalancerCommand,
@@ -39,7 +38,7 @@ describe('GceNetworkLoadBalancerModal', () => {
39
38
  );
40
39
 
41
40
  expect(command).toEqual(
42
- jasmine.objectContaining({
41
+ expect.objectContaining({
43
42
  credentials: 'account-a',
44
43
  loadBalancerType: 'NETWORK',
45
44
  mode: 'edit',
@@ -65,7 +64,7 @@ describe('GceNetworkLoadBalancerModal', () => {
65
64
  },
66
65
  ]);
67
66
  expect(command.healthChecks).toEqual([
68
- jasmine.objectContaining({
67
+ expect.objectContaining({
69
68
  checkIntervalSec: 15,
70
69
  healthyThreshold: 3,
71
70
  name: 'app-main-hc',
@@ -102,7 +101,7 @@ describe('GceNetworkLoadBalancerModal', () => {
102
101
  portRange: '53',
103
102
  protocol: 'UDP',
104
103
  });
105
- expect(command.healthChecks[0]).toEqual(jasmine.objectContaining({ port: 53, requestPath: '/health' }));
104
+ expect(command.healthChecks[0]).toEqual(expect.objectContaining({ port: 53, requestPath: '/health' }));
106
105
  expect(command.sessionAffinity).toBe('CLIENT_IP');
107
106
  });
108
107
 
@@ -113,7 +112,7 @@ describe('GceNetworkLoadBalancerModal', () => {
113
112
  });
114
113
 
115
114
  expect(command).toEqual(
116
- jasmine.objectContaining({
115
+ expect.objectContaining({
117
116
  credentials: 'account-a',
118
117
  loadBalancerType: 'NETWORK',
119
118
  mode: 'create',
@@ -140,31 +139,28 @@ describe('GceNetworkLoadBalancerModal', () => {
140
139
 
141
140
  ([null, undefined] as const).forEach((loadBalancer) => {
142
141
  it(`initializes infrastructure create when the persisted load balancer is ${String(loadBalancer)}`, () => {
143
- const wrapper = shallow(
142
+ const { container } = render(
144
143
  <GceNetworkLoadBalancerModal
145
144
  app={application}
146
- closeModal={jasmine.createSpy('closeModal')}
145
+ closeModal={vi.fn()}
147
146
  data={emptyData()}
148
- dismissModal={jasmine.createSpy('dismissModal')}
147
+ dismissModal={vi.fn()}
149
148
  loadBalancer={loadBalancer as any}
150
149
  mode="create"
151
150
  />,
152
151
  );
153
152
 
154
- const command = wrapper.find(GceNetworkLoadBalancerEditor).prop('command');
155
- expect(command.mode).toBe('create');
156
- expect(command.listeners).toEqual([{ name: 'app', portRange: '8080', protocol: 'TCP' }]);
157
- expect(command.healthChecks).toEqual([
158
- {
159
- checkIntervalSec: 10,
160
- healthCheckType: 'HTTP',
161
- healthyThreshold: 10,
162
- port: 80,
163
- requestPath: '/',
164
- timeoutSec: 5,
165
- unhealthyThreshold: 2,
166
- },
167
- ]);
153
+ expect(screen.getByRole('heading', { name: 'Create Network Load Balancer' })).toBeInTheDocument();
154
+ expect(fieldControl(container, 'name')).toHaveValue('app');
155
+ expect(fieldControl(container, 'protocol')).toHaveValue('TCP');
156
+ expect(fieldControl(container, 'portRange')).toHaveValue('8080');
157
+ expect(fieldControl(container, 'healthCheckEnabled')).toBeChecked();
158
+ expect(fieldControl(container, 'healthCheckPort')).toHaveValue(80);
159
+ expect(fieldControl(container, 'requestPath')).toHaveValue('/');
160
+ expect(fieldControl(container, 'timeoutSec')).toHaveValue(5);
161
+ expect(fieldControl(container, 'checkIntervalSec')).toHaveValue(10);
162
+ expect(fieldControl(container, 'healthyThreshold')).toHaveValue(10);
163
+ expect(fieldControl(container, 'unhealthyThreshold')).toHaveValue(2);
168
164
  });
169
165
  });
170
166
 
@@ -227,7 +223,7 @@ describe('GceNetworkLoadBalancerModal', () => {
227
223
  });
228
224
 
229
225
  it('returns only the normalized command in pipeline mode', () => {
230
- const executeTask = jasmine.createSpy('executeTask');
226
+ const executeTask = vi.fn();
231
227
  const command = validCommand('pipeline');
232
228
 
233
229
  const result = submitGceNetworkLoadBalancerCommand(command, { application, executeTask });
@@ -239,13 +235,13 @@ describe('GceNetworkLoadBalancerModal', () => {
239
235
  (['create', 'edit'] as const).forEach((mode) => {
240
236
  it(`executes the direct normalized job in infrastructure ${mode} mode`, () => {
241
237
  const task = Promise.resolve({ id: 'task' });
242
- const executeTask = jasmine.createSpy('executeTask').and.returnValue(task);
238
+ const executeTask = vi.fn().mockReturnValue(task);
243
239
  const command = validCommand(mode);
244
240
 
245
241
  const result = submitGceNetworkLoadBalancerCommand(command, { application, executeTask });
246
242
 
247
243
  expect(result).toBe(task);
248
- expect(executeTask).toHaveBeenCalledOnceWith({
244
+ expect(executeTask).toHaveBeenCalledExactlyOnceWith({
249
245
  application,
250
246
  description: `${mode === 'edit' ? 'Update' : 'Create'} Load Balancer: app-main`,
251
247
  job: [serializeGceNetworkLoadBalancerCommand(command)],
@@ -256,18 +252,19 @@ describe('GceNetworkLoadBalancerModal', () => {
256
252
  it('exposes pipeline support and passes edit mode to the editor', () => {
257
253
  expect(GceNetworkLoadBalancerModal.supportsPipelineConfig).toBe(true);
258
254
  expect(typeof GceNetworkLoadBalancerModal.show).toBe('function');
259
- const wrapper = shallow(
255
+ const { container } = render(
260
256
  <GceNetworkLoadBalancerModal
261
257
  app={application}
262
- closeModal={jasmine.createSpy('closeModal')}
258
+ closeModal={vi.fn()}
263
259
  data={emptyData()}
264
- dismissModal={jasmine.createSpy('dismissModal')}
260
+ dismissModal={vi.fn()}
265
261
  isNew={false}
266
262
  loadBalancer={{ account: 'account-a', name: 'app-main', region: 'europe-west1' } as any}
267
263
  />,
268
264
  );
269
265
 
270
- expect(wrapper.find(GceNetworkLoadBalancerEditor).prop('command').mode).toBe('edit');
266
+ expect(screen.getByRole('heading', { name: 'Edit app-main' })).toBeInTheDocument();
267
+ expect(fieldControl(container, 'name')).toBeDisabled();
271
268
  });
272
269
  });
273
270
 
@@ -301,3 +298,11 @@ function emptyData(): any {
301
298
  subnets: [],
302
299
  };
303
300
  }
301
+
302
+ function fieldControl(container: HTMLElement, name: string): HTMLInputElement | HTMLSelectElement {
303
+ const control = container.querySelector(`[data-field="${name}"] input, [data-field="${name}"] select`);
304
+ if (!(control instanceof HTMLInputElement || control instanceof HTMLSelectElement)) {
305
+ throw new Error(`No control found for ${name}`);
306
+ }
307
+ return control;
308
+ }