Commit 344d0ec6 authored by Jaden DIEFENBAUGH's avatar Jaden DIEFENBAUGH

[js] added "throttle-debounce" dep, refactored library editor & tests, #124 & #111

parent 47bd4152
......@@ -15689,6 +15689,11 @@
"integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
"dev": true
},
"throttle-debounce": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-2.0.0.tgz",
"integrity": "sha512-wY6Sn/xDzgSfagWSj3Ijt5iEEPFwRwgIJbyUYlsptufSnFK0tkr+/qjKr69PAFeiQJnTzHuBZPk6C7nsN/MAOw=="
},
"through": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
......
......@@ -96,6 +96,7 @@
"reactstrap": "^6.3.0",
"redux": "^4.0.0",
"redux-thunk": "^2.3.0",
"reselect": "^3.0.1"
"reselect": "^3.0.1",
"throttle-debounce": "^2.0.0"
}
}
......@@ -28,7 +28,9 @@ import type { BeatObject } from '@helpers/beat';
import { changeObjFieldName, jsonClone } from '@helpers';
import * as Selectors from '@store/selectors.js';
import { debounce } from 'throttle-debounce';
import * as Actions from '@store/actions.js';
import ValidSchemaBadge from '../ValidSchemaBadge.jsx';
import CacheInput from '../CacheInput.jsx';
import DeleteInputBtn from '../DeleteInputBtn.jsx';
......@@ -39,153 +41,149 @@ type Props = {
data: BeatObject,
libraries: BeatObject[],
saveFunc: (BeatObject) => any,
updateFunc: (BeatObject) => any,
};
type State = {
cache: any,
};
export class LibraryEditor extends React.Component<Props, State> {
export class LibraryEditor extends React.Component<Props> {
constructor(props: Props) {
super(props);
}
state = {
cache: getValidObj(this.props.data),
}
componentWillReceiveProps (nextProps: Props) {
this.setState({
cache: getValidObj(nextProps.data),
});
}
setContents = (newContents: any) => {
this.setState({
cache: {
...this.state.cache,
contents: {
'description': this.state.cache.contents['description'],
...newContents,
}
}
this.props.updateFunc({
...this.props.data,
contents: newContents
});
}
updateLibraries = (libraries: {}) => {
this.setContents({...this.state.cache.contents, uses: libraries});
this.setContents({...this.props.data.contents, uses: libraries});
}
render = () => (
<div>
<div className='d-flex'>
<Button
className='mx-auto'
outline
color='secondary'
onClick={() => this.props.saveFunc(this.state.cache)}
>
Save Changes (Changes are <ValidSchemaBadge entity='library' obj={this.state.cache} />)
</Button>
<TemplateButton
data={this.state.cache}
entity={'library'}
/>
</div>
<Form onSubmit={(e) => e.preventDefault()}>
<FormGroup tag='fieldset'>
<legend>Library Settings</legend>
<FormGroup>
<Label for='description'>Short Description</Label>
<Input
type='text'
name='description'
placeholder='Library description...'
value={this.state.cache.contents['description']}
onChange={e => this.setContents({ ...this.state.cache.contents, 'description': e.target.value})}
/>
</FormGroup>
<FormGroup>
<Label>Language</Label>
<Input
id='libLanguage'
type='select'
className='custom-select'
value={this.state.cache.contents.language}
onChange={() => {}}
>
<option value='python'>Python</option>
<option disabled value='cxx'>C++</option>
<option disabled value='matlab'>Matlab</option>
<option disabled value='r'>R</option>
</Input>
</FormGroup>
</FormGroup>
<FormGroup id='usedLibs'>
<h4>Libraries Used</h4>
{
(Object.entries(this.state.cache.contents.uses): [string, any][]).map(([name, lib], i) => (
<TypedField
key={i}
name={name}
type={lib}
types={this.props.libraries.filter(l => l.name !== this.state.cache.name).map(l => l.name)}
existingFields={Object.keys(this.state.cache.contents.uses)}
nameUpdateFunc={str => {
const newLibs = changeObjFieldName(this.state.cache.contents.uses, name, str);
this.updateLibraries(newLibs);
}}
typeUpdateFunc={str => {
const newLibs = {
...this.state.cache.contents.uses,
[name]: str
};
this.updateLibraries(newLibs);
}}
deleteFunc={() => {
const newLibs = { ...this.state.cache.contents.uses };
delete newLibs[name];
this.updateLibraries(newLibs);
}}
/>
))
}
render = () => {
const data = this.props.data;
return (
<div>
<div className='d-flex'>
<Button
className='mx-auto'
outline
block
id='newLibrary'
onClick={e => {
let newKey = '';
let v = 0;
while(true) {
const tryKey = `lib_${ v }`;
if(Object.keys(this.state.cache.contents.uses).includes(tryKey)){
v++;
continue;
}
newKey = tryKey;
break;
}
this.updateLibraries({
...this.state.cache.contents.uses,
[newKey]: ''
});
}}
color='secondary'
onClick={() => this.props.saveFunc(data)}
>
Use Another Library
Save Changes (Changes are <ValidSchemaBadge entity='library' obj={data} />)
</Button>
</FormGroup>
</Form>
</div>
);
<TemplateButton
data={data}
entity={'library'}
/>
</div>
<Form onSubmit={(e) => e.preventDefault()}>
<FormGroup tag='fieldset'>
<legend>Library Settings</legend>
<FormGroup>
<Label for='description'>Short Description</Label>
<CacheInput
type='text'
name='description'
placeholder='Library description...'
value={data.contents['description']}
onChange={e => this.setContents({ ...data.contents, 'description': e.target.value})}
/>
</FormGroup>
<FormGroup>
<Label>Language</Label>
<Input
id='libLanguage'
type='select'
className='custom-select'
value={data.contents.language}
onChange={() => {}}
>
<option value='python'>Python</option>
<option disabled value='cxx'>C++</option>
<option disabled value='matlab'>Matlab</option>
<option disabled value='r'>R</option>
</Input>
</FormGroup>
</FormGroup>
<FormGroup id='usedLibs'>
<h4>Libraries Used</h4>
{
(Object.entries(data.contents.uses): [string, any][]).map(([name, lib], i) => (
<TypedField
key={i}
name={name}
type={lib}
types={this.props.libraries.filter(l => l.name !== data.name).map(l => l.name)}
existingFields={Object.keys(data.contents.uses)}
nameUpdateFunc={str => {
const newLibs = changeObjFieldName(data.contents.uses, name, str);
this.updateLibraries(newLibs);
}}
typeUpdateFunc={str => {
const newLibs = {
...data.contents.uses,
[name]: str
};
this.updateLibraries(newLibs);
}}
deleteFunc={() => {
const newLibs = { ...data.contents.uses };
delete newLibs[name];
this.updateLibraries(newLibs);
}}
/>
))
}
<Button
outline
block
id='newLibrary'
onClick={e => {
let newKey = '';
let v = 0;
while(true) {
const tryKey = `lib_${ v }`;
if(Object.keys(data.contents.uses).includes(tryKey)){
v++;
continue;
}
newKey = tryKey;
break;
}
this.updateLibraries({
...data.contents.uses,
[newKey]: ''
});
}}
>
Use Another Library
</Button>
</FormGroup>
</Form>
</div>
);
}
}
const mapStateToProps = (state, ownProps) => {
const libs = Selectors.libraryGet(state);
const obj = {
libraries: Selectors.libraryGet(state),
libraries: libs,
data: libs.find(l => l.name === ownProps.data.name) || getValidObj()
};
return obj;
};
export default connect(mapStateToProps)(LibraryEditor);
const mapDispatchToProps = (dispatch, ownProps) => ({
// replace the obj in the Redux store with the new object
updateFunc: (obj) => {
console.log(`dispatching for ${ obj.name }`);
dispatch(Actions[`libraryUpdate`](obj.name, obj));
},
});
export default connect(mapStateToProps, mapDispatchToProps)(LibraryEditor);
......@@ -13,8 +13,9 @@ describe('<LibraryEditor />', () => {
let wrapper;
afterEach(() => {
if(wrapper && wrapper.unmount)
if(wrapper && wrapper.unmount){
wrapper.unmount();
}
});
describe('accepts', () => {
......@@ -23,25 +24,28 @@ describe('<LibraryEditor />', () => {
name: 'test/lib/1',
contents: {
language: 'python',
uses: [],
}
}
].concat(testLibs);
libs.forEach(function(lib){
const saveFunc = () => {};
const updateFunc = () => {};
it(`${ lib.name }`, () => {
wrapper = mount(
<C
data={lib}
libraries={libs}
saveFunc={saveFunc}
updateFunc={updateFunc}
/>
);
expect(wrapper).to.have.props(
['data', 'libraries', 'saveFunc']
['data', 'libraries', 'saveFunc', 'updateFunc']
).deep.equal(
[lib, libs, saveFunc]
[lib, libs, saveFunc, updateFunc]
);
});
});
......@@ -51,11 +55,13 @@ describe('<LibraryEditor />', () => {
it('user/anotherlib/1', () => {
const libName = 'user/anotherlib/1';
const saveFunc = sinon.spy();
const updateFunc = sinon.spy();
wrapper = mount(
<C
data={{name: libName, contents: {}}}
data={{name: libName, contents: { description: '', uses: {}, language: 'python' }}}
libraries={testLibs.filter(l => l.name !== libName)}
saveFunc={saveFunc}
updateFunc={updateFunc}
/>
);
......@@ -63,9 +69,9 @@ describe('<LibraryEditor />', () => {
['data', 'libraries', 'saveFunc']
);
expect(wrapper.state('cache')).to.have.property('name', libName);
expect(wrapper.props().data).to.have.property('name', libName);
expect(wrapper.state('cache').contents).to.deep.equal({
expect(wrapper.props().data.contents).to.deep.equal({
'description': '',
'uses': {},
'language': 'python'
......@@ -75,26 +81,38 @@ describe('<LibraryEditor />', () => {
it('user/thelib/1', () => {
const libName = 'user/thelib/1';
const saveFunc = sinon.spy();
const data = {name: libName, contents: { description: '', uses: {}, language: 'python' }};
const _updateFunc = (obj) => {
wrapper.setProps && wrapper.setProps({ data: obj });
};
const updateFunc = sinon.spy(_updateFunc);
wrapper = mount(
<C
data={{name: libName, contents: {}}}
data={data}
libraries={testLibs.filter(l => l.name !== libName)}
saveFunc={saveFunc}
updateFunc={updateFunc}
/>
);
expect(wrapper).to.have.props(
['data', 'libraries', 'saveFunc']
['data', 'libraries', 'saveFunc', 'updateFunc']
);
expect(wrapper.state('cache')).to.have.property('name', libName);
let props = wrapper.props();
expect(props.data).to.have.property('name', libName);
wrapper.find('button#newLibrary').simulate('click');
expect(updateFunc.callCount).to.equal(1);
wrapper.find('#usedLibs CacheInput[type="text"]').prop('onChange')({ target: { value: 'lib'}});
expect(updateFunc.callCount).to.equal(2);
wrapper.find('#usedLibs select').simulate('change', { target: { value: 'user/anotherlib/1'}});
expect(updateFunc.callCount).to.equal(3);
expect(wrapper.state('cache').contents).to.deep.equal({
props = wrapper.props();
expect(props.data.contents).to.deep.equal({
'description': '',
'uses': {
lib: 'user/anotherlib/1'
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment