Add +/- buttons in scale deployment popup screen (#1191)

* Add @testing-library/jest-dom and @testing-library/react for writing test cases

* Make eslint check __mocks__/*.ts files

* Mock electron.ipcRenderer.on to prevent errors in React compoment test cases

* Mock <Trans /> from @lingui/macro as a component simplely returns children components

* Use optional chaining operator (?.) to avoid TypeError: Cannot read property 'replicas' of undefined

* Add jest-fetch-mock for mocking global.fetch, activate for all test cases in jest.setup.ts

* Add basic tests for <DeploymentScaleDialog /> to ensure it initialises/renders without errors

* Add +/- button for changing desired replicas, and its test cases

* Adjust the styles of +/- buttons container to make sure it aligns with the slider

* Refactor after rebase from master

* Use var defined in vars.scss

* Add flex align-center to slider-container and remove margin-top: -4px

* Use <Icon /> to replace <i/>

* Add flex gaps to plus-minus-container

Signed-off-by: Hung-Han (Henry) Chen <1474479+chenhunghan@users.noreply.github.com>
This commit is contained in:
chh authored and GitHub committed 2020-11-03 20:30:36 +08:00
1 parent 3ed7d93e91
commit 484de2ec21
10 files changed
+414 -11

No files matched your search

@@ -20,11 +20,18 @@
}
.desired-scale {
flex: 1 0;
flex: 1.1 0;
}
.slider-container {
flex: 1.3 0;
flex: 1 0;
}
.plus-minus-container {
margin-left: $margin * 2;
.Icon {
--color-active: black;
}
}
.warning {
@@ -39,4 +46,4 @@
}
}
}
}
}
@@ -0,0 +1,151 @@
import React from 'react';
import { render, waitFor, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect'
import { DeploymentScaleDialog } from "./deployment-scale-dialog";
jest.mock("../../api/endpoints");
import { deploymentApi } from "../../api/endpoints";
const dummyDeployment = {
apiVersion: 'v1',
kind: 'dummy',
metadata: {
uid: 'dummy',
name: 'dummy',
creationTimestamp: 'dummy',
resourceVersion: 'dummy',
selfLink: 'link',
},
selfLink: 'link',
spec: {
replicas: 1,
selector: { matchLabels: { dummy: 'label' } },
template: {
metadata: {
labels: { dummy: 'label' },
},
spec: {
containers: [{
name: 'dummy',
image: 'dummy',
resources: {
requests: {
cpu: '1',
memory: '10Mi',
},
},
terminationMessagePath: 'dummy',
terminationMessagePolicy: 'dummy',
imagePullPolicy: 'dummy',
}],
restartPolicy: 'dummy',
terminationGracePeriodSeconds: 10,
dnsPolicy: 'dummy',
serviceAccountName: 'dummy',
serviceAccount: 'dummy',
securityContext: {},
schedulerName: 'dummy',
},
},
strategy: {
type: 'dummy',
rollingUpdate: {
maxUnavailable: 1,
maxSurge: 10,
},
},
},
status: {
observedGeneration: 1,
replicas: 1,
updatedReplicas: 1,
readyReplicas: 1,
conditions: [{
type: 'dummy',
status: 'dummy',
lastUpdateTime: 'dummy',
lastTransitionTime: 'dummy',
reason: 'dummy',
message: 'dummy',
}],
},
getConditions: jest.fn(),
getConditionsText: jest.fn(),
getReplicas: jest.fn(),
getSelectors: jest.fn(),
getTemplateLabels: jest.fn(),
getAffinity: jest.fn(),
getTolerations: jest.fn(),
getNodeSelectors: jest.fn(),
getAffinityNumber: jest.fn(),
getId: jest.fn(),
getResourceVersion: jest.fn(),
getName: jest.fn(),
getNs: jest.fn(),
getAge: jest.fn(),
getFinalizers: jest.fn(),
getLabels: jest.fn(),
getAnnotations: jest.fn(),
getOwnerRefs: jest.fn(),
getSearchFields: jest.fn(),
toPlainObject: jest.fn(),
update: jest.fn(),
delete: jest.fn(),
}
describe('<DeploymentScaleDialog />', () => {
it('renders w/o errors', () => {
const { container } = render(<DeploymentScaleDialog />);
expect(container).toBeInstanceOf(HTMLElement);
});
it('inits with a dummy deployment with mocked current/desired scale', async () => {
// mock deploymentApi.getReplicas() which will be called
// when <DeploymentScaleDialog /> rendered.
const initReplicas = 3
deploymentApi.getReplicas = jest.fn().mockImplementationOnce(async () => initReplicas);
const { getByTestId } = render(<DeploymentScaleDialog />);
DeploymentScaleDialog.open(dummyDeployment);
// we need to wait for the DeploymentScaleDialog to show up
// because there is an <Animate /> in <Dialog /> which renders null at start.
await waitFor(async () => {
const [currentScale, desiredScale] = await Promise.all([
getByTestId('current-scale'),
getByTestId('desired-scale'),
])
expect(currentScale).toHaveTextContent(`${initReplicas}`);
expect(desiredScale).toHaveTextContent(`${initReplicas}`);
});
});
it('changes the desired scale when clicking the icon buttons +/-', async () => {
const initReplicas = 1
deploymentApi.getReplicas = jest.fn().mockImplementationOnce(async () => initReplicas);
const { getByTestId } = render(<DeploymentScaleDialog />);
DeploymentScaleDialog.open(dummyDeployment);
await waitFor(async () => {
const desiredScale = await getByTestId('desired-scale');
expect(desiredScale).toHaveTextContent(`${initReplicas}`);
});
const up = await getByTestId('desired-replicas-up');
const down = await getByTestId('desired-replicas-down')
fireEvent.click(up);
expect(await getByTestId('desired-scale')).toHaveTextContent(`${initReplicas + 1}`);
fireEvent.click(down);
expect(await getByTestId('desired-scale')).toHaveTextContent('1');
// edge case, desiredScale must > 0
fireEvent.click(down);
fireEvent.click(down);
expect(await getByTestId('desired-scale')).toHaveTextContent('1');
const times = 120;
// edge case, desiredScale must < scaleMax (100)
for (let i = 0; i < times; i++) {
fireEvent.click(up);
}
expect(await getByTestId('desired-scale')).toHaveTextContent('100');
});
});
@@ -83,21 +83,41 @@ export class DeploymentScaleDialog extends Component<Props> {
}
}
desiredReplicasUp = () => {
this.desiredReplicas < this.scaleMax && this.desiredReplicas++
}
desiredReplicasDown = () => {
this.desiredReplicas > 1 && this.desiredReplicas--
};
renderContents() {
const { currentReplicas, desiredReplicas, onChange, scaleMax } = this;
const warning = currentReplicas < 10 && desiredReplicas > 90;
return (
<>
<div className="current-scale">
<div className="current-scale" data-testid="current-scale">
<Trans>Current replica scale: {currentReplicas}</Trans>
</div>
<div className="flex gaps align-center">
<div className="desired-scale">
<div className="desired-scale" data-testid="desired-scale">
<Trans>Desired number of replicas</Trans>: {desiredReplicas}
</div>
<div className="slider-container">
<div className="slider-container flex align-center">
<Slider value={desiredReplicas} max={scaleMax} onChange={onChange as any /** see: https://github.com/mui-org/material-ui/issues/20191 */}/>
</div>
<div className="plus-minus-container flex gaps">
<Icon
material="add_circle_outline"
onClick={this.desiredReplicasUp}
data-testid="desired-replicas-up"
/>
<Icon
material="remove_circle_outline"
onClick={this.desiredReplicasDown}
data-testid="desired-replicas-down"
/>
</div>
</div>
{warning &&
<div className="warning">
@@ -139,4 +159,4 @@ export class DeploymentScaleDialog extends Component<Props> {
</Dialog>
);
}
}
}