<commit_before>// the eslint "no-unused-expressions" rule is triggered by the `expect`
// assertions in the tests. So, in order to avoid problems with eslint, I'm
// disabling that rule in this file
//
/* eslint-disable no-unused-expressions */

import React from 'react';
import { shallow, mount } from 'enzyme';
import chai from 'chai';
import sinon from 'sinon';

import Form from '../Form';

const expect = chai.expect;

/* eslint-disable no-unused-vars */
const debug = (component) => {
    console.info('------------------------------------------------------------');
    console.info(component.debug());
    console.info('------------------------------------------------------------');
};
/* eslint-enable no-unused-vars */

/* *****************************************************************************
a Form component containing a RadioButtonGroup
    should include a <Form.RadioButtonGroup> as a child
    should include a label if the label prop is set
    should not include a label if the label prop is not set
    should include a div.radio for each option
    should include a label for each option
    should include a span containing the option name for each option
    should include an input[type="radio"] for each option
    should select the first item if the value prop is not set
    should select the first item if the value prop is invalid
    should select the appropriate item if the value prop is set to a valid value

    should have a label.col-xs-3 when form is horizontal and labelColumns=3
    should have a div.col-xs-9 when form is horizontal and labelColumns=3

*/
describe('a Form component containing a RadioButtonGroup', () => {

    const options = [
        { value: '1', name: 'One' },
        { value: '2', name: 'Two' },
        { value: '3', name: 'Three' },
    ];

    it('should include a <Form.RadioButtonGroup> as a child', () => {
        const component = shallow(
            <Form>
                <Form.RadioButtonGroup options={options} />
            </Form>
        );

        expect(component.find(Form.RadioButtonGroup)).to.have.length(1);
    });

    it('should include a label if the label prop is set', () => {

        const label = 'fee fi fo fum';

        const component = mount(
            <Form>
                <Form.RadioButtonGroup label={label} options={options} />
            </Form>
        );

        expect(component.find('label.radiobuttongroup')).to.have.length(1);
        expect(component.find('label.radiobuttongroup').text()).to.equal(label);
    });

    it('should not include a label if the label prop is not set', () => {

        const component = mount(
            <Form>
                <Form.RadioButtonGroup options={options} />
            </Form>
        );

        expect(component.find('label.radiobuttongroup')).to.have.length(0);
    });

    it('should include a div.radio for each option', () => {

        const component = mount(
            <Form>
                <Form.RadioButtonGroup options={options} />
            </Form>
        );

        expect(component.find('div.radio')).to.have.length(options.length);
    });

    it('should include a label for each option', () => {

        const component = mount(
            <Form>
                <Form.RadioButtonGroup options={options} />
            </Form>
        );

        expect(component.find('div.radio label')).to.have.length(options.length);
    });

    it('should include a span containing the option name for each option', () => {

        const component = mount(
            <Form>
                <Form.RadioButtonGroup options={options} />
            </Form>
        );

        expect(component.find('div.radio span')).to.have.length(options.length);
        expect(component.find('div.radio span').at(0).text()).to.equal(options[0].name);
        expect(component.find('div.radio span').at(1).text()).to.equal(options[1].name);
        expect(component.find('div.radio span').at(2).text()).to.equal(options[2].name);
    });

    it('should include an input[type="radio"] for each option', () => {

        const component = mount(
            <Form>
                <Form.RadioButtonGroup options={options} />
            </Form>
        );

        expect(component.find('div.radio input[type="radio"]')).to.have.length(options.length);
        expect(component.find('div.radio input[type="radio"]').at(0).props().value)
            .to.equal(options[0].value);
        expect(component.find('div.radio input[type="radio"]').at(1).props().value)
            .to.equal(options[1].value);
        expect(component.find('div.radio input[type="radio"]').at(2).props().value)
            .to.equal(options[2].value);
    });

    it('should select the first item if the value prop is not set', () => {

        const label = 'peter piper';

        const component = mount(
            <Form>
                <Form.RadioButtonGroup label={label} options={options} />
            </Form>
        );

        expect(component.find('div.radio input[type="radio"]')).to.have.length(options.length);
        expect(component.find('div.radio input[type="radio"]').at(0).props().checked)
            .to.equal(true);
        expect(component.find('div.radio input[type="radio"]').at(1).props().checked)
            .to.equal(false);
        expect(component.find('div.radio input[type="radio"]').at(2).props().checked)
            .to.equal(false);
    });

    it('should select the first item if the value prop is invalid', () => {

        const label = 'peter piper';

        const value = 'not valid';

        const component = mount(
            <Form>
                <Form.RadioButtonGroup label={label} options={options} value={value} />
            </Form>
        );

        expect(component.find('div.radio input[type="radio"]')).to.have.length(options.length);
        expect(component.find('div.radio input[type="radio"]').at(0).props().checked)
            .to.equal(true);
        expect(component.find('div.radio input[type="radio"]').at(1).props().checked)
            .to.equal(false);
        expect(component.find('div.radio input[type="radio"]').at(2).props().checked)
            .to.equal(false);
    });

    it('should select the appropriate item if the value prop is set to a valid value', () => {

        const label = 'peter piper';

        const value = 2;

        const component = mount(
            <Form>
                <Form.RadioButtonGroup label={label} options={options} value={value} />
            </Form>
        );

        expect(component.find('div.radio input[type="radio"]')).to.have.length(options.length);
        expect(component.find('div.radio input[type="radio"]').at(0).props().checked)
            .to.equal(false);
        expect(component.find('div.radio input[type="radio"]').at(1).props().checked)
            .to.equal(true);
        expect(component.find('div.radio input[type="radio"]').at(2).props().checked)
            .to.equal(false);
    });

    it('should have a label.col-xs-3 when form is horizontal and labelColumns=3', () => {

        const label = 'peter piper';
        const columns = 3;

        const value = 2;

        const component = mount(
            <Form horizontal labelColumns={columns}>
                <Form.RadioButtonGroup label={label} options={options} value={value} />
            </Form>
        );

        expect(component.find('div.form-horizontal')).to.have.length(1, 'form-horizontal');
        expect(component.find('label.radiobuttongroup').props().className)
            .to.contain('col-xs-3', 'col-xs-3');
    });

    it('should have a div.col-xs-9 when form is horizontal and labelColumns=3', () => {

        const label = 'peter piper';
        const columns = 3;

        const value = 2;

        const component = mount(
            <Form horizontal labelColumns={columns}>
                <Form.RadioButtonGroup label={label} options={options} value={value} />
            </Form>
        );

        expect(component.find('div.form-horizontal')).to.have.length(1, 'form-horizontal');
        expect(component.find('div.form-radiobuttongroup-input-columns').props().className)
            .to.contain('col-xs-9', 'col-xs-9');
    });

});

/* *****************************************************************************
when a radio button in the group is clicked
    the onChange handler should be called if a new value is clicked
    the onChange handler should not be called if the same value is clicked
    the new value should be selected and all others should not be selected
*/
describe('when a radio button in the group is clicked', () => {

    const options = [
        { value: '1', name: 'One' },
        { value: '2', name: 'Two' },
        { value: '3', name: 'Three' },
    ];

    it('the onChange handler should be called if a new value is clicked', () => {

        const onChange = sinon.spy();

        const label = 'little miss muffet';

        const value = '1';

        const clickedItem = 2;

        const component = mount(
            <Form>
                <Form.RadioButtonGroup
                    label={label}
                    options={options}
                    value={value}
                    onChange={onChange}
                />
            </Form>
        );

        expect(onChange.callCount).to.equal(0);

        component.find('div.radio input[type="radio"]').at(1).simulate('change', {
            target: { value: options[clickedItem].value }
        });

        expect(onChange.callCount).to.equal(1);
        expect(onChange.calledWith(options[clickedItem].value)).to.equal(true);
    });

    it('the onChange handler should not be called if the same value is clicked', () => {

        const onChange = sinon.spy();

        const label = 'little miss muffet';

        const value = '1';

        const clickedItem = 0;

        const component = mount(
            <Form>
                <Form.RadioButtonGroup
                    label={label}
                    options={options}
                    value={value}
                    onChange={onChange}
                />
            </Form>
        );

        expect(onChange.callCount).to.equal(0);

        component.find('div.radio input[type="radio"]').at(1).simulate('change', {
            target: { value: options[clickedItem].value }
        });

        expect(onChange.callCount).to.equal(0);
    });

    it('the new value should be selected and all others should not be selected', () => {

        const label = 'little miss muffet';

        const value = '1';

        const clickedItem = 1;

        const component = mount(
            <Form>
                <Form.RadioButtonGroup label={label} options={options} value={value} />
            </Form>
        );

        expect(component.find('div.radio input[type="radio"]')).to.have.length(options.length);
        expect(component.find('div.radio input[type="radio"]').at(0).props().checked)
            .to.equal(true, 'before - 0');
        expect(component.find('div.radio input[type="radio"]').at(1).props().checked)
            .to.equal(false, 'before - 1');
        expect(component.find('div.radio input[type="radio"]').at(2).props().checked)
            .to.equal(false, 'before - 2');

        component.find('div.radio input[type="radio"]').at(1).simulate('change', {
            target: { value: options[clickedItem].value }
        });

        expect(component.find('div.radio input[type="radio"]').at(0).props().checked)
            .to.equal(false, 'after - 0');
        expect(component.find('div.radio input[type="radio"]').at(1).props().checked)
            .to.equal(true, 'after - 1');
        expect(component.find('div.radio input[type="radio"]').at(2).props().checked)
            .to.equal(false, 'after - 2');
    });

});
<commit_msg>corrected label class in specs<commit_after>// the eslint "no-unused-expressions" rule is triggered by the `expect`
// assertions in the tests. So, in order to avoid problems with eslint, I'm
// disabling that rule in this file
//
/* eslint-disable no-unused-expressions */

import React from 'react';
import { shallow, mount } from 'enzyme';
import chai from 'chai';
import sinon from 'sinon';

import Form from '../Form';

const expect = chai.expect;

/* eslint-disable no-unused-vars */
const debug = (component) => {
    console.info('------------------------------------------------------------');
    console.info(component.debug());
    console.info('------------------------------------------------------------');
};
/* eslint-enable no-unused-vars */

