<commit_before>import React, { Component } from 'react'
import { connect } from 'react-redux';
import { withRouter } from 'react-router'
import PropTypes from 'prop-types'
import { fetchInterfaceColList, fetchInterfaceCaseList, setColData } from '../../../../reducer/modules/interfaceCol'
import { autobind } from 'core-decorators';
import axios from 'axios';
import { Input, Icon, Tag, Modal, message, Tooltip, Tree, Dropdown, Menu, Form } from 'antd';

const TreeNode = Tree.TreeNode;
const FormItem = Form.Item;
const confirm = Modal.confirm;

import './InterfaceColMenu.scss'

const ColModalForm = Form.create()((props) => {
  const { visible, onCancel, onCreate, form, title } = props;
  const { getFieldDecorator } = form;
  return (
    <Modal
      visible={visible}
      title={title}
      onCancel={onCancel}
      onOk={onCreate}
    >
      <Form layout="vertical">
        <FormItem label="集合名">
          {getFieldDecorator('colName', {
            rules: [{ required: true, message: '请输入集合命名！' }]
          })(
            <Input />
          )}
        </FormItem>
        <FormItem label="简介">
          {getFieldDecorator('colDesc')(<Input type="textarea" />)}
        </FormItem>
      </Form>
    </Modal>
  )
});

@connect(
  state => {
    return {
      interfaceColList: state.interfaceCol.interfaceColList,
      currColId: state.interfaceCol.currColId,
      currCaseId: state.interfaceCol.currCaseId,
      isShowCol: state.interfaceCol.isShowCol
    }
  },
  {
    fetchInterfaceColList,
    fetchInterfaceCaseList,
    setColData
  }
)
@withRouter
export default class InterfaceColMenu extends Component {

  static propTypes = {
    match: PropTypes.object,
    interfaceColList: PropTypes.array,
    fetchInterfaceColList: PropTypes.func,
    fetchInterfaceCaseList: PropTypes.func,
    setColData: PropTypes.func,
    history: PropTypes.object,
    currColId: PropTypes.number,
    currCaseId: PropTypes.number,
    isShowCol: PropTypes.bool
  }

  state = {
    expandedKeys: [],
    colModalType: '',
    colModalVisible: false,
    editColId: 0
  }

  constructor(props) {
    super(props)
  }

  async componentWillMount() {
    const { isShowCol, currColId, currCaseId } = this.props;
    const action = isShowCol ? 'col' : 'case';
    const actionId = isShowCol ? currColId : currCaseId;
    this.setState({expandedKeys: [action+'_'+actionId]})
  }

  async componentWillReceiveProps(nextProps) {
    const { currColId } = nextProps;
    let expandedKeys = this.state.expandedKeys;
    if (expandedKeys.indexOf('col_'+currColId) === -1) {
      expandedKeys = expandedKeys.concat(['col_'+currColId])
    }
    this.setState({expandedKeys})
  }

  @autobind
  async addorEditCol() {
    const { colName: name, colDesc: desc } = this.form.getFieldsValue();
    const { colModalType, editColId: col_id } = this.state;
    const project_id = this.props.match.params.id;
    let res = {};
    if (colModalType === 'add') {
      res = await axios.post('/api/col/add_col', { name, desc, project_id })
    } else if (colModalType === 'edit') {
      res = await axios.post('/api/col/up_col', { name, desc, col_id })
    }
    if (!res.data.errcode) {
      this.setState({
        colModalVisible: false
      });
      message.success(colModalType === 'edit' ? '修改集合成功' : '添加集合成功');
      await this.props.fetchInterfaceColList(project_id);
    } else {
      message.error(res.data.errmsg);
    }
  }

  onExpand = (keys) => {
    this.setState({expandedKeys: keys})
  }

  onSelect = (keys) => {
    if (keys.length) {
      const type = keys[0].split('_')[0];
      const id = keys[0].split('_')[1];
      const project_id = this.props.match.params.id
      if (type === 'col') {
        this.props.setColData({
          isShowCol: true,
          currColId: +id
        })
        this.props.history.push('/project/' + project_id + '/interface/col/' + id)
      } else {
        this.props.setColData({
          isShowCol: false,
          currCaseId: +id
        })
        this.props.history.push('/project/' + project_id + '/interface/case/' + id)
      }
    }
  }
  showDelColConfirm = (colId) => {
    let that = this;
    confirm({
      title: '您确认删除此测试集合',
      content: '温馨提示：该操作会删除该集合下所有测试用例，用例删除后无法恢复',
      async onOk() {
        const res = await axios.get('/api/col/del_col?col_id=' + colId)
        if (!res.data.errcode) {
          message.success('删除集合成功');
          await that.props.fetchInterfaceColList(that.props.match.params.id);
        } else {
          message.error(res.data.errmsg);
        }
      },
      onCancel() { }
    });
  }
  showColModal = (type, col) => {
    const editCol = type === 'edit' ? {colName: col.name, colDesc: col.desc} : {colName: '', colDesc: ''};
    this.setState({
      colModalVisible: true,
      colModalType: type || 'add',
      editColId: col && col._id
    })
    this.form.setFieldsValue(editCol)
  }
  saveFormRef = (form) => {
    this.form = form;
  }

  render() {
    const { currColId, currCaseId, isShowCol } = this.props;
    const { colModalType, colModalVisible } = this.state;

    const menu = (col) => {
      return (
        <Menu>
          <Menu.Item>
             this.showColModal('edit', col)}>修改集合
          </Menu.Item>
          <Menu.Item>
             {
              this.showDelColConfirm(col._id)
            }}>删除集合
          </Menu.Item>
        </Menu>
      )
    };

    return (

          <Input placeholder="Filter by name" style={{ width: "70%" }} />
          <Tooltip placement="bottom" title="添加集合">
            <Tag color="#108ee9" style={{ marginLeft: "15px" }} onClick={() => this.showColModal('add')} ><Icon type="plus" /></Tag>
          </Tooltip>

        <Tree
          className="col-list-tree"
          expandedKeys={this.state.expandedKeys}
          selectedKeys={[isShowCol ? 'col_'+currColId : 'case_'+currCaseId]}
          onSelect={this.onSelect}
          autoExpandParent
          onExpand={this.onExpand}
        >
          {
            this.props.interfaceColList.map((col) => (
              <TreeNode
                key={'col_' + col._id}
                title={

                    <Icon type="folder-open" style={{marginRight: 5}} />{col.name}
                    <Dropdown overlay={menu(col)}>
                      <Icon type='bars'/>
                    </Dropdown>

                }
              >
                {
                  col.caseList && col.caseList.map((interfaceCase) => (
                    <TreeNode
                      style={{width: '100%'}}
                      key={'case_' + interfaceCase._id}
                      title={

                          {interfaceCase.casename}
                          <Icon type='delete' className="case-delete-icon" onClick={() => { this.showConfirm(interfaceCase._id) }} />

                      }
                    ></TreeNode>
                  ))
                }
              </TreeNode>
            ))
          }
        </Tree>
        <ColModalForm
          ref={this.saveFormRef}
          type={colModalType}
          visible={colModalVisible}
          onCancel={() => { this.setState({ colModalVisible: false }) }}
          onCreate={this.addorEditCol}
        ></ColModalForm>

    )
  }
}
<commit_msg>feat: 删除用例<commit_after>import React, { Component } from 'react'
import { connect } from 'react-redux';
import { withRouter } from 'react-router'
import PropTypes from 'prop-types'
import { fetchInterfaceColList, fetchInterfaceCaseList, setColData } from '../../../../reducer/modules/interfaceCol'
import { autobind } from 'core-decorators';
import axios from 'axios';
import { Input, Icon, Tag, Modal, message, Tooltip, Tree, Dropdown, Menu, Form } from 'antd';

const TreeNode = Tree.TreeNode;
const FormItem = Form.Item;
const confirm = Modal.confirm;

import './InterfaceColMenu.scss'

const ColModalForm = Form.create()((props) => {
  const { visible, onCancel, onCreate, form, title } = props;
  const { getFieldDecorator } = form;
  return (
    <Modal
      visible={visible}
      title={title}
      onCancel={onCancel}
      onOk={onCreate}
    >
      <Form layout="vertical">
        <FormItem label="集合名">
          {getFieldDecorator('colName', {
            rules: [{ required: true, message: '请输入集合命名！' }]
          })(
            <Input />
          )}
        </FormItem>
        <FormItem label="简介">
          {getFieldDecorator('colDesc')(<Input type="textarea" />)}
        </FormItem>
      </Form>
    </Modal>
  )
});

@connect(
  state => {
    return {
      interfaceColList: state.interfaceCol.interfaceColList,
      currColId: state.interfaceCol.currColId,
      currCaseId: state.interfaceCol.currCaseId,
      isShowCol: state.interfaceCol.isShowCol
    }
  },
  {
    fetchInterfaceColList,
    fetchInterfaceCaseList,
    setColData
  }
)
@withRouter
export default class InterfaceColMenu extends Component {

  static propTypes = {
    match: PropTypes.object,
    interfaceColList: PropTypes.array,
    fetchInterfaceColList: PropTypes.func,
    fetchInterfaceCaseList: PropTypes.func,
    setColData: PropTypes.func,
    history: PropTypes.object,
    currColId: PropTypes.number,
    currCaseId: PropTypes.number,
    isShowCol: PropTypes.bool
  }

  state = {
    expandedKeys: [],
    colModalType: '',
    colModalVisible: false,
    editColId: 0
  }

  constructor(props) {
    super(props)
  }

  async componentWillMount() {
    const { isShowCol, currColId, currCaseId } = this.props;
    const action = isShowCol ? 'col' : 'case';
    const actionId = isShowCol ? currColId : currCaseId;
    this.setState({expandedKeys: [action+'_'+actionId]})
  }

  async componentWillReceiveProps(nextProps) {
    const { currColId } = nextProps;
    let expandedKeys = this.state.expandedKeys;
    if (expandedKeys.indexOf('col_'+currColId) === -1) {
      expandedKeys = expandedKeys.concat(['col_'+currColId])
    }
    this.setState({expandedKeys})
  }

  @autobind
  async addorEditCol() {
    const { colName: name, colDesc: desc } = this.form.getFieldsValue();
    const { colModalType, editColId: col_id } = this.state;
    const project_id = this.props.match.params.id;
    let res = {};
    if (colModalType === 'add') {
      res = await axios.post('/api/col/add_col', { name, desc, project_id })
    } else if (colModalType === 'edit') {
      res = await axios.post('/api/col/up_col', { name, desc, col_id })
    }
    if (!res.data.errcode) {
      this.setState({
        colModalVisible: false
      });
      message.success(colModalType === 'edit' ? '修改集合成功' : '添加集合成功');
      await this.props.fetchInterfaceColList(project_id);
    } else {
      message.error(res.data.errmsg);
    }
  }

  onExpand = (keys) => {
    this.setState({expandedKeys: keys})
  }

