1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-07-26 13:48:33 +02:00

Merge pull request #116 from corinnekrych/make.strategies.default.tab

Make.strategies.default.tab
This commit is contained in:
Ivar Conradi Østhus 2018-02-19 09:33:34 +01:00 committed by GitHub
commit 7e81ab81ed
42 changed files with 1902 additions and 94 deletions

View File

@ -67,6 +67,8 @@
"babel-preset-stage-0": "^6.5.0",
"babel-preset-stage-2": "^6.13.0",
"css-loader": "^0.28.4",
"enzyme": "^3.3.0",
"enzyme-adapter-react-16": "^1.1.1",
"eslint": "^4.5.0",
"eslint-config-finn": "^3.0.0",
"eslint-config-finn-prettier": "^3.0.0",
@ -92,6 +94,7 @@
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/src/__mocks__/fileMock.js",
"\\.(css|scss)$": "identity-obj-proxy"
},
"setupTestFrameworkScriptFile": "<rootDir>/src/setupTests.js",
"setupFiles": [
"<rootDir>/jest-setup.js"
]

View File

@ -13,6 +13,8 @@ module.exports = {
ListItem: 'react-mdl-ListItem',
ListItemContent: 'react-mdl-ListItemContent',
ListItemAction: 'react-mdl-ListItemAction',
Menu: 'react-mdl-Menu',
MenuItem: 'react-mdl-MenuItem',
ProgressBar: 'react-mdl-ProgressBar',
Switch: 'react-mdl-Switch',
Tab: 'react-mdl-Tab',

View File

@ -1,6 +1,6 @@
import { connect } from 'react-redux';
import ApplicationEdit from './application-edit-component';
import { fetchApplication, storeApplicationMetaData } from '../../store/application/actions';
import { fetchApplication, storeApplicationMetaData } from './../../store/application/actions';
const mapStateToProps = (state, props) => {
let application = state.applications.getIn(['apps', props.appName]);

View File

@ -1,6 +1,6 @@
import { connect } from 'react-redux';
import ApplicationList from './application-list-component';
import { fetchAll } from '../../store/application/actions';
import { fetchAll } from './../../store/application/actions';
const mapStateToProps = state => ({ applications: state.applications.get('list').toJS() });

View File

@ -27,6 +27,7 @@ class ArchiveList extends Component {
))}
</span>
);
return strategiesList;
}
renderStrategiesInList(feature) {

View File

@ -1,6 +1,6 @@
import { connect } from 'react-redux';
import ListComponent from './archive-list-component';
import { fetchArchive, revive } from '../../store/archive-actions';
import { fetchArchive, revive } from './../../store/archive-actions';
const mapStateToProps = state => {
const archive = state.archive.get('list').toArray();

View File

@ -180,3 +180,6 @@ export function calc(value, total, decimal) {
return (value / total * 100).toFixed(decimal);
}
export function getDisplayName(WrappedComponent) {
return WrappedComponent.displayName || WrappedComponent.name || 'Component';
}

View File

@ -0,0 +1,5 @@
{
"env": {
"jest": true
}
}

View File

@ -0,0 +1,55 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders correctly with one feature 1`] = `
<react-mdl-ListItem
twoLine={true}
>
<span
className="listItemMetric"
>
<svg
className="mdl-color-text--grey-300"
viewBox="0 0 24 24"
>
<path
d="M17.3,18C19,16.5 20,14.4 20,12A8,8 0 0,0 12,4A8,8 0 0,0 4,12C4,14.4 5,16.5 6.7,18C8.2,16.7 10,16 12,16C14,16 15.9,16.7 17.3,18M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M7,9A1,1 0 0,1 8,10A1,1 0 0,1 7,11A1,1 0 0,1 6,10A1,1 0 0,1 7,9M10,6A1,1 0 0,1 11,7A1,1 0 0,1 10,8A1,1 0 0,1 9,7A1,1 0 0,1 10,6M17,9A1,1 0 0,1 18,10A1,1 0 0,1 17,11A1,1 0 0,1 16,10A1,1 0 0,1 17,9M14.4,6.1C14.9,6.3 15.1,6.9 15,7.4L13.6,10.8C13.8,11.1 14,11.5 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12C10,11 10.7,10.1 11.7,10L13.1,6.7C13.3,6.1 13.9,5.9 14.4,6.1Z"
fill="currentColor"
/>
</svg>
</span>
<span
className="listItemToggle"
>
<react-mdl-Switch
checked={false}
onChange={[Function]}
title="Toggle Another"
/>
</span>
<span
className="mdl-list__item-primary-content listItemLink"
>
<a
className="listLink truncate"
onClick={[Function]}
style={Object {}}
>
Another
<span
className="mdl-list__item-sub-title truncate"
>
another's description
</span>
</a>
</span>
<span
className="listItemStrategies hideLt920"
>
<react-mdl-Chip
className="strategyChip"
>
gradualRolloutRandom
</react-mdl-Chip>
</span>
</react-mdl-ListItem>
`;

View File

@ -0,0 +1,107 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders correctly with 0% done no fallback 1`] = `
<svg
viewBox="0 0 100 100"
>
<path
className="trail mdl-color-text--grey-300"
d="
M 50,50 m 0,-42.5
a 42.5,42.5 0 1 1 0,85
a 42.5,42.5 0 1 1 0,-85
"
fillOpacity={0}
strokeWidth={15}
/>
<path
className="path mdl-color-text--primary"
d="
M 50,50 m 0,-42.5
a 42.5,42.5 0 1 1 0,85
a 42.5,42.5 0 1 1 0,-85
"
fillOpacity={0}
strokeWidth={15}
style={
Object {
"strokeDasharray": "267.0353755551324px 267.0353755551324px",
"strokeDashoffset": "267.0353755551324px",
}
}
/>
<text
className="text"
x={50}
y={50}
>
0
%
</text>
</svg>
`;
exports[`renders correctly with 0% done with fallback 1`] = `
<svg
className="mdl-color-text--grey-300"
viewBox="0 0 24 24"
>
<path
d="M17.3,18C19,16.5 20,14.4 20,12A8,8 0 0,0 12,4A8,8 0 0,0 4,12C4,14.4 5,16.5 6.7,18C8.2,16.7 10,16 12,16C14,16 15.9,16.7 17.3,18M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M7,9A1,1 0 0,1 8,10A1,1 0 0,1 7,11A1,1 0 0,1 6,10A1,1 0 0,1 7,9M10,6A1,1 0 0,1 11,7A1,1 0 0,1 10,8A1,1 0 0,1 9,7A1,1 0 0,1 10,6M17,9A1,1 0 0,1 18,10A1,1 0 0,1 17,11A1,1 0 0,1 16,10A1,1 0 0,1 17,9M14.4,6.1C14.9,6.3 15.1,6.9 15,7.4L13.6,10.8C13.8,11.1 14,11.5 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12C10,11 10.7,10.1 11.7,10L13.1,6.7C13.3,6.1 13.9,5.9 14.4,6.1Z"
fill="currentColor"
/>
</svg>
`;
exports[`renders correctly with 15% done no fallback 1`] = `
<svg
viewBox="0 0 100 100"
>
<path
className="trail mdl-color-text--grey-300"
d="
M 50,50 m 0,-42.5
a 42.5,42.5 0 1 1 0,85
a 42.5,42.5 0 1 1 0,-85
"
fillOpacity={0}
strokeWidth={15}
/>
<path
className="path mdl-color-text--primary"
d="
M 50,50 m 0,-42.5
a 42.5,42.5 0 1 1 0,85
a 42.5,42.5 0 1 1 0,-85
"
fillOpacity={0}
strokeWidth={15}
style={
Object {
"strokeDasharray": "267.0353755551324px 267.0353755551324px",
"strokeDashoffset": "226.98006922186255px",
}
}
/>
<text
className="text"
x={50}
y={50}
>
15
%
</text>
</svg>
`;
exports[`renders correctly with 15% done with fallback 1`] = `
<svg
className="mdl-color-text--grey-300"
viewBox="0 0 24 24"
>
<path
d="M17.3,18C19,16.5 20,14.4 20,12A8,8 0 0,0 12,4A8,8 0 0,0 4,12C4,14.4 5,16.5 6.7,18C8.2,16.7 10,16 12,16C14,16 15.9,16.7 17.3,18M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M7,9A1,1 0 0,1 8,10A1,1 0 0,1 7,11A1,1 0 0,1 6,10A1,1 0 0,1 7,9M10,6A1,1 0 0,1 11,7A1,1 0 0,1 10,8A1,1 0 0,1 9,7A1,1 0 0,1 10,6M17,9A1,1 0 0,1 18,10A1,1 0 0,1 17,11A1,1 0 0,1 16,10A1,1 0 0,1 17,9M14.4,6.1C14.9,6.3 15.1,6.9 15,7.4L13.6,10.8C13.8,11.1 14,11.5 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12C10,11 10.7,10.1 11.7,10L13.1,6.7C13.3,6.1 13.9,5.9 14.4,6.1Z"
fill="currentColor"
/>
</svg>
`;

View File

@ -0,0 +1,37 @@
import React from 'react';
import Feature from './../feature-list-item-component';
import renderer from 'react-test-renderer';
jest.mock('react-mdl');
test('renders correctly with one feature', () => {
const feature = {
name: 'Another',
description: "another's description",
enabled: false,
strategies: [
{
name: 'gradualRolloutRandom',
parameters: {
percentage: 50,
},
},
],
createdAt: '2018-02-04T20:27:52.127Z',
};
const featureMetrics = { lastHour: {}, lastMinute: {}, seenApps: {} };
const settings = { sort: 'name' };
const tree = renderer.create(
<Feature
key={0}
settings={settings}
metricsLastHour={featureMetrics.lastHour[feature.name]}
metricsLastMinute={featureMetrics.lastMinute[feature.name]}
feature={feature}
toggleFeature={jest.fn()}
/>
);
expect(tree).toMatchSnapshot();
});

View File

@ -0,0 +1,34 @@
import React from 'react';
import Progress from './../progress';
import renderer from 'react-test-renderer';
jest.mock('react-mdl');
test('renders correctly with 15% done no fallback', () => {
const percent = 15;
const tree = renderer.create(<Progress strokeWidth={15} percentage={percent} isFallback={false} />);
expect(tree).toMatchSnapshot();
});
test('renders correctly with 0% done no fallback', () => {
const percent = 0;
const tree = renderer.create(<Progress strokeWidth={15} percentage={percent} isFallback={false} />);
expect(tree).toMatchSnapshot();
});
test('renders correctly with 15% done with fallback', () => {
const percent = 15;
const tree = renderer.create(<Progress strokeWidth={15} percentage={percent} isFallback />);
expect(tree).toMatchSnapshot();
});
test('renders correctly with 0% done with fallback', () => {
const percent = 0;
const tree = renderer.create(<Progress strokeWidth={15} percentage={percent} isFallback />);
expect(tree).toMatchSnapshot();
});

View File

@ -52,7 +52,7 @@ const Feature = ({
</span>
<span className={['mdl-list__item-primary-content', styles.listItemLink].join(' ')}>
<Link
to={`/features/view/${name}`}
to={`/features/strategies/${name}`}
className={[commonStyles.listLink, commonStyles.truncate].join(' ')}
>
{name}

View File

@ -1,19 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Card, CardTitle } from 'react-mdl';
import FormComponent from './form';
import { styles as commonStyles } from '../common';
const FormAddComponent = ({ title, ...formProps }) => (
<Card className={commonStyles.fullwidth} style={{ overflow: 'visible' }}>
<CardTitle style={{ paddingTop: '24px' }}>{title}</CardTitle>
<FormComponent {...formProps} />
</Card>
);
FormAddComponent.propTypes = {
title: PropTypes.string,
};
export default FormAddComponent;

View File

@ -0,0 +1,5 @@
{
"env": {
"jest": true
}
}

View File

@ -0,0 +1,555 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`render the create feature page 1`] = `
ShallowWrapper {
"length": 1,
Symbol(enzyme.__root__): [Circular],
Symbol(enzyme.__unrendered__): <AddFeatureComponent
addStrategy={[MockFunction]}
initCallRequired={false}
input={
Object {
"description": "Description",
"enabled": false,
"name": "feature",
"nameError": Object {},
}
}
moveStrategy={[MockFunction]}
onCancel={[MockFunction]}
onSubmit={
[MockFunction] {
"calls": Array [
Array [
Object {
"description": "Description",
"enabled": false,
"name": "feature",
"nameError": Object {},
},
],
],
}
}
removeStrategy={[MockFunction]}
setValue={[MockFunction]}
title="title"
updateStrategy={[MockFunction]}
validateName={[MockFunction]}
/>,
Symbol(enzyme.__renderer__): Object {
"batchedUpdates": [Function],
"getNode": [Function],
"render": [Function],
"simulateEvent": [Function],
"unmount": [Function],
},
Symbol(enzyme.__node__): Object {
"instance": null,
"key": undefined,
"nodeType": "host",
"props": Object {
"children": <section
style={
Object {
"padding": "16px",
}
}
>
<react-mdl-Textfield
error={Object {}}
floatingLabel={true}
label="Name"
name="name"
onBlur={[Function]}
onChange={[Function]}
required={true}
value="feature"
/>
<react-mdl-Textfield
floatingLabel={true}
label="Description"
onChange={[Function]}
required={true}
rows={1}
style={
Object {
"width": "100%",
}
}
value="Description"
/>
<div>
<br />
<react-mdl-Switch
checked={false}
onChange={[Function]}
>
Enabled
</react-mdl-Switch>
<hr />
</div>
<StrategiesSection
addStrategy={[MockFunction]}
configuredStrategies={Array []}
moveStrategy={[MockFunction]}
removeStrategy={[MockFunction]}
updateStrategy={[MockFunction]}
/>
<br />
<FormButtons
onCancel={[MockFunction]}
submitText="Create"
/>
</section>,
"onSubmit": undefined,
},
"ref": null,
"rendered": Object {
"instance": null,
"key": undefined,
"nodeType": "host",
"props": Object {
"children": Array [
<react-mdl-Textfield
error={Object {}}
floatingLabel={true}
label="Name"
name="name"
onBlur={[Function]}
onChange={[Function]}
required={true}
value="feature"
/>,
<react-mdl-Textfield
floatingLabel={true}
label="Description"
onChange={[Function]}
required={true}
rows={1}
style={
Object {
"width": "100%",
}
}
value="Description"
/>,
<div>
<br />
<react-mdl-Switch
checked={false}
onChange={[Function]}
>
Enabled
</react-mdl-Switch>
<hr />
</div>,
<StrategiesSection
addStrategy={[MockFunction]}
configuredStrategies={Array []}
moveStrategy={[MockFunction]}
removeStrategy={[MockFunction]}
updateStrategy={[MockFunction]}
/>,
<br />,
<FormButtons
onCancel={[MockFunction]}
submitText="Create"
/>,
],
"style": Object {
"padding": "16px",
},
},
"ref": null,
"rendered": Array [
Object {
"instance": null,
"key": undefined,
"nodeType": "host",
"props": Object {
"error": Object {},
"floatingLabel": true,
"label": "Name",
"name": "name",
"onBlur": [Function],
"onChange": [Function],
"required": true,
"value": "feature",
},
"ref": null,
"rendered": null,
"type": "react-mdl-Textfield",
},
Object {
"instance": null,
"key": undefined,
"nodeType": "host",
"props": Object {
"floatingLabel": true,
"label": "Description",
"onChange": [Function],
"required": true,
"rows": 1,
"style": Object {
"width": "100%",
},
"value": "Description",
},
"ref": null,
"rendered": null,
"type": "react-mdl-Textfield",
},
Object {
"instance": null,
"key": undefined,
"nodeType": "host",
"props": Object {
"children": Array [
<br />,
<react-mdl-Switch
checked={false}
onChange={[Function]}
>
Enabled
</react-mdl-Switch>,
<hr />,
],
},
"ref": null,
"rendered": Array [
Object {
"instance": null,
"key": undefined,
"nodeType": "host",
"props": Object {},
"ref": null,
"rendered": null,
"type": "br",
},
Object {
"instance": null,
"key": undefined,
"nodeType": "host",
"props": Object {
"checked": false,
"children": "Enabled",
"onChange": [Function],
},
"ref": null,
"rendered": "Enabled",
"type": "react-mdl-Switch",
},
Object {
"instance": null,
"key": undefined,
"nodeType": "host",
"props": Object {},
"ref": null,
"rendered": null,
"type": "hr",
},
],
"type": "div",
},
Object {
"instance": null,
"key": undefined,
"nodeType": "host",
"props": Object {
"addStrategy": [MockFunction],
"configuredStrategies": Array [],
"moveStrategy": [MockFunction],
"removeStrategy": [MockFunction],
"updateStrategy": [MockFunction],
},
"ref": null,
"rendered": null,
"type": "StrategiesSection",
},
Object {
"instance": null,
"key": undefined,
"nodeType": "host",
"props": Object {},
"ref": null,
"rendered": null,
"type": "br",
},
Object {
"instance": null,
"key": undefined,
"nodeType": "function",
"props": Object {
"onCancel": [MockFunction],
"submitText": "Create",
},
"ref": null,
"rendered": null,
"type": [Function],
},
],
"type": "section",
},
"type": "form",
},
Symbol(enzyme.__nodes__): Array [
Object {
"instance": null,
"key": undefined,
"nodeType": "host",
"props": Object {
"children": <section
style={
Object {
"padding": "16px",
}
}
>
<react-mdl-Textfield
error={Object {}}
floatingLabel={true}
label="Name"
name="name"
onBlur={[Function]}
onChange={[Function]}
required={true}
value="feature"
/>
<react-mdl-Textfield
floatingLabel={true}
label="Description"
onChange={[Function]}
required={true}
rows={1}
style={
Object {
"width": "100%",
}
}
value="Description"
/>
<div>
<br />
<react-mdl-Switch
checked={false}
onChange={[Function]}
>
Enabled
</react-mdl-Switch>
<hr />
</div>
<StrategiesSection
addStrategy={[MockFunction]}
configuredStrategies={Array []}
moveStrategy={[MockFunction]}
removeStrategy={[MockFunction]}
updateStrategy={[MockFunction]}
/>
<br />
<FormButtons
onCancel={[MockFunction]}
submitText="Create"
/>
</section>,
"onSubmit": undefined,
},
"ref": null,
"rendered": Object {
"instance": null,
"key": undefined,
"nodeType": "host",
"props": Object {
"children": Array [
<react-mdl-Textfield
error={Object {}}
floatingLabel={true}
label="Name"
name="name"
onBlur={[Function]}
onChange={[Function]}
required={true}
value="feature"
/>,
<react-mdl-Textfield
floatingLabel={true}
label="Description"
onChange={[Function]}
required={true}
rows={1}
style={
Object {
"width": "100%",
}
}
value="Description"
/>,
<div>
<br />
<react-mdl-Switch
checked={false}
onChange={[Function]}
>
Enabled
</react-mdl-Switch>
<hr />
</div>,
<StrategiesSection
addStrategy={[MockFunction]}
configuredStrategies={Array []}
moveStrategy={[MockFunction]}
removeStrategy={[MockFunction]}
updateStrategy={[MockFunction]}
/>,
<br />,
<FormButtons
onCancel={[MockFunction]}
submitText="Create"
/>,
],
"style": Object {
"padding": "16px",
},
},
"ref": null,
"rendered": Array [
Object {
"instance": null,
"key": undefined,
"nodeType": "host",
"props": Object {
"error": Object {},
"floatingLabel": true,
"label": "Name",
"name": "name",
"onBlur": [Function],
"onChange": [Function],
"required": true,
"value": "feature",
},
"ref": null,
"rendered": null,
"type": "react-mdl-Textfield",
},
Object {
"instance": null,
"key": undefined,
"nodeType": "host",
"props": Object {
"floatingLabel": true,
"label": "Description",
"onChange": [Function],
"required": true,
"rows": 1,
"style": Object {
"width": "100%",
},
"value": "Description",
},
"ref": null,
"rendered": null,
"type": "react-mdl-Textfield",
},
Object {
"instance": null,
"key": undefined,
"nodeType": "host",
"props": Object {
"children": Array [
<br />,
<react-mdl-Switch
checked={false}
onChange={[Function]}
>
Enabled
</react-mdl-Switch>,
<hr />,
],
},
"ref": null,
"rendered": Array [
Object {
"instance": null,
"key": undefined,
"nodeType": "host",
"props": Object {},
"ref": null,
"rendered": null,
"type": "br",
},
Object {
"instance": null,
"key": undefined,
"nodeType": "host",
"props": Object {
"checked": false,
"children": "Enabled",
"onChange": [Function],
},
"ref": null,
"rendered": "Enabled",
"type": "react-mdl-Switch",
},
Object {
"instance": null,
"key": undefined,
"nodeType": "host",
"props": Object {},
"ref": null,
"rendered": null,
"type": "hr",
},
],
"type": "div",
},
Object {
"instance": null,
"key": undefined,
"nodeType": "host",
"props": Object {
"addStrategy": [MockFunction],
"configuredStrategies": Array [],
"moveStrategy": [MockFunction],
"removeStrategy": [MockFunction],
"updateStrategy": [MockFunction],
},
"ref": null,
"rendered": null,
"type": "StrategiesSection",
},
Object {
"instance": null,
"key": undefined,
"nodeType": "host",
"props": Object {},
"ref": null,
"rendered": null,
"type": "br",
},
Object {
"instance": null,
"key": undefined,
"nodeType": "function",
"props": Object {
"onCancel": [MockFunction],
"submitText": "Create",
},
"ref": null,
"rendered": null,
"type": [Function],
},
],
"type": "section",
},
"type": "form",
},
],
Symbol(enzyme.__options__): Object {
"adapter": ReactSixteenAdapter {
"options": Object {
"enableComponentDidUpdateOnSetState": true,
},
},
},
}
`;

View File

@ -0,0 +1,245 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`render the create feature page 1`] = `
ShallowWrapper {
"length": 1,
Symbol(enzyme.__root__): [Circular],
Symbol(enzyme.__unrendered__): <UpdateFeatureComponent
addStrategy={[MockFunction]}
initCallRequired={false}
input={
Object {
"description": "Description",
"enabled": false,
"name": "feature",
"nameError": Object {},
}
}
moveStrategy={[MockFunction]}
onCancel={[MockFunction]}
onSubmit={
[MockFunction] {
"calls": Array [
Array [
Object {
"description": "Description",
"enabled": false,
"name": "feature",
"nameError": Object {},
},
],
],
}
}
removeStrategy={[MockFunction]}
setValue={[MockFunction]}
title="title"
updateStrategy={[MockFunction]}
validateName={[MockFunction]}
/>,
Symbol(enzyme.__renderer__): Object {
"batchedUpdates": [Function],
"getNode": [Function],
"render": [Function],
"simulateEvent": [Function],
"unmount": [Function],
},
Symbol(enzyme.__node__): Object {
"instance": null,
"key": undefined,
"nodeType": "host",
"props": Object {
"children": <section
style={
Object {
"padding": "16px",
}
}
>
<StrategiesSection
addStrategy={[MockFunction]}
configuredStrategies={Array []}
moveStrategy={[MockFunction]}
removeStrategy={[MockFunction]}
updateStrategy={[MockFunction]}
/>
<br />
<FormButtons
onCancel={[MockFunction]}
submitText="Update"
/>
</section>,
"onSubmit": undefined,
},
"ref": null,
"rendered": Object {
"instance": null,
"key": undefined,
"nodeType": "host",
"props": Object {
"children": Array [
<StrategiesSection
addStrategy={[MockFunction]}
configuredStrategies={Array []}
moveStrategy={[MockFunction]}
removeStrategy={[MockFunction]}
updateStrategy={[MockFunction]}
/>,
<br />,
<FormButtons
onCancel={[MockFunction]}
submitText="Update"
/>,
],
"style": Object {
"padding": "16px",
},
},
"ref": null,
"rendered": Array [
Object {
"instance": null,
"key": undefined,
"nodeType": "host",
"props": Object {
"addStrategy": [MockFunction],
"configuredStrategies": Array [],
"moveStrategy": [MockFunction],
"removeStrategy": [MockFunction],
"updateStrategy": [MockFunction],
},
"ref": null,
"rendered": null,
"type": "StrategiesSection",
},
Object {
"instance": null,
"key": undefined,
"nodeType": "host",
"props": Object {},
"ref": null,
"rendered": null,
"type": "br",
},
Object {
"instance": null,
"key": undefined,
"nodeType": "function",
"props": Object {
"onCancel": [MockFunction],
"submitText": "Update",
},
"ref": null,
"rendered": null,
"type": [Function],
},
],
"type": "section",
},
"type": "form",
},
Symbol(enzyme.__nodes__): Array [
Object {
"instance": null,
"key": undefined,
"nodeType": "host",
"props": Object {
"children": <section
style={
Object {
"padding": "16px",
}
}
>
<StrategiesSection
addStrategy={[MockFunction]}
configuredStrategies={Array []}
moveStrategy={[MockFunction]}
removeStrategy={[MockFunction]}
updateStrategy={[MockFunction]}
/>
<br />
<FormButtons
onCancel={[MockFunction]}
submitText="Update"
/>
</section>,
"onSubmit": undefined,
},
"ref": null,
"rendered": Object {
"instance": null,
"key": undefined,
"nodeType": "host",
"props": Object {
"children": Array [
<StrategiesSection
addStrategy={[MockFunction]}
configuredStrategies={Array []}
moveStrategy={[MockFunction]}
removeStrategy={[MockFunction]}
updateStrategy={[MockFunction]}
/>,
<br />,
<FormButtons
onCancel={[MockFunction]}
submitText="Update"
/>,
],
"style": Object {
"padding": "16px",
},
},
"ref": null,
"rendered": Array [
Object {
"instance": null,
"key": undefined,
"nodeType": "host",
"props": Object {
"addStrategy": [MockFunction],
"configuredStrategies": Array [],
"moveStrategy": [MockFunction],
"removeStrategy": [MockFunction],
"updateStrategy": [MockFunction],
},
"ref": null,
"rendered": null,
"type": "StrategiesSection",
},
Object {
"instance": null,
"key": undefined,
"nodeType": "host",
"props": Object {},
"ref": null,
"rendered": null,
"type": "br",
},
Object {
"instance": null,
"key": undefined,
"nodeType": "function",
"props": Object {
"onCancel": [MockFunction],
"submitText": "Update",
},
"ref": null,
"rendered": null,
"type": [Function],
},
],
"type": "section",
},
"type": "form",
},
],
Symbol(enzyme.__options__): Object {
"adapter": ReactSixteenAdapter {
"options": Object {
"enableComponentDidUpdateOnSetState": true,
},
},
},
}
`;

