// @flow
import React from 'react';
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 '.';
import * as Selectors from '@store/selectors';
import reducer from '@store/reducers';

import testTcs from '@test/test_tcs.json';
import testDbs from '@test/test_dbs.json';
import testAlgs from '@test/test_algs.json';

const getIrisTc = (color1: string = '#008000', color2: string = '#FF0000') => {
	return {
		'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': color2,
					'training_data': color1
				},
				'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': []
		}
	};
};

chai.use(deepEqualInAnyOrder);

describe('<ToolchainEditor />', function() {
	// these tests might take a long time, comparatively
	this.timeout(10000);

	let wrapper;

	afterEach(() => {
		if(wrapper && wrapper.unmount)
			wrapper.unmount();
	});

	describe('accepts', () => {
		const tcs = testTcs.map(tc => getValidObj(tc));
		const dbs = testDbs.map(db => getValidDatabaseObj(db));
		const algs = testAlgs.map(alg => getValidAlgorithmObj(alg));

		const state = {
			...reducer({}, { type: '', payload: {}}),
			toolchain: tcs,
			database: dbs,
			algorithm: algs,
		};

		const sets = Selectors.flattenedDatabases(state);
		const protocols = Selectors.databaseProtocols(state);
		const normalAlgorithms = Selectors.normalBlocks(state);
		const analyzerAlgorithms = Selectors.analyzerBlocks(state);

		tcs.forEach(function(tc){
			const saveFunc = () => {};
			const updateFunc = () => {};
			it(`${ tc.name }`, () => {
				wrapper = mount(
					<C
						data={tc}
						sets={sets}
						protocols={protocols}
						toolchains={state.toolchain}
						databases={state.database}
						normalAlgorithms={normalAlgorithms}
						analyzerAlgorithms={analyzerAlgorithms}
						saveFunc={saveFunc}
						updateFunc={updateFunc}
					/>
				);

				expect(wrapper).to.have.props(
					['data', 'sets', 'protocols', 'toolchains', 'databases', 'normalAlgorithms', 'analyzerAlgorithms', 'saveFunc', 'updateFunc']
				).deep.equal(
					[tc, sets, protocols, tcs, state.database, normalAlgorithms, analyzerAlgorithms, saveFunc, updateFunc]
				);
			});
		});
	});

	describe('creates', () => {
		const tcs = [];
		const dbs = testDbs.map(db => getValidDatabaseObj(db));
		const algs = testAlgs.map(alg => getValidAlgorithmObj(alg));

		const state = {
			...reducer({}, { type: '', payload: {}}),
			toolchain: tcs,
			database: dbs,
			algorithm: algs,
		};

		const sets = Selectors.flattenedDatabases(state);
		const normalAlgorithms = Selectors.normalBlocks(state);
		const protocols = Selectors.databaseProtocols(state);
		const analyzerAlgorithms = Selectors.analyzerBlocks(state);

		it(`test/iris/1`, () => {
			const saveFunc = sinon.spy();
			const _updateFunc = (obj) => {
				wrapper.setProps && wrapper.setProps({ data: obj });
			};
			const updateFunc = sinon.spy(_updateFunc);
			const tcName = 'test/iris/1';
			const tc = getValidObj({name: tcName, contents: {}});
			wrapper = mount(
				<C
					data={tc}
					sets={sets}
					protocols={protocols}
					toolchains={state.toolchain}
					databases={state.database}
					normalAlgorithms={normalAlgorithms}
					analyzerAlgorithms={analyzerAlgorithms}
					saveFunc={saveFunc}
					updateFunc={updateFunc}
				/>
			);

			expect(wrapper).to.have.props(
				['data', 'sets', 'protocols', 'toolchains', 'databases', 'normalAlgorithms', 'analyzerAlgorithms', 'saveFunc', 'updateFunc']
			);

			expect(wrapper.props().data).to.have.property('name', tcName);

			// add lots of blocks
			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
			// 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#tcModalInitFocus').props().value).to.equal('dataset');
			wrapper.find('.modal').find('CacheInput#tcModalInitFocus').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('.modal button.btn-success').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);
			wrapper.update();
			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.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('.modal button.btn-success').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#tcModalInitFocus').props().value).to.equal('block');
			wrapper.find('.modal').find('CacheInput#tcModalInitFocus').prop('onChange')( { target: { value: 'training_alg' }});
			wrapper.update();
			wrapper.find('.modal button.btn-secondary').at(1).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('.modal button.btn-success').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': '',
			});

			// 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#tcModalInitFocus').props().value).to.equal('block0');
			wrapper.find('.modal').find('CacheInput#tcModalInitFocus').prop('onChange')( { target: { value: 'testing_alg' }});
			wrapper.update();
			wrapper.find('.modal button.btn-secondary').at(1).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('.modal button.btn-success').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': '',
			});

			// 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#tcModalInitFocus').props().value).to.equal('analyzer');
			/*
			wrapper.find('.modal button.btn-secondary').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: 'scores' }});
			wrapper.update();
			wrapper.find('.modal CacheInput[value="input0"]').prop('onChange')( { target: { value: 'species' }});
			wrapper.update();
			wrapper.find('.modal button.btn-success').simulate('click');
			wrapper.update();

			expect(wrapper.props().data.contents.analyzers[0]).to.deep.equal({
				'name': 'analyzer',
				'inputs': [
					'scores',
					'species'
				],
				'synchronized_channel': '',
			});

			/* 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: testing_data
			wrapper.instance().createConnections([{ from: 'testing_data.measurements', to: 'testing_alg.measurements', channel: 'testing_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.species', to: 'analyzer.species', channel: 'testing_data' }]);
			// channel: testing_data
			wrapper.instance().createConnections([{ from: 'testing_alg.scores', to: 'analyzer.scores', 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 #channelSelector DropdownItem#channel-testing_data').simulate('click');
			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 #channelSelector DropdownItem#channel-testing_data').simulate('click');
			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(getIrisTc(ch1, ch2));
		});
	});

	describe('Insert Object Modal', () => {
		const timeout = (ms) => {
			return new Promise(resolve => setTimeout(resolve, ms));
		}

		const tcs = testTcs.map(tc => getValidObj(tc));
		const dbs = testDbs.map(db => getValidDatabaseObj(db));
		const algs = testAlgs.map(alg => getValidAlgorithmObj(alg));

		const state = {
			...reducer({}, { type: '', payload: {}}),
			toolchain: tcs,
			database: dbs,
			algorithm: algs,
		};

		const sets = Selectors.flattenedDatabases(state);
		const normalAlgorithms = Selectors.normalBlocks(state);
		const protocols = Selectors.databaseProtocols(state);
		const analyzerAlgorithms = Selectors.analyzerBlocks(state);

		it(`inserts test/iris_advanced/1`, async () => {
			const saveFunc = sinon.spy();
			const _updateFunc = (obj) => {
				wrapper.setProps && wrapper.setProps({ data: obj });
			};
			const updateFunc = sinon.spy(_updateFunc);
			const tcName = 'test/iom/1';
			const tc = getValidObj({name: tcName, contents: {}});
			wrapper = mount(
				<C
					data={tc}
					sets={sets}
					protocols={protocols}
					toolchains={state.toolchain}
					databases={state.database}
					normalAlgorithms={normalAlgorithms}
					analyzerAlgorithms={analyzerAlgorithms}
					saveFunc={saveFunc}
					updateFunc={updateFunc}
				/>
			);

			expect(wrapper).to.have.props(
				['data', 'sets', 'protocols', 'toolchains', 'databases', 'normalAlgorithms', 'analyzerAlgorithms', 'saveFunc', 'updateFunc']
			);

			expect(wrapper.props().data).to.have.property('name', tcName);

			const _selectBlocks = (bNames: string[]) => { return; };
			const selectBlocks = sinon.spy(_selectBlocks);

			// context-menu insert obj modal
			// insert at x:6 because thats where the original toolchain has its first block on the x-axis
			wrapper.instance().handleSvgContextMenu({}, { clicked: 'addObject', x: 6, y: 0, selectBlocks });
			wrapper.update();
			expect(wrapper.find('InsertObjectModal').props().active).to.equal(true);
			expect(updateFunc.callCount).to.equal(0);

			// search for "test/iris" in toolchain list
			wrapper.find('InsertObjectModal Input[placeholder="Search..."]').prop('onChange')( { target: { value: 'test/iris' }});
			wrapper.update();

			// wait for fuse process to search & return result list
			await timeout(1000);
			wrapper.update();

			// click on the iris_advanced one, which should be first
			const res = wrapper.find('ListGroup.searchResults ListGroupItem');
			expect(res).to.have.lengthOf(4);
			expect(res.at(0).html()).to.include('test/iris_advanced/1');
			res.at(0).simulate('click');
			wrapper.update();

			const data = wrapper.props().data;
			const ch1 = data.contents.representation.channel_colors['training_data'];
			const ch2 = data.contents.representation.channel_colors['testing_data'];

			// make sure the toolchain was inserted
			// basically make sure the toolchain is the same as test/iris_advanced/1 but w different name
			expect(data.contents).to.deep.equalInAnyOrder({
				'analyzers': [
					{
						'inputs': [
							'scores',
							'species'
						],
						'name': 'analyzer',
						'synchronized_channel': 'testing_data'
					}
				],
				'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'
					},
					{
						'inputs': [
							'measurements'
						],
						'name': 'pre_training',
						'outputs': [
							'measurements'
						],
						'synchronized_channel': 'training_data'
					},
					{
						'inputs': [
							'measurements'
						],
						'name': 'pre_testing',
						'outputs': [
							'measurements'
						],
						'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.species',
						'to': 'analyzer.species'
					},
					{
						'channel': 'training_data',
						'from': 'training_data.species',
						'to': 'training_alg.species'
					},
					{
						'channel': 'training_data',
						'from': 'training_data.measurements',
						'to': 'pre_training.measurements'
					},
					{
						'channel': 'training_data',
						'from': 'pre_training.measurements',
						'to': 'training_alg.measurements'
					},
					{
						'channel': 'testing_data',
						'from': 'testing_data.measurements',
						'to': 'pre_testing.measurements'
					},
					{
						'channel': 'testing_data',
						'from': 'pre_testing.measurements',
						'to': 'testing_alg.measurements'
					}
				],
				'datasets': [
					{
						'name': 'training_data',
						'outputs': [
							'measurements',
							'species'
						]
					},
					{
						'name': 'testing_data',
						'outputs': [
							'measurements',
							'species'
						]
					}
				],
				'description': '',
				'representation': {
					'blocks': {
						'analyzer': {
							'col': 59,
							'height': 3,
							'row': 6,
							'width': 10
						},
						'pre_testing': {
							'col': 19,
							'height': 3,
							'row': 5,
							'width': 10
						},
						'pre_training': {
							'col': 19,
							'height': 3,
							'row': 0,
							'width': 10
						},
						'testing_alg': {
							'col': 46,
							'height': 3,
							'row': 5,
							'width': 10
						},
						'testing_data': {
							'col': 6,
							'height': 3,
							'row': 6,
							'width': 10
						},
						'training_alg': {
							'col': 32,
							'height': 3,
							'row': 1,
							'width': 10
						},
						'training_data': {
							'col': 6,
							'height': 3,
							'row': 1,
							'width': 10
						}
					},
					'channel_colors': {
						'testing_data': ch2,
						'training_data': ch1
					},
					'connections': {
						'pre_testing.measurements/testing_alg.measurements': [],
						'pre_training.measurements/training_alg.measurements': [],
						'testing_alg.scores/analyzer.scores': [],
						'testing_data.measurements/pre_testing.measurements': [],
						'testing_data.species/analyzer.species': [],
						'training_alg.lda_machine/testing_alg.lda_machine': [],
						'training_data.measurements/pre_training.measurements': [],
						'training_data.species/training_alg.species': []
					}
				}
			});
		});
	});

	describe('Regression Tests', () => {
		const timeout = (ms) => {
			return new Promise(resolve => setTimeout(resolve, ms));
		};

		const tcs = testTcs.map(tc => getValidObj(tc));
		const dbs = testDbs.map(db => getValidDatabaseObj(db));
		const algs = testAlgs.map(alg => getValidAlgorithmObj(alg));

		const state = {
			...reducer({}, { type: '', payload: {}}),
			toolchain: tcs,
			database: dbs,
			algorithm: algs,
		};

		const sets = Selectors.flattenedDatabases(state);
		const normalAlgorithms = Selectors.normalBlocks(state);
		const protocols = Selectors.databaseProtocols(state);
		const analyzerAlgorithms = Selectors.analyzerBlocks(state);

		const saveFunc = sinon.spy();
		const _updateFunc = (obj) => {
			wrapper.setProps && wrapper.setProps({ data: obj });
		};
		const updateFunc = sinon.spy(_updateFunc);
		const tcName = 'test/iris/1';

		it('Properly changes connection names when renaming an input', () => {
			const tc = getValidObj(getIrisTc());
			wrapper = mount(
				<C
					data={tc}
					sets={sets}
					protocols={protocols}
					toolchains={state.toolchain}
					databases={state.database}
					normalAlgorithms={normalAlgorithms}
					analyzerAlgorithms={analyzerAlgorithms}
					saveFunc={saveFunc}
					updateFunc={updateFunc}
				/>
			);

			// change testing_alg.lda_machine to testing_alg.lda
			wrapper.find('rect#block_testing_alg').simulate('click');
			wrapper.update();
			expect(wrapper.find('ToolchainModal').props().active).to.equal(true);
			wrapper.find('.modal CacheInput[value="lda_machine"]').prop('onChange')( { target: { value: 'lda' }});
			wrapper.find('.modal button.btn-success').simulate('click');
			wrapper.update();
			expect(wrapper.find('.modal CacheInput[value="lda"]').props().value).to.equal('lda');

			const data = wrapper.props().data;
			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', () => {
			const tc = getValidObj(getIrisTc());
			wrapper = mount(
				<C
					data={tc}
					sets={sets}
					protocols={protocols}
					toolchains={state.toolchain}
					databases={state.database}
					normalAlgorithms={normalAlgorithms}
					analyzerAlgorithms={analyzerAlgorithms}
					saveFunc={saveFunc}
					updateFunc={updateFunc}
				/>
			);

			// change testing_alg.lda_machine to testing_alg.lda
			wrapper.find('rect#block_training_alg').simulate('click');
			wrapper.update();
			expect(wrapper.find('ToolchainModal').props().active).to.equal(true);
			wrapper.find('.modal CacheInput[value="lda_machine"]').prop('onChange')( { target: { value: 'lda' }});
			wrapper.find('.modal button.btn-success').simulate('click');
			wrapper.update();
			expect(wrapper.find('.modal CacheInput[value="lda"]').props().value).to.equal('lda');

			const data = wrapper.props().data;
			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 () => {
			const conToDel = { from: 'training_data.species', to: 'training_alg.species', channel: 'training_data' };
			const tc = getValidObj(getIrisTc());
			wrapper = mount(
				<C
					data={tc}
					sets={sets}
					protocols={protocols}
					toolchains={state.toolchain}
					databases={state.database}
					normalAlgorithms={normalAlgorithms}
					analyzerAlgorithms={analyzerAlgorithms}
					saveFunc={saveFunc}
					updateFunc={updateFunc}
				/>
			);

			// change testing_alg.lda_machine to testing_alg.lda
			expect(wrapper.exists('#training_data-species-training_alg-species')).to.equal(true);
			wrapper.instance().handleConnectionContextMenu({}, { clicked: 'delete', connection: conToDel });
			wrapper.update();
			expect(wrapper.exists('#training_data-species-training_alg-species')).to.equal(false);

			const data = wrapper.props().data;
			expect(data.contents.representation.connections).to.not.have.property('training_data.species/training_data.species');
			expect(data.contents.connections.findIndex(c => c.from == conToDel.from && c.to == conToDel.to)).to.equal(-1);
		});

		it('Properly deletes conns between training_data & training_alg, and clears training_alg sync channel', async () => {
			const conToDel = { from: 'training_data.species', to: 'training_alg.species', channel: 'training_data' };
			const conToDel2 = { from: 'training_data.measurements', to: 'training_alg.measurements', channel: 'training_data' };
			const tc = getValidObj(getIrisTc());
			wrapper = mount(
				<C
					data={tc}
					sets={sets}
					protocols={protocols}
					toolchains={state.toolchain}
					databases={state.database}
					normalAlgorithms={normalAlgorithms}
					analyzerAlgorithms={analyzerAlgorithms}
					saveFunc={saveFunc}
					updateFunc={updateFunc}
				/>
			);

			// change testing_alg.lda_machine to testing_alg.lda
			expect(wrapper.exists('#training_data-species-training_alg-species')).to.equal(true);
			expect(wrapper.exists('#training_data-measurements-training_alg-measurements')).to.equal(true);
			wrapper.instance().handleConnectionContextMenu({}, { clicked: 'delete', connection: conToDel });
			wrapper.update();
			wrapper.instance().handleConnectionContextMenu({}, { clicked: 'delete', connection: conToDel2 });
			wrapper.update();
			expect(wrapper.exists('#training_data-species-training_alg-species')).to.equal(false);
			expect(wrapper.exists('#training_data-measurements-training_alg-measurements')).to.equal(false);

			const data = wrapper.props().data;
			expect(data.contents.blocks.find(b => b.name == 'training_alg')).to.have.property('synchronized_channel', '');
		});

		it('Disables the layout button when the toolchain is invalid', async () => {
			const conToDel = { from: 'training_data.species', to: 'training_alg.species', channel: 'training_data' };
			const conToDel2 = { from: 'training_data.measurements', to: 'training_alg.measurements', channel: 'training_data' };
			const tc = getValidObj(getIrisTc());
			wrapper = mount(
				<C
					data={tc}
					sets={sets}
					protocols={protocols}
					toolchains={state.toolchain}
					databases={state.database}
					normalAlgorithms={normalAlgorithms}
					analyzerAlgorithms={analyzerAlgorithms}
					saveFunc={saveFunc}
					updateFunc={updateFunc}
				/>
			);

			// change testing_alg.lda_machine to testing_alg.lda
			expect(wrapper.exists('#training_data-species-training_alg-species')).to.equal(true);
			expect(wrapper.exists('#training_data-measurements-training_alg-measurements')).to.equal(true);
			expect(wrapper.find('Button[title="This will use Graphviz to generate a more visually understandable layout for your toolchain."]'))
			.to.have.prop('disabled', false);
			wrapper.instance().handleConnectionContextMenu({}, { clicked: 'delete', connection: conToDel });
			wrapper.update();
			wrapper.instance().handleConnectionContextMenu({}, { clicked: 'delete', connection: conToDel2 });
			wrapper.update();
			expect(wrapper.exists('#training_data-species-training_alg-species')).to.equal(false);
			expect(wrapper.exists('#training_data-measurements-training_alg-measurements')).to.equal(false);

			const data = wrapper.props().data;
			expect(data.contents.blocks.find(b => b.name == 'training_alg')).to.have.property('synchronized_channel', '');
			expect(wrapper.find('Button[title="This will use Graphviz to generate a more visually understandable layout for your toolchain."]'))
			.to.have.prop('disabled', true);
		});
	});
});
