import {
  ContextMenu,
  ContextMenuItem,
  _ContextMenuItem,
} from "content-src/components/ContextMenu/ContextMenu";
import { ContextMenuButton } from "content-src/components/ContextMenu/ContextMenuButton";
import { mount, shallow } from "enzyme";
import React from "react";
import { INITIAL_STATE, reducers } from "common/Reducers.sys.mjs";
import { Provider } from "react-redux";
import { combineReducers, createStore } from "redux";

const DEFAULT_PROPS = {
  onUpdate: () => {},
  options: [],
  tabbableOptionsLength: 0,
};

const DEFAULT_MENU_OPTIONS = [
  "MoveUp",
  "MoveDown",
  "Separator",
  "ManageSection",
];

const FakeMenu = props => {
  return <div>{props.children}</div>;
};

describe("<ContextMenuButton>", () => {
  // eslint-disable-next-line no-shadow
  function mountWithProps(options) {
    const store = createStore(combineReducers(reducers), INITIAL_STATE);
    return mount(
      <Provider store={store}>
        <ContextMenuButton>
          <ContextMenu options={options} />
        </ContextMenuButton>
      </Provider>
    );
  }

  let sandbox;
  beforeEach(() => {
    sandbox = sinon.createSandbox();
  });
  afterEach(() => {
    sandbox.restore();
  });
  it("should call onUpdate when clicked", () => {
    const onUpdate = sandbox.spy();
    const wrapper = mount(
      <ContextMenuButton onUpdate={onUpdate}>
        <FakeMenu />
      </ContextMenuButton>
    );
    wrapper.find(".context-menu-button").simulate("click");
    assert.calledOnce(onUpdate);
  });
  it("should call onUpdate when activated with Enter", () => {
    const onUpdate = sandbox.spy();
    const wrapper = mount(
      <ContextMenuButton onUpdate={onUpdate}>
        <FakeMenu />
      </ContextMenuButton>
    );
    wrapper.find(".context-menu-button").simulate("keydown", { key: "Enter" });
    assert.calledOnce(onUpdate);
  });
  it("should call onClick", () => {
    const onClick = sandbox.spy(ContextMenuButton.prototype, "onClick");
    const wrapper = mount(
      <ContextMenuButton>
        <FakeMenu />
      </ContextMenuButton>
    );
    wrapper.find("button").simulate("click");
    assert.calledOnce(onClick);
  });
  it("should have a default keyboardAccess prop of false", () => {
    const wrapper = mountWithProps(DEFAULT_MENU_OPTIONS);
    wrapper.find(ContextMenuButton).setState({ showContextMenu: true });
    assert.equal(wrapper.find(ContextMenu).prop("keyboardAccess"), false);
  });
  it("should pass the keyboardAccess prop down to ContextMenu", () => {
    const wrapper = mountWithProps(DEFAULT_MENU_OPTIONS);
    wrapper
      .find(ContextMenuButton)
      .setState({ showContextMenu: true, contextMenuKeyboard: true });
    assert.equal(wrapper.find(ContextMenu).prop("keyboardAccess"), true);
  });
  it("should call focusFirst when keyboardAccess is true", () => {
    // eslint-disable-next-line no-shadow
    const options = [{ label: "item1", first: true }];
    const wrapper = mountWithProps(options);
    const focusFirst = sandbox.spy(_ContextMenuItem.prototype, "focusFirst");
    wrapper
      .find(ContextMenuButton)
      .setState({ showContextMenu: true, contextMenuKeyboard: true });
    assert.calledOnce(focusFirst);
  });
});