View File

@ -0,0 +1,296 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders add strategy form with a list of available strategies 1`] = `
ShallowWrapper {
"length": 1,
Symbol(enzyme.__root__): [Circular],
Symbol(enzyme.__unrendered__): <AddStrategy
addStrategy={[MockFunction]}
fetchStrategies={[MockFunction]}
strategies={
Array [
Object {
"description": "Default on/off strategy.",
"editable": false,
"name": "default",
"parameters": Array [
"t",
],
},
]
}
/>,
Symbol(enzyme.__renderer__): Object {
"batchedUpdates": [Function],
"getNode": [Function],
"render": [Function],
"simulateEvent": [Function],
"unmount": [Function],
},
Symbol(enzyme.__node__): Object {
"instance": null,
"key": undefined,
"nodeType": "host",
"props": Object {
"children": Array [
<react-mdl-IconButton
accent={true}
id="strategies-add"
name="add"
onClick={[Function]}
raised={true}
title="Add Strategy"
/>,
<react-mdl-Menu
align="right"
ripple={true}
style={
Object {
"backgroundColor": "rgb(247, 248, 255)",
"maxHeight": "300px",
"overflowY": "auto",
}
}
target="strategies-add"
valign="bottom"
>
<react-mdl-MenuItem
disabled={true}
>
Add Strategy:
</react-mdl-MenuItem>
<react-mdl-MenuItem
onClick={[Function]}
title="Default on/off strategy."
>
default
</react-mdl-MenuItem>
</react-mdl-Menu>,
],
"style": Object {
"display": "inline-block",
"height": "25px",
"position": "relative",
"width": "25px",
},
},
"ref": null,
"rendered": Array [
Object {
"instance": null,
"key": undefined,
"nodeType": "host",
"props": Object {
"accent": true,
"id": "strategies-add",
"name": "add",
"onClick": [Function],
"raised": true,
"title": "Add Strategy",
},
"ref": null,
"rendered": null,
"type": "react-mdl-IconButton",
},
Object {
"instance": null,
"key": undefined,
"nodeType": "host",
"props": Object {
"align": "right",
"children": Array [
<react-mdl-MenuItem
disabled={true}
>
Add Strategy:
</react-mdl-MenuItem>,
Array [
<react-mdl-MenuItem
onClick={[Function]}
title="Default on/off strategy."
>
default
</react-mdl-MenuItem>,
],
],
"ripple": true,
"style": Object {
"backgroundColor": "rgb(247, 248, 255)",
"maxHeight": "300px",
"overflowY": "auto",
},
"target": "strategies-add",
"valign": "bottom",
},
"ref": null,
"rendered": Array [
Object {
"instance": null,
"key": undefined,
"nodeType": "host",
"props": Object {
"children": "Add Strategy:",
"disabled": true,
},
"ref": null,
"rendered": "Add Strategy:",
"type": "react-mdl-MenuItem",
},
Object {
"instance": null,
"key": "default",
"nodeType": "host",
"props": Object {
"children": "default",
"onClick": [Function],
"title": "Default on/off strategy.",
},
"ref": null,
"rendered": "default",
"type": "react-mdl-MenuItem",
},
],
"type": "react-mdl-Menu",
},
],
"type": "div",
},
Symbol(enzyme.__nodes__): Array [
Object {
"instance": null,
"key": undefined,
"nodeType": "host",
"props": Object {
"children": Array [
<react-mdl-IconButton
accent={true}
id="strategies-add"
name="add"
onClick={[Function]}
raised={true}
title="Add Strategy"
/>,
<react-mdl-Menu
align="right"
ripple={true}
style={
Object {
"backgroundColor": "rgb(247, 248, 255)",
"maxHeight": "300px",
"overflowY": "auto",
}
}
target="strategies-add"
valign="bottom"
>
<react-mdl-MenuItem
disabled={true}
>
Add Strategy:
</react-mdl-MenuItem>
<react-mdl-MenuItem
onClick={[Function]}
title="Default on/off strategy."
>
default
</react-mdl-MenuItem>
</react-mdl-Menu>,
],
"style": Object {
"display": "inline-block",
"height": "25px",
"position": "relative",
"width": "25px",
},
},
"ref": null,
"rendered": Array [
Object {
"instance": null,
"key": undefined,
"nodeType": "host",
"props": Object {
"accent": true,
"id": "strategies-add",
"name": "add",
"onClick": [Function],
"raised": true,
"title": "Add Strategy",
},
"ref": null,
"rendered": null,
"type": "react-mdl-IconButton",
},
Object {
"instance": null,
"key": undefined,
"nodeType": "host",
"props": Object {
"align": "right",
"children": Array [
<react-mdl-MenuItem
disabled={true}
>
Add Strategy:
</react-mdl-MenuItem>,
Array [
<react-mdl-MenuItem
onClick={[Function]}
title="Default on/off strategy."
>
default
</react-mdl-MenuItem>,
],
],
"ripple": true,
"style": Object {
"backgroundColor": "rgb(247, 248, 255)",
"maxHeight": "300px",
"overflowY": "auto",
},
"target": "strategies-add",
"valign": "bottom",
},
"ref": null,
"rendered": Array [
Object {
"instance": null,
"key": undefined,
"nodeType": "host",
"props": Object {
"children": "Add Strategy:",
"disabled": true,
},
"ref": null,
"rendered": "Add Strategy:",
"type": "react-mdl-MenuItem",
},
Object {
"instance": null,
"key": "default",
"nodeType": "host",
"props": Object {
"children": "default",
"onClick": [Function],
"title": "Default on/off strategy.",
},
"ref": null,
"rendered": "default",
"type": "react-mdl-MenuItem",
},
],
"type": "react-mdl-Menu",
},
],
"type": "div",
},
],
Symbol(enzyme.__options__): Object {
"adapter": ReactSixteenAdapter {
"options": Object {
"enableComponentDidUpdateOnSetState": true,
},
},
},
}
`;

View File

@ -0,0 +1,103 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders strategy with empty list as param 1`] = `
<div>
<p>
featureName
</p>
<div
style={
Object {
"display": "flex",
}
}
>
<react-mdl-Textfield
floatingLabel={true}
label="Add list entry"
name="featureName_input"
onBlur={[Function]}
onFocus={[Function]}
style={
Object {
"flex": 1,
"width": "100%",
}
}
/>
<react-mdl-IconButton
name="add"
onClick={[Function]}
raised={true}
style={
Object {
"flex": 1,
"flexGrow": 0,
"margin": "20px 0 0 10px",
}
}
/>
</div>
</div>
`;
exports[`renders strategy with list as param 1`] = `
<div>
<p>
featureName
</p>
<react-mdl-Chip
onClose={[Function]}
style={
Object {
"marginRight": "3px",
}
}
>
item1
</react-mdl-Chip>
<react-mdl-Chip
onClose={[Function]}
style={
Object {
"marginRight": "3px",
}
}
>
item2
</react-mdl-Chip>
<div
style={
Object {
"display": "flex",
}
}
>
<react-mdl-Textfield
floatingLabel={true}
label="Add list entry"
name="featureName_input"
onBlur={[Function]}
onFocus={[Function]}
style={
Object {
"flex": 1,
"width": "100%",
}
}
/>
<react-mdl-IconButton
name="add"
onClick={[Function]}
raised={true}
style={
Object {
"flex": 1,
"flexGrow": 0,
"margin": "20px 0 0 10px",
}
}
/>
</div>
</div>
`;