/* *****************************************************************************
a Form component containing a RadioButtonGroup
    should include a <Form.RadioButtonGroup> as a child
    should include a label if the label prop is set
    should not include a label if the label prop is not set
    should include a div.radio for each option
    should include a label for each option
    should include a span containing the option name for each option
    should include an input[type="radio"] for each option
    should select the first item if the value prop is not set
    should select the first item if the value prop is invalid
    should select the appropriate item if the value prop is set to a valid value

    should have a label.col-xs-3 when form is horizontal and labelColumns=3
    should have a div.col-xs-9 when form is horizontal and labelColumns=3

*/
describe('a Form component containing a RadioButtonGroup', () => {

    const options = [
        { value: '1', name: 'One' },
        { value: '2', name: 'Two' },
        { value: '3', name: 'Three' },
    ];

    it('should include a <Form.RadioButtonGroup> as a child', () => {
        const component = shallow(
            <Form>
                <Form.RadioButtonGroup options={options} />
            </Form>
        );

        expect(component.find(Form.RadioButtonGroup)).to.have.length(1);
    });

    it('should include a label if the label prop is set', () => {

        const label = 'fee fi fo fum';

        const component = mount(
            <Form>
                <Form.RadioButtonGroup label={label} options={options} />
            </Form>
        );

        expect(component.find('label.control-label')).to.have.length(1);
        expect(component.find('label.control-label').text()).to.equal(label);
    });

    it('should not include a label if the label prop is not set', () => {

        const component = mount(
            <Form>
                <Form.RadioButtonGroup options={options} />
            </Form>
        );

        expect(component.find('label.control-label')).to.have.length(0);
    });

    it('should include a div.radio for each option', () => {

        const component = mount(
            <Form>
                <Form.RadioButtonGroup options={options} />
            </Form>
        );

        expect(component.find('div.radio')).to.have.length(options.length);
    });

    it('should include a label for each option', () => {

        const component = mount(
            <Form>
                <Form.RadioButtonGroup options={options} />
            </Form>
        );

        expect(component.find('div.radio label')).to.have.length(options.length);
    });

    it('should include a span containing the option name for each option', () => {

        const component = mount(
            <Form>
                <Form.RadioButtonGroup options={options} />
            </Form>
        );

        expect(component.find('div.radio span')).to.have.length(options.length);
        expect(component.find('div.radio span').at(0).text()).to.equal(options[0].name);
        expect(component.find('div.radio span').at(1).text()).to.equal(options[1].name);
        expect(component.find('div.radio span').at(2).text()).to.equal(options[2].name);
    });

    it('should include an input[type="radio"] for each option', () => {

        const component = mount(
            <Form>
                <Form.RadioButtonGroup options={options} />
            </Form>
        );

        expect(component.find('div.radio input[type="radio"]')).to.have.length(options.length);
        expect(component.find('div.radio input[type="radio"]').at(0).props().value)
            .to.equal(options[0].value);
        expect(component.find('div.radio input[type="radio"]').at(1).props().value)
            .to.equal(options[1].value);
        expect(component.find('div.radio input[type="radio"]').at(2).props().value)
            .to.equal(options[2].value);
    });

    it('should select the first item if the value prop is not set', () => {

        const label = 'peter piper';

        const component = mount(
            <Form>
                <Form.RadioButtonGroup label={label} options={options} />
            </Form>
        );

        expect(component.find('div.radio input[type="radio"]')).to.have.length(options.length);
        expect(component.find('div.radio input[type="radio"]').at(0).props().checked)
            .to.equal(true);
        expect(component.find('div.radio input[type="radio"]').at(1).props().checked)
            .to.equal(false);
        expect(component.find('div.radio input[type="radio"]').at(2).props().checked)
            .to.equal(false);
    });

    it('should select the first item if the value prop is invalid', () => {

        const label = 'peter piper';

        const value = 'not valid';

        const component = mount(
            <Form>
                <Form.RadioButtonGroup label={label} options={options} value={value} />
            </Form>
        );

        expect(component.find('div.radio input[type="radio"]')).to.have.length(options.length);
        expect(component.find('div.radio input[type="radio"]').at(0).props().checked)
            .to.equal(true);
        expect(component.find('div.radio input[type="radio"]').at(1).props().checked)
            .to.equal(false);
        expect(component.find('div.radio input[type="radio"]').at(2).props().checked)
            .to.equal(false);
    });

    it('should select the appropriate item if the value prop is set to a valid value', () => {

        const label = 'peter piper';

        const value = 2;

        const component = mount(
            <Form>
                <Form.RadioButtonGroup label={label} options={options} value={value} />
            </Form>
        );

        expect(component.find('div.radio input[type="radio"]')).to.have.length(options.length);
        expect(component.find('div.radio input[type="radio"]').at(0).props().checked)
            .to.equal(false);
        expect(component.find('div.radio input[type="radio"]').at(1).props().checked)
            .to.equal(true);
        expect(component.find('div.radio input[type="radio"]').at(2).props().checked)
            .to.equal(false);
    });

    it('should have a label.col-xs-3 when form is horizontal and labelColumns=3', () => {

        const label = 'peter piper';
        const columns = 3;

        const value = 2;

        const component = mount(
            <Form horizontal labelColumns={columns}>
                <Form.RadioButtonGroup label={label} options={options} value={value} />
            </Form>
        );

        expect(component.find('div.form-horizontal')).to.have.length(1, 'form-horizontal');
        expect(component.find('label.control-label').props().className)
            .to.contain('col-xs-3', 'col-xs-3');
    });

    it('should have a div.col-xs-9 when form is horizontal and labelColumns=3', () => {

        const label = 'peter piper';
        const columns = 3;

        const value = 2;

        const component = mount(
            <Form horizontal labelColumns={columns}>
                <Form.RadioButtonGroup label={label} options={options} value={value} />
            </Form>
        );

        expect(component.find('div.form-horizontal')).to.have.length(1, 'form-horizontal');
        expect(component.find('div.form-radiobuttongroup-input-columns').props().className)
            .to.contain('col-xs-9', 'col-xs-9');
    });

});

/* *****************************************************************************
when a radio button in the group is clicked
    the onChange handler should be called if a new value is clicked
    the onChange handler should not be called if the same value is clicked
    the new value should be selected and all others should not be selected
*/
describe('when a radio button in the group is clicked', () => {

    const options = [
        { value: '1', name: 'One' },
        { value: '2', name: 'Two' },
        { value: '3', name: 'Three' },
    ];

    it('the onChange handler should be called if a new value is clicked', () => {

        const onChange = sinon.spy();

        const label = 'little miss muffet';

        const value = '1';

        const clickedItem = 2;

        const component = mount(
            <Form>
                <Form.RadioButtonGroup
                    label={label}
                    options={options}
                    value={value}
                    onChange={onChange}
                />
            </Form>
        );

        expect(onChange.callCount).to.equal(0);

        component.find('div.radio input[type="radio"]').at(1).simulate('change', {
            target: { value: options[clickedItem].value }
        });

        expect(onChange.callCount).to.equal(1);
        expect(onChange.calledWith(options[clickedItem].value)).to.equal(true);
    });

    it('the onChange handler should not be called if the same value is clicked', () => {

        const onChange = sinon.spy();

        const label = 'little miss muffet';

        const value = '1';

        const clickedItem = 0;

        const component = mount(
            <Form>
                <Form.RadioButtonGroup
                    label={label}
                    options={options}
                    value={value}
                    onChange={onChange}
                />
            </Form>
        );

        expect(onChange.callCount).to.equal(0);

        component.find('div.radio input[type="radio"]').at(1).simulate('change', {
            target: { value: options[clickedItem].value }
        });

        expect(onChange.callCount).to.equal(0);
    });

    it('the new value should be selected and all others should not be selected', () => {

        const label = 'little miss muffet';

        const value = '1';

        const clickedItem = 1;

        const component = mount(
            <Form>
                <Form.RadioButtonGroup label={label} options={options} value={value} />
            </Form>
        );

        expect(component.find('div.radio input[type="radio"]')).to.have.length(options.length);
        expect(component.find('div.radio input[type="radio"]').at(0).props().checked)
            .to.equal(true, 'before - 0');
        expect(component.find('div.radio input[type="radio"]').at(1).props().checked)
            .to.equal(false, 'before - 1');
        expect(component.find('div.radio input[type="radio"]').at(2).props().checked)
            .to.equal(false, 'before - 2');

        component.find('div.radio input[type="radio"]').at(1).simulate('change', {
            target: { value: options[clickedItem].value }
        });

        expect(component.find('div.radio input[type="radio"]').at(0).props().checked)
            .to.equal(false, 'after - 0');
        expect(component.find('div.radio input[type="radio"]').at(1).props().checked)
            .to.equal(true, 'after - 1');
        expect(component.find('div.radio input[type="radio"]').at(2).props().checked)
            .to.equal(false, 'after - 2');
    });

});
<|endoftext|>


<commit_before>// the eslint "no-unused-expressions" rule is triggered by the `expect`
// assertions in the tests. So, in order to avoid problems with eslint, I'm
// disabling that rule in this file
//
/* eslint-disable no-unused-expressions */

import React from 'react';
import { shallow, render, mount } from 'enzyme';
import chai from 'chai';
import sinon from 'sinon';

import Form from '../Form';

const expect = chai.expect;

/* eslint-disable no-unused-vars */
const debug = (component) => {
    console.info('------------------------------------------------------------');
    console.info(component.debug());
    console.info('------------------------------------------------------------');
};
/* eslint-enable no-unused-vars */

/* *****************************************************************************
a Form component containing a RadioButtonGroup
    should include a <Form.RadioButtonGroup> as a child
    should include a label if the label prop is set
    should not include a label if the label prop is not set
    should include a div.radio for each option
    should include a label for each option
    should include a span containing the option name for each option
    should include an input[type="radio"] for each option
    should select the first item if the value prop is not set
    should select the first item if the value prop is invalid
    should select the appropriate item if the value prop is set to a valid value
*/
describe('a Form component containing a RadioButtonGroup', () => {

    const options = [
        { value: '1', name: 'One' },
        { value: '2', name: 'Two' },
        { value: '3', name: 'Three' },
    ];

    it('should include a <Form.RadioButtonGroup> as a child', () => {
        const component = shallow(
            <Form>
                <Form.RadioButtonGroup options={options} />
            </Form>
        );

        expect(component.find(Form.RadioButtonGroup)).to.have.length(1);
    });

    it('should include a label if the label prop is set', () => {

        const label = 'fee fi fo fum';

        const component = mount(
            <Form>
                <Form.RadioButtonGroup label={label} options={options} />
            </Form>
        );

        expect(component.find('label.radiobuttongroup')).to.have.length(1);
        expect(component.find('label.radiobuttongroup').text()).to.equal(label);
    });

    it('should not include a label if the label prop is not set', () => {

        const component = mount(
            <Form>
                <Form.RadioButtonGroup options={options} />
            </Form>
        );

        expect(component.find('label.radiobuttongroup')).to.have.length(0);
    });

    it('should include a div.radio for each option', () => {

        const component = mount(
            <Form>
                <Form.RadioButtonGroup options={options} />
            </Form>
        );

        expect(component.find('div.radio')).to.have.length(options.length);
    });

    it('should include a label for each option', () => {

        const component = mount(
            <Form>
                <Form.RadioButtonGroup options={options} />
            </Form>
        );

        expect(component.find('div.radio label')).to.have.length(options.length);
    });

    it('should include a span containing the option name for each option', () => {

        const component = mount(
            <Form>
                <Form.RadioButtonGroup options={options} />
            </Form>
        );

        expect(component.find('div.radio span')).to.have.length(options.length);
        expect(component.find('div.radio span').at(0).text()).to.equal(options[0].name);
        expect(component.find('div.radio span').at(1).text()).to.equal(options[1].name);
        expect(component.find('div.radio span').at(2).text()).to.equal(options[2].name);
    });

    it('should include an input[type="radio"] for each option', () => {

        const component = mount(
            <Form>
                <Form.RadioButtonGroup options={options} />
            </Form>
        );

        expect(component.find('div.radio input[type="radio"]')).to.have.length(options.length);
        expect(component.find('div.radio input[type="radio"]').at(0).props().value).to.equal(options[0].value);
        expect(component.find('div.radio input[type="radio"]').at(1).props().value).to.equal(options[1].value);
        expect(component.find('div.radio input[type="radio"]').at(2).props().value).to.equal(options[2].value);
    });

    it('should select the first item if the value prop is not set', () => {

        const label = 'peter piper';

        const component = mount(
            <Form>
                <Form.RadioButtonGroup label={label} options={options} />
            </Form>
        );

        expect(component.find('div.radio input[type="radio"]')).to.have.length(options.length);
        expect(component.find('div.radio input[type="radio"]').at(0).props().checked).to.equal(true);
        expect(component.find('div.radio input[type="radio"]').at(1).props().checked).to.equal(false);
        expect(component.find('div.radio input[type="radio"]').at(2).props().checked).to.equal(false);
    });

    it('should select the first item if the value prop is invalid', () => {

        const label = 'peter piper';

        const value = 'not valid';

        const component = mount(
            <Form>
                <Form.RadioButtonGroup label={label} options={options} value={value} />
            </Form>
        );

        expect(component.find('div.radio input[type="radio"]')).to.have.length(options.length);
        expect(component.find('div.radio input[type="radio"]').at(0).props().checked).to.equal(true);
        expect(component.find('div.radio input[type="radio"]').at(1).props().checked).to.equal(false);
        expect(component.find('div.radio input[type="radio"]').at(2).props().checked).to.equal(false);
    });

    it('should select the appropriate item if the value prop is set to a valid value', () => {

        const label = 'peter piper';

        const value = 2;

        const component = mount(
            <Form>
                <Form.RadioButtonGroup label={label} options={options} value={value} />
            </Form>
        );

        expect(component.find('div.radio input[type="radio"]')).to.have.length(options.length);
        expect(component.find('div.radio input[type="radio"]').at(0).props().checked).to.equal(false);
        expect(component.find('div.radio input[type="radio"]').at(1).props().checked).to.equal(true);
        expect(component.find('div.radio input[type="radio"]').at(2).props().checked).to.equal(false);
    });

});

