diff --git a/conda/js/package-lock.json b/conda/js/package-lock.json
index 33f182026dfbf9b0b187a3345b7bb16922d4ac34..ee6e7a5eca8b445b24670376fd3f1652a51b6c84 100644
--- a/conda/js/package-lock.json
+++ b/conda/js/package-lock.json
@@ -4113,6 +4113,24 @@
                         "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=",
                         "dev": true
                 },
+                "deep-equal-in-any-order": {
+                        "version": "1.0.10",
+                        "resolved": "https://registry.npmjs.org/deep-equal-in-any-order/-/deep-equal-in-any-order-1.0.10.tgz",
+                        "integrity": "sha512-hzh3IwlIKwT885r5b/b4bXCXxzR7S9N+Dreuoommdykcnvlg0A+pS81wMU4ZsFz98CH4KBI8eWbAfDHWl7akTg==",
+                        "dev": true,
+                        "requires": {
+                                "lodash": "^4.17.10",
+                                "sort-any": "^1.1.12"
+                        },
+                        "dependencies": {
+                                "lodash": {
+                                        "version": "4.17.10",
+                                        "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz",
+                                        "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==",
+                                        "dev": true
+                                }
+                        }
+                },
                 "deep-is": {
                         "version": "0.1.3",
                         "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
@@ -13030,6 +13048,15 @@
                                 }
                         }
                 },
+                "sort-any": {
+                        "version": "1.1.12",
+                        "resolved": "https://registry.npmjs.org/sort-any/-/sort-any-1.1.12.tgz",
+                        "integrity": "sha512-RaVPeOjzn5tjhAfvQstA34gPiG/HT+1MefSsG0KHIMhXjLlZREYSIkxlYaCRvdwLKNgpsgM6nvpLEY1Y0Ja9FQ==",
+                        "dev": true,
+                        "requires": {
+                                "lodash": "^4.17.4"
+                        }
+                },
                 "source-list-map": {
                         "version": "2.0.0",
                         "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.0.tgz",
diff --git a/conda/js/package.json b/conda/js/package.json
index a402f619623530678c7436c01162998cfacf54ea..9e0d446c48da7856a9505aa4c2c9812175a471d5 100644
--- a/conda/js/package.json
+++ b/conda/js/package.json
@@ -37,6 +37,7 @@
                 "chai-enzyme": "^1.0.0-beta.1",
                 "cross-env": "^5.2.0",
                 "css-loader": "^1.0.0",
+                "deep-equal-in-any-order": "^1.0.10",
                 "enzyme": "^3.3.0",
                 "enzyme-adapter-react-16": "^1.1.1",
                 "eslint": "^5.2.0",
diff --git a/conda/js/src/components/toolchain/ToolchainEditor.jsx b/conda/js/src/components/toolchain/ToolchainEditor.jsx
index dd024c52b6e298af29cf653b30fb2586ad147169..d04a59c6bc1a384df2d85014dbffec7e2afc7042 100644
--- a/conda/js/src/components/toolchain/ToolchainEditor.jsx
+++ b/conda/js/src/components/toolchain/ToolchainEditor.jsx
@@ -910,13 +910,15 @@ export class ToolchainEditor extends React.PureComponent<Props, State> {
 		}
 	}
 