View File

@ -0,0 +1,124 @@
import React from 'react';
import AddFeatureComponent from './../form-add-feature-component';
import { shallow } from 'enzyme/build/index';
jest.mock('react-mdl');
jest.mock('../strategies-section-container', () => 'StrategiesSection');
it('render the create feature page', () => {
let input = {
name: 'feature',
nameError: {},
description: 'Description',
enabled: false,
};
const tree = shallow(
<AddFeatureComponent
input={input}
onSubmit={jest.fn()}
setValue={jest.fn()}
addStrategy={jest.fn()}
removeStrategy={jest.fn()}
moveStrategy={jest.fn()}
onCancel={jest.fn()}
updateStrategy={jest.fn()}
validateName={jest.fn()}
initCallRequired={false}
title="title"
/>
);
expect(tree).toMatchSnapshot();
});
let input = {
name: 'feature',
nameError: {},
description: 'Description',
enabled: false,
};
let validateName = jest.fn();
let setValue = jest.fn();
let onSubmit = jest.fn();
let addStrategy = jest.fn();
let removeStrategy = jest.fn();
let moveStrategy = jest.fn();
let onCancel = jest.fn();
let updateStratgy = jest.fn();
let init = jest.fn();
let eventMock = {
preventDefault: () => {},
stopPropagation: () => {},
target: {
name: 'NAME',
},
};
const buildComponent = (setValue, validateName) => (
<AddFeatureComponent
input={input}
onSubmit={onSubmit}
setValue={setValue}
addStrategy={addStrategy}
removeStrategy={removeStrategy}
moveStrategy={moveStrategy}
onCancel={onCancel}
updateStrategy={updateStratgy}
validateName={validateName}
initCallRequired
title="title"
init={init}
/>
);
it('add a feature name with validation', () => {
let called = false;
validateName = () => {
called = true;
};
const wrapper = shallow(buildComponent(setValue, validateName));
wrapper
.find('react-mdl-Textfield')
.first()
.simulate('blur', eventMock);
expect(called).toBe(true);
});
it('set a value for feature name', () => {
let called = false;
setValue = () => {
called = true;
};
let wrapper = shallow(buildComponent(setValue, validateName));
wrapper
.find('react-mdl-Textfield')
.first()
.simulate('change', eventMock);
expect(called).toBe(true);
});
it('set a description for feature name', () => {
let called = false;
setValue = () => {
called = true;
};
let wrapper = shallow(buildComponent(setValue, validateName));
wrapper
.find('react-mdl-Textfield')
.last()
.simulate('change', eventMock);
expect(called).toBe(true);
});
it('switch the toggle', () => {
let called = false;
setValue = () => {
called = true;
};
let wrapper = shallow(buildComponent(setValue, validateName));
eventMock.target.enabled = false;
wrapper
.find('react-mdl-Switch')
.last()
.simulate('change', eventMock);
expect(called).toBe(true);
});

View File

@ -0,0 +1,31 @@
import React from 'react';
import UpdateFeatureComponent from './../form-update-feature-component';
import { shallow } from 'enzyme/build/index';
jest.mock('react-mdl');
jest.mock('../strategies-section-container', () => 'StrategiesSection');
it('render the create feature page', () => {
let input = {
name: 'feature',
nameError: {},
description: 'Description',
enabled: false,
};
const tree = shallow(
<UpdateFeatureComponent
input={input}
onSubmit={jest.fn()}
setValue={jest.fn()}
addStrategy={jest.fn()}
removeStrategy={jest.fn()}
moveStrategy={jest.fn()}
onCancel={jest.fn()}
updateStrategy={jest.fn()}
validateName={jest.fn()}
initCallRequired={false}
title="title"
/>
);
expect(tree).toMatchSnapshot();
});

View File

@ -0,0 +1,46 @@
import React from 'react';
import AddStrategy from './../strategies-add';
import { shallow } from 'enzyme/build/index';
jest.mock('react-mdl');
const strategies = [
{
name: 'default',
editable: false,
description: 'Default on/off strategy.',
parameters: ['t'],
},
];
let addStrategy = jest.fn();
let fetchStrategies = jest.fn();
let eventMock = {
preventDefault: () => {},
stopPropagation: () => {},
target: {
name: 'default',
},
};
const buildComponent = (addStrategy, fetchStrategies, strategies) => (
<AddStrategy addStrategy={addStrategy} fetchStrategies={fetchStrategies} strategies={strategies} />
);
it('renders add strategy form with a list of available strategies', () => {
const tree = shallow(buildComponent(addStrategy, fetchStrategies, strategies));
expect(tree).toMatchSnapshot();
});
it('add a strategy', () => {
let called = false;
addStrategy = () => {
called = true;
};
const addStrategyProto = jest.spyOn(AddStrategy.prototype, 'addStrategy');
const wrapper = shallow(buildComponent(addStrategy, fetchStrategies, strategies));
wrapper
.find('react-mdl-MenuItem')
.last()
.simulate('click', eventMock);
expect(called).toBe(true);
expect(addStrategyProto).toHaveBeenCalled();
});