  onSelect = (keys) => {
    if (keys.length) {
      const type = keys[0].split('_')[0];
      const id = keys[0].split('_')[1];
      const project_id = this.props.match.params.id
      if (type === 'col') {
        this.props.setColData({
          isShowCol: true,
          currColId: +id
        })
        this.props.history.push('/project/' + project_id + '/interface/col/' + id)
      } else {
        this.props.setColData({
          isShowCol: false,
          currCaseId: +id
        })
        this.props.history.push('/project/' + project_id + '/interface/case/' + id)
      }
    }
  }
  showDelColConfirm = (colId) => {
    let that = this;
    confirm({
      title: '您确认删除此测试集合',
      content: '温馨提示：该操作会删除该集合下所有测试用例，用例删除后无法恢复',
      async onOk() {
        const res = await axios.get('/api/col/del_col?col_id=' + colId)
        if (!res.data.errcode) {
          message.success('删除集合成功');
          await that.props.fetchInterfaceColList(that.props.match.params.id);
        } else {
          message.error(res.data.errmsg);
        }
      }
    });
  }
  showDelCaseConfirm = (caseId) => {
    let that = this;
    confirm({
      title: '您确认删除此测试用例',
      content: '温馨提示：用例删除后无法恢复',
      async onOk() {
        const res = await axios.get('/api/col/del_case?caseid=' + caseId)
        if (!res.data.errcode) {
          message.success('删除用例成功');
          await that.props.fetchInterfaceColList(that.props.match.params.id);
        } else {
          message.error(res.data.errmsg);
        }
      }
    });
  }
  showColModal = (type, col) => {
    const editCol = type === 'edit' ? {colName: col.name, colDesc: col.desc} : {colName: '', colDesc: ''};
    this.setState({
      colModalVisible: true,
      colModalType: type || 'add',
      editColId: col && col._id
    })
    this.form.setFieldsValue(editCol)
  }
  saveFormRef = (form) => {
    this.form = form;
  }

  render() {
    const { currColId, currCaseId, isShowCol } = this.props;
    const { colModalType, colModalVisible } = this.state;

    const menu = (col) => {
      return (
        <Menu>
          <Menu.Item>
             this.showColModal('edit', col)}>修改集合
          </Menu.Item>
          <Menu.Item>
             {
              this.showDelColConfirm(col._id)
            }}>删除集合
          </Menu.Item>
        </Menu>
      )
    };

    return (

          <Input placeholder="Filter by name" style={{ width: "70%" }} />
          <Tooltip placement="bottom" title="添加集合">
            <Tag color="#108ee9" style={{ marginLeft: "15px" }} onClick={() => this.showColModal('add')} ><Icon type="plus" /></Tag>
          </Tooltip>

        <Tree
          className="col-list-tree"
          expandedKeys={this.state.expandedKeys}
          selectedKeys={[isShowCol ? 'col_'+currColId : 'case_'+currCaseId]}
          onSelect={this.onSelect}
          autoExpandParent
          onExpand={this.onExpand}
        >
          {
            this.props.interfaceColList.map((col) => (
              <TreeNode
                key={'col_' + col._id}
                title={

                    <Icon type="folder-open" style={{marginRight: 5}} />{col.name}
                    <Dropdown overlay={menu(col)}>
                      <Icon type='bars'/>
                    </Dropdown>

                }
              >
                {
                  col.caseList && col.caseList.map((interfaceCase) => (
                    <TreeNode
                      style={{width: '100%'}}
                      key={'case_' + interfaceCase._id}
                      title={

                          {interfaceCase.casename}
                          <Icon type='delete' className="case-delete-icon" onClick={() => { this.showDelCaseConfirm(interfaceCase._id) }} />

                      }
                    ></TreeNode>
                  ))
                }
              </TreeNode>
            ))
          }
        </Tree>
        <ColModalForm
          ref={this.saveFormRef}
          type={colModalType}
          visible={colModalVisible}
          onCancel={() => { this.setState({ colModalVisible: false }) }}
          onCreate={this.addorEditCol}
        ></ColModalForm>

    )
  }
}
<|endoftext|>


<commit_before>function DistanceFrame() {

	// Singleton
	if (typeof DistanceFrame.instance === 'object') {
		return DistanceFrame.instance;
	}
	DistanceFrame.instance = this;
	DistanceFrame.min = 100; // Meter
	DistanceFrame.max = 5000;

	var distance = 1;
	var button = null;

	this.showVal = function(value, onchange) {
		value = value / 100000 * (DistanceFrame.max - DistanceFrame.min)
				+ DistanceFrame.min;
		distance = value;
		value = Math.round(value / 100);
		var z = Math.floor(value / 10);
		value %= 10;
		button.controlText.innerHTML = 'Radius: ' + z + ',' + value + 'km';

		if (onchange) {
			new LTmap().update();
		}
	};

	this.getDistance = function() {
		return distance;
	};

	var DistanceControl = function(controlDiv, map) {

		// Set CSS styles for the DIV containing the control
		// Setting padding to 5 px will offset the control
		// from the edge of the map
		controlDiv.style.padding = '5px';

		// Set CSS for the control border
		var controlUI = document.createElement('div');
		controlUI.style.backgroundColor = 'white';
		controlUI.style.borderStyle = 'solid';
		controlUI.style.borderWidth = '2px';
		controlUI.style.cursor = 'pointer';
		controlUI.style.textAlign = 'center';
		controlUI.title = 'Anzeigeradius einstellen';
		controlDiv.appendChild(controlUI);

		// Set CSS for the control interior
		this.controlText = document.createElement('div');
		this.controlText.style.fontFamily = 'Arial,sans-serif';
		this.controlText.style.fontSize = '12px';
		this.controlText.style.paddingLeft = '4px';
		this.controlText.style.paddingRight = '4px';
		this.controlText.innerHTML = 'Radius: ';
		controlUI.appendChild(this.controlText);

		// Setup the click event listeners: simply set the map to
		// Chicago
		google.maps.event.addDomListener(controlUI, 'click', function() {
			$("#DistanceSlider").toggle();
			new TimeFrame().getSlider().hide();
		});
	};

	var distanceControlDiv = document.createElement('div');
	button = new DistanceControl(distanceControlDiv, new LTmap().getGoogleMap());

	distanceControlDiv.index = 1000000;
	new LTmap().getGoogleMap().controls[google.maps.ControlPosition.TOP_RIGHT]
			.push(distanceControlDiv);

	var _this = this;
	$("#DistanceSlider").on("change", null, null, function() {
		_this.showVal($(this).val(), true);
	});

	$("#DistanceSlider").on("input", null, null, function() {
		_this.showVal($(this).val(), false);
	});

	this.showVal($("#DistanceSlider").val(), false);

	this.getSlider = function() {
		return $("#DistanceSlider");
	};
}
<commit_msg>Radius update<commit_after>function DistanceFrame() {

	// Singleton
	if (typeof DistanceFrame.instance === 'object') {
		return DistanceFrame.instance;
	}
	DistanceFrame.instance = this;
	DistanceFrame.min = 100; // Meter
	DistanceFrame.max = 5000;

	var distance = 1;
	var button = null;

	this.showVal = function(value, onchange) {
		value = value / 100000 * (DistanceFrame.max - DistanceFrame.min)
				+ DistanceFrame.min;
		distance = value;
		value = Math.round(value / 100);
		var z = Math.floor(value / 10);
		value %= 10;
		button.controlText.innerHTML = 'Radius: ' + z + ',' + value + 'km';

		new LTmap().setRadius(distance);
		if (onchange) {
			new LTmap().update();
		}
	};

	this.getDistance = function() {
		return distance;
	};

	var DistanceControl = function(controlDiv, map) {

		// Set CSS styles for the DIV containing the control
		// Setting padding to 5 px will offset the control
		// from the edge of the map
		controlDiv.style.padding = '5px';

		// Set CSS for the control border
		var controlUI = document.createElement('div');
		controlUI.style.backgroundColor = 'white';
		controlUI.style.borderStyle = 'solid';
		controlUI.style.borderWidth = '2px';
		controlUI.style.cursor = 'pointer';
		controlUI.style.textAlign = 'center';
		controlUI.title = 'Anzeigeradius einstellen';
		controlDiv.appendChild(controlUI);

		// Set CSS for the control interior
		this.controlText = document.createElement('div');
		this.controlText.style.fontFamily = 'Arial,sans-serif';
		this.controlText.style.fontSize = '12px';
		this.controlText.style.paddingLeft = '4px';
		this.controlText.style.paddingRight = '4px';
		this.controlText.innerHTML = 'Radius: ';
		controlUI.appendChild(this.controlText);

		// Setup the click event listeners: simply set the map to
		// Chicago
		google.maps.event.addDomListener(controlUI, 'click', function() {
			$("#DistanceSlider").toggle();
			new TimeFrame().getSlider().hide();
		});
	};

	var distanceControlDiv = document.createElement('div');
	button = new DistanceControl(distanceControlDiv, new LTmap().getGoogleMap());

	distanceControlDiv.index = 1000000;
	new LTmap().getGoogleMap().controls[google.maps.ControlPosition.TOP_RIGHT]
			.push(distanceControlDiv);

	var _this = this;
	$("#DistanceSlider").on("change", null, null, function() {
		_this.showVal($(this).val(), true);
	});

	$("#DistanceSlider").on("input", null, null, function() {
		_this.showVal($(this).val(), false);
	});

	this.showVal($("#DistanceSlider").val(), false);

	this.getSlider = function() {
		return $("#DistanceSlider");
	};
}
<|endoftext|>


<commit_before>import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import nock from 'nock';

import * as starredBoardActions from './starredBoard';

import reducer from './starredBoard';

const middlewares = [ thunk ];
const mockStore = configureMockStore(middlewares);

describe('starredBoard actions', () => {
  afterEach(() => {
    nock.cleanAll();
  })

  it('should create an action to addBoardStar when the orgId is empty', () => {
    const data = {
      organizations: [],
      starredBoard: [],
      boards: []
    };

    const expectedActions = [
      { type: 'STAR_BOARD_REQUEST' },
      { type: 'STAR_BOARD_SUCCESS' },
      {
        type: 'UPDATE_BOARDS',
        payload: data
      },
      {
        type: 'UPDATE_STARRED_BOARDS',
        payload: data
      },
      {
		    type: 'UPDATE_ORGANIZATIONS',
        payload: data
      }
    ];

    const store = mockStore();

    nock('http://localhost:3001/api/v1/boards/boardId/boardstars', {
      reqheaders: {
        'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
        'authorization': 'JWT ' + localStorage.getItem('userId')
      }
    })
    .post('')
    .reply(200, { data });

    return store.dispatch(starredBoardActions.addBoardStar('userId', '', 'boardId'))
      .then(() => {
        expect(store.getActions()).toEqual(expectedActions)
      }
    );
  })

  it('should create an action to addBoardStar', () => {
    const data = {
      organizations: [],
      starredBoard: [],
      boards: []
    };

    const expectedActions = [
      { type: 'STAR_BOARD_REQUEST' },
      { type: 'STAR_BOARD_SUCCESS' },
      {
        type: 'UPDATE_BOARDS',
        payload: data
      },
      {
        type: 'UPDATE_STARRED_BOARDS',
        payload: data
      },
      {
		    type: 'UPDATE_ORGANIZATIONS',
        payload: data
      }
    ];

    const store = mockStore();

    nock('http://localhost:3001/api/v1/organizations/orgId/boards/boardId/boardstars', {
      reqheaders: {
        'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
        'authorization': 'JWT ' + localStorage.getItem('userId')
      }
    })
    .post('')
    .reply(200, { data });

    return store.dispatch(starredBoardActions.addBoardStar('userId', 'orgId', 'boardId'))
      .then(() => {
        expect(store.getActions()).toEqual(expectedActions)
      }
    );
  })
})

describe('starredBoard reducer', () => {
  it('should return the initial state', () => {
    expect(
      reducer(undefined, {})
    ).toEqual({
        isFetchingStarredBoard: false,
        isFetchingStarredBoardSuccessful: false,

        errorMessage: '',
        starredBoards: []
      }
    )
  })

  it('should handle UPDATE_STARRED_BOARDS', () => {
    const payload = {
      boardStars: [{
        _id: '1',
        id: '2',
        isStarredBoard: true,
        name: 'starredBoardName'
      }]
    };

    expect(
      reducer([], {
        type: 'UPDATE_STARRED_BOARDS',
        payload
      })
    ).toEqual({
        starredBoards: payload.boardStars
      }
    )
  })

  it('should handle STAR_BOARD_REQUEST', () => {
    expect(
      reducer([], {
        type: 'STAR_BOARD_REQUEST'
      })
    ).toEqual({
        isFetchingStarredBoard: true
      }
    )
  })

  it('should handle STAR_BOARD_SUCCESS', () => {
    expect(
      reducer([], {
        type: 'STAR_BOARD_SUCCESS'
      })
    ).toEqual({
        isFetchingStarredBoardSuccessful: true,
        isFetchingStarredBoard: false
      }
    )
  })

  it('should handle STAR_BOARD_FAIL', () => {
    expect(
      reducer([], {
        type: 'STAR_BOARD_FAIL'
      })
    ).toEqual({
        isFetchingStarredBoardSuccessful: false,
        isFetchingStarredBoard: false
      }
    )
  })

  it('should handle UNSTAR_BOARD_REQUEST', () => {
    expect(
      reducer([], {
        type: 'UNSTAR_BOARD_REQUEST'
      })
    ).toEqual({
        isFetchingStarredBoard: true
      }
    )
  })

  it('should handle UNSTAR_BOARD_SUCCESS', () => {
    expect(
      reducer([], {
        type: 'UNSTAR_BOARD_SUCCESS'
      })
    ).toEqual({
        isFetchingStarredBoardSuccessful: true,
        isFetchingStarredBoard: false
      }
    )
  })

  it('should handle UNSTAR_BOARD_FAIL', () => {
    const payload = {
      errorMessage: 'Unstar board request failed'
    };

    expect(
      reducer([], {
        type: 'UNSTAR_BOARD_FAIL',
        payload
      })
    ).toEqual({
        isFetchingStarredBoardSuccessful: false,
        isFetchingStarredBoard: false,
        errorMessage: payload.error
      }
    )
  })
})<commit_msg>Fix typo<commit_after>import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import nock from 'nock';

import * as starredBoardActions from './starredBoard';

import reducer from './starredBoard';

const middlewares = [ thunk ];
const mockStore = configureMockStore(middlewares);

describe('starredBoard actions', () => {
  afterEach(() => {
    nock.cleanAll();
  })

  it('should create an action to addBoardStar when the orgId is empty', () => {
    const data = {
      organizations: [],
      starredBoards: [],
      boards: []
    };

    const expectedActions = [
      { type: 'STAR_BOARD_REQUEST' },
      { type: 'STAR_BOARD_SUCCESS' },
      {
        type: 'UPDATE_BOARDS',
        payload: data
      },
      {
        type: 'UPDATE_STARRED_BOARDS',
        payload: data
      },
      {
		    type: 'UPDATE_ORGANIZATIONS',
        payload: data
      }
    ];

    const store = mockStore();

    nock('http://localhost:3001/api/v1/boards/boardId/boardstars', {
      reqheaders: {
        'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
        'authorization': 'JWT ' + localStorage.getItem('userId')
      }
    })
    .post('')
    .reply(200, { data });

    return store.dispatch(starredBoardActions.addBoardStar('userId', '', 'boardId'))
      .then(() => {
        expect(store.getActions()).toEqual(expectedActions)
      }
    );
  })

  it('should create an action to addBoardStar', () => {
    const data = {
      organizations: [],
      starredBoard: [],
      boards: []
    };

    const expectedActions = [
      { type: 'STAR_BOARD_REQUEST' },
      { type: 'STAR_BOARD_SUCCESS' },
      {
        type: 'UPDATE_BOARDS',
        payload: data
      },
      {
        type: 'UPDATE_STARRED_BOARDS',
        payload: data
      },
      {
		    type: 'UPDATE_ORGANIZATIONS',
        payload: data
      }
    ];

    const store = mockStore();

    nock('http://localhost:3001/api/v1/organizations/orgId/boards/boardId/boardstars', {
      reqheaders: {
        'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
        'authorization': 'JWT ' + localStorage.getItem('userId')
      }
    })
    .post('')
    .reply(200, { data });

    return store.dispatch(starredBoardActions.addBoardStar('userId', 'orgId', 'boardId'))
      .then(() => {
        expect(store.getActions()).toEqual(expectedActions)
      }
    );
  })
})

describe('starredBoard reducer', () => {
  it('should return the initial state', () => {
    expect(
      reducer(undefined, {})
    ).toEqual({
        isFetchingStarredBoard: false,
        isFetchingStarredBoardSuccessful: false,

        errorMessage: '',
        starredBoards: []
      }
    )
  })

  it('should handle UPDATE_STARRED_BOARDS', () => {
    const payload = {
      boardStars: [{
        _id: '1',
        id: '2',
        isStarredBoard: true,
        name: 'starredBoardName'
      }]
    };

    expect(
      reducer([], {
        type: 'UPDATE_STARRED_BOARDS',
        payload
      })
    ).toEqual({
        starredBoards: payload.boardStars
      }
    )
  })

  it('should handle STAR_BOARD_REQUEST', () => {
    expect(
      reducer([], {
        type: 'STAR_BOARD_REQUEST'
      })
    ).toEqual({
        isFetchingStarredBoard: true
      }
    )
  })

  it('should handle STAR_BOARD_SUCCESS', () => {
    expect(
      reducer([], {
        type: 'STAR_BOARD_SUCCESS'
      })
    ).toEqual({
        isFetchingStarredBoardSuccessful: true,
        isFetchingStarredBoard: false
      }
    )
  })

  it('should handle STAR_BOARD_FAIL', () => {
    expect(
      reducer([], {
        type: 'STAR_BOARD_FAIL'
      })
    ).toEqual({
        isFetchingStarredBoardSuccessful: false,
        isFetchingStarredBoard: false
      }
    )
  })

  it('should handle UNSTAR_BOARD_REQUEST', () => {
    expect(
      reducer([], {
        type: 'UNSTAR_BOARD_REQUEST'
      })
    ).toEqual({
        isFetchingStarredBoard: true
      }
    )
  })

  it('should handle UNSTAR_BOARD_SUCCESS', () => {
    expect(
      reducer([], {
        type: 'UNSTAR_BOARD_SUCCESS'
      })
    ).toEqual({
        isFetchingStarredBoardSuccessful: true,
        isFetchingStarredBoard: false
      }
    )
  })

  it('should handle UNSTAR_BOARD_FAIL', () => {
    const payload = {
      errorMessage: 'Unstar board request failed'
    };

    expect(
      reducer([], {
        type: 'UNSTAR_BOARD_FAIL',
        payload
      })
    ).toEqual({
        isFetchingStarredBoardSuccessful: false,
        isFetchingStarredBoard: false,
        errorMessage: payload.error
      }
    )
  })
})<|endoftext|>


<commit_before>/* eslint no-extend-native: 0 */

import { expect } from 'chai';

import runner from '../../../../client/scripts/arcademode/runner';

// Get some real challenges in
import ArcadeChallenges from '../../../../public/json/challenges-arcade.json';

const challenges = ArcadeChallenges.challenges;
const stackChallenge = challenges.find(item => item.title.match(/Queue using/));
const bookshopChallenge = challenges.find(item => item.title.match(/Bookshop/));
const gittacaChallenge = challenges.find(item => item.title.match(/Gittaca/));
const conventuristChallenge = challenges.find(item => item.title.match(/Conventurist/));
const lruCacheChallenge = challenges.find(item => item.title.match(/LRU Cache/));
const petchChallenge = challenges.find(item => item.title.match(/Petch/));
const rotateArrayChallenge = challenges.find(item => item.title.match(/Rotate array/));

const userOutputUndef = 'User output is undefined.';

// CHALLENGES for the test cases

const emptyChallenge = {
  tests: ['// No tests, message: No tests really']
};

const challengeWithTest = {
  tests: ['assert(true === true, "message: This is correct.");']
};

const challengeWithHead = {
  head: ['function seenByUser() {return 1 + 1;}'],
  tests: []
};

const challengeWithTail = {
  tail: ['function tailFunction() {return "tail function";}'],
  tests: ['assert(tailFunction() !== "right", "message: all fine");']
};

// TESTS

describe('runner()', () => {
  it('executes js using eval and returns results', () => {
    const userCode = '// no code';
    const result = runner(userCode, emptyChallenge);
    return result.then(res => {
      expect(res).to.have.property('userOutput');
      expect(res).to.have.property('errorMsgs');
      expect(res).to.have.property('testResults');
      expect(res.errorMsgs).to.have.length(0);
    });
  });

  it('catches invalid syntax correctly', () => {
    const userCode = 'syntaxError [][ = ;];';
    const result = runner(userCode, emptyChallenge);
    return result.then(res => {
      expect(res.errorMsgs).to.have.length(1);
      expect(res.userOutput).to.have.length.above(0);
      expect(res.userOutput).to.match(/SyntaxError/);
      expect(res.testResults).to.have.length(0);
    });
  });

  it('catches invalid execution, non-exec code', () => {
    const userCode = 'let myVar = NonExistingClass()';
    const result = runner(userCode, emptyChallenge);
    return result.then(res => {
      expect(res.errorMsgs).to.have.length(1);
      expect(res.userOutput).to.have.length.above(0);
      expect(res.userOutput).to.match(/Error:/);
      expect(res.testResults).to.have.length(0);
    });
  });

  it('Runs tests for correct code', () => {
    const userCode = '// Error-free code\ntrue;';
    const result = runner(userCode, challengeWithTest);
    return result.then(res => {
      expect(res.testResults).to.have.length(1);
      expect(res.errorMsgs).to.have.length(0);
      expect(res.userOutput).to.equal('true');
    });
  });

  it('adds head before executing the code', () => {
    const userCode = 'seenByUser();';
    const result = runner(userCode, challengeWithHead);
    return result.then(res => {
      expect(res.userOutput).to.equal('2');
    });
  });

  it('adds tail before running the tests', () => {
    const userCode = '// No user code really';
    const result = runner(userCode, challengeWithTail);
    return result.then(res => {
      expect(res.userOutput).to.equal(userOutputUndef);
    });
  });

  it('catches infinite loops in user code', () => {
    const userCode = 'while (true);';
    const result = runner(userCode, challengeWithHead);
    return result.then(res => {
      expect(res.errorMsgs).to.have.length(1);
      expect(res.userOutput).to.match(/infinite loop/);
    });
  });

  it('can execute queue-2-stack challenge', () => {
    let userCode = stackChallenge.solutions.join('');
    userCode = userCode.replace(/^\s*Queue/, 'const Queue');
    const result = runner(userCode, stackChallenge);
    return result.then(res => {
      expect(res.userOutput).to.equal(userOutputUndef);
      expectNoErrorsAndAllTestsRun(res, stackChallenge);
    });
  });

  it('can execute the book shop challenge', () => {
    const sort = Array.prototype.sort;
    const userCode = bookshopChallenge.solutions.join('');
    const result = runner(userCode, bookshopChallenge);
    Array.prototype.sort = sort;
    return result.then(res => {
      expect(res.userOutput).to.equal(userOutputUndef);
      expectNoErrorsAndAllTestsRun(res, bookshopChallenge);
    });
  });

  it('can execute the gittaca challenge', () => {
    const userCode = gittacaChallenge.solutions.join('');
    const result = runner(userCode, gittacaChallenge);
    return result.then(res => {
      expect(res.userOutput).to.equal(userOutputUndef);
      expectNoErrorsAndAllTestsRun(res, gittacaChallenge);
    });
  });

  it('can execute the conventurist challenge', () => {
    const userCode = conventuristChallenge.solutions.join('');
    const result = runner(userCode, conventuristChallenge);
    return result.then(res => {
      expect(res.userOutput).to.equal(userOutputUndef);
      expectNoErrorsAndAllTestsRun(res, conventuristChallenge);
    });
  });

  it('can execute the LRU cache challenge', () => {
    const userCode = lruCacheChallenge.solutions.join('');
    const result = runner(userCode, lruCacheChallenge);
    return result.then(res => {
      expect(res.userOutput).to.equal(userOutputUndef);
      expectNoErrorsAndAllTestsRun(res, lruCacheChallenge);
    });
  });

  it('can execute the Petch challenge', () => {
    const userCode = petchChallenge.solutions.join('');
    const result = runner(userCode, petchChallenge);
    return result.then(res => {
      expect(res.userOutput).to.equal(userOutputUndef);
      expectNoErrorsAndAllTestsRun(res, petchChallenge);
    });
  });

  it('can execute the Rotate array challenge', () => {
    const userCode = rotateArrayChallenge.solutions.join('');
    const result = runner(userCode, rotateArrayChallenge);
    return result.then(res => {
      expect(res.userOutput).to.equal(userOutputUndef);
      expectNoErrorsAndAllTestsRun(res, rotateArrayChallenge);
    });
  });
});

// HELPER FUNCTIONS
function expectNoErrorsAndAllTestsRun(res, challenge) {
  expect(res.errorMsgs).to.have.length(0);
  expect(res.testResults).to.have.length(challenge.tests.length);
}
<commit_msg>Added a unit test for Linked hash map challenge.<commit_after>/* eslint no-extend-native: 0 */

import { expect } from 'chai';

import runner from '../../../../client/scripts/arcademode/runner';

// Get some real challenges in
import ArcadeChallenges from '../../../../public/json/challenges-arcade.json';

const challenges = ArcadeChallenges.challenges;
const stackChallenge = challenges.find(item => item.title.match(/Queue using/));
const bookshopChallenge = challenges.find(item => item.title.match(/Bookshop/));
const gittacaChallenge = challenges.find(item => item.title.match(/Gittaca/));
const conventuristChallenge = challenges.find(item => item.title.match(/Conventurist/));
const lruCacheChallenge = challenges.find(item => item.title.match(/LRU Cache/));
const petchChallenge = challenges.find(item => item.title.match(/Petch/));
const rotateArrayChallenge = challenges.find(item => item.title.match(/Rotate array/));
const linkedHashmapChallenge = challenges.find(item => item.title.match(/Linked Hash Map/));

const userOutputUndef = 'User output is undefined.';

// CHALLENGES for the test cases

const emptyChallenge = {
  tests: ['// No tests, message: No tests really']
};

const challengeWithTest = {
  tests: ['assert(true === true, "message: This is correct.");']
};

const challengeWithHead = {
  head: ['function seenByUser() {return 1 + 1;}'],
  tests: []
};

const challengeWithTail = {
  tail: ['function tailFunction() {return "tail function";}'],
  tests: ['assert(tailFunction() !== "right", "message: all fine");']
};

// TESTS

describe('runner()', () => {
  it('executes js using eval and returns results', () => {
    const userCode = '// no code';
    const result = runner(userCode, emptyChallenge);
    return result.then(res => {
      expect(res).to.have.property('userOutput');
      expect(res).to.have.property('errorMsgs');
      expect(res).to.have.property('testResults');
      expect(res.errorMsgs).to.have.length(0);
    });
  });

  it('catches invalid syntax correctly', () => {
    const userCode = 'syntaxError [][ = ;];';
    const result = runner(userCode, emptyChallenge);
    return result.then(res => {
      expect(res.errorMsgs).to.have.length(1);
      expect(res.userOutput).to.have.length.above(0);
      expect(res.userOutput).to.match(/SyntaxError/);
      expect(res.testResults).to.have.length(0);
    });
  });

  it('catches invalid execution, non-exec code', () => {
    const userCode = 'let myVar = NonExistingClass()';
    const result = runner(userCode, emptyChallenge);
    return result.then(res => {
      expect(res.errorMsgs).to.have.length(1);
      expect(res.userOutput).to.have.length.above(0);
      expect(res.userOutput).to.match(/Error:/);
      expect(res.testResults).to.have.length(0);
    });
  });

  it('Runs tests for correct code', () => {
    const userCode = '// Error-free code\ntrue;';
    const result = runner(userCode, challengeWithTest);
    return result.then(res => {
      expect(res.testResults).to.have.length(1);
      expect(res.errorMsgs).to.have.length(0);
      expect(res.userOutput).to.equal('true');
    });
  });

  it('adds head before executing the code', () => {
    const userCode = 'seenByUser();';
    const result = runner(userCode, challengeWithHead);
    return result.then(res => {
      expect(res.userOutput).to.equal('2');
    });
  });

  it('adds tail before running the tests', () => {
    const userCode = '// No user code really';
    const result = runner(userCode, challengeWithTail);
    return result.then(res => {
      expect(res.userOutput).to.equal(userOutputUndef);
    });
  });

  it('catches infinite loops in user code', () => {
    const userCode = 'while (true);';
    const result = runner(userCode, challengeWithHead);
    return result.then(res => {
      expect(res.errorMsgs).to.have.length(1);
      expect(res.userOutput).to.match(/infinite loop/);
    });
  });

  it('can execute queue-2-stack challenge', () => {
    const userCode = stackChallenge.solutions.join('');
    const result = runner(userCode, stackChallenge);
    return result.then(res => {
      expect(res.userOutput).to.equal(userOutputUndef);
      expectNoErrorsAndAllTestsRun(res, stackChallenge);
    });
  });

  it('can execute the book shop challenge', () => {
    const sort = Array.prototype.sort;
    const userCode = bookshopChallenge.solutions.join('');
    const result = runner(userCode, bookshopChallenge);
    Array.prototype.sort = sort;
    return result.then(res => {
      expect(res.userOutput).to.equal(userOutputUndef);
      expectNoErrorsAndAllTestsRun(res, bookshopChallenge);
    });
  });

  it('can execute the gittaca challenge', () => {
    const userCode = gittacaChallenge.solutions.join('');
    const result = runner(userCode, gittacaChallenge);
    return result.then(res => {
      expect(res.userOutput).to.equal(userOutputUndef);
      expectNoErrorsAndAllTestsRun(res, gittacaChallenge);
    });
  });

  it('can execute the conventurist challenge', () => {
    const userCode = conventuristChallenge.solutions.join('');
    const result = runner(userCode, conventuristChallenge);
    return result.then(res => {
      expect(res.userOutput).to.equal(userOutputUndef);
      expectNoErrorsAndAllTestsRun(res, conventuristChallenge);
    });
  });

  it('can execute the LRU cache challenge', () => {
    const userCode = lruCacheChallenge.solutions.join('');
    const result = runner(userCode, lruCacheChallenge);
    return result.then(res => {
      expect(res.userOutput).to.equal(userOutputUndef);
      expectNoErrorsAndAllTestsRun(res, lruCacheChallenge);
    });
  });

  it('can execute the Petch challenge', () => {
    const userCode = petchChallenge.solutions.join('');
    const result = runner(userCode, petchChallenge);
    return result.then(res => {
      expect(res.userOutput).to.equal(userOutputUndef);
      expectNoErrorsAndAllTestsRun(res, petchChallenge);
    });
  });

  it('can execute the Rotate array challenge', () => {
    const userCode = rotateArrayChallenge.solutions.join('');
    const result = runner(userCode, rotateArrayChallenge);
    return result.then(res => {
      expect(res.userOutput).to.equal(userOutputUndef);
      expectNoErrorsAndAllTestsRun(res, rotateArrayChallenge);
    });
  });

  it('can execute the Linked hash map challenge', () => {
    const userCode = linkedHashmapChallenge.solutions.join('');
    const result = runner(userCode, linkedHashmapChallenge);
    return result.then(res => {
      expect(res.userOutput).to.equal(userOutputUndef);
      expectNoErrorsAndAllTestsRun(res, linkedHashmapChallenge);
    });
  });
});

// HELPER FUNCTIONS
function expectNoErrorsAndAllTestsRun(res, challenge) {
  expect(res.errorMsgs).to.have.length(0);
  expect(res.testResults).to.have.length(challenge.tests.length);
}
<|endoftext|>


<commit_before>// https://github.com/ded/script.js
// import script @ `url` after current script
// http://stackoverflow.com/questions/5168451/javascript-require-on-client-side#answer-8536369
function import (url, options = {}) {
  var script = document.createElement ('script')

  script.src = url.match (/\.js$/) ?
    url : `${url}.js`

  script.async = 'async' in options
    ? options.async : false

  document.currentScript
    .after (script, document.currentScript)

  return inject
}

// Usage:
/*

// local
inject
  ('baz.js')

// local (implicit) - Assumes `.js` extension
inject
  ('baz')

// `inject`tion chaining
inject
  ('foo')
  ('bar')
  ('baz')
  ('bat')
; // Yes that is valid Javascript/ECMAScript code.

// `async`ronous `inject`ions
inject
  ('foo', {async: true})
  ('bar')
  ('baz')

// remote (implicit) - Assumes `.js` extension
inject
  ('http://foo.bar/baz')

// remote
inject
  // CORS settings
  // https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes
  ('https://code.jquery.com/jquery-3.1.1.min.js')
  ('/my-jquery-plugin')

*/
<commit_msg>Remove classic import.js<commit_after><|endoftext|>


<commit_before>'use strict';

let utils = require('./utils');

// real to complex fft
let fft = function(signal){

  if(signal.real === undefined || signal.imag === undefined){
    signal = utils.constructComplexArray(signal);
  }

  const N = signal.real.length;
  const logN = Math.log2(N);

  if(Math.round(logN) != logN) throw new Error('Input size must be a power of 2.');

  if(signal.real.length != signal.imag.length){
    throw new Error('Real and imaginary components must have the same length.');
  }

  const bitReversedIndices = utils.bitReverseArray(N);

  // sort array
  let ordered = {
    'real': [],
    'imag': []
  };

  for(let i = 0; i < N; i++){
    ordered.real[bitReversedIndices[i]] = signal.real[i];
    ordered.imag[bitReversedIndices[i]] = signal.imag[i];
  }

  for(let i = 0; i < N; i++){
    signal.real[i] = ordered.real[i];
    signal.imag[i] = ordered.imag[i];
  }

  // iterate over the number of stages
  for(let n = 1; n <= logN; n++){
    let currN = Math.pow(2, n);

    // find twiddle factors
    for(let k = 0; k < currN / 2; k++){
      let twiddle = utils.euler(k, currN);

      // on each block of FT, implement the butterfly diagram
      for(let m = 0; m < N / currN; m++){
        let currEvenIndex = (currN * m) + k;
        let currOddIndex = (currN * m) + k + (currN / 2);

        let currEvenIndexSample = {
                          'real': signal.real[currEvenIndex],
                          'imag': signal.imag[currEvenIndex]
                        }
        let currOddIndexSample = {
                          'real': signal.real[currOddIndex],
                          'imag': signal.imag[currOddIndex]
                        }

        let odd = utils.multiply(twiddle, currOddIndexSample);

        let subtractionResult = utils.subtract(currEvenIndexSample, odd);
        signal.real[currOddIndex] = subtractionResult.real;
        signal.imag[currOddIndex] = subtractionResult.imag;

        let additionResult = utils.add(odd, currEvenIndexSample);
        signal.real[currEvenIndex] = additionResult.real;
        signal.imag[currEvenIndex] = additionResult.imag;
      }
    }
  }

  return signal;
}

