'use strict';

var _interopRequire = function (obj) { return obj && obj.__esModule ? obj['default'] : obj; };

var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } };

var _inherits = function (subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; };

var _fluxMixin = require('../fluxMixin');

var fluxMixin = _interopRequire(_fluxMixin);

var _Flummox$Store$Actions = require('../../Flux');

var _addContext = require('./addContext');

var addContext = _interopRequire(_addContext);

var _sinon = require('sinon');

var sinon = _interopRequire(_sinon);

var _React = require('react/addons');

var React = _interopRequire(_React);

var PropTypes = React.PropTypes;
var TestUtils = React.addons.TestUtils;

describe('fluxMixin', function () {
  var TestActions = (function (_Actions) {
    function TestActions() {
      _classCallCheck(this, TestActions);

      if (_Actions != null) {
        _Actions.apply(this, arguments);
      }
    }

    _inherits(TestActions, _Actions);

    TestActions.prototype.getSomething = function getSomething(something) {
      return something;
    };

    return TestActions;
  })(_Flummox$Store$Actions.Actions);

  var TestStore = (function (_Store) {
    function TestStore(flux) {
      _classCallCheck(this, TestStore);

      _Store.call(this);

      var testActions = flux.getActions('test');
      this.register(testActions.getSomething, this.handleGetSomething);

      this.state = {
        something: null
      };
    }

    _inherits(TestStore, _Store);

    TestStore.prototype.handleGetSomething = function handleGetSomething(something) {
      this.setState({ something: something });
    };

    return TestStore;
  })(_Flummox$Store$Actions.Store);

  var Flux = (function (_Flummox) {
    function Flux() {
      _classCallCheck(this, Flux);

      _Flummox.call(this);

      this.createActions('test', TestActions);
      this.createStore('test', TestStore, this);
      this.createStore('test2', TestStore, this);
    }

    _inherits(Flux, _Flummox);

    return Flux;
  })(_Flummox$Store$Actions.Flummox);

  var ComponentWithFluxMixin = React.createClass({
    displayName: 'ComponentWithFluxMixin',

    mixins: [fluxMixin()],

    render: function render() {
      return null;
    }
  });

  it('gets flux from either props or context', function () {
    var flux = new Flux();
    var contextComponent = undefined,
        propsComponent = undefined;

    var ContextComponent = addContext(ComponentWithFluxMixin, { flux: flux }, { flux: React.PropTypes.instanceOf(_Flummox$Store$Actions.Flummox) });

    var tree = TestUtils.renderIntoDocument(React.createElement(ContextComponent, null));

    contextComponent = TestUtils.findRenderedComponentWithType(tree, ComponentWithFluxMixin);

    propsComponent = TestUtils.renderIntoDocument(React.createElement(ComponentWithFluxMixin, { flux: flux }));

    expect(contextComponent.flux).to.be.an['instanceof'](_Flummox$Store$Actions.Flummox);
    expect(propsComponent.flux).to.be.an['instanceof'](_Flummox$Store$Actions.Flummox);
  });

  it('exposes flux as context', function () {
    var flux = new Flux();

    var ChildComponent = React.createClass({
      displayName: 'ChildComponent',

      contextTypes: {
        flux: PropTypes.instanceOf(_Flummox$Store$Actions.Flummox) },

      render: function render() {
        return React.createElement('div', null);
      }
    });

    var Component = React.createClass({
      displayName: 'Component',

      mixins: [fluxMixin()],

      render: function render() {
        return React.createElement(
          'div',
          null,
          React.createElement(ChildComponent, { key: 'test' })
        );
      }
    });

    var tree = TestUtils.renderIntoDocument(React.createElement(Component, { flux: flux }));

    var childComponent = TestUtils.findRenderedComponentWithType(tree, ChildComponent);

    expect(childComponent.context.flux).to.equal(flux);
  });

  it('throws error if neither props or context is set', function () {
    var flux = new Flux();

    expect(TestUtils.renderIntoDocument.bind(null, React.createElement(ComponentWithFluxMixin, null))).to['throw']('fluxMixin: Could not find Flux instance. Ensure that your component ' + 'has either `this.context.flux` or `this.props.flux`.');
  });

  it('ignores change event after unmounted', function () {
    var flux = new Flux();
    flux.getActions('test').getSomething('foo');

    var getterMap = {
      test: function test(store) {
        return { something: store.state.something };
      }
    };
    var Component = React.createClass({
      displayName: 'Component',

      mixins: [fluxMixin(getterMap)],

      render: function render() {
        return null;
      }
    });

    var container = document.createElement('div');
    var component = React.render(React.createElement(Component, { flux: flux }), container);
    var listener = flux.getStore('test').listeners('change')[0];

    React.unmountComponentAtNode(container);

    flux.getActions('test').getSomething('bar');
    listener();

    expect(component.state.something).to.equal('foo');
  });

  it('uses #connectToStores() to get initial state', function () {
    var flux = new Flux();

    flux.getActions('test').getSomething('foobar');

    var getterMap = {
      test: function test(store) {
        return {
          something: store.state.something,
          custom: true };
      } };

    var mixin = fluxMixin(getterMap);

    var connectToStores = sinon.spy(mixin, 'connectToStores');

    var Component = React.createClass({
      displayName: 'Component',

      mixins: [mixin],

      getInitialState: function getInitialState() {
        return {
          foobar: 'baz' };
      },

      render: function render() {
        return null;
      }
    });

    var component = TestUtils.renderIntoDocument(React.createElement(Component, { key: 'test', flux: flux }));

    expect(connectToStores.calledOnce).to.be['true'];
    expect(connectToStores.firstCall.args[0]).to.equal(getterMap);

    expect(flux.getStore('test').listeners('change')).to.have.length(1);

    expect(component.state).to.deep.equal({
      something: 'foobar',
      custom: true,
      foobar: 'baz' });
  });

  describe('#connectToStores', function () {

    it('returns initial state', function () {
      var flux = new Flux();

      var component = TestUtils.renderIntoDocument(React.createElement(ComponentWithFluxMixin, { key: 'test', flux: flux }));

      var initialState = component.connectToStores('test');

      expect(initialState).to.deep.equal({
        something: null });
    });

    it('merges store state with component state on change', function () {
      var flux = new Flux();

      var component = TestUtils.renderIntoDocument(React.createElement(ComponentWithFluxMixin, { key: 'test', flux: flux }));

      component.setState({ otherThing: 'barbaz' });

      component.connectToStores('test');
      flux.getActions('test').getSomething('foobar');

      expect(component.state).to.deep.equal({
        something: 'foobar',
        otherThing: 'barbaz' });
    });

    it('uses custom state getter, if given', function () {
      var flux = new Flux();

      var component = TestUtils.renderIntoDocument(React.createElement(ComponentWithFluxMixin, { key: 'test', flux: flux, bar: 'baz' }));

      component.setState({ otherThing: 'barbaz' });

      component.connectToStores('test', function (store, props) {
        return {
          something: store.state.something,
          barbaz: 'bar' + props.bar };
      });

      flux.getActions('test').getSomething('foobar');

      expect(component.state).to.deep.equal({
        something: 'foobar',
        otherThing: 'barbaz',
        barbaz: 'barbaz' });
    });

    it('syncs with store after prop change', function () {
      var flux = new Flux();

      var Component = React.createClass({
        displayName: 'Component',

        mixins: [fluxMixin({
          test: function test(store, props) {
            return {
              foo: 'foo is ' + props.foo };
          } })],

        render: function render() {
          return null;
        }
      });

      var component = TestUtils.renderIntoDocument(React.createElement(Component, { key: 'test', flux: flux, foo: 'bar' }));

      expect(component.state.foo).to.equal('foo is bar');

      component.setProps({ foo: 'baz' });

      expect(component.state.foo).to.equal('foo is baz');
    });

    it('accepts object of keys to state getters', function () {
      var flux = new Flux();

      var component = TestUtils.renderIntoDocument(React.createElement(ComponentWithFluxMixin, { key: 'test', flux: flux }));

      component.setState({ otherThing: 'barbaz' });

      component.connectToStores({
        test: function test(store) {
          return {
            something: store.state.something,
            custom: true };
        } });

      flux.getActions('test').getSomething('foobar');

      expect(component.state).to.deep.equal({
        something: 'foobar',
        otherThing: 'barbaz',
        custom: true });
    });

    it('calls default state getter once with array of stores', function () {
      var flux = new Flux();

      flux.getStore('test2').setState({ otherThing: 'barbaz' });

      var component = TestUtils.renderIntoDocument(React.createElement(ComponentWithFluxMixin, { key: 'test', flux: flux }));

      component.connectToStores(['test', 'test2']);

      flux.getActions('test').getSomething('foobar');

      expect(component.state).to.deep.equal({
        something: 'foobar',
        otherThing: 'barbaz'
      });
    });

    it('calls custom state getter once with array of stores', function () {
      var flux = new Flux();
      var testStore = flux.getStore('test');
      var test2Store = flux.getStore('test2');

      testStore._testId = 'test';
      test2Store._testId = 'test2';

      var component = TestUtils.renderIntoDocument(React.createElement(ComponentWithFluxMixin, { key: 'test', flux: flux }));

      var stateGetter = sinon.stub().returns({ foo: 'bar' });
      var state = component.connectToStores(['test', 'test2'], stateGetter);

      expect(stateGetter.calledOnce).to.be['true'];
      // Use _testId as unique identifier on store.
      expect(stateGetter.firstCall.args[0][0]._testId).to.equal('test');
      expect(stateGetter.firstCall.args[0][1]._testId).to.equal('test2');

      expect(state).to.deep.equal({
        foo: 'bar'
      });
    });

    it('uses default getter if null is passed as getter', function () {
      var flux = new Flux();

      var component = TestUtils.renderIntoDocument(React.createElement(ComponentWithFluxMixin, { key: 'test', flux: flux }));

      component.setState({ otherThing: 'barbaz' });

      component.connectToStores('test', null);

      flux.getActions('test').getSomething('foobar');

      expect(component.state).to.deep.equal({
        something: 'foobar',
        otherThing: 'barbaz' });
    });

    it('removes listener before unmounting', function () {
      var flux = new Flux();
      var div = document.createElement('div');

      var component = React.render(React.createElement(ComponentWithFluxMixin, { flux: flux }), div);

      var store = flux.getStore('test');
      component.connectToStores('test');

      expect(store.listeners('change').length).to.equal(1);
      React.unmountComponentAtNode(div);
      expect(store.listeners('change').length).to.equal(0);
    });
  });

  describe('#getStoreState', function () {
    it('gets combined state of connected stores', function () {
      var flux = new Flux();

      var component = TestUtils.renderIntoDocument(React.createElement(ComponentWithFluxMixin, { key: 'test', flux: flux }));

      component.connectToStores({
        test: function test(store) {
          return {
            foo: 'bar' };
        },
        test2: function test2(store) {
          return {
            bar: 'baz'
          };
        }
      });

      component.setState({ baz: 'foo' });

      expect(component.getStoreState()).to.deep.equal({
        foo: 'bar',
        bar: 'baz'
      });
    });
  });
});