describe("<ContextMenu>", () => {
  function mountWithProps(props) {
    const store = createStore(combineReducers(reducers), INITIAL_STATE);
    return mount(
      <Provider store={store}>
        <ContextMenu {...props} />
      </Provider>
    );
  }

  it("should render all the options provided", () => {
    // eslint-disable-next-line no-shadow
    const options = [
      { label: "item1" },
      { type: "separator" },
      { label: "item2" },
    ];
    const wrapper = shallow(
      <ContextMenu {...DEFAULT_PROPS} options={options} />
    );
    assert.lengthOf(wrapper.find(".context-menu-list").children(), 3);
  });
  it("should not add a link for a separator", () => {
    // eslint-disable-next-line no-shadow
    const options = [{ label: "item1" }, { type: "separator" }];
    const wrapper = shallow(
      <ContextMenu {...DEFAULT_PROPS} options={options} />
    );
    assert.lengthOf(wrapper.find(".separator"), 1);
  });
  it("should add a link for all types that are not separators", () => {
    // eslint-disable-next-line no-shadow
    const options = [{ label: "item1" }, { type: "separator" }];
    const wrapper = shallow(
      <ContextMenu {...DEFAULT_PROPS} options={options} />
    );
    assert.lengthOf(wrapper.find(ContextMenuItem), 1);
  });
  it("should not add an icon to any items", () => {
    const props = Object.assign({}, DEFAULT_PROPS, {
      options: [{ label: "item1", icon: "icon1" }, { type: "separator" }],
    });
    const wrapper = mountWithProps(props);
    assert.lengthOf(wrapper.find(".icon-icon1"), 0);
  });
  it("should be tabbable", () => {
    const props = {
      options: [{ label: "item1", icon: "icon1" }, { type: "separator" }],
    };
    const wrapper = mountWithProps(props);
    assert.equal(
      wrapper.find(".context-menu-item").props().role,
      "presentation"
    );
  });
  it("should call onUpdate with false when an option is clicked", () => {
    const onUpdate = sinon.spy();
    const onClick = sinon.spy();
    const props = Object.assign({}, DEFAULT_PROPS, {
      onUpdate,
      options: [{ label: "item1", onClick }],
    });
    const wrapper = mountWithProps(props);
    wrapper.find(".context-menu-item button").simulate("click");
    assert.calledOnce(onUpdate);
    assert.calledOnce(onClick);
  });
  it("should not have disabled className by default", () => {
    const props = Object.assign({}, DEFAULT_PROPS, {
      options: [{ label: "item1", icon: "icon1" }, { type: "separator" }],
    });
    const wrapper = mountWithProps(props);
    assert.lengthOf(wrapper.find(".context-menu-item a.disabled"), 0);
  });
  it("should add disabled className to any disabled options", () => {
    // eslint-disable-next-line no-shadow
    const options = [
      { label: "item1", icon: "icon1", disabled: true },
      { type: "separator" },
    ];
    const props = Object.assign({}, DEFAULT_PROPS, { options });
    const wrapper = mountWithProps(props);
    assert.lengthOf(wrapper.find(".context-menu-item button.disabled"), 1);
  });
  it("should have the context-menu-item class", () => {
    // eslint-disable-next-line no-shadow
    const options = [{ label: "item1", icon: "icon1" }];
    const props = Object.assign({}, DEFAULT_PROPS, { options });
    const wrapper = mountWithProps(props);
    assert.lengthOf(wrapper.find(".context-menu-item"), 1);
  });
  it("should call onClick when onKeyDown is called with Enter", () => {
    const onClick = sinon.spy();
    const props = Object.assign({}, DEFAULT_PROPS, {
      options: [{ label: "item1", onClick }],
    });
    const wrapper = mountWithProps(props);
    wrapper
      .find(".context-menu-item button")
      .simulate("keydown", { key: "Enter" });
    assert.calledOnce(onClick);
  });
  it("should call focusSibling when onKeyDown is called with ArrowUp", () => {
    const props = Object.assign({}, DEFAULT_PROPS, {
      options: [{ label: "item1" }],
    });
    const wrapper = mountWithProps(props);
    const focusSibling = sinon.stub(
      wrapper.find(_ContextMenuItem).instance(),
      "focusSibling"
    );
    wrapper
      .find(".context-menu-item button")
      .simulate("keydown", { key: "ArrowUp" });
    assert.calledOnce(focusSibling);
  });
  it("should call focusSibling when onKeyDown is called with ArrowDown", () => {
    const props = Object.assign({}, DEFAULT_PROPS, {
      options: [{ label: "item1" }],
    });
    const wrapper = mountWithProps(props);
    const focusSibling = sinon.stub(
      wrapper.find(_ContextMenuItem).instance(),
      "focusSibling"
    );
    wrapper
      .find(".context-menu-item button")
      .simulate("keydown", { key: "ArrowDown" });
    assert.calledOnce(focusSibling);
  });
});