// complex to real ifft
let ifft = function(signal){
  const N = signal.real.length;

  //take complex conjugate in order to be able to use the regular FFT for IFFT
  for(let i = 0; i < N; i++){
    let currentSample = {
      'real': signal.real[i],
      'imag': signal.imag[i]
    };

    let conjugateSample = utils.conj(currentSample);
    signal.imag[i] = conjugateSample.imag;
  }

  //compute
  let X = fft(signal);

  var complexSignal = {
    'real': [],
    'imag': []
  };

  //normalize
  complexSignal.real = X.real.map((val) => {
    return val / N;
  });

  complexSignal.imag = X.imag.map((val) => {
    return val / N;
  });

  return complexSignal;
}

module.exports = {
  fft: fft,
  ifft: ifft
};
<commit_msg>throws error if input to ifft is not suitable<commit_after>'use strict';

let utils = require('./utils');

// real to complex fft
let fft = function(signal){

  if(signal.real === undefined || signal.imag === undefined){
    signal = utils.constructComplexArray(signal);
  }

  const N = signal.real.length;
  const logN = Math.log2(N);

  if(Math.round(logN) != logN) throw new Error('Input size must be a power of 2.');

  if(signal.real.length != signal.imag.length){
    throw new Error('Real and imaginary components must have the same length.');
  }

  const bitReversedIndices = utils.bitReverseArray(N);

  // sort array
  let ordered = {
    'real': [],
    'imag': []
  };

  for(let i = 0; i < N; i++){
    ordered.real[bitReversedIndices[i]] = signal.real[i];
    ordered.imag[bitReversedIndices[i]] = signal.imag[i];
  }

  for(let i = 0; i < N; i++){
    signal.real[i] = ordered.real[i];
    signal.imag[i] = ordered.imag[i];
  }

  // iterate over the number of stages
  for(let n = 1; n <= logN; n++){
    let currN = Math.pow(2, n);

    // find twiddle factors
    for(let k = 0; k < currN / 2; k++){
      let twiddle = utils.euler(k, currN);

      // on each block of FT, implement the butterfly diagram
      for(let m = 0; m < N / currN; m++){
        let currEvenIndex = (currN * m) + k;
        let currOddIndex = (currN * m) + k + (currN / 2);

        let currEvenIndexSample = {
                          'real': signal.real[currEvenIndex],
                          'imag': signal.imag[currEvenIndex]
                        }
        let currOddIndexSample = {
                          'real': signal.real[currOddIndex],
                          'imag': signal.imag[currOddIndex]
                        }

        let odd = utils.multiply(twiddle, currOddIndexSample);

        let subtractionResult = utils.subtract(currEvenIndexSample, odd);
        signal.real[currOddIndex] = subtractionResult.real;
        signal.imag[currOddIndex] = subtractionResult.imag;

        let additionResult = utils.add(odd, currEvenIndexSample);
        signal.real[currEvenIndex] = additionResult.real;
        signal.imag[currEvenIndex] = additionResult.imag;
      }
    }
  }

  return signal;
}

// complex to real ifft
let ifft = function(signal){

  if(signal.real === undefined || signal.imag === undefined){
    throw new Error("IFFT only accepts a complex input.")
  }

  const N = signal.real.length;

  //take complex conjugate in order to be able to use the regular FFT for IFFT
  for(let i = 0; i < N; i++){
    let currentSample = {
      'real': signal.real[i],
      'imag': signal.imag[i]
    };

    let conjugateSample = utils.conj(currentSample);
    signal.imag[i] = conjugateSample.imag;
  }

  //compute
  let X = fft(signal);

  var complexSignal = {
    'real': [],
    'imag': []
  };

  //normalize
  complexSignal.real = X.real.map((val) => {
    return val / N;
  });

  complexSignal.imag = X.imag.map((val) => {
    return val / N;
  });

  return complexSignal;
}

module.exports = {
  fft: fft,
  ifft: ifft
};
<|endoftext|>


<commit_before>const assert = require('assert');
const EventEmitter = require('events');
const Promise = require('bluebird');

const Worker = require('./worker');
const plans = require('./plans');
const Attorney = require('./attorney');

const completedJobPrefix = plans.completedJobPrefix;

const events = {
  error: 'error'
};

class Manager extends EventEmitter {
  constructor(db, config){
    super();

    this.config = config;
    this.db = db;

    this.events = events;
    this.subscriptions = {};

    this.nextJobCommand = plans.fetchNextJob(config.schema);
    this.insertJobCommand = plans.insertJob(config.schema);
    this.completeJobsCommand = plans.completeJobs(config.schema);
    this.cancelJobsCommand = plans.cancelJobs(config.schema);
    this.failJobsCommand = plans.failJobs(config.schema);
    this.deleteQueueCommand = plans.deleteQueue(config.schema);
    this.deleteAllQueuesCommand = plans.deleteAllQueues(config.schema);

    // exported api to index
    this.functions = [
      this.fetch,
      this.complete,
      this.cancel,
      this.fail,
      this.publish,
      this.subscribe,
      this.unsubscribe,
      this.onComplete,
      this.offComplete,
      this.fetchCompleted,
      this.publishDebounced,
      this.publishThrottled,
      this.publishOnce,
      this.publishAfter,
      this.deleteQueue,
      this.deleteAllQueues
    ];
  }

  stop() {
    Object.keys(this.subscriptions).forEach(name => this.unsubscribe(name));
    this.subscriptions = {};
    return Promise.resolve(true);
  }

  subscribe(name, ...args){
    return Attorney.checkSubscribeArgs(name, args)
      .then(({options, callback}) => this.watch(name, options, callback));
  }

  onComplete(name, ...args) {
    return Attorney.checkSubscribeArgs(name, args)
      .then(({options, callback}) => this.watch(completedJobPrefix + name, options, callback));
  }

  watch(name, options, callback){
    // watch() is always nested in a promise, so assert()s are welcome

    if('newJobCheckInterval' in options || 'newJobCheckIntervalSeconds' in options)
      options = Attorney.applyNewJobCheckInterval(options);
    else
      options.newJobCheckInterval = this.config.newJobCheckInterval;

    if('teamConcurrency' in options){
      const teamConcurrencyErrorMessage = 'teamConcurrency must be an integer between 1 and 1000';
      assert(Number.isInteger(options.teamConcurrency) && options.teamConcurrency >= 1 && options.teamConcurrency <= 1000, teamConcurrencyErrorMessage);
    }

    if('teamSize' in options){
      const teamSizeErrorMessage = 'teamSize must be an integer > 0';
      assert(Number.isInteger(options.teamSize) && options.teamSize >= 1, teamSizeErrorMessage);
    }

    if('batchSize' in options) {
      const batchSizeErrorMessage = 'batchSize must be an integer > 0';
      assert(Number.isInteger(options.batchSize) && options.batchSize >= 1, batchSizeErrorMessage);
    }

    let sendItBruh = (jobs) => {
        if (!jobs)
          return Promise.resolve();

        // If you get a batch, for now you should use complete() so you can control
        //   whether individual or group completion responses apply to your use case
        // Failing will fail all fetched jobs
        if(options.batchSize)
          return Promise.all([callback(jobs)]).catch(err => this.fail(jobs.map(job => job.id), err));

        // either no option was set, or teamSize was used
        return Promise.map(jobs, job => {
          return callback(job).then(value => this.complete(job.id, value)).catch(err => this.fail(job.id, err))
        }, {concurrency: options.teamConcurrency || 2});
    };

    let onError = error => this.emit(events.error, error);

    let workerConfig = {
      name,
      fetch: () => this.fetch(name, options.batchSize || options.teamSize || 1),
      onFetch: jobs => sendItBruh(jobs).catch(err => null), // just send it, bruh
      onError,
      interval: options.newJobCheckInterval
    };

    let worker = new Worker(workerConfig);
    worker.start();

    if(!this.subscriptions[name])
      this.subscriptions[name] = { workers: [] };

    this.subscriptions[name].workers.push(worker);

    return Promise.resolve(true);
  }

  unsubscribe(name){
    if(!this.subscriptions[name]) return Promise.reject(`No subscriptions for ${name} were found.`);

    this.subscriptions[name].workers.forEach(worker => worker.stop());
    delete this.subscriptions[name];

    return Promise.resolve(true);
  }

  offComplete(name){
    return this.unsubscribe(completedJobPrefix + name);
  }

  publish(...args){
    return Attorney.checkPublishArgs(args)
      .then(({name, data, options}) => this.createJob(name, data, options));
  }

  publishOnce(name, data, options, key) {
    return Attorney.checkPublishArgs([name, data, options])
      .then(({name, data, options}) => {

        options.singletonKey = key;

        return this.createJob(name, data, options);
      });
  }

  publishAfter(name, data, options, after) {
    return Attorney.checkPublishArgs([name, data, options])
      .then(({name, data, options}) => {

        options.startAfter = after;

        return this.createJob(name, data, options);
      });
  }

  publishThrottled(name, data, options, seconds, key) {
    return Attorney.checkPublishArgs([name, data, options])
      .then(({name, data, options}) => {

        options.singletonSeconds = seconds;
        options.singletonNextSlot = false;
        options.singletonKey = key;

        return this.createJob(name, data, options);
      });
  }

  publishDebounced(name, data, options, seconds, key) {
    return Attorney.checkPublishArgs([name, data, options])
      .then(({name, data, options}) => {

        options.singletonSeconds = seconds;
        options.singletonNextSlot = true;
        options.singletonKey = key;

        return this.createJob(name, data, options);
      });
  }

  createJob(name, data, options, singletonOffset){

    let startAfter = options.startAfter;

    startAfter = (startAfter instanceof Date && typeof startAfter.toISOString === 'function') ? startAfter.toISOString()
      : (startAfter > 0) ? '' + startAfter
      : (typeof startAfter === 'string') ? startAfter
      : null;

    if('retryDelay' in options)
        assert(Number.isInteger(options.retryDelay) && options.retryDelay >= 0, 'retryDelay must be an integer >= 0');

    if('retryBackoff' in options)
      assert(options.retryBackoff === true || options.retryBackoff === false, 'retryBackoff must be either true or false');

    if('retryLimit' in options)
      assert(Number.isInteger(options.retryLimit) && options.retryLimit >= 0, 'retryLimit must be an integer >= 0');

    let retryLimit = options.retryLimit || 0;
    let retryBackoff = !!options.retryBackoff;
    let retryDelay = options.retryDelay || 0;

    if(retryBackoff && !retryDelay)
      retryDelay = 1;

    if(retryDelay && !retryLimit)
      retryLimit = 1;

    let expireIn = options.expireIn || '15 minutes';
    let priority = options.priority || 0;

    let singletonSeconds =
      (options.singletonSeconds > 0) ? options.singletonSeconds
        : (options.singletonMinutes > 0) ? options.singletonMinutes * 60
        : (options.singletonHours > 0) ? options.singletonHours * 60 * 60
          : null;

    let singletonKey = options.singletonKey || null;

    singletonOffset = singletonOffset || 0;

    let id = require(`uuid/${this.config.uuid}`)();

    // ordinals! [1,  2,    3,        4,          5,          6,        7,    8,            9,                10,              11,         12          ]
    let values = [id, name, priority, retryLimit, startAfter, expireIn, data, singletonKey, singletonSeconds, singletonOffset, retryDelay, retryBackoff];

    return this.db.executeSql(this.insertJobCommand, values)
      .then(result => {
        if(result.rowCount === 1)
          return result.rows[0].id;

        if(!options.singletonNextSlot)
          return null;

        // delay starting by the offset to honor throttling config
        options.startAfter = singletonSeconds;
        // toggle off next slot config for round 2
        options.singletonNextSlot = false;

        let singletonOffset = singletonSeconds;

        return this.createJob(name, data, options, singletonOffset);
      });
  }

