import {
  _Base as Base,
  BaseContent,
  WithDsAdmin,
} from "content-src/components/Base/Base";
import { DiscoveryStreamAdmin } from "content-src/components/DiscoveryStreamAdmin/DiscoveryStreamAdmin";
import { ErrorBoundary } from "content-src/components/ErrorBoundary/ErrorBoundary";
import React from "react";
import { Search } from "content-src/components/Search/Search";
import { shallow } from "enzyme";
import { actionCreators as ac } from "common/Actions.mjs";

describe("<Base>", () => {
  let DEFAULT_PROPS = {
    store: { getState: () => {} },
    App: { initialized: true },
    Prefs: { values: {} },
    Sections: [],
    DiscoveryStream: { config: { enabled: false } },
    dispatch: () => {},
    adminContent: {
      message: {},
    },
    document: {
      visibilityState: "visible",
      addEventListener: sinon.stub(),
      removeEventListener: sinon.stub(),
    },
  };

  it("should render Base component", () => {
    const wrapper = shallow(<Base {...DEFAULT_PROPS} />);
    assert.ok(wrapper.exists());
  });

  it("should render the BaseContent component, passing through all props", () => {
    const wrapper = shallow(<Base {...DEFAULT_PROPS} />);
    const props = wrapper.find(BaseContent).props();
    assert.deepEqual(
      props,
      DEFAULT_PROPS,
      JSON.stringify([props, DEFAULT_PROPS], null, 3)
    );
  });

  it("should render an ErrorBoundary with class base-content-fallback", () => {
    const wrapper = shallow(<Base {...DEFAULT_PROPS} />);

    assert.equal(
      wrapper.find(ErrorBoundary).first().prop("className"),
      "base-content-fallback"
    );
  });

  it("should render an WithDsAdmin if the devtools pref is true", () => {
    const wrapper = shallow(
      <Base
        {...DEFAULT_PROPS}
        Prefs={{ values: { "asrouter.devtoolsEnabled": true } }}
      />
    );
    assert.lengthOf(wrapper.find(WithDsAdmin), 1);
  });

  it("should not render an WithDsAdmin if the devtools pref is false", () => {
    const wrapper = shallow(
      <Base
        {...DEFAULT_PROPS}
        Prefs={{ values: { "asrouter.devtoolsEnabled": false } }}
      />
    );
    assert.lengthOf(wrapper.find(WithDsAdmin), 0);
  });
});