/* *****************************************************************************
when a radio button in the group is clicked
    the onChange handler should be called if a new value is clicked
    the onChange handler should not be called if the same value is clicked
    the new value should be selected and all others should not be selected
*/
<commit_msg>added specs for onChange handler<commit_after>// the eslint "no-unused-expressions" rule is triggered by the `expect`
// assertions in the tests. So, in order to avoid problems with eslint, I'm
// disabling that rule in this file
//
/* eslint-disable no-unused-expressions */

import React from 'react';
import { shallow, render, mount } from 'enzyme';
import chai from 'chai';
import sinon from 'sinon';

import Form from '../Form';

const expect = chai.expect;

/* eslint-disable no-unused-vars */
const debug = (component) => {
    console.info('------------------------------------------------------------');
    console.info(component.debug());
    console.info('------------------------------------------------------------');
};
/* eslint-enable no-unused-vars */

/* *****************************************************************************
a Form component containing a RadioButtonGroup
    should include a <Form.RadioButtonGroup> as a child
    should include a label if the label prop is set
    should not include a label if the label prop is not set
    should include a div.radio for each option
    should include a label for each option
    should include a span containing the option name for each option
    should include an input[type="radio"] for each option
    should select the first item if the value prop is not set
    should select the first item if the value prop is invalid
    should select the appropriate item if the value prop is set to a valid value
*/
describe('a Form component containing a RadioButtonGroup', () => {

    const options = [
        { value: '1', name: 'One' },
        { value: '2', name: 'Two' },
        { value: '3', name: 'Three' },
    ];

    it('should include a <Form.RadioButtonGroup> as a child', () => {
        const component = shallow(
            <Form>
                <Form.RadioButtonGroup options={options} />
            </Form>
        );

        expect(component.find(Form.RadioButtonGroup)).to.have.length(1);
    });

    it('should include a label if the label prop is set', () => {

        const label = 'fee fi fo fum';

        const component = mount(
            <Form>
                <Form.RadioButtonGroup label={label} options={options} />
            </Form>
        );

        expect(component.find('label.radiobuttongroup')).to.have.length(1);
        expect(component.find('label.radiobuttongroup').text()).to.equal(label);
    });

    it('should not include a label if the label prop is not set', () => {

        const component = mount(
            <Form>
                <Form.RadioButtonGroup options={options} />
            </Form>
        );

        expect(component.find('label.radiobuttongroup')).to.have.length(0);
    });

    it('should include a div.radio for each option', () => {

        const component = mount(
            <Form>
                <Form.RadioButtonGroup options={options} />
            </Form>
        );

        expect(component.find('div.radio')).to.have.length(options.length);
    });

    it('should include a label for each option', () => {

        const component = mount(
            <Form>
                <Form.RadioButtonGroup options={options} />
            </Form>
        );

        expect(component.find('div.radio label')).to.have.length(options.length);
    });

    it('should include a span containing the option name for each option', () => {

        const component = mount(
            <Form>
                <Form.RadioButtonGroup options={options} />
            </Form>
        );

        expect(component.find('div.radio span')).to.have.length(options.length);
        expect(component.find('div.radio span').at(0).text()).to.equal(options[0].name);
        expect(component.find('div.radio span').at(1).text()).to.equal(options[1].name);
        expect(component.find('div.radio span').at(2).text()).to.equal(options[2].name);
    });

    it('should include an input[type="radio"] for each option', () => {

        const component = mount(
            <Form>
                <Form.RadioButtonGroup options={options} />
            </Form>
        );

        expect(component.find('div.radio input[type="radio"]')).to.have.length(options.length);
        expect(component.find('div.radio input[type="radio"]').at(0).props().value).to.equal(options[0].value);
        expect(component.find('div.radio input[type="radio"]').at(1).props().value).to.equal(options[1].value);
        expect(component.find('div.radio input[type="radio"]').at(2).props().value).to.equal(options[2].value);
    });

    it('should select the first item if the value prop is not set', () => {

        const label = 'peter piper';

        const component = mount(
            <Form>
                <Form.RadioButtonGroup label={label} options={options} />
            </Form>
        );

        expect(component.find('div.radio input[type="radio"]')).to.have.length(options.length);
        expect(component.find('div.radio input[type="radio"]').at(0).props().checked).to.equal(true);
        expect(component.find('div.radio input[type="radio"]').at(1).props().checked).to.equal(false);
        expect(component.find('div.radio input[type="radio"]').at(2).props().checked).to.equal(false);
    });

    it('should select the first item if the value prop is invalid', () => {

        const label = 'peter piper';

        const value = 'not valid';

        const component = mount(
            <Form>
                <Form.RadioButtonGroup label={label} options={options} value={value} />
            </Form>
        );

        expect(component.find('div.radio input[type="radio"]')).to.have.length(options.length);
        expect(component.find('div.radio input[type="radio"]').at(0).props().checked).to.equal(true);
        expect(component.find('div.radio input[type="radio"]').at(1).props().checked).to.equal(false);
        expect(component.find('div.radio input[type="radio"]').at(2).props().checked).to.equal(false);
    });

    it('should select the appropriate item if the value prop is set to a valid value', () => {

        const label = 'peter piper';

        const value = 2;

        const component = mount(
            <Form>
                <Form.RadioButtonGroup label={label} options={options} value={value} />
            </Form>
        );

        expect(component.find('div.radio input[type="radio"]')).to.have.length(options.length);
        expect(component.find('div.radio input[type="radio"]').at(0).props().checked).to.equal(false);
        expect(component.find('div.radio input[type="radio"]').at(1).props().checked).to.equal(true);
        expect(component.find('div.radio input[type="radio"]').at(2).props().checked).to.equal(false);
    });

});

/* *****************************************************************************
when a radio button in the group is clicked
    the onChange handler should be called if a new value is clicked
    the onChange handler should not be called if the same value is clicked
    the new value should be selected and all others should not be selected
*/
describe('when a radio button in the group is clicked', () => {

    // it('the onChange handler should be called if a new value is clicked', () => {});

    // it('the onChange handler should not be called if the same value is clicked', () => {});

    // it('the new value should be selected and all others should not be selected', () => {});

});
<|endoftext|>


<commit_before>const { registrationPinRepo } = require('../repo/index');

const getRegistrationPinsForUser = async (email) => {
	return registrationPinRepo.getRegistrationPinsForUser(email);
};

const deleteRegistrationPins = async (pinIds) => {
	return registrationPinRepo.deleteRegistrationPins(pinIds);
};

const deleteRegistrationPinsByEmail = async (email) => {
	return { complete: true, data: registrationPinRepo.deleteRegistrationPinsByEmail(email) };
};

module.exports = {
	getRegistrationPinsForUser,
	deleteRegistrationPins,
	deleteRegistrationPinsByEmail,
};
<commit_msg>SC-8030 Remove unused use case methods<commit_after>const { registrationPinRepo } = require('../repo/index');

const deleteRegistrationPinsByEmail = async (email) => {
	return { complete: true, data: registrationPinRepo.deleteRegistrationPinsByEmail(email) };
};

module.exports = {
	deleteRegistrationPinsByEmail,
};
<|endoftext|>


<commit_before>
var map; // instance to the leaflet map
var mouseTimer = null; // delay to increase map
var increasedMode = false; // map is in increased mode

// initialize the map so the user can pick a location
var initializeMap = function(mapElement) {

  // Leaflet settings
  L.Icon.Default.imagePath = Meteor.absoluteUrl("img/leaflet");

  // set up the map
  map = new L.Map(mapElement);

  // start the map in Lyon
  map.setView(new L.LatLng(45.764043, 4.835659), 13);

  // create the tile layer with correct attribution
  // var osmUrl='http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
  // var osmAttrib='Map data © OpenStreetMap contributors';
  // var osm = new L.TileLayer(osmUrl, {attribution: osmAttrib});
  // map.addLayer(osm);

  // create the tile layer with correct attribution
  var api_key = SETTINGS.CLOUDEMADE_API_KEY;
  var url = 'http://{s}.tile.cloudmade.com/'+api_key+'/997/256/{z}/{x}/{y}.png';
  var attribution = 'Map data © OpenStreetMap contributors, CC-BY-SA, Imagery © CloudMade';
  var cloudMadeLayer = L.tileLayer(url, {attribution: '', maxZoom: 18});
  map.addLayer(cloudMadeLayer);

  // init user location marker
  initMarker();
}

// show user's location by showing a marker on the map
var initMarker = function() {
  var defaultLocation = { lat: 45.764043, lng: 4.835659 }; // Lyon
  var location = Meteor.user().profile.location || defaultLocation;

  // create marker
  var marker = L.marker(location, {draggable: true});
  marker.on('dragend', markerLocationChanged);
  marker.addTo(map);
}

// fired when marker location is changed by dragging the marker
var markerLocationChanged = function(event) {
  var marker = event.target;
  var latlng = marker.getLatLng();
  saveLocation(latlng);
}