  fetch(name, batchSize) {
    return Attorney.checkFetchArgs(name, batchSize)
      .then(values => this.db.executeSql(this.nextJobCommand, [values.name, values.batchSize || 1]))
      .then(result => {

        const jobs = result.rows.map(job => {
          job.done = (error, response) => error ? this.fail(job.id, error) : this.complete(job.id, response);
          return job;
        });

        return jobs.length === 0 ? null :
               jobs.length === 1 && !batchSize ? jobs[0] :
               jobs;
      });
  }

  fetchCompleted(name, batchSize){
    return this.fetch(completedJobPrefix + name, batchSize);
  }

  mapCompletionIdArg(id, funcName) {
    const errorMessage = `${funcName}() requires an id`;

    return Attorney.assertAsync(id, errorMessage)
      .then(() => {
        let ids = Array.isArray(id) ? id : [id];
        assert(ids.length, errorMessage);
        return ids;
      });
  }

  mapCompletionDataArg(data) {
    if(data === null || typeof data === 'undefined' || typeof data === 'function')
      return null;

    if(data instanceof Error)
      data = JSON.parse(JSON.stringify(data, Object.getOwnPropertyNames(data)));

    return (typeof data === 'object' && !Array.isArray(data))
      ? data
      : { value:data };
  }

  mapCompletionResponse(ids, result) {
    return {
      jobs: ids,
      requested: ids.length,
      updated: result.rowCount
    };
  }

  complete(id, data){
    return this.mapCompletionIdArg(id, 'complete')
      .then(ids => this.db.executeSql(this.completeJobsCommand, [ids, this.mapCompletionDataArg(data)])
                    .then(result => this.mapCompletionResponse(ids, result))
      );
  }

  fail(id, data){
    return this.mapCompletionIdArg(id, 'fail')
      .then(ids => this.db.executeSql(this.failJobsCommand, [ids, this.mapCompletionDataArg(data)])
        .then(result => this.mapCompletionResponse(ids, result))
      );
  }

  cancel(id) {
    return this.mapCompletionIdArg(id, 'cancel')
      .then(ids => this.db.executeSql(this.cancelJobsCommand, [ids])
        .then(result => this.mapCompletionResponse(ids, result))
      );
  }

  deleteQueue(queue){
    return this.db.executeSql(this.deleteQueueCommand, [queue]);
  }

  deleteAllQueues(){
    return this.db.executeSql(this.deleteAllQueuesCommand);
  }

}

module.exports = Manager;
<commit_msg>setting default concurrency to 1<commit_after>const assert = require('assert');
const EventEmitter = require('events');
const Promise = require('bluebird');

const Worker = require('./worker');
const plans = require('./plans');
const Attorney = require('./attorney');

const completedJobPrefix = plans.completedJobPrefix;

const events = {
  error: 'error'
};

class Manager extends EventEmitter {
  constructor(db, config){
    super();

    this.config = config;
    this.db = db;

    this.events = events;
    this.subscriptions = {};

    this.nextJobCommand = plans.fetchNextJob(config.schema);
    this.insertJobCommand = plans.insertJob(config.schema);
    this.completeJobsCommand = plans.completeJobs(config.schema);
    this.cancelJobsCommand = plans.cancelJobs(config.schema);
    this.failJobsCommand = plans.failJobs(config.schema);
    this.deleteQueueCommand = plans.deleteQueue(config.schema);
    this.deleteAllQueuesCommand = plans.deleteAllQueues(config.schema);

    // exported api to index
    this.functions = [
      this.fetch,
      this.complete,
      this.cancel,
      this.fail,
      this.publish,
      this.subscribe,
      this.unsubscribe,
      this.onComplete,
      this.offComplete,
      this.fetchCompleted,
      this.publishDebounced,
      this.publishThrottled,
      this.publishOnce,
      this.publishAfter,
      this.deleteQueue,
      this.deleteAllQueues
    ];
  }

  stop() {
    Object.keys(this.subscriptions).forEach(name => this.unsubscribe(name));
    this.subscriptions = {};
    return Promise.resolve(true);
  }

  subscribe(name, ...args){
    return Attorney.checkSubscribeArgs(name, args)
      .then(({options, callback}) => this.watch(name, options, callback));
  }

  onComplete(name, ...args) {
    return Attorney.checkSubscribeArgs(name, args)
      .then(({options, callback}) => this.watch(completedJobPrefix + name, options, callback));
  }

  watch(name, options, callback){
    // watch() is always nested in a promise, so assert()s are welcome

    if('newJobCheckInterval' in options || 'newJobCheckIntervalSeconds' in options)
      options = Attorney.applyNewJobCheckInterval(options);
    else
      options.newJobCheckInterval = this.config.newJobCheckInterval;

    if('teamConcurrency' in options){
      const teamConcurrencyErrorMessage = 'teamConcurrency must be an integer between 1 and 1000';
      assert(Number.isInteger(options.teamConcurrency) && options.teamConcurrency >= 1 && options.teamConcurrency <= 1000, teamConcurrencyErrorMessage);
    }

    if('teamSize' in options){
      const teamSizeErrorMessage = 'teamSize must be an integer > 0';
      assert(Number.isInteger(options.teamSize) && options.teamSize >= 1, teamSizeErrorMessage);
    }

    if('batchSize' in options) {
      const batchSizeErrorMessage = 'batchSize must be an integer > 0';
      assert(Number.isInteger(options.batchSize) && options.batchSize >= 1, batchSizeErrorMessage);
    }

    let sendItBruh = (jobs) => {
        if (!jobs)
          return Promise.resolve();

        // If you get a batch, for now you should use complete() so you can control
        //   whether individual or group completion responses apply to your use case
        // Failing will fail all fetched jobs
        if(options.batchSize)
          return Promise.all([callback(jobs)]).catch(err => this.fail(jobs.map(job => job.id), err));

        // either no option was set, or teamSize was used
        return Promise.map(jobs, job => {
          return callback(job).then(value => this.complete(job.id, value)).catch(err => this.fail(job.id, err))
        }, {concurrency: options.teamConcurrency || 1});
    };

    let onError = error => this.emit(events.error, error);

    let workerConfig = {
      name,
      fetch: () => this.fetch(name, options.batchSize || options.teamSize || 1),
      onFetch: jobs => sendItBruh(jobs).catch(err => null), // just send it, bruh
      onError,
      interval: options.newJobCheckInterval
    };

    let worker = new Worker(workerConfig);
    worker.start();

    if(!this.subscriptions[name])
      this.subscriptions[name] = { workers: [] };

    this.subscriptions[name].workers.push(worker);

    return Promise.resolve(true);
  }

  unsubscribe(name){
    if(!this.subscriptions[name]) return Promise.reject(`No subscriptions for ${name} were found.`);

    this.subscriptions[name].workers.forEach(worker => worker.stop());
    delete this.subscriptions[name];

    return Promise.resolve(true);
  }

  offComplete(name){
    return this.unsubscribe(completedJobPrefix + name);
  }

  publish(...args){
    return Attorney.checkPublishArgs(args)
      .then(({name, data, options}) => this.createJob(name, data, options));
  }

  publishOnce(name, data, options, key) {
    return Attorney.checkPublishArgs([name, data, options])
      .then(({name, data, options}) => {

        options.singletonKey = key;

        return this.createJob(name, data, options);
      });
  }

  publishAfter(name, data, options, after) {
    return Attorney.checkPublishArgs([name, data, options])
      .then(({name, data, options}) => {

        options.startAfter = after;

        return this.createJob(name, data, options);
      });
  }

  publishThrottled(name, data, options, seconds, key) {
    return Attorney.checkPublishArgs([name, data, options])
      .then(({name, data, options}) => {

        options.singletonSeconds = seconds;
        options.singletonNextSlot = false;
        options.singletonKey = key;

        return this.createJob(name, data, options);
      });
  }

  publishDebounced(name, data, options, seconds, key) {
    return Attorney.checkPublishArgs([name, data, options])
      .then(({name, data, options}) => {

        options.singletonSeconds = seconds;
        options.singletonNextSlot = true;
        options.singletonKey = key;

        return this.createJob(name, data, options);
      });
  }

  createJob(name, data, options, singletonOffset){

    let startAfter = options.startAfter;

    startAfter = (startAfter instanceof Date && typeof startAfter.toISOString === 'function') ? startAfter.toISOString()
      : (startAfter > 0) ? '' + startAfter
      : (typeof startAfter === 'string') ? startAfter
      : null;

    if('retryDelay' in options)
        assert(Number.isInteger(options.retryDelay) && options.retryDelay >= 0, 'retryDelay must be an integer >= 0');

    if('retryBackoff' in options)
      assert(options.retryBackoff === true || options.retryBackoff === false, 'retryBackoff must be either true or false');

    if('retryLimit' in options)
      assert(Number.isInteger(options.retryLimit) && options.retryLimit >= 0, 'retryLimit must be an integer >= 0');

    let retryLimit = options.retryLimit || 0;
    let retryBackoff = !!options.retryBackoff;
    let retryDelay = options.retryDelay || 0;

    if(retryBackoff && !retryDelay)
      retryDelay = 1;

    if(retryDelay && !retryLimit)
      retryLimit = 1;

    let expireIn = options.expireIn || '15 minutes';
    let priority = options.priority || 0;

    let singletonSeconds =
      (options.singletonSeconds > 0) ? options.singletonSeconds
        : (options.singletonMinutes > 0) ? options.singletonMinutes * 60
        : (options.singletonHours > 0) ? options.singletonHours * 60 * 60
          : null;

    let singletonKey = options.singletonKey || null;

    singletonOffset = singletonOffset || 0;

    let id = require(`uuid/${this.config.uuid}`)();

    // ordinals! [1,  2,    3,        4,          5,          6,        7,    8,            9,                10,              11,         12          ]
    let values = [id, name, priority, retryLimit, startAfter, expireIn, data, singletonKey, singletonSeconds, singletonOffset, retryDelay, retryBackoff];

    return this.db.executeSql(this.insertJobCommand, values)
      .then(result => {
        if(result.rowCount === 1)
          return result.rows[0].id;

        if(!options.singletonNextSlot)
          return null;

        // delay starting by the offset to honor throttling config
        options.startAfter = singletonSeconds;
        // toggle off next slot config for round 2
        options.singletonNextSlot = false;

        let singletonOffset = singletonSeconds;

        return this.createJob(name, data, options, singletonOffset);
      });
  }