+	lastClickBlockName = ''
 	lastClickMs = 0
 	// handles left clicking on a block
 	handleBlockClick = (blockName: string, set: BlockSet) => {
 		const currTime = Date.now();
 		// 1sec throttling
 		const delay = 1000;
-		if(currTime - this.lastClickMs > delay){
+		if(currTime - this.lastClickMs > delay || blockName !== this.lastClickBlockName){
+			this.lastClickBlockName = blockName;
 			const newMBI = {
 				set,
 				name: blockName,
@@ -926,6 +928,7 @@ export class ToolchainEditor extends React.PureComponent<Props, State> {
 				modalBlockInfo: newMBI,
 			});
 		}
+		this.lastClickBlockName = blockName;
 		this.lastClickMs = currTime;
 	}
 
diff --git a/conda/js/src/components/toolchain/ToolchainEditor.spec.jsx b/conda/js/src/components/toolchain/ToolchainEditor.spec.jsx
index 50c2a386f535ed255fc641cbd1c87816d71a210e..15a67644a07b88e7c838dea534c6b929eeec702c 100644
--- a/conda/js/src/components/toolchain/ToolchainEditor.spec.jsx
+++ b/conda/js/src/components/toolchain/ToolchainEditor.spec.jsx
@@ -1,9 +1,11 @@
 // @flow
 import React from 'react';
-import { expect } from 'chai';
+import chai, { expect } from 'chai';
 import { mount } from 'enzyme';
 import sinon from 'sinon';
 import { spies } from '@test';
+// sometimes we dont care about order of items in arrays when comparing objects deeply
+import deepEqualInAnyOrder from 'deep-equal-in-any-order';
 
 import { getValidToolchainObj as getValidObj, getValidDatabaseObj, getValidAlgorithmObj } from '@helpers/beat';
 import { ToolchainEditor as C } from '.';
@@ -14,7 +16,12 @@ import testTcs from '@test/test_tcs.json';
 import testDbs from '@test/test_dbs.json';
 import testAlgs from '@test/test_algs.json';
 
-describe('<ToolchainEditor />', () => {
+chai.use(deepEqualInAnyOrder);
+
+describe('<ToolchainEditor />', function() {
+	// these tests might take a long time, comparatively
+	this.timeout(10000);
+
 	let wrapper;
 
 	afterEach(() => {
@@ -115,11 +122,520 @@ describe('<ToolchainEditor />', () => {
 			const _selectBlocks = (bNames: string[]) => { return; };
 			const selectBlocks = sinon.spy(_selectBlocks);
 
+
+			/* add blocks via contextmenu handler */
+
 			// pretend to right click at a spot by calling the event handler
-			wrapper.instance().handleSvgContextMenu({}, { clicked: 'addDataset', x: 1, y: 1, selectBlocks });
+			// dataset 1 training_data @ 6,0
+			wrapper.instance().handleSvgContextMenu({}, { clicked: 'addDataset', x: 6, y: 0, selectBlocks });
 			wrapper.update();
 			expect(updateFunc.callCount).to.equal(1);
 			expect(wrapper.props().data.contents.datasets.length).to.equal(1);
+
+			// dataset 2 @ 6,5
+			wrapper.instance().handleSvgContextMenu({}, { clicked: 'addDataset', x: 6, y: 5, selectBlocks });
+			wrapper.update();
+			expect(updateFunc.callCount).to.equal(2);
+			expect(wrapper.props().data.contents.datasets.length).to.equal(2);
+
+			// block 1 (training) @ 19,0
+			wrapper.instance().handleSvgContextMenu({}, { clicked: 'addBlock', x: 19, y: 0, selectBlocks });
+			wrapper.update();
+			expect(updateFunc.callCount).to.equal(3);
+			expect(wrapper.props().data.contents.blocks.length).to.equal(1);
+
+			// block 2 (testing) @ 32,3
+			wrapper.instance().handleSvgContextMenu({}, { clicked: 'addBlock', x: 32, y: 3, selectBlocks });
+			wrapper.update();
+			expect(updateFunc.callCount).to.equal(4);
+			expect(wrapper.props().data.contents.blocks.length).to.equal(2);
+
+			// block 3 (analyzer) @ 46,4
+			wrapper.instance().handleSvgContextMenu({}, { clicked: 'addAnalyzer', x: 46, y: 4, selectBlocks });
+			wrapper.update();
+			expect(updateFunc.callCount).to.equal(5);
+			expect(wrapper.props().data.contents.analyzers.length).to.equal(1);
+
+			/* open edit modal for each & edit blocks via input onChange stuff */
+
+			// training_data
+			wrapper.find('rect#block_dataset').simulate('click');
+			wrapper.update();
+			expect(wrapper.find('ToolchainModal').props().active).to.equal(true);
+			expect(wrapper.find('.modal').find('CacheInput').props().value).to.equal('dataset');
+			wrapper.find('.modal').find('CacheInput').prop('onChange')( { target: { value: 'training_data' }});
+			wrapper.update();
+			wrapper.find('.modal button.btn-secondary').simulate('click');
+			wrapper.update();
+			wrapper.find('.modal button.btn-secondary').simulate('click');
+			wrapper.update();
+			wrapper.find('.modal CacheInput[value="output"]').prop('onChange')( { target: { value: 'measurements' }});
+			wrapper.update();
+			wrapper.find('.modal CacheInput[value="output0"]').prop('onChange')( { target: { value: 'species' }});
+			wrapper.update();
+			wrapper.find('button.close').simulate('click');
+			wrapper.update();
+			expect(wrapper.find('ToolchainModal').props().active).to.equal(false);
+
+			expect(wrapper.props().data.contents.datasets[0]).to.deep.equal({
+				'name': 'training_data',
+				'outputs': [
+					'measurements',
+					'species'
+				]
+			});
+
+			// testing_data
+			wrapper.find('rect#block_dataset0').simulate('click');
+			wrapper.update();
+			expect(wrapper.find('ToolchainModal').props().active).to.equal(true);
+			expect(wrapper.find('.modal').find('CacheInput').props().value).to.equal('dataset0');
+			wrapper.find('.modal').find('CacheInput').prop('onChange')( { target: { value: 'testing_data' }});
+			wrapper.update();
+			wrapper.find('.modal button.btn-secondary').simulate('click');
+			wrapper.update();
+			wrapper.find('.modal button.btn-secondary').simulate('click');
+			wrapper.update();
+			wrapper.find('.modal CacheInput[value="output"]').prop('onChange')( { target: { value: 'measurements' }});
+			wrapper.update();
+			wrapper.find('.modal CacheInput[value="output0"]').prop('onChange')( { target: { value: 'species' }});
+			wrapper.update();
+			wrapper.find('button.close').simulate('click');
+			wrapper.update();
+
+			expect(wrapper.props().data.contents.datasets[1]).to.deep.equal({
+				'name': 'testing_data',
+				'outputs': [
+					'measurements',
+					'species'
+				]
+			});
+
+			// training_alg
+			wrapper.find('rect#block_block').simulate('click');
+			wrapper.update();
+			expect(wrapper.find('ToolchainModal').props().active).to.equal(true);
+			expect(wrapper.find('.modal').find('CacheInput').props().value).to.equal('block');
+			wrapper.find('.modal').find('CacheInput').prop('onChange')( { target: { value: 'training_alg' }});
+			wrapper.update();
+			wrapper.find('.modal button.btn-secondary').at(0).simulate('click');
+			wrapper.update();
+			wrapper.find('.modal button.btn-secondary').at(0).simulate('click');
+			wrapper.update();
+			wrapper.find('.modal button.btn-secondary').at(1).simulate('click');
+			wrapper.update();
+			wrapper.find('.modal CacheInput[value="input"]').prop('onChange')( { target: { value: 'measurements' }});
+			wrapper.update();
+			wrapper.find('.modal CacheInput[value="input0"]').prop('onChange')( { target: { value: 'species' }});
+			wrapper.update();
+			wrapper.find('.modal CacheInput[value="output"]').prop('onChange')( { target: { value: 'lda_machine' }});
+			wrapper.update();
+			wrapper.find('button.close').simulate('click');
+			wrapper.update();
+
+			expect(wrapper.props().data.contents.blocks[0]).to.deep.equal({
+				'name': 'training_alg',
+				'inputs': [
+					'measurements',
+					'species'
+				],
+				'outputs': [
+					'lda_machine'
+				],
+				'synchronized_channel': 'training_data',
+			});
+
+			// testing_alg
+			wrapper.find('rect#block_block0').simulate('click');
+			wrapper.update();
+			expect(wrapper.find('ToolchainModal').props().active).to.equal(true);
+			expect(wrapper.find('.modal').find('CacheInput').props().value).to.equal('block0');
+			wrapper.find('.modal').find('CacheInput').prop('onChange')( { target: { value: 'testing_alg' }});
+			wrapper.update();
+			wrapper.find('.modal button.btn-secondary').at(0).simulate('click');
+			wrapper.update();
+			wrapper.find('.modal button.btn-secondary').at(0).simulate('click');
+			wrapper.update();
+			wrapper.find('.modal button.btn-secondary').at(1).simulate('click');
+			wrapper.update();
+			wrapper.find('.modal CacheInput[value="input"]').prop('onChange')( { target: { value: 'measurements' }});
+			wrapper.update();
+			wrapper.find('.modal CacheInput[value="input0"]').prop('onChange')( { target: { value: 'lda_machine' }});
+			wrapper.update();
+			wrapper.find('.modal CacheInput[value="output"]').prop('onChange')( { target: { value: 'scores' }});
+			wrapper.update();
+			wrapper.find('button.close').simulate('click');
+			wrapper.update();
+
+			expect(wrapper.props().data.contents.blocks[1]).to.deep.equal({
+				'name': 'testing_alg',
+				'inputs': [
+					'measurements',
+					'lda_machine'
+				],
+				'outputs': [
+					'scores'
+				],
+				'synchronized_channel': 'training_data',
+			});
+
+			// analyzer
+			wrapper.find('rect#block_analyzer').simulate('click');
+			wrapper.update();
+			expect(wrapper.find('ToolchainModal').props().active).to.equal(true);
+			expect(wrapper.find('.modal').find('CacheInput').props().value).to.equal('analyzer');
+			wrapper.find('.modal button.btn-secondary').simulate('click');
+			wrapper.update();
+			wrapper.find('.modal button.btn-secondary').simulate('click');
+			wrapper.update();
+			wrapper.find('.modal CacheInput[value="input"]').prop('onChange')( { target: { value: 'scores' }});
+			wrapper.update();
+			wrapper.find('.modal CacheInput[value="input0"]').prop('onChange')( { target: { value: 'species' }});
+			wrapper.update();
+			wrapper.find('button.close').simulate('click');
+			wrapper.update();
+
+			expect(wrapper.props().data.contents.analyzers[0]).to.deep.equal({
+				'name': 'analyzer',
+				'inputs': [
+					'scores',
+					'species'
+				],
+				'synchronized_channel': 'training_data',
+			});
+
+			/* connect stuff via createConnections() */
+			// channel: training_data
+			wrapper.instance().createConnections([{ from: 'training_data.measurements', to: 'training_alg.measurements', channel: 'training_data' }]);
+			// channel: training_data
+			wrapper.instance().createConnections([{ from: 'training_data.species', to: 'training_alg.species', channel: 'training_data' }]);
+			// channel: training_data
+			wrapper.instance().createConnections([{ from: 'training_alg.lda_machine', to: 'testing_alg.lda_machine', channel: 'training_data' }]);
+			// channel: testing_data
+			wrapper.instance().createConnections([{ from: 'testing_data.measurements', to: 'testing_alg.measurements', channel: 'testing_data' }]);
+			// channel: training_data
+			wrapper.instance().createConnections([{ from: 'testing_alg.scores', to: 'analyzer.scores', channel: 'training_data' }]);
+			// channel: testing_data
+			wrapper.instance().createConnections([{ from: 'testing_data.species', to: 'analyzer.species', channel: 'testing_data' }]);
+
+			/* fix channels */
+			// testing_alg
+			wrapper.find('rect#block_testing_alg').simulate('click');
+			wrapper.update();
+			expect(wrapper.find('ToolchainModal').props().active).to.equal(true);
+			wrapper.find('.modal select').prop('onChange')( { target: { value: 'testing_data' }});
+			wrapper.update();
+			wrapper.find('button.close').simulate('click');
+			wrapper.update();
+
+			expect(wrapper.props().data.contents.blocks[1]).to.deep.equal({
+				'name': 'testing_alg',
+				'inputs': [
+					'measurements',
+					'lda_machine'
+				],
+				'outputs': [
+					'scores'
+				],
+				'synchronized_channel': 'testing_data',
+			});
+
+			// analyzer
+			wrapper.find('rect#block_analyzer').simulate('click');
+			wrapper.update();
+			expect(wrapper.find('ToolchainModal').props().active).to.equal(true);
+			wrapper.find('.modal select').prop('onChange')( { target: { value: 'testing_data' }});
+			wrapper.update();
+			wrapper.find('button.close').simulate('click');
+			wrapper.update();
+
+			expect(wrapper.props().data.contents.analyzers[0]).to.deep.equal({
+				'name': 'analyzer',
+				'inputs': [
+					'scores',
+					'species'
+				],
+				'synchronized_channel': 'testing_data',
+			});
+
+			/* theres alot of expect statements here for a reason:
+			 * each statement checks a sub-part of the toolchain
+			 * its redundant when the test passes but helpful when somethings off,
+			 * you can narrow the part of the tc that isn't right alot quicker
+			 */
+			const data = wrapper.props().data;
+			const ch1 = data.contents.representation.channel_colors['training_data'];
+			const ch2 = data.contents.representation.channel_colors['testing_data'];
+			expect(data.contents.datasets).to.deep.equalInAnyOrder( [
+				{
+					'name': 'training_data',
+					'outputs': [
+						'measurements',
+						'species'
+					]
+				},
+				{
+					'name': 'testing_data',
+					'outputs': [
+						'measurements',
+						'species'
+					]
+				}
+			]);
+
+			expect(data.contents.blocks).to.deep.equalInAnyOrder([
+				{
+					'inputs': [
+						'measurements',
+						'species'
+					],
+					'name': 'training_alg',
+					'outputs': [
+						'lda_machine'
+					],
+					'synchronized_channel': 'training_data'
+				},
+				{
+					'inputs': [
+						'lda_machine',
+						'measurements'
+					],
+					'name': 'testing_alg',
+					'outputs': [
+						'scores'
+					],
+					'synchronized_channel': 'testing_data'
+				}
+			]);
+
+			expect(data.contents.analyzers).to.deep.equalInAnyOrder([
+				{
+					'inputs': [
+						'scores',
+						'species'
+					],
+					'name': 'analyzer',
+					'synchronized_channel': 'testing_data'
+				}
+			]);
+
+			expect(data.contents.connections).to.deep.equalInAnyOrder([
+				{
+					'channel': 'testing_data',
+					'from': 'testing_alg.scores',
+					'to': 'analyzer.scores'
+				},
+				{
+					'channel': 'training_data',
+					'from': 'training_alg.lda_machine',
+					'to': 'testing_alg.lda_machine'
+				},
+				{
+					'channel': 'testing_data',
+					'from': 'testing_data.measurements',
+					'to': 'testing_alg.measurements'
+				},
+				{
+					'channel': 'training_data',
+					'from': 'training_data.measurements',
+					'to': 'training_alg.measurements'
+				},
+				{
+					'channel': 'training_data',
+					'from': 'training_data.species',
+					'to': 'training_alg.species'
+				},
+				{
+					'channel': 'testing_data',
+					'from': 'testing_data.species',
+					'to': 'analyzer.species'
+				}
+			]);
+
+			expect(data.contents.representation).to.deep.equalInAnyOrder({
+				'blocks': {
+					'analyzer': {
+						'col': 46,
+						'height': 3,
+						'row': 4,
+						'width': 10
+					},
+					'testing_alg': {
+						'col': 32,
+						'height': 3,
+						'row': 3,
+						'width': 10
+					},
+					'testing_data': {
+						'col': 6,
+						'height': 3,
+						'row': 5,
+						'width': 10
+					},
+					'training_alg': {
+						'col': 19,
+						'height': 3,
+						'row': 0,
+						'width': 10
+					},
+					'training_data': {
+						'col': 6,
+						'height': 3,
+						'row': 0,
+						'width': 10
+					}
+				},
+				'channel_colors': {
+					'testing_data': ch2,
+					'training_data': ch1
+				},
+				'connections': {
+					'testing_alg.scores/analyzer.scores': [],
+					'testing_data.measurements/testing_alg.measurements': [],
+					'testing_data.species/analyzer.species': [],
+					'training_alg.lda_machine/testing_alg.lda_machine': [],
+					'training_data.measurements/training_alg.measurements': [],
+					'training_data.species/training_alg.species': []
+				}
+			});
+
+			expect(data).to.deep.equalInAnyOrder({
+				'name': 'test/iris/1',
+				'contents': {
+					'description': '',
+					'datasets': [
+						{
+							'name': 'training_data',
+							'outputs': [
+								'measurements',
+								'species'
+							]
+						},
+						{
+							'name': 'testing_data',
+							'outputs': [
+								'measurements',
+								'species'
+							]
+						}
+					],
+					'blocks': [
+						{
+							'inputs': [
+								'measurements',
+								'species'
+							],
+							'name': 'training_alg',
+							'outputs': [
+								'lda_machine'
+							],
+							'synchronized_channel': 'training_data'
+						},
+						{
+							'inputs': [
+								'lda_machine',
+								'measurements'
+							],
+							'name': 'testing_alg',
+							'outputs': [
+								'scores'
+							],
+							'synchronized_channel': 'testing_data'
+						}
+					],
+					'analyzers': [
+						{
+							'inputs': [
+								'scores',
+								'species'
+							],
+							'name': 'analyzer',
+							'synchronized_channel': 'testing_data'
+						}
+					],
+					'connections': [
+						{
+							'channel': 'testing_data',
+							'from': 'testing_alg.scores',
+							'to': 'analyzer.scores'
+						},
+						{
+							'channel': 'training_data',
+							'from': 'training_alg.lda_machine',
+							'to': 'testing_alg.lda_machine'
+						},
+						{
+							'channel': 'testing_data',
+							'from': 'testing_data.measurements',
+							'to': 'testing_alg.measurements'
+						},
+						{
+							'channel': 'training_data',
+							'from': 'training_data.measurements',
+							'to': 'training_alg.measurements'
+						},
+						{
+							'channel': 'training_data',
+							'from': 'training_data.species',
+							'to': 'training_alg.species'
+						},
+						{
+							'channel': 'testing_data',
+							'from': 'testing_data.species',
+							'to': 'analyzer.species'
+						}
+					],
+					'representation': {
+						'blocks': {
+							'analyzer': {
+								'col': 46,
+								'height': 3,
+								'row': 4,
+								'width': 10
+							},
+							'testing_alg': {
+								'col': 32,
+								'height': 3,
+								'row': 3,
+								'width': 10
+							},
+							'testing_data': {
+								'col': 6,
+								'height': 3,
+								'row': 5,
+								'width': 10
+							},
+							'training_alg': {
+								'col': 19,
+								'height': 3,
+								'row': 0,
+								'width': 10
+							},
+							'training_data': {
+								'col': 6,
+								'height': 3,
+								'row': 0,
+								'width': 10
+							}
+						},
+						'channel_colors': {
+							'testing_data': ch2,
+							'training_data': ch1
+						},
+						'connections': {
+							'testing_alg.scores/analyzer.scores': [],
+							'testing_data.measurements/testing_alg.measurements': [],
+							'testing_data.species/analyzer.species': [],
+							'training_alg.lda_machine/testing_alg.lda_machine': [],
+							'training_data.measurements/training_alg.measurements': [],
+							'training_data.species/training_alg.species': []
+						}
+					}
+				},
+				'extraContents': {
+					'groups': []
+				}
+			});
 		});
 	});
 });