// when mouse enters the map
var enterMap = function(event) {
  var $map = $(event.currentTarget);
  var delay = 400;
  var execute = _.partial(increaseMapSize, $map);
  mouseTimer = Meteor.setTimeout(execute, delay);
}

// when mouse leaves the map
var leaveMap = function() {
  var $map = $(event.currentTarget);
  if (mouseTimer)
    Meteor.clearTimeout(mouseTimer);
  resetMapSize($map);
}

// increase the size of the map with animation
var increaseMapSize = function($map) {
  resetMapSize($map); // make sure that previous animation will be canceled

  var startY = $map.offset().top - $(document).scrollTop();

  // inital values to make the map floats
  $map.css({
    position: 'fixed',
    top: startY,
    left: $map.offset().left
  });

  increasedMode = true;
  map.zoomIn(1);

  // increase size with animation
  $map.animate({
    left: 0,
    top: startY - 50,
    width: '100%',
    height: '400px'
  }, { step: function() { map.invalidateSize() } });
}

// shrink map to original size
var resetMapSize = function($map, init) {
  if(increasedMode) {
    map.zoomOut(1);
    increasedMode = false;
  }

  $map.stop(true).css({
    position: 'relative',
    top: 0,
    left: 0,
    width: 'inherit',
    height: 'inherit'
  });

  map.invalidateSize();
}

// DATABASE operations

var saveLocation = function(location) {
  Meteor.users.update(Meteor.userId(), {$set: {'profile.location': location}});
}

// EVENTS

Template.hackerEdit.events({
  'mouseenter #map': enterMap,
  'mouseleave #map': leaveMap
});

// RENDERING

Template.hackerEdit.rendered = function() {
  if (!this.initialized)
    initializeMap(this.find('#map')); // initialize map

  this.initialized = true;
}

<commit_msg>bugfix: map<commit_after>
var map; // instance to the leaflet map
var mouseTimer = null; // delay to increase map
var increasedMode = false; // map is in increased mode

// initialize the map so the user can pick a location
var initializeMap = function(mapElement) {

  // Leaflet settings
  L.Icon.Default.imagePath = Meteor.absoluteUrl("img/leaflet");

  // set up the map
  map = new L.Map(mapElement);

  // start the map in Lyon
  map.setView(new L.LatLng(45.764043, 4.835659), 13);

  // create the tile layer with correct attribution
  // var osmUrl='http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
  // var osmAttrib='Map data © OpenStreetMap contributors';
  // var osm = new L.TileLayer(osmUrl, {attribution: osmAttrib});
  // map.addLayer(osm);

  // create the tile layer with correct attribution
  var api_key = SETTINGS.CLOUDEMADE_API_KEY;
  var url = 'http://{s}.tile.cloudmade.com/'+api_key+'/997/256/{z}/{x}/{y}.png';
  var attribution = 'Map data © OpenStreetMap contributors, CC-BY-SA, Imagery © CloudMade';
  var cloudMadeLayer = L.tileLayer(url, {attribution: '', maxZoom: 18});
  map.addLayer(cloudMadeLayer);

  // init user location marker
  initMarker();
}

// show user's location by showing a marker on the map
var initMarker = function() {
  var defaultLocation = { lat: 45.764043, lng: 4.835659 }; // Lyon
  var location = Meteor.user().profile.location || defaultLocation;

  // create marker
  var marker = L.marker(location, {draggable: true});
  marker.on('dragend', markerLocationChanged);
  marker.addTo(map);
}

// fired when marker location is changed by dragging the marker
var markerLocationChanged = function(event) {
  var marker = event.target;
  var latlng = marker.getLatLng();
  saveLocation(latlng);
}

// when mouse enters the map
var enterMap = function(event) {
  var $map = $(event.currentTarget);
  var delay = 400;
  var execute = _.partial(increaseMapSize, $map);
  mouseTimer = Meteor.setTimeout(execute, delay);
}

// when mouse leaves the map
var leaveMap = function() {
  var $map = $(event.currentTarget);
  if (mouseTimer)
    Meteor.clearTimeout(mouseTimer);
  resetMapSize($map);
}

// increase the size of the map with animation
var increaseMapSize = function($map) {
  if (increasedMode)
    return; //already increased

  increasedMode = true;
  map.zoomIn(1, {animate: false});

  var startY = $map.offset().top - $(document).scrollTop();

  // inital values to make the map floats
  $map.css({
    position: 'fixed',
    top: startY,
    left: $map.offset().left
  });

  // increase size with animation
  $map.animate({
    left: 0,
    top: startY - 50,
    width: '100%',
    height: '400px'
  }, { step: function() { map.invalidateSize() } });
}

// shrink map to original size
var resetMapSize = function($map, init) {
  $map.stop(true).css({
    position: 'relative',
    top: 0,
    left: 0,
    width: 'inherit',
    height: 'inherit'
  });

  if(increasedMode) {
    map.zoomOut(1, {animate: false});
    increasedMode = false;
  }

  map.invalidateSize();
}

// DATABASE operations

var saveLocation = function(location) {
  Meteor.users.update(Meteor.userId(), {$set: {'profile.location': location}});
}

// EVENTS

Template.hackerEdit.events({
  'mouseenter #map': enterMap,
  'mouseleave #map': leaveMap
});

// RENDERING

Template.hackerEdit.rendered = function() {
  if (!this.initialized)
    initializeMap(this.find('#map')); // initialize map

  this.initialized = true;
}

<|endoftext|>