  fetch(name, batchSize) {
    return Attorney.checkFetchArgs(name, batchSize)
      .then(values => this.db.executeSql(this.nextJobCommand, [values.name, values.batchSize || 1]))
      .then(result => {

        const jobs = result.rows.map(job => {
          job.done = (error, response) => error ? this.fail(job.id, error) : this.complete(job.id, response);
          return job;
        });

        return jobs.length === 0 ? null :
               jobs.length === 1 && !batchSize ? jobs[0] :
               jobs;
      });
  }

  fetchCompleted(name, batchSize){
    return this.fetch(completedJobPrefix + name, batchSize);
  }

  mapCompletionIdArg(id, funcName) {
    const errorMessage = `${funcName}() requires an id`;

    return Attorney.assertAsync(id, errorMessage)
      .then(() => {
        let ids = Array.isArray(id) ? id : [id];
        assert(ids.length, errorMessage);
        return ids;
      });
  }

  mapCompletionDataArg(data) {
    if(data === null || typeof data === 'undefined' || typeof data === 'function')
      return null;

    if(data instanceof Error)
      data = JSON.parse(JSON.stringify(data, Object.getOwnPropertyNames(data)));

    return (typeof data === 'object' && !Array.isArray(data))
      ? data
      : { value:data };
  }

  mapCompletionResponse(ids, result) {
    return {
      jobs: ids,
      requested: ids.length,
      updated: result.rowCount
    };
  }

  complete(id, data){
    return this.mapCompletionIdArg(id, 'complete')
      .then(ids => this.db.executeSql(this.completeJobsCommand, [ids, this.mapCompletionDataArg(data)])
                    .then(result => this.mapCompletionResponse(ids, result))
      );
  }

  fail(id, data){
    return this.mapCompletionIdArg(id, 'fail')
      .then(ids => this.db.executeSql(this.failJobsCommand, [ids, this.mapCompletionDataArg(data)])
        .then(result => this.mapCompletionResponse(ids, result))
      );
  }

  cancel(id) {
    return this.mapCompletionIdArg(id, 'cancel')
      .then(ids => this.db.executeSql(this.cancelJobsCommand, [ids])
        .then(result => this.mapCompletionResponse(ids, result))
      );
  }

  deleteQueue(queue){
    return this.db.executeSql(this.deleteQueueCommand, [queue]);
  }

  deleteAllQueues(){
    return this.db.executeSql(this.deleteAllQueuesCommand);
  }

}

module.exports = Manager;
<|endoftext|>


<commit_before>
import { expect } from 'chai';

import runner from '../../../../client/scripts/arcademode/runner';

import RosettaChallenges from '../../../../public/json/challenges-rosetta.json';

const challenges = RosettaChallenges.challenges;

const userOutputUndef = 'User output is undefined.';

challenges.forEach(challenge => {
  describe(`RosettaChallenge ${challenge.title}`, () => {
    it(`runner can execute ${challenge.title} challenge properly`, () => {
      const userCode = challenge.solutions.join('');
      const result = runner(userCode, challenge);
      expect(challenge.tests).to.have.property('length');

      return result.then(res => {
        expect(res.userOutput).to.equal(userOutputUndef);
        expectNoErrorsAndAllTestsRun(res, challenge);
      })
      .catch(err => {
        throw new Error(err);
      });
    });
  });
});

// HELPER FUNCTIONS
function expectNoErrorsAndAllTestsRun(res, challenge) {
  expect(res.errorMsgs).to.have.length(0);
  expect(res.testResults).to.have.length(challenge.tests.length);
}
<commit_msg>Added a test to check that test fails without proper solution. Added checks that each result.pass is true for correct solution.<commit_after>
import { expect } from 'chai';

import runner from '../../../../client/scripts/arcademode/runner';

import RosettaChallenges from '../../../../public/json/challenges-rosetta.json';

const challenges = RosettaChallenges.challenges;

const userOutputUndef = 'User output is undefined.';

describe('Rosetta challenges', () => {
  it('fails to pass at least one test without solution', () => {
    const challenge = challenges.find(item => item.title.match(/Zig/));
    const userCode = challenge.challengeSeed.join('\n');
    const result = runner(userCode, challenge);

    return result.then(res => {
      expect(res.errorMsgs).to.have.length(0);
      expect(res.testResults).to.have.length(challenge.tests.length);

      let atLeastOneTestFailed = false;
      res.testResults.forEach(tr => {
        atLeastOneTestFailed = atLeastOneTestFailed || tr.pass === false;
      });
      expect(atLeastOneTestFailed).to.equal(true);
    });
  });
});

challenges.forEach(challenge => {
  describe(`RosettaChallenge ${challenge.title}`, () => {
    it(`runner can execute ${challenge.title} challenge properly`, () => {
      const userCode = challenge.solutions.join('');
      expect(challenge.tests).to.have.property('length');
      const result = runner(userCode, challenge);

      return result.then(res => {
        expect(res.userOutput).to.equal(userOutputUndef);
        expectNoErrorsAndAllTestsRun(res, challenge);
      });
    });
  });
});

// HELPER FUNCTIONS
function expectNoErrorsAndAllTestsRun(res, challenge) {
  expect(res.errorMsgs).to.have.length(0);
  expect(res.testResults).to.have.length(challenge.tests.length);
  res.testResults.forEach(result => {
    expect(result.pass).to.be.true;
  });
}
<|endoftext|>


<commit_before>const urls = require('../../../../../src/lib/urls')
const fixtures = require('../../fixtures')
const formSelectors = require('../../../../selectors/pipeline-form')

const tabPanelSelector = '[data-auto-id="pipelineSubTabNav"] [role="tabpanel"]'

function addAssertion(assertion) {
  return (content) => ({
    assertion,
    content,
  })
}

function assertTabListItem({ contain = [], notContain = [] }) {
  const checks = [
    ...contain.map(addAssertion('contain')),
    ...notContain.map(addAssertion('not.contain')),
  ]

  checks.reduce(
    ($tab, { assertion, content }) => $tab.should(assertion, content),
    cy.get(tabPanelSelector).find('ol > li').should('have.length', 1)
  )
}

describe('My Pipeline tab on the dashboard', () => {
  context('When there are no companies on the pipeline', () => {
    it('Should show an empty pipeline message for each status', () => {
      const tabs = [
        {
          url: urls.pipeline.index(),
        },
        {
          url: urls.pipeline.active(),
        },
        {
          url: urls.pipeline.won(),
        },
      ]

      tabs.forEach((tab) => {
        cy.visit(tab.url)
        cy.contains(
          'My pipeline allows you to track the progress of your projects.'
        )
          .contains('a', 'help centre article')
          .should('have.attr', 'href', urls.external.helpCentre.pipeline())
      })
    })
  })

  context('Adding a company as a lead', () => {
    const projectName = 'Test add project'
    const company = fixtures.company.create.lambda('pipeline testing')

    before(() => {
      cy.loadFixture([company])
    })

    it('Should add the company and return to the my pipeline tab', () => {
      cy.visit(urls.companies.pipelineAdd(company.pk))

      cy.get(formSelectors.name).type(projectName)
      cy.get(formSelectors.status.prospect).click()
      cy.contains('button', 'Create project').click()

      cy.url().should('include', urls.pipeline.index())
      assertTabListItem({ contain: [projectName] })
    })

    context('Edit a company on the pipeline', () => {
      context('Change the status from Prospect to Active', () => {
        it('Should change the status and go to the active tab', () => {
          cy.visit(urls.pipeline.index())

          cy.get(tabPanelSelector).contains('a', 'Edit').click()

          cy.get(formSelectors.status.active).click()
          cy.contains('button', 'Save').click()

          cy.url().should('include', urls.pipeline.active())
          assertTabListItem({ contain: [projectName] })
        })
      })

      context(
        'Change the status from Active to Won and edit the project name',
        () => {
          it('Should change the values and go to the won tab', () => {
            const newProjectName = projectName + ' edited'

            cy.visit(urls.pipeline.active())

            cy.get(tabPanelSelector).contains('a', 'Edit').click()

            cy.get(formSelectors.name).clear().type(newProjectName)
            cy.get(formSelectors.status.won).click()
            cy.contains('button', 'Save').click()

            cy.url().should('include', urls.pipeline.won())
            assertTabListItem({ contain: [newProjectName] })
          })
        }
      )

      context('Add the optional fields', () => {
        it('should save the values and go to the won tab', () => {
          cy.visit(urls.pipeline.won())

          cy.get(tabPanelSelector).contains('a', 'Edit').click()

          cy.get(formSelectors.likelihood.low).click()
          cy.get(formSelectors.fields.sector).selectTypeaheadOption('Aero')
          cy.get(formSelectors.fields.contacts).selectTypeaheadOption('Dean')
          cy.get(formSelectors.value).type('1000')
          cy.get(formSelectors.fields.expectedWinDate)
            .find('input')
            .then((element) => {
              cy.wrap(element[0]).type('06')
              cy.wrap(element[1]).type('2025')
            })

          cy.contains('button', 'Save').click()

          cy.url().should('include', urls.pipeline.won())
          assertTabListItem({
            contain: [
              projectName + ' edited',
              'Export sectorAerospace',
              'Company contactDean Cox',
              'Potential export value£1,000',
              'Expected date for winJun 2025',
            ],
          })
        })
      })

      context('Edit optional fields to remove the values', () => {
        it('Should remove the values and go to the won tab', () => {
          cy.visit(urls.pipeline.won())

          cy.get(tabPanelSelector).contains('a', 'Edit').click()

          cy.get(formSelectors.fields.sector).removeAllTypeaheadValues()
          cy.get(formSelectors.fields.contacts).removeAllTypeaheadValues()
          cy.get(formSelectors.value).clear()
          cy.get(formSelectors.fields.expectedWinDate).find('input').clear()

          cy.contains('button', 'Save').click()

          cy.url().should('include', urls.pipeline.won())
          assertTabListItem({
            contain: [projectName + ' edited', 'LOW'],
            notContain: [
              'Export sector',
              'Company contact',
              'Potential export value',
              'Expected date for win',
            ],
          })
        })
      })
    })
  })
})
<commit_msg>Create contact for pipeline test<commit_after>const urls = require('../../../../../src/lib/urls')
const fixtures = require('../../fixtures')
const formSelectors = require('../../../../selectors/pipeline-form')

const tabPanelSelector = '[data-auto-id="pipelineSubTabNav"] [role="tabpanel"]'

function addAssertion(assertion) {
  return (content) => ({
    assertion,
    content,
  })
}

function assertTabListItem({ contain = [], notContain = [] }) {
  const checks = [
    ...contain.map(addAssertion('contain')),
    ...notContain.map(addAssertion('not.contain')),
  ]

  checks.reduce(
    ($tab, { assertion, content }) => $tab.should(assertion, content),
    cy.get(tabPanelSelector).find('ol > li').should('have.length', 1)
  )
}

