@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.
- package/dist/autoscalingPolicy/GceAutoScalingFieldLayout.d.ts +2 -2
- package/dist/domain/loadBalancer.d.ts +6 -3
- package/dist/image/ImageSelect.d.ts +1 -1
- package/dist/index.js +159 -1
- package/dist/index.js.map +1 -1
- package/dist/loadBalancer/configure/common/gceLoadBalancerModels.d.ts +13 -2
- package/dist/loadBalancer/configure/http/GceHttpLoadBalancerEditor.d.ts +3 -0
- package/dist/loadBalancer/configure/http/GceHttpLoadBalancerModal.d.ts +1 -1
- package/dist/loadBalancer/configure/network/GceRegionalExternalNetworkLoadBalancerEditor.d.ts +30 -0
- package/dist/loadBalancer/configure/network/GceRegionalExternalNetworkLoadBalancerModal.d.ts +53 -0
- package/dist/loadBalancer/httpLoadBalancerUtils.service.d.ts +3 -1
- package/dist/serverGroup/configure/wizard/GceCloneServerGroupModal.d.ts +3 -0
- package/dist/serverGroup/configure/wizard/GceServerGroupWizard.types.d.ts +1 -0
- package/dist/serverGroup/configure/wizard/customInstance/CustomInstanceConfigurer.d.ts +1 -1
- package/dist/serverGroup/configure/wizard/customInstance/GceCustomInstanceBuilder.d.ts +2 -2
- package/dist/serverGroup/configure/wizard/pages/AdvancedSettings.d.ts +3 -0
- package/dist/serverGroup/configure/wizard/pages/GceServerGroupLoadBalancers.d.ts +1 -0
- package/dist/serverGroup/configure/wizard/pages/Policies.d.ts +21 -3
- package/dist/serverGroup/configure/wizard/pages/ServerGroupBasicSettings.d.ts +1 -0
- package/dist/serverGroup/configure/wizard/pages/ServerGroupCapacity.d.ts +1 -0
- package/dist/serverGroup/configure/wizard/pages/ServerGroupImageSettings.d.ts +1 -0
- package/dist/serverGroup/configure/wizard/pages/ServerGroupInstanceType.d.ts +1 -0
- package/dist/serverGroup/configure/wizard/zones/GceInstanceFlexibilityConfigurer.d.ts +2 -2
- package/dist/serverGroup/details/GceInstanceFlexibilityPolicyDetails.d.ts +2 -2
- package/dist/serverGroup/details/stateful/MarkDiskStatefulButton.d.ts +2 -2
- package/dist/serverGroup/details/stateful/UpdateBootImageButton.d.ts +2 -2
- package/package.json +5 -5
- package/src/autoHealingPolicy/GceAutoHealingPolicyEditor.spec.tsx +68 -52
- package/src/autoscalingPolicy/GceAutoscalingPolicyEditor.spec.tsx +43 -50
- package/src/autoscalingPolicy/GceAutoscalingPolicyEditor.tsx +1 -1
- package/src/autoscalingPolicy/GceAutoscalingPolicyWriter.spec.ts +1 -1
- package/src/domain/loadBalancer.ts +6 -3
- package/src/gce.module.spec.ts +132 -153
- package/src/instance/details/GceInstanceDetails.spec.tsx +86 -83
- package/src/loadBalancer/configure/choice/GceLoadBalancerChoiceModal.spec.tsx +162 -121
- package/src/loadBalancer/configure/choice/GceLoadBalancerChoiceModal.tsx +13 -1
- package/src/loadBalancer/configure/common/GceProxyLoadBalancerEditor.spec.tsx +47 -30
- package/src/loadBalancer/configure/common/GceProxyLoadBalancerEditor.tsx +2 -1
- package/src/loadBalancer/configure/common/GceProxyLoadBalancerModal.spec.tsx +85 -57
- package/src/loadBalancer/configure/common/gceLoadBalancerData.spec.ts +17 -16
- package/src/loadBalancer/configure/common/gceLoadBalancerModels.spec.ts +230 -5
- package/src/loadBalancer/configure/common/gceLoadBalancerModels.ts +186 -22
- package/src/loadBalancer/configure/common/gceLoadBalancerSubmission.spec.ts +298 -2
- package/src/loadBalancer/configure/common/gceLoadBalancerSubmission.ts +35 -5
- package/src/loadBalancer/configure/common/useGceLoadBalancerData.spec.tsx +37 -46
- package/src/loadBalancer/configure/http/GceHttpLoadBalancerEditor.spec.tsx +399 -58
- package/src/loadBalancer/configure/http/GceHttpLoadBalancerEditor.tsx +148 -41
- package/src/loadBalancer/configure/http/GceHttpLoadBalancerListenerEditor.spec.tsx +169 -36
- package/src/loadBalancer/configure/http/GceHttpLoadBalancerListenerEditor.tsx +80 -12
- package/src/loadBalancer/configure/http/GceHttpLoadBalancerModal.spec.tsx +224 -124
- package/src/loadBalancer/configure/http/GceHttpLoadBalancerModal.tsx +8 -2
- package/src/loadBalancer/configure/http/GceHttpLoadBalancerResourceEditors.spec.tsx +57 -22
- package/src/loadBalancer/configure/http/GceHttpLoadBalancerResourceEditors.tsx +21 -0
- package/src/loadBalancer/configure/http/GceHttpLoadBalancerRoutingEditor.spec.tsx +24 -20
- package/src/loadBalancer/configure/network/GceNetworkLoadBalancerEditor.spec.tsx +41 -32
- package/src/loadBalancer/configure/network/GceNetworkLoadBalancerModal.spec.tsx +35 -30
- package/src/loadBalancer/configure/network/GceRegionalExternalNetworkLoadBalancerEditor.spec.tsx +443 -0
- package/src/loadBalancer/configure/network/GceRegionalExternalNetworkLoadBalancerEditor.tsx +401 -0
- package/src/loadBalancer/configure/network/GceRegionalExternalNetworkLoadBalancerModal.spec.tsx +330 -0
- package/src/loadBalancer/configure/network/GceRegionalExternalNetworkLoadBalancerModal.tsx +348 -0
- package/src/loadBalancer/details/gceLoadBalancerDetails.spec.tsx +439 -35
- package/src/loadBalancer/details/gceLoadBalancerDetails.tsx +104 -21
- package/src/loadBalancer/httpLoadBalancerUtils.service.spec.ts +126 -0
- package/src/loadBalancer/httpLoadBalancerUtils.service.ts +27 -11
- package/src/loadBalancer/loadBalancer.setTransformer.spec.ts +165 -0
- package/src/loadBalancer/loadBalancer.setTransformer.ts +15 -2
- package/src/pipeline/stages/googleStageConfig.spec.js +22 -16
- package/src/securityGroup/configure/GceSecurityGroupModal.spec.tsx +177 -193
- package/src/securityGroup/configure/GceSecurityGroupModal.tsx +14 -2
- package/src/securityGroup/details/GceSecurityGroupDetails.spec.tsx +69 -78
- package/src/serverGroup/configure/serverGroupCommandBuilder.service.spec.js +35 -15
- package/src/serverGroup/configure/serverGroupConfiguration.service.js +7 -1
- package/src/serverGroup/configure/serverGroupConfiguration.service.spec.js +74 -4
- package/src/serverGroup/configure/wizard/GceCloneServerGroupModal.spec.tsx +593 -855
- package/src/serverGroup/configure/wizard/GceCloneServerGroupModal.tsx +120 -38
- package/src/serverGroup/configure/wizard/GceServerGroupWizard.types.ts +1 -0
- package/src/serverGroup/configure/wizard/GceServerGroupWizardPage.spec.tsx +53 -50
- package/src/serverGroup/configure/wizard/customInstance/GceCustomInstanceBuilder.spec.tsx +65 -49
- package/src/serverGroup/configure/wizard/hiddenMetadataKeys.value.js +1 -0
- package/src/serverGroup/configure/wizard/pages/AdvancedSettings.spec.tsx +101 -108
- package/src/serverGroup/configure/wizard/pages/AdvancedSettings.tsx +32 -28
- package/src/serverGroup/configure/wizard/pages/GceServerGroupFirewalls.spec.tsx +60 -59
- package/src/serverGroup/configure/wizard/pages/GceServerGroupLoadBalancers.spec.tsx +240 -114
- package/src/serverGroup/configure/wizard/pages/GceServerGroupLoadBalancers.tsx +152 -63
- package/src/serverGroup/configure/wizard/pages/Policies.spec.tsx +93 -48
- package/src/serverGroup/configure/wizard/pages/Policies.tsx +80 -55
- package/src/serverGroup/configure/wizard/pages/ServerGroupBasicSettings.spec.tsx +123 -94
- package/src/serverGroup/configure/wizard/pages/ServerGroupBasicSettings.tsx +24 -18
- package/src/serverGroup/configure/wizard/pages/ServerGroupCapacity.spec.tsx +152 -133
- package/src/serverGroup/configure/wizard/pages/ServerGroupCapacity.tsx +40 -37
- package/src/serverGroup/configure/wizard/pages/ServerGroupImageSettings.spec.tsx +66 -60
- package/src/serverGroup/configure/wizard/pages/ServerGroupImageSettings.tsx +8 -2
- package/src/serverGroup/configure/wizard/pages/ServerGroupInstanceType.spec.tsx +131 -101
- package/src/serverGroup/configure/wizard/pages/ServerGroupInstanceType.tsx +29 -28
- package/src/serverGroup/configure/wizard/zones/GceInstanceFlexibilityConfigurer.spec.tsx +104 -81
- package/src/serverGroup/details/GceInstanceFlexibilityPolicyDetails.spec.tsx +10 -11
- package/src/serverGroup/details/autoHealingPolicy/GceAutoHealingPolicyDetails.spec.tsx +51 -59
- package/src/serverGroup/details/autoHealingPolicy/modal/GceUpsertAutoHealingPolicyModal.spec.tsx +3 -3
- package/src/serverGroup/details/autoscalingPolicy/GceAutoscalingPolicyDetails.spec.tsx +49 -58
- package/src/serverGroup/details/autoscalingPolicy/modal/GceUpsertAutoscalingPolicyModal.spec.tsx +3 -3
- package/src/serverGroup/details/gceServerGroupDetails.spec.tsx +162 -141
- package/src/serverGroup/details/resize/GceResizeServerGroupModal.spec.tsx +102 -86
- package/src/serverGroup/details/resize/GceResizeServerGroupModal.tsx +4 -0
- package/src/serverGroup/details/rollback/GceRollbackServerGroupModal.spec.tsx +40 -31
- package/src/serverGroup/serverGroup.transformer.js +1 -0
- package/src/serverGroup/serverGroup.transformer.spec.js +48 -0
- package/src/validation/ApplicationNameValidator.spec.ts +1 -1
- /package/dist/{gce.logo2ae85d8704b1d6b8.png → gce.logoChI3w_A5.png} +0 -0
|
@@ -25,49 +25,53 @@ type CapacityValue = number | string | null | undefined;
|
|
|
25
25
|
|
|
26
26
|
const INTEGER_ERROR = 'must be a finite non-negative integer.';
|
|
27
27
|
|
|
28
|
-
export
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const desired = values.capacity?.desired;
|
|
28
|
+
export function validateGceServerGroupCapacity(values: IGceServerGroupCommand): ICapacityValidationErrors {
|
|
29
|
+
const errors: ICapacityValidationErrors = {};
|
|
30
|
+
const desired = values.capacity?.desired;
|
|
32
31
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (values.autoscalingPolicy) {
|
|
38
|
-
const min = values.autoscalingPolicy?.minNumReplicas;
|
|
39
|
-
const max = values.autoscalingPolicy?.maxNumReplicas;
|
|
32
|
+
if (!isValidCapacityValue(desired, values)) {
|
|
33
|
+
errors.capacity = { desired: `Desired capacity ${INTEGER_ERROR}` };
|
|
34
|
+
}
|
|
40
35
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
minNumReplicas: `Minimum capacity ${INTEGER_ERROR}`,
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
if (!isValidCapacityValue(max, values)) {
|
|
48
|
-
errors.autoscalingPolicy = {
|
|
49
|
-
...errors.autoscalingPolicy,
|
|
50
|
-
maxNumReplicas: `Maximum capacity ${INTEGER_ERROR}`,
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
if (isNonNegativeInteger(desired) && isNonNegativeInteger(min) && desired < min) {
|
|
54
|
-
errors.capacity = { desired: 'Desired capacity must be at least minimum capacity.' };
|
|
55
|
-
} else if (isNonNegativeInteger(desired) && isNonNegativeInteger(max) && desired > max) {
|
|
56
|
-
errors.capacity = { desired: 'Desired capacity must not exceed maximum capacity.' };
|
|
57
|
-
}
|
|
58
|
-
}
|
|
36
|
+
if (values.autoscalingPolicy) {
|
|
37
|
+
const min = values.autoscalingPolicy?.minNumReplicas;
|
|
38
|
+
const max = values.autoscalingPolicy?.maxNumReplicas;
|
|
59
39
|
|
|
60
|
-
if (!
|
|
61
|
-
errors.
|
|
40
|
+
if (!isValidCapacityValue(min, values)) {
|
|
41
|
+
errors.autoscalingPolicy = {
|
|
42
|
+
...errors.autoscalingPolicy,
|
|
43
|
+
minNumReplicas: `Minimum capacity ${INTEGER_ERROR}`,
|
|
44
|
+
};
|
|
62
45
|
}
|
|
63
|
-
if (
|
|
64
|
-
errors.
|
|
46
|
+
if (!isValidCapacityValue(max, values)) {
|
|
47
|
+
errors.autoscalingPolicy = {
|
|
48
|
+
...errors.autoscalingPolicy,
|
|
49
|
+
maxNumReplicas: `Maximum capacity ${INTEGER_ERROR}`,
|
|
50
|
+
};
|
|
65
51
|
}
|
|
66
|
-
if (
|
|
67
|
-
errors.
|
|
52
|
+
if (isNonNegativeInteger(desired) && isNonNegativeInteger(min) && desired < min) {
|
|
53
|
+
errors.capacity = { desired: 'Desired capacity must be at least minimum capacity.' };
|
|
54
|
+
} else if (isNonNegativeInteger(desired) && isNonNegativeInteger(max) && desired > max) {
|
|
55
|
+
errors.capacity = { desired: 'Desired capacity must not exceed maximum capacity.' };
|
|
68
56
|
}
|
|
57
|
+
}
|
|
69
58
|
|
|
70
|
-
|
|
59
|
+
if (!values.regional && !values.zone?.trim()) {
|
|
60
|
+
errors.zone = 'Zone required.';
|
|
61
|
+
}
|
|
62
|
+
if (values.regional && values.selectZones && !values.distributionPolicy?.zones?.length) {
|
|
63
|
+
errors.distributionPolicy = { zones: 'At least one zone required.' };
|
|
64
|
+
}
|
|
65
|
+
if (!hasValidFlexibilityPolicy(values)) {
|
|
66
|
+
errors.instanceFlexibilityPolicy = 'Instance flexibility policy is invalid.';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return errors;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export class ServerGroupCapacity extends GceServerGroupWizardPage {
|
|
73
|
+
public validate(values: IGceServerGroupCommand): ICapacityValidationErrors {
|
|
74
|
+
return validateGceServerGroupCapacity(values);
|
|
71
75
|
}
|
|
72
76
|
|
|
73
77
|
private commandChanged = (
|
|
@@ -87,7 +91,6 @@ export class ServerGroupCapacity extends GceServerGroupWizardPage {
|
|
|
87
91
|
if (value === undefined) {
|
|
88
92
|
return;
|
|
89
93
|
}
|
|
90
|
-
|
|
91
94
|
const { values } = this.props.formik;
|
|
92
95
|
if (!values.autoscalingPolicy) {
|
|
93
96
|
this.props.formik.setFieldValue('capacity', { min: value, max: value, desired: value });
|
|
@@ -1,41 +1,53 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { fireEvent, render, screen, within } from '@testing-library/react';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import { shallow } from 'enzyme';
|
|
4
3
|
|
|
5
|
-
import { Markdown, StageArtifactSelectorDelegate } from '@spinnaker/core';
|
|
6
|
-
|
|
7
|
-
import { ServerGroupImageSettings } from './ServerGroupImageSettings';
|
|
8
4
|
import type { IGceServerGroupCommand } from '../GceServerGroupWizard.types';
|
|
5
|
+
import { ServerGroupImageSettings, validateGceServerGroupImageSettings } from './ServerGroupImageSettings';
|
|
6
|
+
|
|
7
|
+
vi.mock('@spinnaker/core', async (importOriginal) => {
|
|
8
|
+
const actual = await importOriginal<typeof import('@spinnaker/core')>();
|
|
9
|
+
return {
|
|
10
|
+
...actual,
|
|
11
|
+
StageArtifactSelectorDelegate: ({ onArtifactEdited, onExpectedArtifactSelected }: any) => (
|
|
12
|
+
<div data-testid="artifact-selector">
|
|
13
|
+
<button onClick={() => onArtifactEdited({ type: 'custom/object', reference: 'new' })} type="button">
|
|
14
|
+
Edit artifact
|
|
15
|
+
</button>
|
|
16
|
+
<button onClick={() => onExpectedArtifactSelected({ id: 'expected-id' })} type="button">
|
|
17
|
+
Select expected artifact
|
|
18
|
+
</button>
|
|
19
|
+
</div>
|
|
20
|
+
),
|
|
21
|
+
};
|
|
22
|
+
});
|
|
9
23
|
|
|
10
24
|
describe('ServerGroupImageSettings', () => {
|
|
11
25
|
it('renders accessible image and source controls while preserving unavailable persisted values', () => {
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
);
|
|
15
|
-
const
|
|
26
|
+
const values = command({
|
|
27
|
+
backingData: { allImages: [{ imageName: 'known-image' }, { imageName: 'known-image' }] },
|
|
28
|
+
});
|
|
29
|
+
const { setFieldValue } = renderImageSettings(values);
|
|
16
30
|
|
|
17
|
-
expect(selectOptions(
|
|
31
|
+
expect(selectOptions('Image source')).toEqual([
|
|
18
32
|
['artifact', 'Artifact'],
|
|
19
33
|
['priorStage', 'Prior Stage'],
|
|
20
34
|
['persisted-source', 'persisted-source (unavailable)'],
|
|
21
35
|
]);
|
|
22
|
-
expect(selectOptions(
|
|
36
|
+
expect(selectOptions('Image')).toEqual([
|
|
23
37
|
['', 'Select...'],
|
|
24
38
|
['known-image', 'known-image'],
|
|
25
39
|
['persisted-image', 'persisted-image (unavailable)'],
|
|
26
40
|
]);
|
|
27
|
-
expect(
|
|
28
|
-
expect(formik.setValues).not.toHaveBeenCalled();
|
|
41
|
+
expect(setFieldValue).not.toHaveBeenCalled();
|
|
29
42
|
});
|
|
30
43
|
|
|
31
44
|
it('updates image source and image selection as page-owned fields', () => {
|
|
32
|
-
const
|
|
33
|
-
const wrapper = shallow(<ServerGroupImageSettings app={{ name: 'app' } as any} formik={formik} />);
|
|
45
|
+
const { setFieldValue } = renderImageSettings(command());
|
|
34
46
|
|
|
35
|
-
|
|
36
|
-
|
|
47
|
+
fireEvent.change(screen.getByLabelText('Image source'), { target: { value: 'priorStage' } });
|
|
48
|
+
fireEvent.change(screen.getByLabelText('Image'), { target: { value: 'known-image' } });
|
|
37
49
|
|
|
38
|
-
expect(
|
|
50
|
+
expect(setFieldValue.mock.calls).toEqual([
|
|
39
51
|
['imageSource', 'priorStage'],
|
|
40
52
|
['image', 'known-image'],
|
|
41
53
|
]);
|
|
@@ -46,11 +58,11 @@ describe('ServerGroupImageSettings', () => {
|
|
|
46
58
|
imageSource: 'artifact',
|
|
47
59
|
viewState: { mode: 'editPipeline', showImageSourceSelector: true, imageSourceText: 'From **trigger**' },
|
|
48
60
|
});
|
|
49
|
-
|
|
61
|
+
renderImageSettings(values);
|
|
50
62
|
|
|
51
|
-
expect(
|
|
52
|
-
expect(
|
|
53
|
-
expect(
|
|
63
|
+
expect(screen.queryByLabelText('Image source')).not.toBeInTheDocument();
|
|
64
|
+
expect(screen.getByText((_, element) => element?.textContent === 'From trigger')).toBeInTheDocument();
|
|
65
|
+
expect(screen.getByTestId('artifact-selector')).toBeInTheDocument();
|
|
54
66
|
});
|
|
55
67
|
|
|
56
68
|
it('edits inline and expected image artifacts without retaining the other reference', () => {
|
|
@@ -59,17 +71,14 @@ describe('ServerGroupImageSettings', () => {
|
|
|
59
71
|
imageArtifactId: 'old-id',
|
|
60
72
|
imageArtifact: { type: 'custom/object', reference: 'old' },
|
|
61
73
|
});
|
|
62
|
-
const
|
|
63
|
-
const wrapper = shallow(<ServerGroupImageSettings app={{ name: 'app' } as any} formik={formik} />);
|
|
64
|
-
const selector = wrapper.find(StageArtifactSelectorDelegate);
|
|
65
|
-
const editedArtifact = { type: 'custom/object', reference: 'new' };
|
|
74
|
+
const { setFieldValue } = renderImageSettings(values);
|
|
66
75
|
|
|
67
|
-
|
|
68
|
-
|
|
76
|
+
fireEvent.click(screen.getByRole('button', { name: 'Edit artifact' }));
|
|
77
|
+
fireEvent.click(screen.getByRole('button', { name: 'Select expected artifact' }));
|
|
69
78
|
|
|
70
|
-
expect(
|
|
79
|
+
expect(setFieldValue.mock.calls).toEqual([
|
|
71
80
|
['imageArtifactId', null],
|
|
72
|
-
['imageArtifact',
|
|
81
|
+
['imageArtifact', { type: 'custom/object', reference: 'new' }],
|
|
73
82
|
['imageArtifactId', 'expected-id'],
|
|
74
83
|
['imageArtifact', null],
|
|
75
84
|
]);
|
|
@@ -77,48 +86,45 @@ describe('ServerGroupImageSettings', () => {
|
|
|
77
86
|
|
|
78
87
|
it('does not render or require image selection when it is disabled', () => {
|
|
79
88
|
const values = command({ image: null, viewState: { mode: 'editPipeline', disableImageSelection: true } });
|
|
80
|
-
|
|
81
|
-
const page = new ServerGroupImageSettings({ app: { name: 'app' } as any, formik } as any);
|
|
82
|
-
const wrapper = shallow(<ServerGroupImageSettings app={{ name: 'app' } as any} formik={formik} />);
|
|
89
|
+
renderImageSettings(values);
|
|
83
90
|
|
|
84
|
-
expect(
|
|
85
|
-
expect(
|
|
86
|
-
expect(
|
|
91
|
+
expect(screen.queryByLabelText('Image')).not.toBeInTheDocument();
|
|
92
|
+
expect(screen.getByText('Image selection is disabled for this command.')).toBeInTheDocument();
|
|
93
|
+
expect(validateGceServerGroupImageSettings(values)).toEqual({});
|
|
87
94
|
});
|
|
88
95
|
|
|
89
96
|
it('requires an image when selection is enabled', () => {
|
|
90
|
-
|
|
91
|
-
const formik = testFormik(values);
|
|
92
|
-
const page = new ServerGroupImageSettings({ app: { name: 'app' } as any, formik } as any);
|
|
93
|
-
|
|
94
|
-
expect(page.validate(values)).toEqual({ image: 'Image required.' });
|
|
97
|
+
expect(validateGceServerGroupImageSettings(command({ image: '' }))).toEqual({ image: 'Image required.' });
|
|
95
98
|
});
|
|
96
99
|
|
|
97
100
|
it('renders image validation next to the required control with an accessible description', () => {
|
|
98
|
-
|
|
99
|
-
const
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
expect(image
|
|
103
|
-
expect(image.
|
|
104
|
-
expect(image.
|
|
105
|
-
expect(
|
|
106
|
-
expect(
|
|
101
|
+
renderImageSettings(command({ image: '' }));
|
|
102
|
+
const image = screen.getByLabelText('Image');
|
|
103
|
+
const alert = screen.getByRole('alert');
|
|
104
|
+
|
|
105
|
+
expect(image).toBeRequired();
|
|
106
|
+
expect(image).toHaveAttribute('aria-invalid', 'true');
|
|
107
|
+
expect(image).toHaveAttribute('aria-describedby', 'gce-server-group-image-error');
|
|
108
|
+
expect(alert).toHaveAttribute('id', 'gce-server-group-image-error');
|
|
109
|
+
expect(alert).toHaveTextContent('Image required.');
|
|
107
110
|
});
|
|
108
111
|
});
|
|
109
112
|
|
|
110
|
-
function
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
113
|
+
function renderImageSettings(values: IGceServerGroupCommand) {
|
|
114
|
+
const setFieldValue = vi.fn();
|
|
115
|
+
render(
|
|
116
|
+
<ServerGroupImageSettings
|
|
117
|
+
app={{ name: 'app' } as any}
|
|
118
|
+
formik={{ errors: {}, setFieldValue, setValues: vi.fn(), values } as any}
|
|
119
|
+
/>,
|
|
120
|
+
);
|
|
121
|
+
return { setFieldValue };
|
|
114
122
|
}
|
|
115
123
|
|
|
116
|
-
function
|
|
117
|
-
return (
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
setValues: jasmine.createSpy('setValues'),
|
|
121
|
-
} as unknown) as FormikProps<IGceServerGroupCommand>;
|
|
124
|
+
function selectOptions(label: string): string[][] {
|
|
125
|
+
return within(screen.getByLabelText(label))
|
|
126
|
+
.getAllByRole('option')
|
|
127
|
+
.map((option) => [(option as HTMLOptionElement).value, option.textContent || '']);
|
|
122
128
|
}
|
|
123
129
|
|
|
124
130
|
function command(overrides: Partial<IGceServerGroupCommand> = {}): IGceServerGroupCommand {
|
|
@@ -19,8 +19,7 @@ const IMAGE_SOURCES = [
|
|
|
19
19
|
|
|
20
20
|
export class ServerGroupImageSettings extends GceServerGroupWizardPage {
|
|
21
21
|
public validate(values: IGceServerGroupCommand): IGceServerGroupCommandValidationErrors {
|
|
22
|
-
|
|
23
|
-
return image ? { image } : {};
|
|
22
|
+
return validateGceServerGroupImageSettings(values);
|
|
24
23
|
}
|
|
25
24
|
|
|
26
25
|
private imageArtifactEdited = (artifact: any): void => {
|
|
@@ -146,6 +145,13 @@ export class ServerGroupImageSettings extends GceServerGroupWizardPage {
|
|
|
146
145
|
}
|
|
147
146
|
}
|
|
148
147
|
|
|
148
|
+
export function validateGceServerGroupImageSettings(
|
|
149
|
+
values: IGceServerGroupCommand,
|
|
150
|
+
): IGceServerGroupCommandValidationErrors {
|
|
151
|
+
const image = validateGceServerGroupCommand(values).image;
|
|
152
|
+
return image ? { image } : {};
|
|
153
|
+
}
|
|
154
|
+
|
|
149
155
|
function imageOptions(rawImages: readonly any[], persistedImage: string | null | undefined): IImageOption[] {
|
|
150
156
|
const images = Array.from(
|
|
151
157
|
new Set(rawImages.map((image) => (typeof image === 'string' ? image : image.imageName)).filter(Boolean)),
|