<commit_before>const path = require('path');
const webpack = require('webpack');
const ExtraWatchWebpackPlugin = require('extra-watch-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const VersionFile = require('webpack-version-file-plugin');

const sharedConfig = {
    module: {
        rules: [
            {
                test: /\.(html|ico)$/,
                use: [{
                    loader: 'file-loader',
                    options: { name: '[name].[ext]' }
                }]
            },
            {
                test: /\.(s*)css$/,
                use: [
                    MiniCssExtractPlugin.loader,
                    { loader: 'css-loader', options: { sourceMap: false } },
                    { loader: 'sass-loader', options: { sourceMap: false } },
                ]
            }
        ]
    },
    plugins: [
        new ExtraWatchWebpackPlugin({
            files: [
                './lib/**/*.scss',
                './lib/**/*.html'
            ],
        }),
        new webpack.DefinePlugin({
            'process.env.DEBUG': JSON.stringify(process.env.DEBUG),
            '__MOLSTAR_DEBUG_TIMESTAMP__': webpack.DefinePlugin.runtimeValue(() => `${new Date().valueOf()}`, true)
        }),
        new MiniCssExtractPlugin({ filename: 'molstar.css',  }),
        new VersionFile({
            extras: { timestamp: `${new Date().valueOf()}` },
            packageFile: path.resolve(__dirname, 'package.json'),
            templateString: `export const PLUGIN_VERSION = '<%= package.version %>';\nexport const PLUGIN_VERSION_DATE = new Date(typeof __MOLSTAR_DEBUG_TIMESTAMP__ !== 'undefined' ? __MOLSTAR_DEBUG_TIMESTAMP__ : <%= extras.timestamp %>);`,
            outputFile: path.resolve(__dirname, 'lib/mol-plugin/version.js')
        })
    ],
    resolve: {
        modules: [
            'node_modules',
            path.resolve(__dirname, 'lib/')
        ],
    },
    watchOptions: {
        aggregateTimeout: 750
    },
    devtool: ''
};

function createEntry(src, outFolder, outFilename, isNode) {
    return {
        node: isNode ? void 0 : { fs: 'empty' }, // TODO find better solution? Currently used in file-handle.ts
        target: isNode ? 'node' : void 0,
        entry: path.resolve(__dirname, `lib/${src}.js`),
        output: { filename: `${outFilename}.js`, path: path.resolve(__dirname, `build/${outFolder}`) },
        ...sharedConfig
    };
}

function createEntryPoint(name, dir, out, library) {
    return {
        node: { fs: 'empty' }, // TODO find better solution? Currently used in file-handle.ts
        entry: path.resolve(__dirname, `lib/${dir}/${name}.js`),
        output: { filename: `${library || name}.js`, path: path.resolve(__dirname, `build/${out}`), library: library || out, libraryTarget: 'umd' },
        ...sharedConfig
    };
}

function createNodeEntryPoint(name, dir, out) {
    return {
        target: 'node',
        entry: path.resolve(__dirname, `lib/${dir}/${name}.js`),
        output: { filename: `${name}.js`, path: path.resolve(__dirname, `build/${out}`) },
        externals: {
            argparse: 'require("argparse")',
            'node-fetch': 'require("node-fetch")',
            'util.promisify': 'require("util.promisify")',
            xhr2: 'require("xhr2")',
        },
        ...sharedConfig
    };
}

function createApp(name, library) { return createEntryPoint('index', `apps/${name}`, name, library); }
function createExample(name) { return createEntry(`examples/${name}/index`, `examples/${name}`, 'index'); }
function createBrowserTest(name) { return createEntryPoint(name, 'tests/browser', 'tests'); }
function createNodeApp(name) { return createNodeEntryPoint('index', `apps/${name}`, name); }

module.exports = {
    createApp,
    createEntry,
    createExample,
    createBrowserTest,
    createNodeEntryPoint,
    createNodeApp
};<commit_msg>use var in webpack version template<commit_after>const path = require('path');
const webpack = require('webpack');
const ExtraWatchWebpackPlugin = require('extra-watch-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const VersionFile = require('webpack-version-file-plugin');

const sharedConfig = {
    module: {
        rules: [
            {
                test: /\.(html|ico)$/,
                use: [{
                    loader: 'file-loader',
                    options: { name: '[name].[ext]' }
                }]
            },
            {
                test: /\.(s*)css$/,
                use: [
                    MiniCssExtractPlugin.loader,
                    { loader: 'css-loader', options: { sourceMap: false } },
                    { loader: 'sass-loader', options: { sourceMap: false } },
                ]
            }
        ]
    },
    plugins: [
        new ExtraWatchWebpackPlugin({
            files: [
                './lib/**/*.scss',
                './lib/**/*.html'
            ],
        }),
        new webpack.DefinePlugin({
            'process.env.DEBUG': JSON.stringify(process.env.DEBUG),
            '__MOLSTAR_DEBUG_TIMESTAMP__': webpack.DefinePlugin.runtimeValue(() => `${new Date().valueOf()}`, true)
        }),
        new MiniCssExtractPlugin({ filename: 'molstar.css',  }),
        new VersionFile({
            extras: { timestamp: `${new Date().valueOf()}` },
            packageFile: path.resolve(__dirname, 'package.json'),
            templateString: `export var PLUGIN_VERSION = '<%= package.version %>';\nexport var PLUGIN_VERSION_DATE = new Date(typeof __MOLSTAR_DEBUG_TIMESTAMP__ !== 'undefined' ? __MOLSTAR_DEBUG_TIMESTAMP__ : <%= extras.timestamp %>);`,
            outputFile: path.resolve(__dirname, 'lib/mol-plugin/version.js')
        })
    ],
    resolve: {
        modules: [
            'node_modules',
            path.resolve(__dirname, 'lib/')
        ],
    },
    watchOptions: {
        aggregateTimeout: 750
    },
    devtool: ''
};

function createEntry(src, outFolder, outFilename, isNode) {
    return {
        node: isNode ? void 0 : { fs: 'empty' }, // TODO find better solution? Currently used in file-handle.ts
        target: isNode ? 'node' : void 0,
        entry: path.resolve(__dirname, `lib/${src}.js`),
        output: { filename: `${outFilename}.js`, path: path.resolve(__dirname, `build/${outFolder}`) },
        ...sharedConfig
    };
}

function createEntryPoint(name, dir, out, library) {
    return {
        node: { fs: 'empty' }, // TODO find better solution? Currently used in file-handle.ts
        entry: path.resolve(__dirname, `lib/${dir}/${name}.js`),
        output: { filename: `${library || name}.js`, path: path.resolve(__dirname, `build/${out}`), library: library || out, libraryTarget: 'umd' },
        ...sharedConfig
    };
}

function createNodeEntryPoint(name, dir, out) {
    return {
        target: 'node',
        entry: path.resolve(__dirname, `lib/${dir}/${name}.js`),
        output: { filename: `${name}.js`, path: path.resolve(__dirname, `build/${out}`) },
        externals: {
            argparse: 'require("argparse")',
            'node-fetch': 'require("node-fetch")',
            'util.promisify': 'require("util.promisify")',
            xhr2: 'require("xhr2")',
        },
        ...sharedConfig
    };
}

function createApp(name, library) { return createEntryPoint('index', `apps/${name}`, name, library); }
function createExample(name) { return createEntry(`examples/${name}/index`, `examples/${name}`, 'index'); }
function createBrowserTest(name) { return createEntryPoint(name, 'tests/browser', 'tests'); }
function createNodeApp(name) { return createNodeEntryPoint('index', `apps/${name}`, name); }

module.exports = {
    createApp,
    createEntry,
    createExample,
    createBrowserTest,
    createNodeEntryPoint,
    createNodeApp
};<|endoftext|>


<commit_before>// acorn.shells.MultiShell -- one shell to contain them all.
// ---------------------------------------------------------

var MultiShell = acorn.shells.MultiShell = Shell.extend({
  // leelo dallas multi shell

  shellid: 'acorn.MultiShell',

  // The cannonical type of this media. One of `acorn.types`.
  type: 'multimedia',

  // The shell-specific control components to use.
  controls: [
    'LeftControl',
    'ListControl',
    'RightControl',
  ],

  initialize: function() {
    Shell.prototype.initialize.call(this);

    this.data.shells = this.data.shells || [];
  },

  // **title** returns a simple title of the shell
  title: function() {
    var items = _.keys(this.data.shells).length;
    var s = (items == 1 ? '' : 's');
    return 'playlist with ' + items + ' item' + s;
  },

  // **thumbnailLink** use the first shell's thumbnail
  thumbnailLink: function() {
    var first = this.data.shells[0];
    if (!first)
      return '';

    var shell = acorn.shellWithData(first);
    return shell.thumbnailLink();
  },

  // **addShellData** adds another shell (via data)
  addShellData: function(shellData) {
    this.data.shells.push(shellData);
  },

  // **addShell** adds another shell
  addShell: function(shell) {
    this.addShellData(shell.data)
  },

});

// **ContentView** - Render each subshell in sequence.
// ---------------------------------------------------

MultiShell.ContentView = Shell.ContentView.extend({

  // **shellViews** is a container for sub shellViews.
  shellViews: [],

  // overwrite 'acorn-shell' as classname
  className: 'acorn-multishell',

  // Supported trigger events
  //
  // * change:subview - fired when subview currently shown changes.

  initialize: function() {
    Shell.ContentView.prototype.initialize.call(this);

    // controls
    this.parent.on('controls:left', this.onShowPrevious);
    this.parent.on('controls:list', this.onTogglePlaylist);
    this.parent.on('controls:right', this.onShowNext);

    // multishell events
    this.on('change:subview', this.onChangedSubview);

    // initialize shells
    this.shells = _.map(this.shell.data.shells, acorn.shellWithData);
    _.map(this.shells, function (shell) { shell.retrieveExtraInfo(); });

  },

  render: function() {
    // remove all previously rendered shellViews.
    this.map(function(shellView) { shellView.remove(); });

    // construct all the views
    this.shellViews = _.map(this.shells, function (shell) {
      var contentView = new shell.shellClass.ContentView({
        shell: shell,
        parent: this,
        autoplay: true,
      });

      // subshell events
      contentView.on('playback:ended', this.onSubShellPlaybackEnded);

      return contentView;
    }, this);

    // clean up our elem
    this.$el.empty();

    this.showView(0)
  },

  indexOfView: function(view) {
    return _.indexOf(this.shellViews, view);
  },

  currentViewIndex: function() {
    return this.indexOfView(this.currentView);
  },

  showView: function(index) {
    var shellView = this.shellViews[index];

    // bail if no view at that index.
    if (!shellView)
      return;

    // tear down previous ``currentView``
    if (this.currentView) {
      this.currentView.remove();
      // removing may be a bit drastic. perhaps:
      // this.triggerPlaybackStop();
      // this.currentView.$el.hide();
    };

    // set up shellView as ``currentView``
    this.currentView = shellView;

    if (!this.currentView.el.parentNode) {
      this.currentView.render();
      this.$el.append(this.currentView.el);
    };

    this.currentView.$el.show();

    // announce changes
    this.trigger('change:subview');
  },

  // **togglePlaylist** toggle a container with subview summaries
  togglePlaylist: function() {
    if (this.playlistView) {
      this.playlistView.close();
      this.playlistView = null;
      return;
    };

    var playlistView = new this.shell.shellClass.PlaylistView({
      shell: this.shell,
      parent: this,
    });

    this.playlistView = playlistView;

    playlistView.render();
    this.$el.append(playlistView.el);

    // stop playback on the currently-playing view
    this.triggerPlaybackStop();
  },

  // -- MultiShell Events

  onChangedSubview: function() {
    var contentView = this.parent;
    var controlsView = contentView.player.controlsView;

    var left = controlsView.controlWithId('left');
    var list = controlsView.controlWithId('list');
    var right = controlsView.controlWithId('right');

    left.$el.removeAttr('disabled');
    right.$el.removeAttr('disabled');

    if (this.currentView == _.first(this.shellViews))
      left.$el.attr('disabled', 'disabled');

    if (this.currentView == _.last(this.shellViews))
      right.$el.attr('disabled', 'disabled');
  },

  triggerPlaybackStop: function() {
    this.trigger('playback:stop');
  },

  // **onPlaybackStop** forward 'stop:playback' event from parent
  onPlaybackStop: function() {
    this.triggerPlaybackStop();
  },

  // **onShowPrevious** move back in the playlist.
  onShowPrevious: function() {
    this.showView(this.currentViewIndex() - 1);
  },

  // **onShowNext** move forward in the playlist.
  onShowNext: function() {
    this.showView(this.currentViewIndex() + 1);
  },

  // **onTogglePlaylist** toggle showing the playlist to the user.
  onTogglePlaylist: function() {
    this.togglePlaylist();
  },

  // -- SubShell Events

  // **onSubShellPlaybackEnded** advance subshells
  onSubShellPlaybackEnded: function() {
    // if there are more subshells to play, play them.
    if (this.hasNext())
      this.onShowNext();

    // otherwise, signal multishell playback ended.
    else
      this.trigger('playback:ended');
  },

  // helper to know whether there are more subshells forward.
  hasNext: function() {
    return this.currentViewIndex() < this.shellViews.length - 1;
  },

  // helper to know whether there are more subshells backward.
  hasPrevious: function() {
    return this.currentViewIndex() > 0;
  },

  // helper to map `func` through `shellViews` with `this` as context
  map: function(func) {
    _.map(this.shellViews, func, this);
  },

});

// **PlaylistView** - A view to summarize the shells within a MultiShell
// ---------------------------------------------------------------------

MultiShell.PlaylistView = ShellView.extend({

  id: 'acorn-multishell-playlist',

  template: _.template('\

\

\

\
        <button id="close" class="btn">\
           Close\
        </button>\

\

\

\
  '),

  events: {
    'click button#close': 'onClickClose',
    'click button#view': 'onClickView',
  },

  render: function() {
    this.$el.empty();
    this.$el.html(this.template());

    var title = this.shell.title();
    this.$el.find('#title').text(title);

    var summaries = this.$el.find('#summaries');
    _.map(this.parent.shells, function(shell, idx) {

      var summary = new shell.shellClass.SummaryView({
        shell: shell,
        parent: this,
        autplay: this.options.autoplay,
      });

      summary.render();
      summaries.append(summary.el);
      summary.$el.attr('data-index', idx);

      // an action to view shells from the playlist
      var view_btn =
        $('<button>')
          .text('View')
          .addClass('btn')
          .attr('id', 'view')
          .attr('data-index', idx);

      summary.$el.find('#buttons').append(view_btn);

    }, this);

    // select current shell and respond when current shell changes
    this.parent.on('change:subview', this.updateSelected);
    this.updateSelected();

  },

  updateSelected: function() {
    var summaries = this.$el.find('#summaries');
    var currentIndex = this.parent.currentViewIndex();
    var selector = "[data-index='"+currentIndex+"']:not('button')";

    // unselect any selected summaries and select the current shell's summary
    summaries.find('.selected').removeClass('selected');
    summaries.find(selector).addClass('selected');
  },

  close: function() {
    this.remove();
  },

  onClickClose: function() {
    this.close();
  },

  onClickView: function(event) {
    var index = $(event.target).attr('data-index');
    this.parent.showView(index);
    this.remove();
  },

});

// EditView -- displays the subshell EditViews.
// --------------------------------------------

MultiShell.EditView = Shell.EditView.extend({

  // **shellViews** is a container for sub shellViews.
  shellViews: [],

  events: {
    'click button#add': 'onClickAdd',
  },

  template: _.template('\

\
    <button class="btn btn-large" id="add">Add Link</button>\
  '),

  render: function() {

    // remove all previously rendered shellViews.
    this.map(function(shellView) { shellView.remove(); });

    // construct all the views
    this.shellViews = _.map(this.shell.data.shells, function(data, index) {
      return this.constructView(data, index);
    }, this);

    // clean up our elem
    this.$el.empty();
    this.$el.append(this.template());

    var subshells_el = this.$el.find('#subshells');

    // render and append all shellViews.
    this.map(function (shellView) {
      shellView.render();
      subshells_el.append(shellView.el);
    });

  },

  constructView: function(shellData, index) {
    // retrieve shell class from data info
    var shell = acorn.shellWithData(shellData);

    // construct this shell's EditView.
    var shellView = new shell.shellClass.EditView({
      shell: shell,
      parent: this,
    });

    // listen to events of shell EditView
    shellView.on('swap:shell', function(data) {
      this.onSwapSubShell(data, shellView);
    }, this);

    shellView.on('change:shell', function(data) {
      this.onChangeSubShell(data, shellView);
    }, this);

    shellView.on('delete:shell', function() {
      this.onDeleteSubShell(shellView);
    }, this);

    shellView.on('change:editState', this.onChangeEditState);

    return shellView;
  },

  // -- Shell Overrides

  // **isEditing** returns whether any shellView isEditing
  isEditing: function(value) {
    return _.any(this.shellViews, function (shellView) {
      return shellView.isEditing();
    });
  },

  // **onChangeShell** overriden to prevent re-render
  onChangeShell: function() {},

  // **finalizeEdit** propagate to subshells
  finalizeEdit: function() {
    this.map(function (shellView) {
      return shellView.finalizeEdit();
    });

    // remove empty shells (but ensure at least one shell remains)
    var emptyShellData = this.emptyShellData();
    var shells = this.shells();
    shells = _.filter(shells, function(shell) {
      return !_.isEqual(shell, emptyShellData);
    });
    shells = shells.length === 0 ? [emptyShellData] : shells;
    this.shells(shells);
  },

  // -- MultiShell Events

  onChangeEditState: function() {
    this.trigger('change:editState');
  },

  // **onClickAdd** add another link + shell
  onClickAdd: function() {
    // // additional, placeholder adding shells
    var nextIndex = this.shellViews.length;
    var addData = this.emptyShellData();
    var addView = this.constructView(addData, nextIndex);

    addView.render();
    this.shellViews.push(addView);

    var subshells_el = this.$el.find('#subshells');
    subshells_el.append(addView.el);
  },

  // -- Sub Shell Events

  onSwapSubShell: function(data, shellView) {
    var index = this.indexOfView(shellView);
    var oldShellView = this.shellViews[index];
    var newShellView = this.constructView(data, index);

    // render and add the new shellView.
    newShellView.render();
    oldShellView.$el.after(newShellView.el);

    // remove old shellView
    oldShellView.remove();
    this.shellViews[index] = newShellView;

    // update the data itself
    var shells = this.shells();
    shells[index] = data;
    this.shells(shells);
  },

  onChangeSubShell: function(shell, shellView) {
    // update the data itself
    var index = this.indexOfView(shellView);
    var shells = this.shells();
    shells[index] = shell.data;
    this.shells(shells);
  },

  onDeleteSubShell: function(shellView) {
    // remove shell from `this.shellViews`
    var index = this.indexOfView(shellView);
    this.shellViews.splice(index, 1);

    // remove shell from `this.shells`
    var shells = this.shells();
    shells.splice(index, 1);
    this.shells(shells);

    // remove shellview
    shellView.remove();
  },

  // get/setter for shells value
  shells: function(shells) {
    if (shells !== undefined) {
      this.shell.data.shells = shells;

      // if there is at most 1 shell, swap.
      if (shells.length <= 1)
        this.trigger('swap:shell', shells[0] || {});

      // else, announce that the shell has changed.
      else
        this.trigger('change:shell', this.shell);
    }
    return this.shell.data.shells;
  },

  // create an empty link shell
  emptyShellData: function() {
    return acorn.shellForLink('').data;
  },

  // helper to return a view's index in `shellViews`
  indexOfView: function(view) {
    var indexOf = this.shell.shellClass.ContentView.prototype.indexOfView;
    return indexOf.call(this, view);
  },

  // helper to map `func` through `shellViews` with `this` as context
  map: function(func) {
    return _.map(this.shellViews, func, this);
  },

});

// Register the shell with the acorn object.
acorn.registerShell(MultiShell);
<commit_msg>f/playlist: set `this.playlistView` to undefined instead of null when playlist closes. tweak of e1485dc4c6b5e6792bf324d6dd1db66a948a6d4f - #96<commit_after>// acorn.shells.MultiShell -- one shell to contain them all.
// ---------------------------------------------------------

var MultiShell = acorn.shells.MultiShell = Shell.extend({
  // leelo dallas multi shell

  shellid: 'acorn.MultiShell',

  // The cannonical type of this media. One of `acorn.types`.
  type: 'multimedia',

  // The shell-specific control components to use.
  controls: [
    'LeftControl',
    'ListControl',
    'RightControl',
  ],

  initialize: function() {
    Shell.prototype.initialize.call(this);

    this.data.shells = this.data.shells || [];
  },

  // **title** returns a simple title of the shell
  title: function() {
    var items = _.keys(this.data.shells).length;
    var s = (items == 1 ? '' : 's');
    return 'playlist with ' + items + ' item' + s;
  },

  // **thumbnailLink** use the first shell's thumbnail
  thumbnailLink: function() {
    var first = this.data.shells[0];
    if (!first)
      return '';

    var shell = acorn.shellWithData(first);
    return shell.thumbnailLink();
  },

  // **addShellData** adds another shell (via data)
  addShellData: function(shellData) {
    this.data.shells.push(shellData);
  },

  // **addShell** adds another shell
  addShell: function(shell) {
    this.addShellData(shell.data)
  },

});

// **ContentView** - Render each subshell in sequence.
// ---------------------------------------------------

MultiShell.ContentView = Shell.ContentView.extend({

  // **shellViews** is a container for sub shellViews.
  shellViews: [],

  // overwrite 'acorn-shell' as classname
  className: 'acorn-multishell',

  // Supported trigger events
  //
  // * change:subview - fired when subview currently shown changes.

  initialize: function() {
    Shell.ContentView.prototype.initialize.call(this);

    // controls
    this.parent.on('controls:left', this.onShowPrevious);
    this.parent.on('controls:list', this.onTogglePlaylist);
    this.parent.on('controls:right', this.onShowNext);

    // multishell events
    this.on('change:subview', this.onChangedSubview);

    // initialize shells
    this.shells = _.map(this.shell.data.shells, acorn.shellWithData);
    _.map(this.shells, function (shell) { shell.retrieveExtraInfo(); });

  },

  render: function() {
    // remove all previously rendered shellViews.
    this.map(function(shellView) { shellView.remove(); });

    // construct all the views
    this.shellViews = _.map(this.shells, function (shell) {
      var contentView = new shell.shellClass.ContentView({
        shell: shell,
        parent: this,
        autoplay: true,
      });

      // subshell events
      contentView.on('playback:ended', this.onSubShellPlaybackEnded);

      return contentView;
    }, this);

    // clean up our elem
    this.$el.empty();

    this.showView(0)
  },

  indexOfView: function(view) {
    return _.indexOf(this.shellViews, view);
  },

  currentViewIndex: function() {
    return this.indexOfView(this.currentView);
  },

  showView: function(index) {
    var shellView = this.shellViews[index];

    // bail if no view at that index.
    if (!shellView)
      return;

    // tear down previous ``currentView``
    if (this.currentView) {
      this.currentView.remove();
      // removing may be a bit drastic. perhaps:
      // this.triggerPlaybackStop();
      // this.currentView.$el.hide();
    };

    // set up shellView as ``currentView``
    this.currentView = shellView;

    if (!this.currentView.el.parentNode) {
      this.currentView.render();
      this.$el.append(this.currentView.el);
    };

    this.currentView.$el.show();

    // announce changes
    this.trigger('change:subview');
  },

  // **togglePlaylist** toggle a container with subview summaries
  togglePlaylist: function() {
    if (this.playlistView) {
      this.playlistView.close();
      this.playlistView = undefined;
      return;
    };

    var playlistView = new this.shell.shellClass.PlaylistView({
      shell: this.shell,
      parent: this,
    });

    this.playlistView = playlistView;

    playlistView.render();
    this.$el.append(playlistView.el);

    // stop playback on the currently-playing view
    this.triggerPlaybackStop();
  },

  // -- MultiShell Events

  onChangedSubview: function() {
    var contentView = this.parent;
    var controlsView = contentView.player.controlsView;

    var left = controlsView.controlWithId('left');
    var list = controlsView.controlWithId('list');
    var right = controlsView.controlWithId('right');

    left.$el.removeAttr('disabled');
    right.$el.removeAttr('disabled');

    if (this.currentView == _.first(this.shellViews))
      left.$el.attr('disabled', 'disabled');

    if (this.currentView == _.last(this.shellViews))
      right.$el.attr('disabled', 'disabled');
  },

  triggerPlaybackStop: function() {
    this.trigger('playback:stop');
  },

  // **onPlaybackStop** forward 'stop:playback' event from parent
  onPlaybackStop: function() {
    this.triggerPlaybackStop();
  },

  // **onShowPrevious** move back in the playlist.
  onShowPrevious: function() {
    this.showView(this.currentViewIndex() - 1);
  },

  // **onShowNext** move forward in the playlist.
  onShowNext: function() {
    this.showView(this.currentViewIndex() + 1);
  },

  // **onTogglePlaylist** toggle showing the playlist to the user.
  onTogglePlaylist: function() {
    this.togglePlaylist();
  },

  // -- SubShell Events

  // **onSubShellPlaybackEnded** advance subshells
  onSubShellPlaybackEnded: function() {
    // if there are more subshells to play, play them.
    if (this.hasNext())
      this.onShowNext();

    // otherwise, signal multishell playback ended.
    else
      this.trigger('playback:ended');
  },

  // helper to know whether there are more subshells forward.
  hasNext: function() {
    return this.currentViewIndex() < this.shellViews.length - 1;
  },

  // helper to know whether there are more subshells backward.
  hasPrevious: function() {
    return this.currentViewIndex() > 0;
  },

  // helper to map `func` through `shellViews` with `this` as context
  map: function(func) {
    _.map(this.shellViews, func, this);
  },

});

// **PlaylistView** - A view to summarize the shells within a MultiShell
// ---------------------------------------------------------------------

MultiShell.PlaylistView = ShellView.extend({

  id: 'acorn-multishell-playlist',

  template: _.template('\

\

\

\
        <button id="close" class="btn">\
           Close\
        </button>\

\

\

\
  '),

  events: {
    'click button#close': 'onClickClose',
    'click button#view': 'onClickView',
  },

  render: function() {
    this.$el.empty();
    this.$el.html(this.template());

    var title = this.shell.title();
    this.$el.find('#title').text(title);

    var summaries = this.$el.find('#summaries');
    _.map(this.parent.shells, function(shell, idx) {

      var summary = new shell.shellClass.SummaryView({
        shell: shell,
        parent: this,
        autplay: this.options.autoplay,
      });

      summary.render();
      summaries.append(summary.el);
      summary.$el.attr('data-index', idx);

      // an action to view shells from the playlist
      var view_btn =
        $('<button>')
          .text('View')
          .addClass('btn')
          .attr('id', 'view')
          .attr('data-index', idx);

      summary.$el.find('#buttons').append(view_btn);

    }, this);

    // select current shell and respond when current shell changes
    this.parent.on('change:subview', this.updateSelected);
    this.updateSelected();

  },

  updateSelected: function() {
    var summaries = this.$el.find('#summaries');
    var currentIndex = this.parent.currentViewIndex();
    var selector = "[data-index='"+currentIndex+"']:not('button')";

    // unselect any selected summaries and select the current shell's summary
    summaries.find('.selected').removeClass('selected');
    summaries.find(selector).addClass('selected');
  },

  close: function() {
    this.remove();
  },

  onClickClose: function() {
    this.close();
  },

  onClickView: function(event) {
    var index = $(event.target).attr('data-index');
    this.parent.showView(index);
    this.remove();
  },

});

// EditView -- displays the subshell EditViews.
// --------------------------------------------

MultiShell.EditView = Shell.EditView.extend({

  // **shellViews** is a container for sub shellViews.
  shellViews: [],

  events: {
    'click button#add': 'onClickAdd',
  },

  template: _.template('\

\
    <button class="btn btn-large" id="add">Add Link</button>\
  '),

  render: function() {

    // remove all previously rendered shellViews.
    this.map(function(shellView) { shellView.remove(); });

    // construct all the views
    this.shellViews = _.map(this.shell.data.shells, function(data, index) {
      return this.constructView(data, index);
    }, this);

    // clean up our elem
    this.$el.empty();
    this.$el.append(this.template());

    var subshells_el = this.$el.find('#subshells');

    // render and append all shellViews.
    this.map(function (shellView) {
      shellView.render();
      subshells_el.append(shellView.el);
    });

  },

  constructView: function(shellData, index) {
    // retrieve shell class from data info
    var shell = acorn.shellWithData(shellData);

    // construct this shell's EditView.
    var shellView = new shell.shellClass.EditView({
      shell: shell,
      parent: this,
    });

    // listen to events of shell EditView
    shellView.on('swap:shell', function(data) {
      this.onSwapSubShell(data, shellView);
    }, this);

    shellView.on('change:shell', function(data) {
      this.onChangeSubShell(data, shellView);
    }, this);

    shellView.on('delete:shell', function() {
      this.onDeleteSubShell(shellView);
    }, this);

    shellView.on('change:editState', this.onChangeEditState);

    return shellView;
  },

  // -- Shell Overrides

  // **isEditing** returns whether any shellView isEditing
  isEditing: function(value) {
    return _.any(this.shellViews, function (shellView) {
      return shellView.isEditing();
    });
  },

  // **onChangeShell** overriden to prevent re-render
  onChangeShell: function() {},

  // **finalizeEdit** propagate to subshells
  finalizeEdit: function() {
    this.map(function (shellView) {
      return shellView.finalizeEdit();
    });

    // remove empty shells (but ensure at least one shell remains)
    var emptyShellData = this.emptyShellData();
    var shells = this.shells();
    shells = _.filter(shells, function(shell) {
      return !_.isEqual(shell, emptyShellData);
    });
    shells = shells.length === 0 ? [emptyShellData] : shells;
    this.shells(shells);
  },

  // -- MultiShell Events

  onChangeEditState: function() {
    this.trigger('change:editState');
  },

  // **onClickAdd** add another link + shell
  onClickAdd: function() {
    // // additional, placeholder adding shells
    var nextIndex = this.shellViews.length;
    var addData = this.emptyShellData();
    var addView = this.constructView(addData, nextIndex);

    addView.render();
    this.shellViews.push(addView);

    var subshells_el = this.$el.find('#subshells');
    subshells_el.append(addView.el);
  },

  // -- Sub Shell Events

  onSwapSubShell: function(data, shellView) {
    var index = this.indexOfView(shellView);
    var oldShellView = this.shellViews[index];
    var newShellView = this.constructView(data, index);

    // render and add the new shellView.
    newShellView.render();
    oldShellView.$el.after(newShellView.el);

    // remove old shellView
    oldShellView.remove();
    this.shellViews[index] = newShellView;

    // update the data itself
    var shells = this.shells();
    shells[index] = data;
    this.shells(shells);
  },

  onChangeSubShell: function(shell, shellView) {
    // update the data itself
    var index = this.indexOfView(shellView);
    var shells = this.shells();
    shells[index] = shell.data;
    this.shells(shells);
  },

  onDeleteSubShell: function(shellView) {
    // remove shell from `this.shellViews`
    var index = this.indexOfView(shellView);
    this.shellViews.splice(index, 1);

    // remove shell from `this.shells`
    var shells = this.shells();
    shells.splice(index, 1);
    this.shells(shells);

    // remove shellview
    shellView.remove();
  },

  // get/setter for shells value
  shells: function(shells) {
    if (shells !== undefined) {
      this.shell.data.shells = shells;

      // if there is at most 1 shell, swap.
      if (shells.length <= 1)
        this.trigger('swap:shell', shells[0] || {});

      // else, announce that the shell has changed.
      else
        this.trigger('change:shell', this.shell);
    }
    return this.shell.data.shells;
  },

  // create an empty link shell
  emptyShellData: function() {
    return acorn.shellForLink('').data;
  },

  // helper to return a view's index in `shellViews`
  indexOfView: function(view) {
    var indexOf = this.shell.shellClass.ContentView.prototype.indexOfView;
    return indexOf.call(this, view);
  },

  // helper to map `func` through `shellViews` with `this` as context
  map: function(func) {
    return _.map(this.shellViews, func, this);
  },

});

// Register the shell with the acorn object.
acorn.registerShell(MultiShell);
<|endoftext|>


<commit_before>import * as is from '../is';
import Selector from '../selector';

let elesfn = ({
  nodes: function( selector ){
    return this.filter( ele => ele.isNode() ).filter( selector );
  },

  edges: function( selector ){
    return this.filter( ele => ele.isEdge() ).filter( selector );
  },

  filter: function( filter, thisArg ){
    if( filter === undefined ){ // check this first b/c it's the most common/performant case
      return this;
    } else if( is.string( filter ) || is.elementOrCollection( filter ) ){
      return new Selector( filter ).filter( this );
    } else if( is.fn( filter ) ){
      let filterEles = this.spawn();
      let eles = this;

      for( let i = 0; i < eles.length; i++ ){
        let ele = eles[ i ];
        let include = thisArg ? filter.apply( thisArg, [ ele, i, eles ] ) : filter( ele, i, eles );

        if( include ){
          filterEles.merge( ele );
        }
      }

      return filterEles;
    }

    return this.spawn(); // if not handled by above, give 'em an empty collection
  },

  not: function( toRemove ){
    if( !toRemove ){
      return this;
    } else {

      if( is.string( toRemove ) ){
        toRemove = this.filter( toRemove );
      }

      let elements = [];
      let rMap = toRemove._private.map;

      for( let i = 0; i < this.length; i++ ){
        let element = this[ i ];

        let remove = rMap.has( element.id() );
        if( !remove ){
          elements.push( element );
        }
      }

      return this.spawn( elements );
    }

  },

  absoluteComplement: function(){
    let cy = this.cy();

    return cy.mutableElements().not( this );
  },

  intersect: function( other ){
    // if a selector is specified, then filter by it instead
    if( is.string( other ) ){
      let selector = other;
      return this.filter( selector );
    }

    let elements = [];
    let col1 = this;
    let col2 = other;
    let col1Smaller = this.length < other.length;
    let map2 = col1Smaller ? col2._private.map : col1._private.map;
    let col = col1Smaller ? col1 : col2;

    for( let i = 0; i < col.length; i++ ){
      let id = col[ i ]._private.data.id;
      let entry = map2.get( id );

      if( entry ){
        elements.push( entry.ele );
      }
    }

    return this.spawn( elements );
  },

  xor: function( other ){
    let cy = this._private.cy;

    if( is.string( other ) ){
      other = cy.$( other );
    }

    let elements = [];
    let col1 = this;
    let col2 = other;

    let add = function( col, other ){
      for( let i = 0; i < col.length; i++ ){
        let ele = col[ i ];
        let id = ele._private.data.id;
        let inOther = other.hasElementWithId( id );

        if( !inOther ){
          elements.push( ele );
        }
      }

    };

    add( col1, col2 );
    add( col2, col1 );

    return this.spawn( elements );
  },

  diff: function( other ){
    let cy = this._private.cy;

    if( is.string( other ) ){
      other = cy.$( other );
    }

    let left = [];
    let right = [];
    let both = [];
    let col1 = this;
    let col2 = other;

    let add = function( col, other, retEles ){

      for( let i = 0; i < col.length; i++ ){
        let ele = col[ i ];
        let id = ele._private.data.id;
        let inOther = other.hasElementWithId( id );

        if( inOther ){
          both.push( ele );
        } else {
          retEles.push( ele );
        }
      }

    };

    add( col1, col2, left );
    add( col2, col1, right );

    return {
      left: this.spawn( left, { unique: true } ),
      right: this.spawn( right, { unique: true } ),
      both: this.spawn( both, { unique: true } )
    };
  },

  add: function( toAdd ){
    let cy = this._private.cy;

    if( !toAdd ){
      return this;
    }

    if( is.string( toAdd ) ){
      let selector = toAdd;
      toAdd = cy.mutableElements().filter( selector );
    }

    let elements = [];

    for( let i = 0; i < this.length; i++ ){
      elements.push( this[ i ] );
    }

    let map = this._private.map;

    for( let i = 0; i < toAdd.length; i++ ){

      let add = !map.has( toAdd[ i ].id() );
      if( add ){
        elements.push( toAdd[ i ] );
      }
    }

    return this.spawn( elements );
  },

  // in place merge on calling collection
  merge: function( toAdd ){
    let _p = this._private;
    let cy = _p.cy;

    if( !toAdd ){
      return this;
    }

    if( toAdd && is.string( toAdd ) ){
      let selector = toAdd;
      toAdd = cy.mutableElements().filter( selector );
    }

    let map = _p.map;

    for( let i = 0; i < toAdd.length; i++ ){
      let toAddEle = toAdd[ i ];
      let id = toAddEle._private.data.id;
      let add = !map.has( id );

      if( add ){
        let index = this.length++;

        this[ index ] = toAddEle;

        map.set( id, { ele: toAddEle, index: index } );
      } else { // replace
        let index = map.get( id ).index;

        this[ index ] = toAddEle;
        map.set( id, { ele: toAddEle, index: index } );
      }
    }

    return this; // chaining
  },

  // remove single ele in place in calling collection
  unmergeOne: function( ele ){
    ele = ele[0];

    let _p = this._private;
    let id = ele._private.data.id;
    let map = _p.map;
    let entry =  map.get( id );

    if( !entry ){
      return this; // no need to remove
    }

    let i = entry.index;

    // remove ele
    this[ i ] = undefined;
    map.delete( id );

    let unmergedLastEle = i === this.length - 1;

    // replace empty spot with last ele in collection
    if( this.length > 1 && !unmergedLastEle ){
      let lastEleI = this.length - 1;
      let lastEle = this[ lastEleI ];
      let lastEleId = lastEle._private.data.id;

      this[ lastEleI ] = undefined;
      this[ i ] = lastEle;
      map.set( lastEleId, { ele: lastEle, index: i } );
    }

    // the collection is now 1 ele smaller
    this.length--;

    return this;
  },

  // remove eles in place on calling collection
  unmerge: function( toRemove ){
    let cy = this._private.cy;

    if( !toRemove ){
      return this;
    }

    if( toRemove && is.string( toRemove ) ){
      let selector = toRemove;
      toRemove = cy.mutableElements().filter( selector );
    }

    for( let i = 0; i < toRemove.length; i++ ){
      this.unmergeOne( toRemove[ i ] );
    }

    return this; // chaining
  },

  map: function( mapFn, thisArg ){
    let arr = [];
    let eles = this;

    for( let i = 0; i < eles.length; i++ ){
      let ele = eles[ i ];
      let ret = thisArg ? mapFn.apply( thisArg, [ ele, i, eles ] ) : mapFn( ele, i, eles );

      arr.push( ret );
    }

    return arr;
  },

  reduce: function( fn, initialValue ){
    let val = initialValue;
    let eles = this;

    for( let i = 0; i < eles.length; i++ ){
      val = fn( val, eles[i], i, eles );
    }

    return val;
  },

  max: function( valFn, thisArg ){
    let max = -Infinity;
    let maxEle;
    let eles = this;

    for( let i = 0; i < eles.length; i++ ){
      let ele = eles[ i ];
      let val = thisArg ? valFn.apply( thisArg, [ ele, i, eles ] ) : valFn( ele, i, eles );

      if( val > max ){
        max = val;
        maxEle = ele;
      }
    }

    return {
      value: max,
      ele: maxEle
    };
  },

  min: function( valFn, thisArg ){
    let min = Infinity;
    let minEle;
    let eles = this;

    for( let i = 0; i < eles.length; i++ ){
      let ele = eles[ i ];
      let val = thisArg ? valFn.apply( thisArg, [ ele, i, eles ] ) : valFn( ele, i, eles );

      if( val < min ){
        min = val;
        minEle = ele;
      }
    }

    return {
      value: min,
      ele: minEle
    };
  }
});

// aliases
let fn = elesfn;
fn[ 'u' ] = fn[ '|' ] = fn[ '+' ] = fn.union = fn.or = fn.add;
fn[ '\\' ] = fn[ '!' ] = fn[ '-' ] = fn.difference = fn.relativeComplement = fn.subtract = fn.not;
fn[ 'n' ] = fn[ '&' ] = fn[ '.' ] = fn.and = fn.intersection = fn.intersect;
fn[ '^' ] = fn[ '(+)' ] = fn[ '(-)' ] = fn.symmetricDifference = fn.symdiff = fn.xor;
fn.fnFilter = fn.filterFn = fn.stdFilter = fn.filter;
fn.complement = fn.abscomp = fn.absoluteComplement;

export default elesfn;
<commit_msg>Add internal `eles.byGroup()` function to get the nodes separate from the edges in the calling collection, e.g.`let { nodes, edges } = eles.byGroup();`<commit_after>import * as is from '../is';
import Selector from '../selector';

let elesfn = ({
  nodes: function( selector ){
    return this.filter( ele => ele.isNode() ).filter( selector );
  },

  edges: function( selector ){
    return this.filter( ele => ele.isEdge() ).filter( selector );
  },

  // internal helper to get nodes and edges as separate collections with single iteration over elements
  byGroup: function(){
    let cy = this.cy();
    let nodes = cy.collection();
    let edges = cy.collection();

    for( let i = 0; i < this.length; i++ ){
      let ele = this[i];

      if( ele.isNode() ){
        nodes.merge(ele);
      } else {
        edges.merge(ele);
      }
    }

    return { nodes, edges };
  },

  filter: function( filter, thisArg ){
    if( filter === undefined ){ // check this first b/c it's the most common/performant case
      return this;
    } else if( is.string( filter ) || is.elementOrCollection( filter ) ){
      return new Selector( filter ).filter( this );
    } else if( is.fn( filter ) ){
      let filterEles = this.spawn();
      let eles = this;

      for( let i = 0; i < eles.length; i++ ){
        let ele = eles[ i ];
        let include = thisArg ? filter.apply( thisArg, [ ele, i, eles ] ) : filter( ele, i, eles );

        if( include ){
          filterEles.merge( ele );
        }
      }

      return filterEles;
    }

    return this.spawn(); // if not handled by above, give 'em an empty collection
  },

  not: function( toRemove ){
    if( !toRemove ){
      return this;
    } else {

      if( is.string( toRemove ) ){
        toRemove = this.filter( toRemove );
      }

      let elements = [];
      let rMap = toRemove._private.map;

      for( let i = 0; i < this.length; i++ ){
        let element = this[ i ];

        let remove = rMap.has( element.id() );
        if( !remove ){
          elements.push( element );
        }
      }

      return this.spawn( elements );
    }

  },

  absoluteComplement: function(){
    let cy = this.cy();

    return cy.mutableElements().not( this );
  },

  intersect: function( other ){
    // if a selector is specified, then filter by it instead
    if( is.string( other ) ){
      let selector = other;
      return this.filter( selector );
    }

    let elements = [];
    let col1 = this;
    let col2 = other;
    let col1Smaller = this.length < other.length;
    let map2 = col1Smaller ? col2._private.map : col1._private.map;
    let col = col1Smaller ? col1 : col2;

    for( let i = 0; i < col.length; i++ ){
      let id = col[ i ]._private.data.id;
      let entry = map2.get( id );

      if( entry ){
        elements.push( entry.ele );
      }
    }

    return this.spawn( elements );
  },

  xor: function( other ){
    let cy = this._private.cy;

    if( is.string( other ) ){
      other = cy.$( other );
    }

    let elements = [];
    let col1 = this;
    let col2 = other;

    let add = function( col, other ){
      for( let i = 0; i < col.length; i++ ){
        let ele = col[ i ];
        let id = ele._private.data.id;
        let inOther = other.hasElementWithId( id );

        if( !inOther ){
          elements.push( ele );
        }
      }

    };

    add( col1, col2 );
    add( col2, col1 );

    return this.spawn( elements );
  },

  diff: function( other ){
    let cy = this._private.cy;

    if( is.string( other ) ){
      other = cy.$( other );
    }

    let left = [];
    let right = [];
    let both = [];
    let col1 = this;
    let col2 = other;

    let add = function( col, other, retEles ){

      for( let i = 0; i < col.length; i++ ){
        let ele = col[ i ];
        let id = ele._private.data.id;
        let inOther = other.hasElementWithId( id );

        if( inOther ){
          both.push( ele );
        } else {
          retEles.push( ele );
        }
      }

    };

    add( col1, col2, left );
    add( col2, col1, right );

    return {
      left: this.spawn( left, { unique: true } ),
      right: this.spawn( right, { unique: true } ),
      both: this.spawn( both, { unique: true } )
    };
  },

  add: function( toAdd ){
    let cy = this._private.cy;

    if( !toAdd ){
      return this;
    }

    if( is.string( toAdd ) ){
      let selector = toAdd;
      toAdd = cy.mutableElements().filter( selector );
    }

    let elements = [];

    for( let i = 0; i < this.length; i++ ){
      elements.push( this[ i ] );
    }

    let map = this._private.map;

    for( let i = 0; i < toAdd.length; i++ ){

      let add = !map.has( toAdd[ i ].id() );
      if( add ){
        elements.push( toAdd[ i ] );
      }
    }

    return this.spawn( elements );
  },

  // in place merge on calling collection
  merge: function( toAdd ){
    let _p = this._private;
    let cy = _p.cy;

    if( !toAdd ){
      return this;
    }

    if( toAdd && is.string( toAdd ) ){
      let selector = toAdd;
      toAdd = cy.mutableElements().filter( selector );
    }

    let map = _p.map;

    for( let i = 0; i < toAdd.length; i++ ){
      let toAddEle = toAdd[ i ];
      let id = toAddEle._private.data.id;
      let add = !map.has( id );

      if( add ){
        let index = this.length++;

        this[ index ] = toAddEle;

        map.set( id, { ele: toAddEle, index: index } );
      } else { // replace
        let index = map.get( id ).index;

        this[ index ] = toAddEle;
        map.set( id, { ele: toAddEle, index: index } );
      }
    }

    return this; // chaining
  },

  // remove single ele in place in calling collection
  unmergeOne: function( ele ){
    ele = ele[0];

    let _p = this._private;
    let id = ele._private.data.id;
    let map = _p.map;
    let entry =  map.get( id );

    if( !entry ){
      return this; // no need to remove
    }

    let i = entry.index;

    // remove ele
    this[ i ] = undefined;
    map.delete( id );

    let unmergedLastEle = i === this.length - 1;

    // replace empty spot with last ele in collection
    if( this.length > 1 && !unmergedLastEle ){
      let lastEleI = this.length - 1;
      let lastEle = this[ lastEleI ];
      let lastEleId = lastEle._private.data.id;

      this[ lastEleI ] = undefined;
      this[ i ] = lastEle;
      map.set( lastEleId, { ele: lastEle, index: i } );
    }

    // the collection is now 1 ele smaller
    this.length--;

    return this;
  },

  // remove eles in place on calling collection
  unmerge: function( toRemove ){
    let cy = this._private.cy;

    if( !toRemove ){
      return this;
    }

    if( toRemove && is.string( toRemove ) ){
      let selector = toRemove;
      toRemove = cy.mutableElements().filter( selector );
    }

    for( let i = 0; i < toRemove.length; i++ ){
      this.unmergeOne( toRemove[ i ] );
    }

    return this; // chaining
  },

  map: function( mapFn, thisArg ){
    let arr = [];
    let eles = this;

    for( let i = 0; i < eles.length; i++ ){
      let ele = eles[ i ];
      let ret = thisArg ? mapFn.apply( thisArg, [ ele, i, eles ] ) : mapFn( ele, i, eles );

      arr.push( ret );
    }

    return arr;
  },

  reduce: function( fn, initialValue ){
    let val = initialValue;
    let eles = this;

    for( let i = 0; i < eles.length; i++ ){
      val = fn( val, eles[i], i, eles );
    }

    return val;
  },

  max: function( valFn, thisArg ){
    let max = -Infinity;
    let maxEle;
    let eles = this;

    for( let i = 0; i < eles.length; i++ ){
      let ele = eles[ i ];
      let val = thisArg ? valFn.apply( thisArg, [ ele, i, eles ] ) : valFn( ele, i, eles );

      if( val > max ){
        max = val;
        maxEle = ele;
      }
    }

    return {
      value: max,
      ele: maxEle
    };
  },

  min: function( valFn, thisArg ){
    let min = Infinity;
    let minEle;
    let eles = this;

    for( let i = 0; i < eles.length; i++ ){
      let ele = eles[ i ];
      let val = thisArg ? valFn.apply( thisArg, [ ele, i, eles ] ) : valFn( ele, i, eles );

      if( val < min ){
        min = val;
        minEle = ele;
      }
    }

    return {
      value: min,
      ele: minEle
    };
  }
});

// aliases
let fn = elesfn;
fn[ 'u' ] = fn[ '|' ] = fn[ '+' ] = fn.union = fn.or = fn.add;
fn[ '\\' ] = fn[ '!' ] = fn[ '-' ] = fn.difference = fn.relativeComplement = fn.subtract = fn.not;
fn[ 'n' ] = fn[ '&' ] = fn[ '.' ] = fn.and = fn.intersection = fn.intersect;
fn[ '^' ] = fn[ '(+)' ] = fn[ '(-)' ] = fn.symmetricDifference = fn.symdiff = fn.xor;
fn.fnFilter = fn.filterFn = fn.stdFilter = fn.filter;
fn.complement = fn.abscomp = fn.absoluteComplement;

export default elesfn;
<|endoftext|>