import { fireEvent, render } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { Slider } from "./Slider";

const BASE_CLASS = "mobius";
const SLIDER_CLASS = "mobius-slider";

describe("Slider", () => {
  it("should render without errors", () => {
    const { container } = render(
      <Slider className="test" label="Test" defaultValue={0} />,
    );
    expect(container).toBeInTheDocument();
  });

  describe("labels", () => {
    it("should render a min label", () => {
      const { getByTestId } = render(<Slider label="Test" minLabel="Min" />);
      expect(getByTestId("min-label")).toHaveTextContent("Min");
    });

    it("should render a max label", () => {
      const { getByTestId } = render(<Slider label="Test" maxLabel="Max" />);
      expect(getByTestId("max-label")).toHaveTextContent("Max");
    });

    it("should render an aria-label", () => {
      const { container } = render(<Slider aria-label="Test" maxLabel="Max" />);
      const slider = container.getElementsByTagName("input")[0];
      expect(slider).toHaveAttribute("aria-label", "Test");
    });
  });

  describe("classnames", () => {
    let result: any;

    beforeEach(() => {
      result = render(
        <Slider className="test" label="test" defaultValue={0} />,
      );
    });

    const classNames = [BASE_CLASS, SLIDER_CLASS, "test"];
    it.each(classNames)("should have classname %s", className => {
      expect(result.container.firstChild).toHaveClass(className);
    });
  });

  /**
   * Note: React Testing Library does not yet support
   * <input type="range" />
   * https://github.com/testing-library/user-event/issues/871
   * We cannot use keyboard user events.  For now, the
   * following tests work with fireEvent.change().
   */
  describe("events", () => {
    it("should call events on input change", async () => {
      const onChange = vi.fn();
      const onChangeEnd = vi.fn();

      const { container } = render(
        <Slider
          label="test"
          defaultValue={1}
          minValue={1}
          maxValue={10}
          onChange={onChange}
          onChangeEnd={onChangeEnd}
        />,
      );

      await userEvent.tab();
      const slider = container.getElementsByTagName("input")[0];
      expect(slider).toHaveFocus();

      fireEvent.change(slider, { target: { value: "5" } });
      expect(slider.value).toEqual("5");
      expect(onChange).toHaveBeenCalledWith(5);

      fireEvent.keyUp(slider);
      expect(onChangeEnd).toHaveBeenCalledWith(5);
    });

    it("should not call onChange when value is already min", async () => {
      const onChange = vi.fn();
      const onChangeEnd = vi.fn();

      const { container } = render(
        <Slider
          label="test"
          defaultValue={1}
          minValue={1}
          maxValue={10}
          onChange={onChange}
          onChangeEnd={onChangeEnd}
        />,
      );

      const slider = container.getElementsByTagName("input")[0];
      fireEvent.change(slider, { target: { value: "1" } });
      expect(onChange).not.toHaveBeenCalled();
      // Cannot set value below min
      fireEvent.change(slider, { target: { value: "0" } });
      expect(onChange).not.toHaveBeenCalled();
    });

    it("should not call onChange when value is already max", async () => {
      const onChange = vi.fn();
      const onChangeEnd = vi.fn();

      const { container } = render(
        <Slider
          label="test"
          defaultValue={10}
          minValue={1}
          maxValue={10}
          onChange={onChange}
          onChangeEnd={onChangeEnd}
        />,
      );

      const slider = container.getElementsByTagName("input")[0];
      fireEvent.change(slider, { target: { value: "10" } });
      expect(onChange).not.toHaveBeenCalled();
      // Cannot set value above max
      fireEvent.change(slider, { target: { value: "11" } });
      expect(onChange).not.toHaveBeenCalled();
    });
  });
});
