import {
  create,
  ReactTestRenderer,
  ReactTestRendererJSON,
} from "react-test-renderer";
import { createStore } from "../../..";
import { Provider } from "@frontity/connect";
import Slot from "../slot";

/**
 * This is a type guard for app.toJSON(), because it can return either a
 * ReactTestRendererJSON or a ReactTestRendererJSON[] and TypeScript doesn't
 * know which one is it.
 *
 * When we use this function, we know it is a ReactTestRendererJSON and
 * therefore we want to access its properties, like `app.toJSON().children` or
 * `app.toJSON().props`.
 *
 * @param app - The app generated by `create`.
 *
 * @returns The result of app.toJSON() but making sure it's not an array.
 */
export const toJson = (app: ReactTestRenderer): ReactTestRendererJSON => {
  if (!Array.isArray(app.toJSON()))
    return app.toJSON() as ReactTestRendererJSON;
  throw new Error("Please use the toJsonArray function instead.");
};

/**
 * This is a type guard for app.toJSON(), because it can return either a
 * ReactTestRendererJSON or a ReactTestRendererJSON[] and TypeScript doesn't
 * know which one is it.
 *
 * When we use this function, we know it is a ReactTestRendererJSON[] and
 * therefore we want to access its elements, like `app.toJSON()[0].props`.
 *
 * @param app - The app generated by `create`.
 *
 * @returns The result of app.toJSON() but making sure it's an array.
 */
export const toJsonArray = (
  app: ReactTestRenderer
): ReactTestRendererJSON[] => {
  if (Array.isArray(app.toJSON()))
    return app.toJSON() as ReactTestRendererJSON[];
  throw new Error("Please use the toJson function instead.");
};

let store;

const SimpleFill = () => <div>Im a Fill</div>;
const DataFill = ({ data }) => <div>{data}</div>;
const PropFill = ({ whateverProp }) => <div>{whateverProp}</div>;
const AllPropsFill = (allProps) => (
  <div>{JSON.stringify(allProps, null, 2)}</div>
);

beforeEach(() => {
  store = createStore({
    state: {
      source: {
        get: jest.fn(() => () => "This would normally be data for this route"),
      },
      router: {},
      fills: {
        namespace1: {
          "simple fill": {
            slot: "slot 1",
            library: "namespace1.SimpleFill",
          },
          "fill with data": {
            slot: "slot 2",
            library: "namespace1.DataFill",
          },
          "fill with a prop": {
            slot: "slot 3",
            library: "namespace1.PropFill",
          },
          "fill with overriden prop": {
            slot: "slot 4",
            library: "namespace1.PropFill",
            props: {
              whateverProp: "This should render instead",
            },
          },
          "fill 5.1": {
            slot: "slot 5",
            library: "namespace1.AllPropsFill",
          },
          "fill 5.2": {
            slot: "slot 5",
            library: "namespace1.AllPropsFill",
          },
        },
      },
    },
    libraries: {
      fills: {
        namespace1: {
          SimpleFill,
          DataFill,
          PropFill,
          AllPropsFill,
        },
      },
    },
  });
});

describe("Slot", () => {
  it("should work in the most basic case", () => {
    const app = create(
      <Provider value={store}>
        <Slot name="slot 1" />
      </Provider>
    );

    expect(toJson(app).children[0]).toEqual("Im a Fill");
    expect(toJson(app)).toMatchSnapshot();
  });

  it("should render children as a fallback", () => {
    const app = create(
      <Provider value={store}>
        <Slot name="this slot does not exist">fallback</Slot>
      </Provider>
    );

    expect(toJson(app)).toEqual("fallback");
    expect(toJson(app)).toMatchSnapshot();
  });

  it("should read the data from state.source and pass it to the Fill", () => {
    const app = create(
      <Provider value={store}>
        <Slot name="slot 2" />
      </Provider>
    );

    expect(toJson(app).children[0]).toEqual(
      "This would normally be data for this route"
    );
    expect(toJson(app)).toMatchSnapshot();
  });

  it("should pass the data prop to the Fill", () => {
    const app = create(
      <Provider value={store}>
        <Slot name="slot 2" data={"Data passed as prop"} />
      </Provider>
    );

    expect(toJson(app).children[0]).toEqual("Data passed as prop");
    expect(toJson(app)).toMatchSnapshot();
  });

  it("should pass any other extra prop to the Fill", () => {
    const app = create(
      <Provider value={store}>
        <Slot name="slot 3" whateverProp={"Data passed as prop"} />
      </Provider>
    );

    expect(toJson(app).children[0]).toEqual("Data passed as prop");
    expect(toJson(app)).toMatchSnapshot();
  });

  it("should override the prop provided by the theme creator with the one given by the user", () => {
    const app = create(
      <Provider value={store}>
        <Slot name="slot 4" whateverProp={"This should NOT render"} />
      </Provider>
    );

    expect(toJson(app).children[0]).toEqual("This should render instead");
    expect(toJson(app)).toMatchSnapshot();
  });

  it("should render 2 elements with all props", () => {
    const app = create(
      <Provider value={store}>
        <Slot name="slot 5" />
      </Provider>
    );

    expect(toJsonArray(app).length).toEqual(2); // We render 2 elements
    expect(toJsonArray(app)).toMatchSnapshot();
  });
});