describe("<BaseContent>", () => {
  let DEFAULT_PROPS = {
    store: { getState: () => {} },
    App: { initialized: true },
    Prefs: { values: {} },
    Sections: [],
    DiscoveryStream: { config: { enabled: false }, spocs: {} },
    Weather: {},
    dispatch: () => {},
    document: {
      visibilityState: "visible",
      addEventListener: sinon.stub(),
      removeEventListener: sinon.stub(),
    },
  };

  it("should render an ErrorBoundary with a Search child", () => {
    const searchEnabledProps = Object.assign({}, DEFAULT_PROPS, {
      Prefs: { values: { showSearch: true } },
    });

    const wrapper = shallow(<BaseContent {...searchEnabledProps} />);

    assert.isTrue(wrapper.find(Search).parent().is(ErrorBoundary));
  });

  it("should dispatch a user event when the customize menu is opened or closed", () => {
    const dispatch = sinon.stub();
    const wrapper = shallow(
      <BaseContent
        {...DEFAULT_PROPS}
        dispatch={dispatch}
        App={{ customizeMenuVisible: true }}
      />
    );
    wrapper.instance().openCustomizationMenu();
    assert.calledWith(dispatch, { type: "SHOW_PERSONALIZE" });
    assert.calledWith(dispatch, ac.UserEvent({ event: "SHOW_PERSONALIZE" }));
    wrapper.instance().closeCustomizationMenu();
    assert.calledWith(dispatch, { type: "HIDE_PERSONALIZE" });
    assert.calledWith(dispatch, ac.UserEvent({ event: "HIDE_PERSONALIZE" }));
  });

  it("should render only search if no Sections are enabled", () => {
    const onlySearchProps = Object.assign({}, DEFAULT_PROPS, {
      Sections: [{ id: "highlights", enabled: false }],
      Prefs: { values: { showSearch: true } },
    });

    const wrapper = shallow(<BaseContent {...onlySearchProps} />);
    assert.lengthOf(wrapper.find(".only-search"), 1);
  });

  it("should update firstVisibleTimestamp if it is visible immediately with no event listener", () => {
    const props = Object.assign({}, DEFAULT_PROPS, {
      document: {
        visibilityState: "visible",
        addEventListener: sinon.spy(),
        removeEventListener: sinon.spy(),
      },
    });

    const wrapper = shallow(<BaseContent {...props} />);
    assert.notCalled(props.document.addEventListener);
    assert.isDefined(wrapper.state("firstVisibleTimestamp"));
  });
  it("should attach an event listener for visibility change if it is not visible", () => {
    const props = Object.assign({}, DEFAULT_PROPS, {
      document: {
        visibilityState: "hidden",
        addEventListener: sinon.spy(),
        removeEventListener: sinon.spy(),
      },
    });

    const wrapper = shallow(<BaseContent {...props} />);
    assert.calledWith(props.document.addEventListener, "visibilitychange");
    assert.notExists(wrapper.state("firstVisibleTimestamp"));
  });
  it("should remove the event listener for visibility change when unmounted", () => {
    const props = Object.assign({}, DEFAULT_PROPS, {
      document: {
        visibilityState: "hidden",
        addEventListener: sinon.spy(),
        removeEventListener: sinon.spy(),
      },
    });

    const wrapper = shallow(<BaseContent {...props} />);
    const [, listener] = props.document.addEventListener.firstCall.args;

    wrapper.unmount();
    assert.calledWith(
      props.document.removeEventListener,
      "visibilitychange",
      listener
    );
  });
  it("should remove the event listener for visibility change after becoming visible", () => {
    const listeners = new Set();
    const props = Object.assign({}, DEFAULT_PROPS, {
      document: {
        visibilityState: "hidden",
        addEventListener: (ev, cb) => listeners.add(cb),
        removeEventListener: (ev, cb) => listeners.delete(cb),
      },
    });

    const wrapper = shallow(<BaseContent {...props} />);
    assert.equal(listeners.size, 1);
    assert.notExists(wrapper.state("firstVisibleTimestamp"));

    // Simulate listeners getting called
    props.document.visibilityState = "visible";
    listeners.forEach(l => l());

    assert.equal(listeners.size, 0);
    assert.isDefined(wrapper.state("firstVisibleTimestamp"));
  });
});

describe("WithDsAdmin", () => {
  describe("rendering inner content", () => {
    it("should not set devtoolsCollapsed state for about:newtab (no hash)", () => {
      const wrapper = shallow(<WithDsAdmin hash="" />);
      assert.isTrue(
        wrapper.find(DiscoveryStreamAdmin).prop("devtoolsCollapsed")
      );
      assert.lengthOf(wrapper.find(BaseContent), 1);
    });

    it("should set devtoolsCollapsed state for about:newtab#devtools", () => {
      const wrapper = shallow(<WithDsAdmin hash="#devtools" />);
      assert.isFalse(
        wrapper.find(DiscoveryStreamAdmin).prop("devtoolsCollapsed")
      );
      assert.lengthOf(wrapper.find(BaseContent), 0);
    });

    it("should set devtoolsCollapsed state for about:newtab#devtools subroutes", () => {
      const wrapper = shallow(<WithDsAdmin hash="#devtools-foo" />);
      assert.isFalse(
        wrapper.find(DiscoveryStreamAdmin).prop("devtoolsCollapsed")
      );
      assert.lengthOf(wrapper.find(BaseContent), 0);
    });
  });
});
