import React from 'react';
import { mount, configure } from 'enzyme';
import { spy } from 'sinon';
import Adapter from 'enzyme-adapter-react-16';
import Dropdown from '../src/Dropdown';
import { Spinner } from '@procore/core-react';

configure({ adapter: new Adapter() });

function getSpies (actions = []) {
  return actions.reduce((acc, action) => {
    acc[action] = spy();
    return acc;
  }, {});
}

function fullRender (actions, ...rest) {
  const requiredActions = {
    advance: () => {},
    back: () => {},
    clearSearch: () => {},
    onChange: () => {},
    onSelect: () => {},
    onKeyDown: () => {},
    setHighlightIndex: () => {},
  };

  const requiredProps = {
    actions: Object.assign(requiredActions, getSpies(actions)),
    children: () => {},
    composedStyles: {
      clearIcon: 'clear-icon',
      clearTarget: 'clear-target',
      crumbs: 'crumbs',
      search: 'search',
      searchIcon: 'search-icon',
      clippedResults: 'clippped-results'
    },
    i18n: {
      loading: 'Loading',
      noSearchResults: 'No Results Found'
    },
    didClipData: false
  };

  const mergedProps = Object.assign({}, requiredProps, ...rest);
  const wrapper = mount(<Dropdown { ...mergedProps } />);

  return {
    actions: mergedProps.actions,
    children: mergedProps.children,
    composedStyles: mergedProps.composedStyles,
    wrapper
  }
};

describe('Dropdown', () => {
  describe('navigation', () => {
    it('should render a bread crumb navigation', () => {
      const { composedStyles, wrapper } = fullRender();
      expect(wrapper.find('nav')).toHaveLength(1);
    });

    it('should render with an search input', () => {
      const { composedStyles, wrapper } = fullRender();

      expect(wrapper.find(`.${composedStyles.search}`)).toHaveLength(1);
    });

    describe('search icon', () => {
      describe('when there is no search term', () => {
        const { composedStyles, wrapper } = fullRender();

        it('should render the search icon', () => {
          expect(wrapper.find(`.${composedStyles.searchIcon}`)).toHaveLength(1);
        });
      });

      describe('where there is a search term', () => {
        const { actions, composedStyles, wrapper } = fullRender(
          ['setHighlightIndex', 'clearSearch'],
          { searchTerm: 'procore' }
        );

        it('shoud render the target button and clear icon', () => {
          expect(wrapper.find(`.${composedStyles.clearTarget}`)).toHaveLength(1);
          expect(wrapper.find(`.${composedStyles.clearIcon}`)).toHaveLength(1);
        });

        describe('when the clear icon is clicked', () => {
          wrapper.find(`.${composedStyles.clearTarget}`).simulate('click');

          it('should clear the search input', () => {
            expect(actions.clearSearch.calledOnce).toBe(true);
          });
        });

        it('should reset the highlight index to 0', () => {
          expect(actions.setHighlightIndex.calledWith(0)).toBe(true);
        });
      });
    });

    describe('when a previous crumb is clicked', () =>{
      const breadcrumbs = [ 'Companies', 'Projects', 'Tools' ]
      const { actions, wrapper } = fullRender(
        ['back', 'setHighlightIndex', 'clearSearch'],
        { breadcrumbs }
      );
      const crumbs = wrapper.find('nav').find('a');

      expect(crumbs.length).toEqual(breadcrumbs.length - 1);

      const instance = wrapper.instance();

      spy(instance.searchBarRef, 'focus');

      describe('when the last linked breadcrumb is clicked', () => {
        it('should call back(..) with how far back to move in the list', () => {
          crumbs.last().simulate('click');

          expect(actions.back.calledWith(
            breadcrumbs.length - 1 - 1,
            instance.listInitialState
          )).toBe(true);
        });
      });

      describe('when the first breadcrumb is clicked', () => {
        it('should call back(..) with how far back to move in the list', () => {
          crumbs.first().simulate('click');

          expect(actions.back.calledWith(
            breadcrumbs.length - 0 - 1,
            instance.listInitialState
          )).toBe(true);
        });
      });
    });
  });

  describe('list', () => {
    const props = {
      data: [
        { id: 1, name: 'Brickworks, Inc.' },
        { id: 2, name: 'Procore, Inc.' }
      ],
      isLoading: false
    };

    describe('handlers', () => {
      const { actions, wrapper } = fullRender(
        ['onSelect', 'setHighlightIndex'],
        props
      );

      const list = wrapper.find('li');

      it('should render a list with the same number of items as the props data', () => {
        expect(list.length).toBe(props.data.length);
      });

      xit('should not render the spinner when there is no data loading', () => {
          // TODO: core-react Spinner is present when no data is loading
          expect(wrapper.find('Spinner')).toHaveLength(0);
      });

      it('should call onSelect(..) with the correct data when an items is clicked', () => {
        list.map((item, i) => {
          item.simulate('click');
          expect(actions.onSelect.args[i][0]).toBe(props.data[i]);
        });
      });

      it('should call setHighlightIndex(..) with the correct item index when moused over', () => {
        list.map((item, i) => {
          item.simulate('mouseover');
          expect(actions.setHighlightIndex.args[i][0]).toBe(i);
        });
      });
    });

    describe('when data is loading', () => {
      const clonedProps = Object.assign({}, props, { isLoading: true })
      const { wrapper } = fullRender([], clonedProps);

      it('should render the spinner', () => {
        expect(wrapper.find('Spinner')).toHaveLength(1);
      });

      it('should render the list items', () => {
        expect(wrapper.find('li')).toHaveLength(2);
      });
    });

    describe('list children', () => {
      const clonedProps = Object.assign({}, props, { children: spy() })

      const { actions, children, wrapper } = fullRender(['advance'], clonedProps);
      const e = { stopPropagation: spy() };

      const list = wrapper.find('li');
      const instance = wrapper.instance();

      it('should pass the correct parameters to the list children', () => {
        list.map((item, i) => {
          expect(children.args[i][0]).toBe(props.data[i]);
        });
      });

      describe('partial application of click handlers', () => {
        list.map((item, i) => {
          children.args[i][1](e);
        });

        it('should call stopPropagation on the event object', () => {
          list.map((item, i) => {
            expect(e.stopPropagation.args[i].length).toEqual(0);
          });
        });

        it('should call advance with the correct option data', () => {
          list.map((item, i) => {
            expect(actions.advance.args[i][0]).toBe(props.data[i]);
          });
        });

        it('should pass in listInitialState(..) as a callback to advance(..)', () => {
          list.map((item, i) => {
            expect(actions.advance.args[i][1]).toBe(instance.listInitialState);
          });
        });
      });
    });

    describe('clipped results', () => {
      describe('when there are no clipped results', () => {
        const { composedStyles, wrapper } = fullRender([], props);

        it('should not show the clipped results message', () => {
          expect(wrapper.find(`.${composedStyles.clippedResults}`)).toHaveLength(0);
        });
      });
      describe('when there are clipped results', () => {
        const clippedResultsMessage = 'Oops, too many results!';
        const clonedProps = Object.assign({}, props, {
          didClipData: true,
          clippedResultsMessage
        });
        const { composedStyles, wrapper } = fullRender([], clonedProps);
        const clippedResults = wrapper.find(`.${composedStyles.clippedResults}`);

        it('should have one clipped results element', () => {
          expect(clippedResults).toHaveLength(1);
        });

        it('should show the clipped results message', () => {
          expect(clippedResults.text()).toEqual(clippedResultsMessage);
        });
      });
    });
  });
});