View File

@ -0,0 +1,81 @@
import React from 'react';
import InputList from './../strategy-input-list';
import renderer from 'react-test-renderer';
import { shallow } from 'enzyme';
it('renders strategy with list as param', () => {
const list = ['item1', 'item2'];
const name = 'featureName';
const tree = renderer.create(<InputList list={list} name={name} setConfig={jest.fn()} />);
expect(tree).toMatchSnapshot();
});
it('renders strategy with empty list as param', () => {
const list = [];
const name = 'featureName';
const tree = renderer.create(<InputList list={list} name={name} setConfig={jest.fn()} />);
expect(tree).toMatchSnapshot();
});
it('renders an item as chip', () => {
let list = ['item1'];
const name = 'featureName';
let wrapper = shallow(<InputList list={list} name={name} setConfig={jest.fn()} />);
expect(wrapper.find('react-mdl-Chip').length).toEqual(1);
expect(wrapper.find('react-mdl-Chip').text()).toEqual('item1');
});
it('go inside onFocus', () => {
let list = ['item1'];
const name = 'featureName';
const wrapper = shallow(<InputList list={list} name={name} setConfig={jest.fn()} />);
let focusMock = {
preventDefault: () => {},
stopPropagation: () => {},
key: 'e',
};
wrapper.find('react-mdl-Textfield').simulate('focus', focusMock);
});
// https://github.com/airbnb/enzyme/issues/944
it('spy onFocus', () => {
let list = ['item1'];
const name = 'featureName';
const onFocus = jest.spyOn(InputList.prototype, 'onFocus');
let focusMock = {
preventDefault: () => {},
stopPropagation: () => {},
key: 'e',
};
const wrapper = shallow(<InputList list={list} name={name} setConfig={jest.fn()} />);
wrapper.find('react-mdl-Textfield').simulate('focus', focusMock);
expect(onFocus).toHaveBeenCalled();
});
it('spy onBlur', () => {
let list = ['item1'];
const name = 'featureName';
const onFocus = jest.spyOn(InputList.prototype, 'onBlur');
let focusMock = {
preventDefault: () => {},
stopPropagation: () => {},
};
const wrapper = shallow(<InputList list={list} name={name} setConfig={jest.fn()} />);
wrapper.find('react-mdl-Textfield').simulate('blur', focusMock);
expect(onFocus).toHaveBeenCalled();
});
it('spy onClose', () => {
let list = ['item1'];
const name = 'featureName';
const onClose = jest.spyOn(InputList.prototype, 'onClose');
let closeMock = {
preventDefault: () => {},
stopPropagation: () => {},
};
const wrapper = shallow(<InputList list={list} name={name} setConfig={jest.fn()} />);
wrapper.find('react-mdl-Chip').simulate('close', closeMock);
expect(onClose).toHaveBeenCalled();
});