describe('My Pipeline tab on the dashboard', () => {
  context('When there are no companies on the pipeline', () => {
    it('Should show an empty pipeline message for each status', () => {
      const tabs = [
        {
          url: urls.pipeline.index(),
        },
        {
          url: urls.pipeline.active(),
        },
        {
          url: urls.pipeline.won(),
        },
      ]

      tabs.forEach((tab) => {
        cy.visit(tab.url)
        cy.contains(
          'My pipeline allows you to track the progress of your projects.'
        )
          .contains('a', 'help centre article')
          .should('have.attr', 'href', urls.external.helpCentre.pipeline())
      })
    })
  })

  context('Adding a company as a lead', () => {
    const projectName = 'Test add project'
    const company = fixtures.company.create.lambda('pipeline testing')
    const contact = fixtures.contact.create(company.pk)

    before(() => {
      cy.loadFixture([company])
      cy.loadFixture([contact])
    })

    it('Should add the company and return to the my pipeline tab', () => {
      cy.visit(urls.companies.pipelineAdd(company.pk))

      cy.get(formSelectors.name).type(projectName)
      cy.get(formSelectors.status.prospect).click()
      cy.contains('button', 'Create project').click()

      cy.url().should('include', urls.pipeline.index())
      assertTabListItem({ contain: [projectName] })
    })

    context('Edit a company on the pipeline', () => {
      context('Change the status from Prospect to Active', () => {
        it('Should change the status and go to the active tab', () => {
          cy.visit(urls.pipeline.index())

          cy.get(tabPanelSelector).contains('a', 'Edit').click()

          cy.get(formSelectors.status.active).click()
          cy.contains('button', 'Save').click()

          cy.url().should('include', urls.pipeline.active())
          assertTabListItem({ contain: [projectName] })
        })
      })

      context(
        'Change the status from Active to Won and edit the project name',
        () => {
          it('Should change the values and go to the won tab', () => {
            const newProjectName = projectName + ' edited'

            cy.visit(urls.pipeline.active())

            cy.get(tabPanelSelector).contains('a', 'Edit').click()

            cy.get(formSelectors.name).clear().type(newProjectName)
            cy.get(formSelectors.status.won).click()
            cy.contains('button', 'Save').click()

            cy.url().should('include', urls.pipeline.won())
            assertTabListItem({ contain: [newProjectName] })
          })
        }
      )

      context('Add the optional fields', () => {
        it('should save the values and go to the won tab', () => {
          cy.visit(urls.pipeline.won())

          cy.get(tabPanelSelector).contains('a', 'Edit').click()

          cy.get(formSelectors.likelihood.low).click()
          cy.get(formSelectors.fields.sector).selectTypeaheadOption('Aero')
          cy.get(formSelectors.fields.contacts).selectTypeaheadOption('Johnny')
          cy.get(formSelectors.value).type('1000')
          cy.get(formSelectors.fields.expectedWinDate)
            .find('input')
            .then((element) => {
              cy.wrap(element[0]).type('06')
              cy.wrap(element[1]).type('2025')
            })

          cy.contains('button', 'Save').click()

          cy.url().should('include', urls.pipeline.won())
          assertTabListItem({
            contain: [
              projectName + ' edited',
              'Export sectorAerospace',
              'Company contactDean Cox',
              'Potential export value£1,000',
              'Expected date for winJun 2025',
            ],
          })
        })
      })

      context('Edit optional fields to remove the values', () => {
        it('Should remove the values and go to the won tab', () => {
          cy.visit(urls.pipeline.won())

          cy.get(tabPanelSelector).contains('a', 'Edit').click()

          cy.get(formSelectors.fields.sector).removeAllTypeaheadValues()
          cy.get(formSelectors.fields.contacts).removeAllTypeaheadValues()
          cy.get(formSelectors.value).clear()
          cy.get(formSelectors.fields.expectedWinDate).find('input').clear()

          cy.contains('button', 'Save').click()

          cy.url().should('include', urls.pipeline.won())
          assertTabListItem({
            contain: [projectName + ' edited', 'LOW'],
            notContain: [
              'Export sector',
              'Company contact',
              'Potential export value',
              'Expected date for win',
            ],
          })
        })
      })
    })
  })
})
<|endoftext|>


<commit_before>const assert = require('assert');
const EventEmitter = require('events');
const Db = require('./db');
const uuid = require('node-uuid');
const Worker = require('./worker');

const nextJobCommand = `
  WITH nextJob as (
      SELECT id
      FROM pgboss.job
      WHERE (state = 'created' OR (state = 'expired' AND retryCount <= retryLimit))
        AND name = $1
        AND (createdOn + startIn) < now()
        AND completedOn IS NULL
      ORDER BY createdOn, id
      LIMIT 1
      FOR UPDATE SKIP LOCKED
    )
    UPDATE pgboss.job SET
      state = 'active',
      startedOn = now(),
      retryCount = CASE WHEN state = 'expired' THEN retryCount + 1 ELSE retryCount END
    FROM nextJob
    WHERE pgboss.job.id = nextJob.id
    RETURNING pgboss.job.id, pgboss.job.data
`;

class Manager extends EventEmitter {
    constructor(config){
        super();

        config = config || {};

        config.uuid = config.uuid || 'v1';
        assert(config.uuid == 'v1' || config.uuid == 'v4', "uuid option only supports v1 or v4");

        this.workerInterval = (config.newJobCheckIntervalSeconds || 1) * 1000;

        this.config = config;
        this.monitorInterval = 1000 * 60;
        this.workers = [];
    }

    monitor(){
        var self = this;

        setImmediate(timeoutOldJobs);
        setInterval(timeoutOldJobs, self.monitorInterval);

        function timeoutOldJobs(){

            const timeoutCommand = `
                UPDATE pgboss.job
                SET state = 'expired',
                    expiredOn = now()
                WHERE state = 'active'
                AND (startedOn + expireIn) > now()
            `;

            let db = new Db(self.config);

            return db.executeSql(timeoutCommand)
                .catch(error => self.emit('error', error));
        }
    }

    registerJob(name, config, callback){

        assert(name, 'boss requires all jobs to have a name');

        config = config || {};
        assert(typeof config == 'object', 'expected config to be an object');

        config.teamSize = config.teamSize || 1;

        let db = new Db(this.config);

        let jobFetcher = () =>
            db.executeSql(nextJobCommand, name)
                .then(result => result.rows.length ? result.rows[0] : null);

        let workerConfig = {name: name, fetcher: jobFetcher, interval: this.workerInterval};

        for(let w=0;w<config.teamSize; w++){

            let worker = new Worker(workerConfig);

            worker.on('error', error => this.emit('error', error));

            worker.on(name, job => {
                this.emit('job', {name, id: job.id});

                callback(job, () => this.closeJob(job.id));
            });

            this.workers.push(worker);
        }
    }

    submitJob(name, data, options){

        options = options || {};

        // convert numeric to seconds
        options.startIn =
            (options.startIn > 0) ? options.startIn = '' + options.startIn
            : (typeof options.startIn == 'string') ? options.startIn
            : '0';

        let id = uuid[this.config.uuid](),
            state = 'created',
            retryLimit = options.retryLimit || 0,
            startIn = options.startIn || '0',
            expireIn = options.expireIn || '15 minutes';

        const newJobcommand = `
            INSERT INTO pgboss.job (id, name, state, retryLimit, startIn, expireIn, data)
            VALUES ($1, $2, $3, $4, CAST($5 as interval), CAST($6 as interval), $7)`;

        let values = [id, name, state, retryLimit, startIn, expireIn, data];

        let db = new Db(this.config);

        return db.executeSql(newJobcommand, values)
            .then(() => id)
            .catch(error => this.emit('error', error));

    }

    closeJob(id){
        const closeJobCommand = `
            UPDATE pgboss.job
            SET completedOn = now(),
                state = 'complete'
            WHERE id = $1`;

        let values = [id];

        let db = new Db(this.config);

        return db.executeSql(closeJobCommand, values)
            .catch(error => this.emit('error', error));
    }
}

module.exports = Manager;<commit_msg>fixed job expire logic<commit_after>const assert = require('assert');
const EventEmitter = require('events');
const Db = require('./db');
const uuid = require('node-uuid');
const Worker = require('./worker');

const nextJobCommand = `
  WITH nextJob as (
      SELECT id
      FROM pgboss.job
      WHERE (state = 'created' OR (state = 'expired' AND retryCount <= retryLimit))
        AND name = $1
        AND (createdOn + startIn) < now()
        AND completedOn IS NULL
      ORDER BY createdOn, id
      LIMIT 1
      FOR UPDATE SKIP LOCKED
    )
    UPDATE pgboss.job SET
      state = 'active',
      startedOn = now(),
      retryCount = CASE WHEN state = 'expired' THEN retryCount + 1 ELSE retryCount END
    FROM nextJob
    WHERE pgboss.job.id = nextJob.id
    RETURNING pgboss.job.id, pgboss.job.data
`;

class Manager extends EventEmitter {
    constructor(config){
        super();

        config = config || {};

        config.uuid = config.uuid || 'v1';
        assert(config.uuid == 'v1' || config.uuid == 'v4', "uuid option only supports v1 or v4");

        this.workerInterval = (config.newJobCheckIntervalSeconds || 1) * 1000;

        this.config = config;
        this.monitorInterval = 1000 * 60;
        this.workers = [];
    }

    monitor(){
        var self = this;

        setImmediate(timeoutOldJobs);
        setInterval(timeoutOldJobs, self.monitorInterval);

        function timeoutOldJobs(){

            const timeoutCommand = `
                UPDATE pgboss.job
                SET state = 'expired',
                    expiredOn = now()
                WHERE state = 'active'
                AND (startedOn + expireIn) < now()
            `;

            let db = new Db(self.config);

            return db.executeSql(timeoutCommand)
                .catch(error => self.emit('error', error));
        }
    }

    registerJob(name, config, callback){

        assert(name, 'boss requires all jobs to have a name');

        config = config || {};
        assert(typeof config == 'object', 'expected config to be an object');

        config.teamSize = config.teamSize || 1;

        let db = new Db(this.config);

        let jobFetcher = () =>
            db.executeSql(nextJobCommand, name)
                .then(result => result.rows.length ? result.rows[0] : null);

        let workerConfig = {name: name, fetcher: jobFetcher, interval: this.workerInterval};

        for(let w=0;w<config.teamSize; w++){

            let worker = new Worker(workerConfig);

            worker.on('error', error => this.emit('error', error));

            worker.on(name, job => {
                this.emit('job', {name, id: job.id});

                callback(job, () => this.closeJob(job.id));
            });

            this.workers.push(worker);
        }
    }

    submitJob(name, data, options){

        options = options || {};

        // convert numeric to seconds
        options.startIn =
            (options.startIn > 0) ? options.startIn = '' + options.startIn
            : (typeof options.startIn == 'string') ? options.startIn
            : '0';

        let id = uuid[this.config.uuid](),
            state = 'created',
            retryLimit = options.retryLimit || 0,
            startIn = options.startIn || '0',
            expireIn = options.expireIn || '15 minutes';

        const newJobcommand = `
            INSERT INTO pgboss.job (id, name, state, retryLimit, startIn, expireIn, data)
            VALUES ($1, $2, $3, $4, CAST($5 as interval), CAST($6 as interval), $7)`;

        let values = [id, name, state, retryLimit, startIn, expireIn, data];

        let db = new Db(this.config);

        return db.executeSql(newJobcommand, values)
            .then(() => id)
            .catch(error => this.emit('error', error));

    }

    closeJob(id){
        const closeJobCommand = `
            UPDATE pgboss.job
            SET completedOn = now(),
                state = 'complete'
            WHERE id = $1`;

        let values = [id];

        let db = new Db(this.config);

        return db.executeSql(closeJobCommand, values)
            .catch(error => this.emit('error', error));
    }
}

module.exports = Manager;<|endoftext|>