Commit 3acbb40e authored by Flavio TARSETTI's avatar Flavio TARSETTI

Merge branch 'tc-ux' into 'master'

Toolchain Editor UX Improvements

Closes #129, #117, #125, #44, #134, and #79

See merge request !27
parents c1dd28f6 d8faed66
Pipeline #25283 passed with stages
in 81 minutes and 6 seconds
...@@ -27,6 +27,8 @@ type Props = { ...@@ -27,6 +27,8 @@ type Props = {
// update delay in ms, defaults to 500 // update delay in ms, defaults to 500
// basically just simple throttling to keep from overwhelming complex updates in the parent // basically just simple throttling to keep from overwhelming complex updates in the parent
delay?: number, delay?: number,
// danger color highlight
invalid?: boolean,
}; };
type State = { type State = {
...@@ -163,6 +165,8 @@ class CacheInput extends React.Component<Props, State>{ ...@@ -163,6 +165,8 @@ class CacheInput extends React.Component<Props, State>{
target = null target = null
render () { render () {
// either if invalid or if the consuming component set it
const dangerColor = !this.state.valid || this.props.invalid;
return ( return (
<InputGroup> <InputGroup>
{ this.props.children } { this.props.children }
...@@ -171,7 +175,8 @@ class CacheInput extends React.Component<Props, State>{ ...@@ -171,7 +175,8 @@ class CacheInput extends React.Component<Props, State>{
{...this.getInputProps()} {...this.getInputProps()}
value={this.state.cache} value={this.state.cache}
onChange={this.change} onChange={this.change}
valid={this.state.valid} valid={!dangerColor}
invalid={dangerColor}
/> />
{ !this.state.valid && { !this.state.valid &&
<div ref={target => {this.target = target;}} className='input-group-append'> <div ref={target => {this.target = target;}} className='input-group-append'>
......
...@@ -99,7 +99,7 @@ export const blockWidth = 200; ...@@ -99,7 +99,7 @@ export const blockWidth = 200;
// spacing between grid rows/cols // spacing between grid rows/cols
export const gridDistance = 20; export const gridDistance = 20;
// how much the scaling changes at once // how much the scaling changes at once
export const zoomAdjustmentAmount = 1000; export const zoomAdjustmentAmount = 100;
// convert coords in the SVG element to coords to save in data // convert coords in the SVG element to coords to save in data
export const convertWorldToDataCoords = (x: number, y: number) => { export const convertWorldToDataCoords = (x: number, y: number) => {
return [x - svgWidthHalf, y - svgHeightHalf]; return [x - svgWidthHalf, y - svgHeightHalf];
...@@ -510,7 +510,9 @@ export default class GraphicalEditor extends React.PureComponent<Props, State> { ...@@ -510,7 +510,9 @@ export default class GraphicalEditor extends React.PureComponent<Props, State> {
}, 50); }, 50);
}; };
// d3 behaviour for dragging lines from an output to an input and creating a connection // d3 behaviour for connecting an output and input and creating a connection
// allows users to drag from output to input,
// or click an output and click an input
initD3Connections = () => { initD3Connections = () => {
// creating connections // creating connections
setTimeout(() => { setTimeout(() => {
...@@ -519,8 +521,17 @@ export default class GraphicalEditor extends React.PureComponent<Props, State> { ...@@ -519,8 +521,17 @@ export default class GraphicalEditor extends React.PureComponent<Props, State> {
const channelColors = this.props.repData.channel_colors; const channelColors = this.props.repData.channel_colors;
const {datasets, blocks} = this.props; const {datasets, blocks} = this.props;
let startBlock; let startBlock;
// the user has started dragging from an output block let startX = 0;
let startY = 0;
// the user has started dragging from an output block,
// or it might be starting a click event!
// to see if the user is clicking or dragging,
// check in endDrag() if the distance travelled is small
// enough to be considered a click or not
function startDrag(d) { function startDrag(d) {
//console.log('startDrag');
startX = d3.event.x;
startY = d3.event.y;
// find the block that the user started from // find the block that the user started from
const rawId = d3.select(this).attr('id'); const rawId = d3.select(this).attr('id');
const bName = rawId.split('-output-')[0]; const bName = rawId.split('-output-')[0];
...@@ -533,37 +544,57 @@ export default class GraphicalEditor extends React.PureComponent<Props, State> { ...@@ -533,37 +544,57 @@ export default class GraphicalEditor extends React.PureComponent<Props, State> {
.append('line') .append('line')
.classed('tmp', true) .classed('tmp', true)
.attr('stroke', 'black') .attr('stroke', 'black')
.attr('x1', d3.event.x) .attr('x1', startX)
.attr('y1', d3.event.y) .attr('y1', startY)
.attr('x2', d3.event.x) .attr('x2', startX)
.attr('y2', d3.event.y); .attr('y2', startY);
} }
// create a connection if the user ended the drag on an input block, const processEndBlock = (endInput: HTMLElement) => {
// else just remove the temp svg line
function endDrag() {
const {x, y} = d3.event;
d3.select('.tmp')
.remove();
if(!startBlock) if(!startBlock)
return; return;
// with the location of the end event, //console.log(endInput);
// compare the location to the location & dims of all input blocks
// in the svg.
const endInput = Array.from(document.querySelectorAll('.iBlock'))
.find(n => {
const nx = n.x.baseVal.value;
const ny = n.y.baseVal.value;
const width = n.width.baseVal.value;
const height = n.height.baseVal.value;
return nx <= x && x <= nx + width && ny <= y && y <= ny + height;
});
if(endInput === undefined)
return;
const endId = endInput.id.replace('-input-', '.'); const endId = endInput.id.replace('-input-', '.');
// create a new connection // create a new connection assuming theres a given func that does it
if(createConnection) if(createConnection)
createConnection(startId, endId, startBlock.synchronized_channel || startBlock.name); createConnection(startId, endId, startBlock.synchronized_channel || startBlock.name);
startBlock = undefined;
};
// create a connection if the user ended the drag on an input block,
// else just remove the temp svg line
function endDrag(d) {
//console.log('endDrag');
const {x, y} = d3.event;
if(Math.abs(startX - x) < 10 && Math.abs(startY - y) < 10) {
// click event not drag
} else {
// probably not a click event
d3.select('.tmp').remove();
// with the location of the end event,
// compare the location to the location & dims of all input blocks
// in the svg.
const endInput = Array.from(document.querySelectorAll('.iBlock'))
.find(n => {
const nx = n.x.baseVal.value;
const ny = n.y.baseVal.value;
const width = n.width.baseVal.value;
const height = n.height.baseVal.value;
return nx <= x && x <= nx + width && ny <= y && y <= ny + height;
});
if(endInput === undefined){
startBlock = undefined;
} else {
processEndBlock(endInput);
}
}
}
// create a connection if the user clicked an input block,
// else just reset the startBlock
function endClick() {
processEndBlock(this);
} }
// update the temp line // update the temp line
...@@ -573,12 +604,16 @@ export default class GraphicalEditor extends React.PureComponent<Props, State> { ...@@ -573,12 +604,16 @@ export default class GraphicalEditor extends React.PureComponent<Props, State> {
.attr('y2', d3.event.y); .attr('y2', d3.event.y);
} }
d3.selectAll('.oBlock').call( d3.selectAll('.oBlock')
.call(
d3.drag() d3.drag()
.on('start', startDrag) .on('start', startDrag)
.on('end', endDrag) .on('end', endDrag)
.on('drag', dragged) .on('drag', dragged)
); );
d3.selectAll('.iBlock')
.on('click', endClick);
}, 50); }, 50);
} }
......
...@@ -49,7 +49,7 @@ class GraphicalEditorHelpModal extends React.PureComponent<Props> { ...@@ -49,7 +49,7 @@ class GraphicalEditorHelpModal extends React.PureComponent<Props> {
</p> </p>
<h5>Change the information of a block?</h5> <h5>Change the information of a block?</h5>
<p> <p>
Left-clicking on a block will bring up the Block Editor modal, which lets you change the block name, add &amp; remove &amp; rename inputs and outputs, change the synchronized channel, and delete the block. Left-clicking on a block will bring up the Block Editor modal, which lets you change the block name, add &amp; remove &amp; rename inputs and outputs, change the synchronized channel, and delete the block. Remember to click <i>Save Changes</i>! If you want to discard your changes, you can either click outside of the modal or click the <b>x</b> button on the top-right corner.
</p> </p>
<h5>Create a connection?</h5> <h5>Create a connection?</h5>
<p> <p>
...@@ -63,6 +63,10 @@ class GraphicalEditorHelpModal extends React.PureComponent<Props> { ...@@ -63,6 +63,10 @@ class GraphicalEditorHelpModal extends React.PureComponent<Props> {
<p> <p>
The editor only displays connections connected to selected blocks. For example, to only see connections to/from Block X, select Block X (via shift+clicking on the block). The editor only displays connections connected to selected blocks. For example, to only see connections to/from Block X, select Block X (via shift+clicking on the block).
</p> </p>
<h5>Insert blocks from another toolchain, an algorithm, or a database?</h5>
<p>
Right-click on the background grid and select <i>Insert Object Here</i> to insert blocks for an object relative to the position of your mouse. A modal will pop up, and lets you insert a toolchain, algorithm, or part of a database, to easily create toolchains using BEAT objects that you already have created.
</p>
<h3>Keyboard Shortcuts</h3> <h3>Keyboard Shortcuts</h3>
<Table striped> <Table striped>
<thead> <thead>
...@@ -73,6 +77,17 @@ class GraphicalEditorHelpModal extends React.PureComponent<Props> { ...@@ -73,6 +77,17 @@ class GraphicalEditorHelpModal extends React.PureComponent<Props> {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr>
<td>
Pan
</td>
<td><pre className='preInline'>
Middle-click and move
</pre></td>
<td>
The preferred way to pan around the editor is to use a mouse with middle-click functionality. You can middle-click anywhere in the canvas and then move your mouse to pan around on both axis. If this does not work for you, you may need to turn on <i>autoscrolling</i> in your browser settings.
</td>
</tr>
<tr> <tr>
<td> <td>
Pan vertically Pan vertically
...@@ -136,7 +151,7 @@ class GraphicalEditorHelpModal extends React.PureComponent<Props> { ...@@ -136,7 +151,7 @@ class GraphicalEditorHelpModal extends React.PureComponent<Props> {
</tr> </tr>
<tr> <tr>
<td> <td>
Open a context menu Open a context (right-click) menu
</td> </td>
<td><pre className='preInline'> <td><pre className='preInline'>
Right click Right click
...@@ -180,6 +195,24 @@ class GraphicalEditorHelpModal extends React.PureComponent<Props> { ...@@ -180,6 +195,24 @@ class GraphicalEditorHelpModal extends React.PureComponent<Props> {
</span> </span>
</td> </td>
<td> <td>
You can also create a connection by clicking on the black rectangle of an output and then clicking on the black rectangle of an input. This lets you create connections between distant blocks and move around the canvas.
</td>
</tr>
<tr>
<td>
Create an Algorithm based on a block
</td>
<td>
<pre className='preInline'>
Left click
</pre>
{' '}
<span>
on the block to bring up the block editor. At the top of the modal, enter the username and name for the algorithm to be created, and click the <i>Create</i> button to generate an algorithm based off of the connections, inputs, and outputs of the block. You will have to edit the algorithm in the editor to choose the types, and to add any any parameters or results.
</span>
</td>
<td>
This only works for normal and analyzer blocks. If you do not see your new algorithm, refresh the browser tab/window.
</td> </td>
</tr> </tr>
<tr> <tr>
...@@ -192,14 +225,12 @@ class GraphicalEditorHelpModal extends React.PureComponent<Props> { ...@@ -192,14 +225,12 @@ class GraphicalEditorHelpModal extends React.PureComponent<Props> {
</pre> </pre>
</td> </td>
<td> <td>
Some elements in the editor have extra information found by hovering over the element for a couple seconds. Blocks and connections in the editor have extra information found by hovering over the element for a couple seconds.
</td> </td>
</tr> </tr>
</tbody> </tbody>
</Table> </Table>
</ModalBody> </ModalBody>
<ModalFooter>
</ModalFooter>
</Modal> </Modal>
); );
} }
......
...@@ -311,6 +311,13 @@ class InsertObjectModal extends React.PureComponent<Props, State> { ...@@ -311,6 +311,13 @@ class InsertObjectModal extends React.PureComponent<Props, State> {
Insert blocks from an existing object Insert blocks from an existing object
</ModalHeader> </ModalHeader>
<ModalBody> <ModalBody>
<FormGroup row>
<Col>
<span className='text-muted'>
Insert the blocks from a compatible BEAT object into this toolchain. You may insert an existing toolchain or blocks generated from a database protocol, database protocol set, or algorithm.
</span>
</Col>
</FormGroup>
<FormGroup row> <FormGroup row>
<Col> <Col>
</Col> </Col>
......
...@@ -315,7 +315,7 @@ describe('<ToolchainEditor />', function() { ...@@ -315,7 +315,7 @@ describe('<ToolchainEditor />', function() {
wrapper.update(); wrapper.update();
wrapper.find('.modal CacheInput[value="output0"]').prop('onChange')( { target: { value: 'species' }}); wrapper.find('.modal CacheInput[value="output0"]').prop('onChange')( { target: { value: 'species' }});
wrapper.update(); wrapper.update();
wrapper.find('button.close').simulate('click'); wrapper.find('.modal button.btn-success').simulate('click');
wrapper.update(); wrapper.update();
expect(wrapper.find('ToolchainModal').props().active).to.equal(false); expect(wrapper.find('ToolchainModal').props().active).to.equal(false);
...@@ -331,6 +331,7 @@ describe('<ToolchainEditor />', function() { ...@@ -331,6 +331,7 @@ describe('<ToolchainEditor />', function() {
wrapper.find('rect#block_dataset0').simulate('click'); wrapper.find('rect#block_dataset0').simulate('click');
wrapper.update(); wrapper.update();
expect(wrapper.find('ToolchainModal').props().active).to.equal(true); expect(wrapper.find('ToolchainModal').props().active).to.equal(true);
wrapper.update();
expect(wrapper.find('.modal').find('CacheInput#tcModalInitFocus').props().value).to.equal('dataset0'); expect(wrapper.find('.modal').find('CacheInput#tcModalInitFocus').props().value).to.equal('dataset0');
wrapper.find('.modal').find('CacheInput#tcModalInitFocus').prop('onChange')( { target: { value: 'testing_data' }}); wrapper.find('.modal').find('CacheInput#tcModalInitFocus').prop('onChange')( { target: { value: 'testing_data' }});
wrapper.update(); wrapper.update();
...@@ -344,7 +345,7 @@ describe('<ToolchainEditor />', function() { ...@@ -344,7 +345,7 @@ describe('<ToolchainEditor />', function() {
wrapper.update(); wrapper.update();
wrapper.find('.modal CacheInput[value="output0"]').prop('onChange')( { target: { value: 'species' }}); wrapper.find('.modal CacheInput[value="output0"]').prop('onChange')( { target: { value: 'species' }});
wrapper.update(); wrapper.update();
wrapper.find('button.close').simulate('click'); wrapper.find('.modal button.btn-success').simulate('click');
wrapper.update(); wrapper.update();
expect(wrapper.props().data.contents.datasets[1]).to.deep.equal({ expect(wrapper.props().data.contents.datasets[1]).to.deep.equal({
...@@ -362,7 +363,7 @@ describe('<ToolchainEditor />', function() { ...@@ -362,7 +363,7 @@ describe('<ToolchainEditor />', function() {
expect(wrapper.find('.modal').find('CacheInput#tcModalInitFocus').props().value).to.equal('block'); expect(wrapper.find('.modal').find('CacheInput#tcModalInitFocus').props().value).to.equal('block');
wrapper.find('.modal').find('CacheInput#tcModalInitFocus').prop('onChange')( { target: { value: 'training_alg' }}); wrapper.find('.modal').find('CacheInput#tcModalInitFocus').prop('onChange')( { target: { value: 'training_alg' }});
wrapper.update(); wrapper.update();
wrapper.find('.modal button.btn-secondary').at(0).simulate('click'); wrapper.find('.modal button.btn-secondary').at(1).simulate('click');
wrapper.update(); wrapper.update();
/* /*
wrapper.find('.modal button.btn-secondary').at(0).simulate('click'); wrapper.find('.modal button.btn-secondary').at(0).simulate('click');
...@@ -376,7 +377,7 @@ describe('<ToolchainEditor />', function() { ...@@ -376,7 +377,7 @@ describe('<ToolchainEditor />', function() {
wrapper.update(); wrapper.update();
wrapper.find('.modal CacheInput[value="output"]').prop('onChange')( { target: { value: 'lda_machine' }}); wrapper.find('.modal CacheInput[value="output"]').prop('onChange')( { target: { value: 'lda_machine' }});
wrapper.update(); wrapper.update();
wrapper.find('button.close').simulate('click'); wrapper.find('.modal button.btn-success').simulate('click');
wrapper.update(); wrapper.update();
expect(wrapper.props().data.contents.blocks[0]).to.deep.equal({ expect(wrapper.props().data.contents.blocks[0]).to.deep.equal({
...@@ -398,7 +399,7 @@ describe('<ToolchainEditor />', function() { ...@@ -398,7 +399,7 @@ describe('<ToolchainEditor />', function() {
expect(wrapper.find('.modal').find('CacheInput#tcModalInitFocus').props().value).to.equal('block0'); expect(wrapper.find('.modal').find('CacheInput#tcModalInitFocus').props().value).to.equal('block0');
wrapper.find('.modal').find('CacheInput#tcModalInitFocus').prop('onChange')( { target: { value: 'testing_alg' }}); wrapper.find('.modal').find('CacheInput#tcModalInitFocus').prop('onChange')( { target: { value: 'testing_alg' }});
wrapper.update(); wrapper.update();
wrapper.find('.modal button.btn-secondary').at(0).simulate('click'); wrapper.find('.modal button.btn-secondary').at(1).simulate('click');
wrapper.update(); wrapper.update();
/* /*
wrapper.find('.modal button.btn-secondary').at(0).simulate('click'); wrapper.find('.modal button.btn-secondary').at(0).simulate('click');
...@@ -412,7 +413,7 @@ describe('<ToolchainEditor />', function() { ...@@ -412,7 +413,7 @@ describe('<ToolchainEditor />', function() {
wrapper.update(); wrapper.update();
wrapper.find('.modal CacheInput[value="output"]').prop('onChange')( { target: { value: 'scores' }}); wrapper.find('.modal CacheInput[value="output"]').prop('onChange')( { target: { value: 'scores' }});
wrapper.update(); wrapper.update();
wrapper.find('button.close').simulate('click'); wrapper.find('.modal button.btn-success').simulate('click');
wrapper.update(); wrapper.update();
expect(wrapper.props().data.contents.blocks[1]).to.deep.equal({ expect(wrapper.props().data.contents.blocks[1]).to.deep.equal({
...@@ -436,13 +437,13 @@ describe('<ToolchainEditor />', function() { ...@@ -436,13 +437,13 @@ describe('<ToolchainEditor />', function() {
wrapper.find('.modal button.btn-secondary').simulate('click'); wrapper.find('.modal button.btn-secondary').simulate('click');
wrapper.update(); wrapper.update();
*/ */
wrapper.find('.modal button.btn-secondary').simulate('click'); wrapper.find('.modal button.btn-secondary').at(1).simulate('click');
wrapper.update(); wrapper.update();
wrapper.find('.modal CacheInput[value="input"]').prop('onChange')( { target: { value: 'scores' }}); wrapper.find('.modal CacheInput[value="input"]').prop('onChange')( { target: { value: 'scores' }});
wrapper.update(); wrapper.update();
wrapper.find('.modal CacheInput[value="input0"]').prop('onChange')( { target: { value: 'species' }}); wrapper.find('.modal CacheInput[value="input0"]').prop('onChange')( { target: { value: 'species' }});
wrapper.update(); wrapper.update();
wrapper.find('button.close').simulate('click'); wrapper.find('.modal button.btn-success').simulate('click');
wrapper.update(); wrapper.update();
expect(wrapper.props().data.contents.analyzers[0]).to.deep.equal({ expect(wrapper.props().data.contents.analyzers[0]).to.deep.equal({
...@@ -964,12 +965,13 @@ describe('<ToolchainEditor />', function() { ...@@ -964,12 +965,13 @@ describe('<ToolchainEditor />', function() {
wrapper.update(); wrapper.update();
expect(wrapper.find('ToolchainModal').props().active).to.equal(true); expect(wrapper.find('ToolchainModal').props().active).to.equal(true);
wrapper.find('.modal CacheInput[value="lda_machine"]').prop('onChange')( { target: { value: 'lda' }}); wrapper.find('.modal CacheInput[value="lda_machine"]').prop('onChange')( { target: { value: 'lda' }});
wrapper.find('.modal button.btn-success').simulate('click');
wrapper.update(); wrapper.update();
expect(wrapper.find('.modal CacheInput[value="lda"]').props().value).to.equal('lda'); expect(wrapper.find('.modal CacheInput[value="lda"]').props().value).to.equal('lda');
const data = wrapper.props().data; const data = wrapper.props().data;
expect(data.contents.representation.connections).to.have.property('training_alg.lda_machine/testing_alg.lda');
expect(data.contents.representation.connections).to.not.have.property('training_alg.lda_machine/testing_alg.lda_machine'); expect(data.contents.representation.connections).to.not.have.property('training_alg.lda_machine/testing_alg.lda_machine');
expect(data.contents.representation.connections).to.have.property('training_alg.lda_machine/testing_alg.lda');
}); });
it('Properly changes connection names when renaming an output', () => { it('Properly changes connection names when renaming an output', () => {
...@@ -993,12 +995,13 @@ describe('<ToolchainEditor />', function() { ...@@ -993,12 +995,13 @@ describe('<ToolchainEditor />', function() {
wrapper.update(); wrapper.update();
expect(wrapper.find('ToolchainModal').props().active).to.equal(true); expect(wrapper.find('ToolchainModal').props().active).to.equal(true);
wrapper.find('.modal CacheInput[value="lda_machine"]').prop('onChange')( { target: { value: 'lda' }}); wrapper.find('.modal CacheInput[value="lda_machine"]').prop('onChange')( { target: { value: 'lda' }});
wrapper.find('.modal button.btn-success').simulate('click');
wrapper.update(); wrapper.update();
expect(wrapper.find('.modal CacheInput[value="lda"]').props().value).to.equal('lda'); expect(wrapper.find('.modal CacheInput[value="lda"]').props().value).to.equal('lda');
const data = wrapper.props().data; const data = wrapper.props().data;
expect(data.contents.representation.connections).to.have.property('training_alg.lda/testing_alg.lda_machine');
expect(data.contents.representation.connections).to.not.have.property('training_alg.lda_machine/testing_alg.lda_machine'); expect(data.contents.representation.connections).to.not.have.property('training_alg.lda_machine/testing_alg.lda_machine');
expect(data.contents.representation.connections).to.have.property('training_alg.lda/testing_alg.lda_machine');
}); });
it('Properly deletes the training_data.species/training_alg.species connection', async () => { it('Properly deletes the training_data.species/training_alg.species connection', async () => {
......
...@@ -12,7 +12,9 @@ const sleep = (ms) => { ...@@ -12,7 +12,9 @@ const sleep = (ms) => {
return new Promise(resolve => setTimeout(resolve, ms)); return new Promise(resolve => setTimeout(resolve, ms));
}; };
describe('<ToolchainModal />', () => { describe('<ToolchainModal />', function () {
this.timeout(10000);
let wrapper; let wrapper;
afterEach(() => { afterEach(() => {
...@@ -31,6 +33,18 @@ describe('<ToolchainModal />', () => { ...@@ -31,6 +33,18 @@ describe('<ToolchainModal />', () => {
synchronized_channel: 'dataset1', synchronized_channel: 'dataset1',
}; };
const testBlockData2 = {
name: 'block1',
inputs: [
'input0',
'input1',
],
outputs: [
'output1',
],
synchronized_channel: 'dataset1',
};
const testChannels = { 'dataset1': '#880000' }; const testChannels = { 'dataset1': '#880000' };
const testBlockNames = ['block1']; const testBlockNames = ['block1'];
...@@ -43,12 +57,9 @@ describe('<ToolchainModal />', () => { ...@@ -43,12 +57,9 @@ describe('<ToolchainModal />', () => {
blockNames={testBlockNames} blockNames={testBlockNames}
possibleChannels={testChannels} possibleChannels={testChannels}
toggle={() => {}} toggle={() => {}}
updateBlockName={() => {}}
updateBlockChannel={() => {}}
updateBlockIOName={() => {}}
addBlockIO={() => {}}
deleteBlockIO={() => {}}
deleteBlock={() => {}} deleteBlock={() => {}}
updateBlockData={() => {}}
createAlgorithmFromBlock={() => {}}
/> />
); );
expect(wrapper).to.have.props([ expect(wrapper).to.have.props([
...@@ -57,20 +68,14 @@ describe('<ToolchainModal />', () => { ...@@ -57,20 +68,14 @@ describe('<ToolchainModal />', () => {
'blockNames', 'blockNames',
'possibleChannels', 'possibleChannels',
'toggle', 'toggle',
'updateBlockName',
'updateBlockChannel',
'updateBlockIOName',
'addBlockIO',
'deleteBlockIO',
'deleteBlock', 'deleteBlock',
'updateBlockData',
'createAlgorithmFromBlock',
]); ]);
}); });
it('lets you change the name to another valid name', async () => { it('lets you change the name to another valid name', async () => {
const newBlockName = 'block2'; const newBlockName = 'block2';
const _updateBlockName = (str: string) => {
expect(str).to.equal(newBlockName);
};
wrapper = mount( wrapper = mount(
<C <C
active={true} active={true}
...@@ -78,12 +83,9 @@ describe('<ToolchainModal />', () => { ...@@ -78,12 +83,9 @@ describe('<ToolchainModal />', () => {
blockNames={testBlockNames} blockNames={testBlockNames}
possibleChannels={testChannels} possibleChannels={testChannels}
toggle={() => {}} toggle={() => {}}
updateBlockName={_updateBlockName}
updateBlockChannel={() => {}}
updateBlockIOName={() => {}}
addBlockIO={() => {}}
deleteBlockIO={() => {}}
deleteBlock={() => {}} deleteBlock={() => {}}
updateBlockData={() => {}}
createAlgorithmFromBlock={() => {}}
/> />
); );
wrapper.find('input#tcModalInitFocus').prop('onChange')( { target: { value: newBlockName }}); wrapper.find('input#tcModalInitFocus').prop('onChange')( { target: { value: newBlockName }});
...@@ -95,9 +97,6 @@ describe('<ToolchainModal />', () => { ...@@ -95,9 +97,6 @@ describe('<ToolchainModal />', () => {
it('doesnt let you change the name to a valid name with a dash ("-") in it', async () => { it('doesnt let you change the name to a valid name with a dash ("-") in it', async () => {
const newBlockName = 'block-2'; const newBlockName = 'block-2';
const _updateBlockName = (str: string) => {
expect(str).to.equal(newBlockName);
};
wrapper = mount( wrapper = mount(
<C <C
active={true} active={true}
...@@ -105,12 +104,9 @@ describe('<ToolchainModal />', () => { ...@@ -105,12 +104,9 @@ describe('<ToolchainModal />', () => {
blockNames={testBlockNames} blockNames={testBlockNames}
possibleChannels={testChannels} possibleChannels={testChannels}
toggle={() => {}} toggle={() => {}}
updateBlockName={_updateBlockName}
updateBlockChannel={() => {}}
updateBlockIOName={() => {}}
addBlockIO={() => {}}
deleteBlockIO={() => {}}
deleteBlock={() => {}} deleteBlock={() => {}}
updateBlockData={() => {}}
createAlgorithmFromBlock={() => {}}
/> />
); );
wrapper.find('input#tcModalInitFocus').prop('onChange')( { target: { value: newBlockName }}); wrapper.find('input#tcModalInitFocus').prop('onChange')( { target: { value: newBlockName }});
...@@ -119,4 +115,58 @@ describe('<ToolchainModal />', () => { ...@@ -119,4 +115,58 @@ describe('<ToolchainModal />', () => {