import { shallow } from 'enzyme';
import React from 'react';
import { Placeholder } from 'semantic-ui-react';

import { Item, mapStateToProps } from './Item';
import { Site } from '../../../tests/storyshare';

/* eslint-disable no-console */

describe('mapStateToProps', () => {
  const site = {
    getObjectByRoute: jest.fn(),
    getObjectBySlug: jest.fn(),
  };

  beforeEach(() => {
    site.getObjectByRoute.mockClear();
    site.getObjectBySlug.mockClear();
  });

  it('retrieves the item from the DataLayer cache by ID', () => {
    const props = { id: 42, site, typeSlug: 'type-slug' };
    mapStateToProps(null, props);

    expect(site.getObjectByRoute).toHaveBeenCalled();
    expect(site.getObjectByRoute).toHaveBeenCalledWith('type-slug/42');
  });

  it('passes the item retrieved by ID to the component', () => {
    const props = { id: 42, site, typeSlug: 'type-slug' };
    site.getObjectByRoute.mockReturnValue('test item');

    expect(mapStateToProps(null, props).item).toBe('test item');
  });

  it('retrieves the item from the DataLayer cache by ID', () => {
    const props = { itemSlug: 'item-slug', site, typeSlug: 'type-slug' };
    mapStateToProps(null, props);

    expect(site.getObjectBySlug).toHaveBeenCalled();
    expect(site.getObjectBySlug).toHaveBeenCalledWith('item-slug', 'type-slug');
  });

  it('passes the item retrieved by ID to the component', () => {
    const props = { itemSlug: 'item-slug', site, typeSlug: 'type-slug' };
    site.getObjectBySlug.mockReturnValue('test item');

    expect(mapStateToProps(null, props).item).toBe('test item');
  });

  it('copes if neither an ID nor a slug are provided', () => {
    expect(mapStateToProps(null, { site }).item).toBeUndefined();
  });
});

describe('Item component', () => {
  let wrapper;

  const ITEM_FIXTURE = { };

  const defaultProps = {
    item: ITEM_FIXTURE,
    site: new Site(),
    typeSlug: 'type-slug',
  };

  beforeAll(() => {
    jest.spyOn(console, 'error').mockReturnValue();
  });

  beforeEach(() => {
    console.error.mockClear();
    wrapper = shallow(
      <Item id={ 42 } { ...defaultProps }>
        { (item) => <div>{ item }</div> }
      </Item>
    );
  });

  afterAll(() => {
    console.error.mockRestore();
  });

  it('renders', () => {
    expect(wrapper).toExist();
  });

  it('logs an error on mount if both an ID and slug are specified', () => {
    shallow(<Item id={ 42 } itemSlug="item-slug" { ...defaultProps } />);
    expect(console.error).toHaveBeenCalled();
  });

  it('logs an error on update if both an ID and slug are specified', () => {
    wrapper.setProps({ id: 42, itemSlug: 'item-slug' });
    expect(console.error).toHaveBeenCalled();
  });

  it('requests an item on mount by ID', () => {
    const site = new Site();
    const request = {
      read: jest.fn(),
    };
    jest.spyOn(site, 'req').mockReturnValue(request);

    shallow(<Item id={ 42 } { ...defaultProps } site={ site } />);

    expect(site.req).toHaveBeenCalled();
    expect(site.req).toHaveBeenCalledWith('type-slug/42');
    expect(request.read).toHaveBeenCalled();
  });

  it('requests an item on mount by slug', () => {
    const site = new Site();
    const request = {
      addParam: jest.fn().mockReturnThis(),
      read: jest.fn(),
    };
    jest.spyOn(site, 'req').mockReturnValue(request);

    shallow(<Item itemSlug="item-slug" { ...defaultProps } site={ site } />);

    expect(site.req).toHaveBeenCalled();
    expect(site.req).toHaveBeenCalledWith('type-slug');
    expect(request.addParam).toHaveBeenCalled();
    expect(request.addParam).toHaveBeenCalledWith('slug', 'item-slug');
    expect(request.read).toHaveBeenCalled();
  });

  it('requests nothing on mount if neither ID nor slug are provided', () => {
    const site = new Site();
    jest.spyOn(site, 'req');

    shallow(<Item { ...defaultProps } site={ site } />);

    expect(site.req).not.toHaveBeenCalled();
  });

  it('requests an item if the ID changes', () => {
    const site = new Site();
    const request = {
      read: jest.fn(),
    };
    jest.spyOn(site, 'req').mockReturnValue(request);

    wrapper.setProps({ id: 43, site });

    expect(site.req).toHaveBeenCalled();
    expect(site.req).toHaveBeenCalledWith('type-slug/43');
    expect(request.read).toHaveBeenCalled();
  });

  it('requests an item if the slug changes', () => {
    const site = new Site();
    const request = {
      addParam: jest.fn().mockReturnThis(),
      read: jest.fn(),
    };
    jest.spyOn(site, 'req').mockReturnValue(request);
    wrapper.setProps({ id: undefined, itemSlug: 'item-slug', site });
    site.req.mockClear();

    wrapper.setProps({ itemSlug: 'item-slug-2' });

    expect(site.req).toHaveBeenCalled();
    expect(site.req).toHaveBeenCalledWith('type-slug');
    expect(request.addParam).toHaveBeenCalled();
    expect(request.addParam).toHaveBeenCalledWith('slug', 'item-slug-2');
    expect(request.read).toHaveBeenCalled();
  });

  it('requests nothing on update if neither ID nor slug are provided', () => {
    const site = new Site();
    jest.spyOn(site, 'req');

    wrapper.setProps({ id: undefined, site });

    expect(site.req).not.toHaveBeenCalled();
  });

  it('requests a new item if the type changes', () => {
    const site = new Site();
    const request = {
      read: jest.fn(),
    };
    jest.spyOn(site, 'req').mockReturnValue(request);

    wrapper.setProps({ site, typeSlug: 'type-slug-2' });

    expect(site.req).toHaveBeenCalled();
    expect(site.req).toHaveBeenCalledWith('type-slug-2/42');
    expect(request.read).toHaveBeenCalled();
  });

  it('avoids requesting an item if props are unchanged', () => {
    const site = new Site();
    jest.spyOn(site, 'req');
    const props = { id: 42, site, typeSlug: 'type-slug' };
    wrapper.setProps(props);
    site.req.mockClear();

    wrapper.instance().componentDidUpdate(props);

    expect(site.req).not.toHaveBeenCalled();
  });

  it('calls the provided function with the item', () => {
    const children = jest.fn();
    wrapper.setProps({ children });
    expect(children).toHaveBeenCalled();
    expect(children).toHaveBeenCalledWith(ITEM_FIXTURE);
  });

  it('does nothing until the item is available', () => {
    const children = jest.fn();
    wrapper.setProps({ children, item: undefined });
    expect(wrapper).toBeEmptyRender();
    expect(children).not.toHaveBeenCalled();
  });

  it('renders the result of the provided function', () => {
    expect(wrapper).toContainReact(<div>{ ITEM_FIXTURE }</div>);
  });

  it('renders nothing if no function is provided', () => {
    wrapper.setProps({ children: undefined });
    expect(wrapper).toBeEmptyRender();
  });

  it('can display a placeholder until the item is available', () => {
    wrapper.setProps({ item: undefined, placeholder: <Placeholder /> });
    expect(wrapper).toContainReact(<Placeholder />);
  });

  it('removes the placeholder when the item arrives', () => {
    wrapper.setProps({ placeholder: <Placeholder /> });
    expect(wrapper).not.toContainReact(<Placeholder />);
  });
});