View File

@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import { Textfield, Switch } from 'react-mdl';
import StrategiesSection from './strategies-section-container';
import { FormButtons } from '../../common';
import { FormButtons } from './../../common';
const trim = value => {
if (value && value.trim) {
@ -13,7 +13,8 @@ const trim = value => {
}
};
class AddFeatureToggleComponent extends Component {
class AddFeatureComponent extends Component {
// static displayName = `AddFeatureComponent-${getDisplayName(Component)}`;
componentWillMount() {
// TODO unwind this stuff
if (this.props.initCallRequired === true) {
@ -32,7 +33,6 @@ class AddFeatureToggleComponent extends Component {
moveStrategy,
onSubmit,
onCancel,
editmode = false,
} = this.props;
const {
@ -50,14 +50,12 @@ class AddFeatureToggleComponent extends Component {
floatingLabel
label="Name"
name="name"
disabled={editmode}
required
value={name}
error={nameError}
onBlur={v => validateName(v.target.value)}
onChange={v => setValue('name', trim(v.target.value))}
/>
<br />
<Textfield
floatingLabel
style={{ width: '100%' }}
@ -67,21 +65,19 @@ class AddFeatureToggleComponent extends Component {
value={description}
onChange={v => setValue('description', v.target.value)}
/>
<div>
<br />
<Switch
checked={enabled}
onChange={() => {
setValue('enabled', !enabled);
}}
>
Enabled
</Switch>
<hr />
</div>
{!editmode && (
<div>
<br />
<Switch
checked={enabled}
onChange={() => {
setValue('enabled', !enabled);
}}
>
Enabled
</Switch>
<hr />
</div>
)}
<StrategiesSection
configuredStrategies={configuredStrategies}
addStrategy={addStrategy}
@ -91,14 +87,14 @@ class AddFeatureToggleComponent extends Component {
/>
<br />
<FormButtons submitText={editmode ? 'Update' : 'Create'} onCancel={onCancel} />
<FormButtons submitText={'Create'} onCancel={onCancel} />
</section>
</form>
);
}
}
AddFeatureToggleComponent.propTypes = {
AddFeatureComponent.propTypes = {
input: PropTypes.object,
setValue: PropTypes.func.isRequired,
addStrategy: PropTypes.func.isRequired,
@ -108,9 +104,8 @@ AddFeatureToggleComponent.propTypes = {
onSubmit: PropTypes.func.isRequired,
onCancel: PropTypes.func.isRequired,
validateName: PropTypes.func.isRequired,
editmode: PropTypes.bool,
initCallRequired: PropTypes.bool,
init: PropTypes.func,
};
export default AddFeatureToggleComponent;
export default AddFeatureComponent;

View File

@ -1,8 +1,8 @@
import { connect } from 'react-redux';
import { hashHistory } from 'react-router';
import { createFeatureToggles, validateName } from '../../store/feature-actions';
import { createMapper, createActions } from '../input-helpers';
import FormAddComponent from './form-add-component';
import { createFeatureToggles, validateName } from './../../../store/feature-actions';
import { createMapper, createActions } from './../../input-helpers';
import AddFeatureComponent from './form-add-feature-component';
const ID = 'add-feature-toggle';
const mapStateToProps = createMapper({
@ -31,7 +31,7 @@ const prepare = (methods, dispatch) => {
createFeatureToggles(input)(dispatch)
.then(() => methods.clear())
.then(() => hashHistory.push(`/features/edit/${input.name}`));
.then(() => hashHistory.push(`/features/strategies/${input.name}`));
};
methods.onCancel = evt => {
@ -67,6 +67,6 @@ const prepare = (methods, dispatch) => {
};
const actions = createActions({ id: ID, prepare });
const FormAddContainer = connect(mapStateToProps, actions)(FormAddComponent);
const FormAddContainer = connect(mapStateToProps, actions)(AddFeatureComponent);
export default FormAddContainer;

View File

@ -0,0 +1,57 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import StrategiesSection from './strategies-section-container';
import { FormButtons } from './../../common';
class UpdateFeatureComponent extends Component {
// static displayName = `UpdateFeatureComponent-{getDisplayName(Component)}`;
componentWillMount() {
// TODO unwind this stuff
if (this.props.initCallRequired === true) {
this.props.init(this.props.input);
}
}
render() {
const { input, addStrategy, removeStrategy, updateStrategy, moveStrategy, onSubmit, onCancel } = this.props;
const {
name, // eslint-disable-line
} = input;
const configuredStrategies = input.strategies || [];
return (
<form onSubmit={onSubmit(input)}>
<section style={{ padding: '16px' }}>
<StrategiesSection
configuredStrategies={configuredStrategies}
addStrategy={addStrategy}
updateStrategy={updateStrategy}
moveStrategy={moveStrategy}
removeStrategy={removeStrategy}
/>
<br />
<FormButtons submitText={'Update'} onCancel={onCancel} />
</section>
</form>
);
}
}
UpdateFeatureComponent.propTypes = {
input: PropTypes.object,
setValue: PropTypes.func.isRequired,
addStrategy: PropTypes.func.isRequired,
removeStrategy: PropTypes.func.isRequired,
moveStrategy: PropTypes.func.isRequired,
updateStrategy: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired,
onCancel: PropTypes.func.isRequired,
validateName: PropTypes.func.isRequired,
initCallRequired: PropTypes.bool,
init: PropTypes.func,
};
export default UpdateFeatureComponent;

View File

@ -1,9 +1,9 @@
import { connect } from 'react-redux';
import { hashHistory } from 'react-router';
import { requestUpdateFeatureToggle } from '../../store/feature-actions';
import { createMapper, createActions } from '../input-helpers';
import FormComponent from './form';
import { requestUpdateFeatureToggle } from '../../../store/feature-actions';
import { createMapper, createActions } from '../../input-helpers';
import UpdateFeatureToggleComponent from './form-update-feature-component';
const ID = 'edit-feature-toggle';
function getId(props) {
@ -34,10 +34,12 @@ const prepare = (methods, dispatch) => {
delete s.id;
});
}
delete input.description;
// TODO: should add error handling
requestUpdateFeatureToggle(input)(dispatch)
.then(() => methods.clear())
.then(() => hashHistory.push(`/features/view/${input.name}`));
.then(() => hashHistory.push(`/features`));
};
methods.onCancel = evt => {
@ -73,4 +75,4 @@ const actions = createActions({
prepare,
});
export default connect(mapStateToProps, actions)(FormComponent);
export default connect(mapStateToProps, actions)(UpdateFeatureToggleComponent);

View File

@ -9,7 +9,7 @@ class AddStrategy extends React.Component {
fetchStrategies: PropTypes.func.isRequired,
};
addStrategy = strategyName => {
addStrategy(strategyName) {
const selectedStrategy = this.props.strategies.find(s => s.name === strategyName);
const parameters = {};
@ -21,7 +21,7 @@ class AddStrategy extends React.Component {
name: selectedStrategy.name,
parameters,
});
};
}
stopPropagation(e) {
e.stopPropagation();

View File

@ -1,10 +1,11 @@
import { connect } from 'react-redux';
import StrategiesSection from './strategies-section';
import StrategiesSectionComponent from './strategies-section';
import { fetchStrategies } from '../../../store/strategy/actions';
export default connect(
const StrategiesSection = connect(
state => ({
strategies: state.strategies.get('list').toArray(),
}),
{ fetchStrategies }
)(StrategiesSection);
)(StrategiesSectionComponent);
export default StrategiesSection;

View File

@ -5,7 +5,7 @@ import StrategiesList from './strategies-list';
import AddStrategy from './strategies-add';
import { HeaderTitle } from '../../common';
class StrategiesSection extends React.Component {
class StrategiesSectionComponent extends React.Component {
static propTypes = {
strategies: PropTypes.array.isRequired,
addStrategy: PropTypes.func.isRequired,
@ -32,4 +32,4 @@ class StrategiesSection extends React.Component {
}
}
export default StrategiesSection;
export default StrategiesSectionComponent;

View File

@ -9,16 +9,16 @@ export default class InputList extends Component {
setConfig: PropTypes.func.isRequired,
};
onBlur = e => {
onBlur(e) {
this.setValue(e);
window.removeEventListener('keydown', this.onKeyHandler, false);
};
}
onFocus = e => {
onFocus(e) {
e.preventDefault();
e.stopPropagation();
window.addEventListener('keydown', this.onKeyHandler, false);
};
}
onKeyHandler = e => {
if (e.key === 'Enter') {
@ -35,10 +35,9 @@ export default class InputList extends Component {
}
const { name, list, setConfig } = this.props;
const inputValue = this.refs.input.inputRef;
if (inputValue && inputValue.value) {
list.push(inputValue.value);
inputValue.value = '';
if (this.textInput && this.textInput.inputRef && this.textInput.inputRef.value) {
list.push(this.textInput.inputRef.value);
this.textInput.inputRef.value = '';
setConfig(name, list.join(','));
}
};
@ -66,9 +65,11 @@ export default class InputList extends Component {
style={{ width: '100%', flex: 1 }}
floatingLabel
label="Add list entry"
onFocus={this.onFocus}
onBlur={this.onBlur}
ref="input"
onFocus={this.onFocus.bind(this)}
onBlur={this.onBlur.bind(this)}
ref={input => {
this.textInput = input;
}}
/>
<IconButton
name="add"

View File

@ -1,16 +1,16 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Tabs, Tab, ProgressBar, Button, Card, CardTitle, CardText, CardActions, Switch } from 'react-mdl';
import { Tabs, Tab, ProgressBar, Button, Card, CardText, CardTitle, CardActions, Textfield, Switch } from 'react-mdl';
import { hashHistory, Link } from 'react-router';
import HistoryComponent from '../history/history-list-toggle-container';
import MetricComponent from './metric-container';
import EditFeatureToggle from './form-edit-container.jsx';
import EditFeatureToggle from './form/form-update-feature-container';
import { styles as commonStyles } from '../common';
const TABS = {
view: 0,
edit: 1,
strategies: 0,
view: 1,
history: 2,
};
@ -26,6 +26,7 @@ export default class ViewFeatureToggleComponent extends React.Component {
toggleFeature: PropTypes.func.isRequired,
removeFeatureToggle: PropTypes.func.isRequired,
fetchFeatureToggles: PropTypes.func.isRequired,
editFeatureToggle: PropTypes.func.isRequired,
featureToggle: PropTypes.object,
};
@ -40,7 +41,7 @@ export default class ViewFeatureToggleComponent extends React.Component {
if (TABS[activeTab] === TABS.history) {
return <HistoryComponent toggleName={featureToggleName} />;
} else if (TABS[activeTab] === TABS.edit) {
} else if (TABS[activeTab] === TABS.strategies) {
return <EditFeatureToggle featureToggle={featureToggle} />;
} else {
return <MetricComponent featureToggle={featureToggle} />;
@ -56,6 +57,7 @@ export default class ViewFeatureToggleComponent extends React.Component {
featureToggle,
features,
activeTab,
// setValue,
featureToggleName,
toggleFeature,
removeFeatureToggle,
@ -80,7 +82,7 @@ export default class ViewFeatureToggleComponent extends React.Component {
);
}
const activeTabId = TABS[this.props.activeTab] ? TABS[this.props.activeTab] : TABS.view;
const activeTabId = TABS[this.props.activeTab] ? TABS[this.props.activeTab] : TABS.strategies;
const tabContent = this.getTabContent(activeTab);
const removeToggle = () => {
@ -92,11 +94,37 @@ export default class ViewFeatureToggleComponent extends React.Component {
hashHistory.push('/features');
}
};
const updateFeatureToggle = () => {
let feature = { ...featureToggle };
if (Array.isArray(feature.strategies)) {
feature.strategies.forEach(s => {
delete s.id;
});
}
this.props.editFeatureToggle(feature);
};
const setValue = (v, event) => {
featureToggle[v] = event.target.value;
this.forceUpdate();
};
return (
<Card shadow={0} className={commonStyles.fullwidth} style={{ overflow: 'visible' }}>
<CardTitle style={{ paddingTop: '24px', wordBreak: 'break-all' }}>{featureToggle.name}</CardTitle>
<CardText>{featureToggle.description}</CardText>
<CardText>
<Textfield
floatingLabel
style={{ width: '100%' }}
rows={1}
label="Description"
required
value={featureToggle.description}
onChange={v => setValue('description', v)}
onBlur={updateFeatureToggle}
/>
</CardText>
<CardActions
border
style={{
@ -125,8 +153,8 @@ export default class ViewFeatureToggleComponent extends React.Component {
tabBarProps={{ style: { width: '100%' } }}
className="mdl-color--grey-100"
>
<Tab onClick={() => this.goToTab('strategies', featureToggleName)}>Strategies</Tab>
<Tab onClick={() => this.goToTab('view', featureToggleName)}>Metrics</Tab>
<Tab onClick={() => this.goToTab('edit', featureToggleName)}>Edit</Tab>
<Tab onClick={() => this.goToTab('history', featureToggleName)}>History</Tab>
</Tabs>
{tabContent}

View File

@ -1,6 +1,11 @@
import { connect } from 'react-redux';
import { fetchFeatureToggles, toggleFeature, removeFeatureToggle } from '../../store/feature-actions';
import {
fetchFeatureToggles,
toggleFeature,
removeFeatureToggle,
editFeatureToggle,
} from './../../store/feature-actions';
import ViewToggleComponent from './view-component';
@ -14,5 +19,6 @@ export default connect(
fetchFeatureToggles,
toggleFeature,
removeFeatureToggle,
editFeatureToggle,
}
)(ViewToggleComponent);

View File

@ -1,6 +1,6 @@
import { connect } from 'react-redux';
import HistoryComponent from './history-component';
import { fetchHistory } from '../../store/history-actions';
import { fetchHistory } from './../../store/history-actions';
const mapStateToProps = state => {
const history = state.history.get('list').toArray();

View File

@ -1,7 +1,7 @@
import { connect } from 'react-redux';
import { createMapper, createActions } from '../input-helpers';
import { createStrategy } from '../../store/strategy/actions';
import { createMapper, createActions } from './../input-helpers';
import { createStrategy } from './../../store/strategy/actions';
import AddStrategy from './add-strategy';

View File

@ -1,6 +1,6 @@
import { connect } from 'react-redux';
import StrategiesListComponent from './list-component.jsx';
import { fetchStrategies, removeStrategy } from '../../store/strategy/actions';
import { fetchStrategies, removeStrategy } from './../../store/strategy/actions';
const mapStateToProps = state => {
const list = state.strategies.get('list').toArray();

View File

@ -54,7 +54,7 @@ export default class StrategyDetails extends Component {
}
render() {
const activeTabId = TABS[this.props.activeTab] ? TABS[this.props.activeTab] : TABS.view;
const activeTabId = TABS[this.props.activeTab] ? TABS[this.props.activeTab] : TABS.strategies;
const strategy = this.props.strategy;
if (!strategy) {
return <ProgressBar indeterminate />;

View File

@ -1,8 +1,8 @@
import { connect } from 'react-redux';
import ShowStrategy from './strategy-details-component';
import { fetchStrategies } from '../../store/strategy/actions';
import { fetchAll } from '../../store/application/actions';
import { fetchFeatureToggles } from '../../store/feature-actions';
import { fetchStrategies } from './../../store/strategy/actions';
import { fetchAll } from './../../store/application/actions';
import { fetchFeatureToggles } from './../../store/feature-actions';
const mapStateToProps = (state, props) => {
let strategy = state.strategies.get('list').find(n => n.name === props.strategyName);

View File

@ -1,5 +1,5 @@
import React from 'react';
import AddFeatureToggleForm from '../../component/feature/form-add-container';
import AddFeatureToggleForm from '../../component/feature/form/form-add-feature-container';
const render = () => <AddFeatureToggleForm title="Create feature toggle" />;

View File

@ -1,5 +1,5 @@
import React from 'react';
import FeatureListContainer from '../../component/feature/list-container';
import FeatureListContainer from './../../component/feature/list-container';
const render = () => <FeatureListContainer />;

View File

@ -1,6 +1,6 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import ViewFeatureToggle from '../../component/feature/view-container';
import ViewFeatureToggle from './../../component/feature/view-container';
export default class Features extends PureComponent {
static propTypes = {

View File

@ -0,0 +1,4 @@
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
configure({ adapter: new Adapter() });