Merge pull request #6130 from mabashian/general-toggle-component

Refactors YamlJsonToggle component into a generic Toggle component

Reviewed-by: https://github.com/apps/softwarefactory-project-zuul
This commit is contained in:
softwarefactory-project-zuul[bot]
2020-03-03 18:46:05 +00:00
committed by GitHub
9 changed files with 126 additions and 92 deletions

View File

@@ -2,9 +2,9 @@ import React, { useState, useEffect } from 'react';
import { string, node, number } from 'prop-types'; import { string, node, number } from 'prop-types';
import { Split, SplitItem, TextListItemVariants } from '@patternfly/react-core'; import { Split, SplitItem, TextListItemVariants } from '@patternfly/react-core';
import { DetailName, DetailValue } from '@components/DetailList'; import { DetailName, DetailValue } from '@components/DetailList';
import MultiButtonToggle from '@components/MultiButtonToggle';
import { yamlToJson, jsonToYaml, isJson } from '@util/yaml'; import { yamlToJson, jsonToYaml, isJson } from '@util/yaml';
import CodeMirrorInput from './CodeMirrorInput'; import CodeMirrorInput from './CodeMirrorInput';
import YamlJsonToggle from './YamlJsonToggle';
import { JSON_MODE, YAML_MODE } from './constants'; import { JSON_MODE, YAML_MODE } from './constants';
function getValueAsMode(value, mode) { function getValueAsMode(value, mode) {
@@ -50,8 +50,9 @@ function VariablesDetail({ value, label, rows }) {
</div> </div>
</SplitItem> </SplitItem>
<SplitItem> <SplitItem>
<YamlJsonToggle <MultiButtonToggle
mode={mode} buttons={[[YAML_MODE, 'YAML'], [JSON_MODE, 'JSON']]}
value={mode}
onChange={newMode => { onChange={newMode => {
try { try {
setCurrentValue(getValueAsMode(currentValue, newMode)); setCurrentValue(getValueAsMode(currentValue, newMode));

View File

@@ -31,12 +31,12 @@ describe('<VariablesDetail>', () => {
const wrapper = shallow( const wrapper = shallow(
<VariablesDetail value="---foo: bar" label="Variables" /> <VariablesDetail value="---foo: bar" label="Variables" />
); );
wrapper.find('YamlJsonToggle').invoke('onChange')('javascript'); wrapper.find('MultiButtonToggle').invoke('onChange')('javascript');
const input = wrapper.find('Styled(CodeMirrorInput)'); const input = wrapper.find('Styled(CodeMirrorInput)');
expect(input.prop('mode')).toEqual('javascript'); expect(input.prop('mode')).toEqual('javascript');
expect(input.prop('value')).toEqual('{\n "foo": "bar"\n}'); expect(input.prop('value')).toEqual('{\n "foo": "bar"\n}');
wrapper.find('YamlJsonToggle').invoke('onChange')('yaml'); wrapper.find('MultiButtonToggle').invoke('onChange')('yaml');
const input2 = wrapper.find('Styled(CodeMirrorInput)'); const input2 = wrapper.find('Styled(CodeMirrorInput)');
expect(input2.prop('mode')).toEqual('yaml'); expect(input2.prop('mode')).toEqual('yaml');
expect(input2.prop('value')).toEqual('foo: bar\n'); expect(input2.prop('value')).toEqual('foo: bar\n');
@@ -53,7 +53,7 @@ describe('<VariablesDetail>', () => {
<VariablesDetail value="---foo: bar" label="Variables" /> <VariablesDetail value="---foo: bar" label="Variables" />
); );
act(() => { act(() => {
wrapper.find('YamlJsonToggle').invoke('onChange')('javascript'); wrapper.find('MultiButtonToggle').invoke('onChange')('javascript');
}); });
wrapper.setProps({ wrapper.setProps({
value: '---bar: baz', value: '---bar: baz',
@@ -73,7 +73,7 @@ describe('<VariablesDetail>', () => {
test('should default empty json to "{}"', () => { test('should default empty json to "{}"', () => {
const wrapper = mount(<VariablesDetail value="" label="Variables" />); const wrapper = mount(<VariablesDetail value="" label="Variables" />);
act(() => { act(() => {
wrapper.find('YamlJsonToggle').invoke('onChange')('javascript'); wrapper.find('MultiButtonToggle').invoke('onChange')('javascript');
}); });
wrapper.setProps({ value: '' }); wrapper.setProps({ value: '' });
const input = wrapper.find('Styled(CodeMirrorInput)'); const input = wrapper.find('Styled(CodeMirrorInput)');

View File

@@ -6,9 +6,9 @@ import { useField } from 'formik';
import styled from 'styled-components'; import styled from 'styled-components';
import { Split, SplitItem } from '@patternfly/react-core'; import { Split, SplitItem } from '@patternfly/react-core';
import { CheckboxField } from '@components/FormField'; import { CheckboxField } from '@components/FormField';
import MultiButtonToggle from '@components/MultiButtonToggle';
import { yamlToJson, jsonToYaml, isJson } from '@util/yaml'; import { yamlToJson, jsonToYaml, isJson } from '@util/yaml';
import CodeMirrorInput from './CodeMirrorInput'; import CodeMirrorInput from './CodeMirrorInput';
import YamlJsonToggle from './YamlJsonToggle';
import { JSON_MODE, YAML_MODE } from './constants'; import { JSON_MODE, YAML_MODE } from './constants';
const FieldHeader = styled.div` const FieldHeader = styled.div`
@@ -34,8 +34,9 @@ function VariablesField({ i18n, id, name, label, readOnly, promptId }) {
</label> </label>
</SplitItem> </SplitItem>
<SplitItem> <SplitItem>
<YamlJsonToggle <MultiButtonToggle
mode={mode} buttons={[[YAML_MODE, 'YAML'], [JSON_MODE, 'JSON']]}
value={mode}
onChange={newMode => { onChange={newMode => {
try { try {
const newVal = const newVal =

View File

@@ -1,21 +1,16 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { string, func, bool, number } from 'prop-types'; import { string, func, bool, number } from 'prop-types';
import { Button, Split, SplitItem } from '@patternfly/react-core'; import { Split, SplitItem } from '@patternfly/react-core';
import styled from 'styled-components'; import styled from 'styled-components';
import { yamlToJson, jsonToYaml, isJson } from '@util/yaml'; import { yamlToJson, jsonToYaml, isJson } from '@util/yaml';
import MultiButtonToggle from '@components/MultiButtonToggle';
import CodeMirrorInput from './CodeMirrorInput'; import CodeMirrorInput from './CodeMirrorInput';
import ButtonGroup from './ButtonGroup';
import { JSON_MODE, YAML_MODE } from './constants'; import { JSON_MODE, YAML_MODE } from './constants';
function formatJson(jsonString) { function formatJson(jsonString) {
return JSON.stringify(JSON.parse(jsonString), null, 2); return JSON.stringify(JSON.parse(jsonString), null, 2);
} }
const SmallButton = styled(Button)`
padding: 3px 8px;
font-size: var(--pf-global--FontSize--xs);
`;
const SplitItemRight = styled(SplitItem)` const SplitItemRight = styled(SplitItem)`
margin-bottom: 5px; margin-bottom: 5px;
`; `;
@@ -47,40 +42,22 @@ function VariablesInput(props) {
</label> </label>
</SplitItem> </SplitItem>
<SplitItemRight> <SplitItemRight>
<ButtonGroup> <MultiButtonToggle
<SmallButton buttons={[[YAML_MODE, 'YAML'], [JSON_MODE, 'JSON']]}
onClick={() => { value={mode}
if (mode === YAML_MODE) { onChange={newMode => {
return; try {
}
try {
onChange(jsonToYaml(value));
setMode(YAML_MODE);
} catch (err) {
onError(err.message);
}
}}
variant={mode === YAML_MODE ? 'primary' : 'secondary'}
>
YAML
</SmallButton>
<SmallButton
onClick={() => {
if (mode === JSON_MODE) { if (mode === JSON_MODE) {
return; onChange(jsonToYaml(value));
} } else {
try {
onChange(yamlToJson(value)); onChange(yamlToJson(value));
setMode(JSON_MODE);
} catch (err) {
onError(err.message);
} }
}} setMode(newMode);
variant={mode === JSON_MODE ? 'primary' : 'secondary'} } catch (err) {
> onError(err.message);
JSON }
</SmallButton> }}
</ButtonGroup> />
</SplitItemRight> </SplitItemRight>
</Split> </Split>
<CodeMirrorInput <CodeMirrorInput

View File

@@ -1,44 +0,0 @@
import React from 'react';
import { oneOf, func } from 'prop-types';
import styled from 'styled-components';
import { Button } from '@patternfly/react-core';
import ButtonGroup from './ButtonGroup';
const SmallButton = styled(Button)`
padding: 3px 8px;
font-size: var(--pf-global--FontSize--xs);
`;
const YAML_MODE = 'yaml';
const JSON_MODE = 'javascript';
function YamlJsonToggle({ mode, onChange }) {
const setMode = newMode => {
if (mode !== newMode) {
onChange(newMode);
}
};
return (
<ButtonGroup>
<SmallButton
onClick={() => setMode(YAML_MODE)}
variant={mode === YAML_MODE ? 'primary' : 'secondary'}
>
YAML
</SmallButton>
<SmallButton
onClick={() => setMode(JSON_MODE)}
variant={mode === JSON_MODE ? 'primary' : 'secondary'}
>
JSON
</SmallButton>
</ButtonGroup>
);
}
YamlJsonToggle.propTypes = {
mode: oneOf([YAML_MODE, JSON_MODE]).isRequired,
onChange: func.isRequired,
};
export default YamlJsonToggle;

View File

@@ -0,0 +1,67 @@
import React from 'react';
import { func, string } from 'prop-types';
import styled from 'styled-components';
import { Button } from '@patternfly/react-core';
import ButtonGroup from './ButtonGroup';
const SmallButton = styled(Button)`
padding: 3px 8px;
font-size: var(--pf-global--FontSize--xs);
`;
function MultiButtonToggle({ buttons, value, onChange }) {
const setValue = newValue => {
if (value !== newValue) {
onChange(newValue);
}
};
return (
<ButtonGroup>
{buttons &&
buttons.map(([buttonValue, buttonLabel]) => (
<SmallButton
key={buttonLabel}
onClick={() => setValue(buttonValue)}
variant={buttonValue === value ? 'primary' : 'secondary'}
>
{buttonLabel}
</SmallButton>
))}
</ButtonGroup>
);
}
const buttonsPropType = {
isRequired: ({ buttons }) => {
if (!buttons) {
return new Error(
`The prop buttons is marked as required in MultiButtonToggle, but its value is '${buttons}'`
);
}
// We expect this data structure to look like:
// [[value(unrestricted type), label(string)], [value(unrestricted type), label(string)], ...]
if (
!Array.isArray(buttons) ||
buttons.length < 2 ||
buttons.reduce(
(prevVal, button) => prevVal || typeof button[1] !== 'string',
false
)
) {
return new Error(
`Invalid prop buttons supplied to MultiButtonToggle. Validation failed.`
);
}
return null;
},
};
MultiButtonToggle.propTypes = {
buttons: buttonsPropType.isRequired,
value: string.isRequired,
onChange: func.isRequired,
};
export default MultiButtonToggle;

View File

@@ -0,0 +1,31 @@
import React from 'react';
import { mount } from 'enzyme';
import MultiButtonToggle from './MultiButtonToggle';
describe('<MultiButtonToggle />', () => {
let wrapper;
const onChange = jest.fn();
beforeAll(() => {
wrapper = mount(
<MultiButtonToggle
buttons={[['yaml', 'YAML'], ['json', 'JSON']]}
value="yaml"
onChange={onChange}
/>
);
});
afterAll(() => {
wrapper.unmount();
});
it('should render buttons successfully', () => {
const buttons = wrapper.find('Button');
expect(buttons.length).toBe(2);
expect(buttons.at(0).props().variant).toBe('primary');
expect(buttons.at(1).props().variant).toBe('secondary');
});
it('should call onChange function when button clicked', () => {
const buttons = wrapper.find('Button');
buttons.at(1).simulate('click');
expect(onChange).toHaveBeenCalledWith('json');
});
});

View File

@@ -0,0 +1 @@
export { default } from './MultiButtonToggle';