import * as React from "react";
import { renderHook, render, act } from "@testing-library/react-native";
import { View } from "react-native";

import { useSequentialLoader, useSequentialRenderItem } from "..";

describe("flatList hooks", () => {
  describe("useSequentialRenderItem", () => {
    it("returns function and loading indicator ", () => {
      const mockData = [{ test: true }];
      const { result } = renderHook(() => useSequentialRenderItem(mockData));

      expect(typeof result.current.renderItem).toBe("function");
      expect(typeof result.current.allLoaded).toBeTruthy();
    });

    it("returns works with empty data", () => {
      const mockData = [];
      const { result } = renderHook(() => useSequentialRenderItem(mockData));

      expect(typeof result.current.renderItem).toBe("function");
      expect(typeof result.current.allLoaded).toBeTruthy();
    });

    describe("Data passed to renderItem function", () => {
      it("passes correct item element over", () => {
        const element = { id: 1 };
        const mockData = [element];

        const { result } = renderHook(() => useSequentialRenderItem(mockData));

        result.current.renderItem(({ item }) => {
          expect(item).toBe(element);
        });
      });

      it("passes onLoadFinished method to renderItem", () => {
        const element = { id: 1 };
        const mockData = [element];

        const { result } = renderHook(() => useSequentialRenderItem(mockData));

        result.current.renderItem(({ onLoadFinished }) => {
          expect(onLoadFinished).toBeDefined();
        });
      });
    });

    describe("Calling onLoadFinished callback", () => {
      it("allLoaded return true after onLoadFinished is called for all components", () => {
        const element1 = { id: 1 };
        const element2 = { id: 2 };
        const mockData = [element1, element2];

        const { result } = renderHook(() => useSequentialRenderItem(mockData));

        expect(result.current.allLoaded).toBe(false);

        result.current.renderItem(
          ({ onLoadFinished }) => {
            onLoadFinished();
          },
          { index: 0 }
        );

        expect(result.current.allLoaded).toBe(false);

        act(() => {
          result.current.renderItem(
            ({ onLoadFinished }) => {
              onLoadFinished();
            },
            { index: 1 }
          );
        });

        expect(result.current.allLoaded).toBe(true);
      });
    });

    describe("Rendering wrapper", () => {
      it("wraps the elements in the View with display: none style", async () => {
        const element1 = { id: 1 };
        const element2 = { id: 2 };
        const mockData = [element1, element2];

        const { result } = renderHook(() => useSequentialRenderItem(mockData));

        const res = render(
          result.current.renderItem(
            () => {
              return <View testID={"test"} />;
            },
            { item: element1 }
          )
        );

        const element = res.root.findByProps({
          testID: "sequential-river-wrapper-1",
        });

        expect(element.props.style.display).toEqual("none");
      });

      it("calling on LoadFinish flips the View style to flex", () => {
        const element1 = { id: 1 };
        const element2 = { id: 2 };
        const mockData = [element1, element2];

        const { result } = renderHook(() => useSequentialRenderItem(mockData));

        act(() => {
          result.current.renderItem(
            ({ onLoadFinished }) => {
              onLoadFinished();
            },
            { item: element1, index: 0 }
          );
        });

        const { root } = render(
          result.current.renderItem(
            ({ onLoadFinished }) => {
              onLoadFinished();

              return <View testID={"test"} />;
            },
            { item: element1, index: 1 }
          )
        );

        const element = root.findByProps({
          testID: "sequential-river-wrapper-1",
        });

        expect(element.props.style.display).toEqual("flex");
      });

      it("View stays hidden if previous views are not loaded", () => {
        const element1 = { id: 1 };
        const element2 = { id: 2 };
        const mockData = [element1, element2];

        const { result } = renderHook(() => useSequentialRenderItem(mockData));

        const { root } = render(
          result.current.renderItem(
            ({ onLoadFinished }) => {
              onLoadFinished();

              return <View testID={"test"} />;
            },
            { item: element1, index: 1 }
          )
        );

        const element = root.findByProps({
          testID: "sequential-river-wrapper-1",
        });

        expect(element.props.style.display).toEqual("none");
      });
    });
  });

  describe("useSequentialLoader", () => {
    it("returns correct elements", () => {
      const mockData = [{ test: true }];

      const { result } = renderHook(() =>
        useSequentialLoader(true, mockData.length)
      );

      expect(typeof result.current.isReadyToRender).toBe("function");
      expect(typeof result.current.onLoadFinished).toBe("function");
      expect(typeof result.current.onViewableItemsChanged).toBe("function");
      expect(typeof result.current.isRendered).toBe("function");
      expect(typeof result.current.allLoaded).toBe("boolean");
    });
  });

  describe("isReadyToRender", () => {
    const viewableMock = {
      viewableItems: [
        { index: 0, item: 1, key: "1", isViewable: true },
        { index: 1, item: 2, key: "2", isViewable: true },
      ],
      changed: [],
    };

    it("returns true when element is flagged as rendered", () => {
      const mockData = [1, 2];

      const { result } = renderHook(() =>
        useSequentialLoader(true, mockData.length)
      );

      act(() => {
        result.current.onViewableItemsChanged(viewableMock);

        result.current.onLoadFinished(1)();
      });

      expect(result.current.isReadyToRender(1)).toBe(true);
    });

    it("returns false when element is not flagged as rendered", () => {
      const mockData = [1, 2];

      const { result } = renderHook(() =>
        useSequentialLoader(true, mockData.length)
      );

      act(() => {
        result.current.onViewableItemsChanged(viewableMock);
      });

      expect(result.current.isReadyToRender(1)).toBe(false);
    });

    it("always returns true for index 0", () => {
      const mockData = [1, 2];

      const { result } = renderHook(() =>
        useSequentialLoader(true, mockData.length)
      );

      expect(result.current.isReadyToRender(0)).toBe(true);
    });

    it("returns false if there are no viewable items", () => {
      const mockData = [1, 2];

      const { result } = renderHook(() =>
        useSequentialLoader(true, mockData.length)
      );

      act(() => {
        result.current.onLoadFinished(1)();
      });

      expect(result.current.isReadyToRender(1)).toBe(false);
    });

    it("returns true if it's in the viewport and previous element is loaded", () => {
      const mockData = [1, 2, 3];

      const { result } = renderHook(() =>
        useSequentialLoader(true, mockData.length)
      );

      act(() => {
        result.current.onViewableItemsChanged(viewableMock);
        result.current.onLoadFinished(0)();
        result.current.onLoadFinished(1)();
      });

      expect(result.current.isReadyToRender(2)).toBe(true);
    });

    it("returns true if it's first, second or third component outside the viewport and previous element are loaded", () => {
      const mockData = [1, 2, 3, 4, 5];

      const { result } = renderHook(() =>
        useSequentialLoader(true, mockData.length)
      );

      act(() => {
        result.current.onViewableItemsChanged(viewableMock);
        result.current.onLoadFinished(0)();
        result.current.onLoadFinished(1)();
      });

      expect(result.current.isReadyToRender(2)).toBe(true);

      act(() => {
        result.current.onLoadFinished(2)();
      });

      expect(result.current.isReadyToRender(2)).toBe(true);

      act(() => {
        result.current.onLoadFinished(3)();
      });

      expect(result.current.isReadyToRender(3)).toBe(true);

      act(() => {
        result.current.onLoadFinished(4)();
      });

      expect(result.current.isReadyToRender(4)).toBe(true);

      expect(result.current.isReadyToRender(5)).toBe(false);
    });

    describe("allLoaded", () => {
      it("returns true if onLoadFinished was called for all elements", () => {
        const mockData = [1, 2];

        const { result } = renderHook(() =>
          useSequentialLoader(true, mockData.length)
        );

        act(() => {
          result.current.onLoadFinished(0)();
          result.current.onLoadFinished(1)();
        });

        expect(result.current.allLoaded).toBe(true);
      });

      it("returns true if onLoadFinished wasn't called for all elements", () => {
        const mockData = [1, 2];

        const { result } = renderHook(() =>
          useSequentialLoader(true, mockData.length)
        );

        expect(result.current.allLoaded).toBe(false);
      });
    });

    describe("isRendered", () => {
      it("returns true if onLoadFinished was called for x element", () => {
        const mockData = [1, 2];

        const { result } = renderHook(() =>
          useSequentialLoader(true, mockData.length)
        );

        act(() => {
          result.current.onLoadFinished(1)();
        });

        expect(result.current.isRendered(1)).toBe(true);
      });

      it("returns true if onLoadFinished wasn't called for x element", () => {
        const mockData = [1, 2, 3];

        const { result } = renderHook(() =>
          useSequentialLoader(true, mockData.length)
        );

        act(() => {
          result.current.onLoadFinished(1)();
        });

        expect(result.current.isRendered(2)).toBe(false);
      });
    });
  });
});
