import { createRef } from "react";
import { render, screen } from "@testing-library/react";
import { Table } from ".";

const TABLE_CLASS_NAME = "mobius-table";
const TABLE_HEAD_CLASS_NAME = "mobius-table__head";
const TABLE_HEAD_CELL_CLASS_NAME = "mobius-table__head-cell";
const TABLE_BODY_CLASS_NAME = "mobius-table__body";
const TABLE_FOOT_CLASS_NAME = "mobius-table__foot";
const TABLE_ROW_CLASS_NAME = "mobius-table__row";

describe("Test for Table", () => {
  it("renders an empty table", () => {
    const view = render(<Table />);
    expect(view).toBeTruthy();
  });

  describe("uses correct class names", () => {
    describe("uses the Mobius class name", () => {
      beforeEach(() => {
        render(
          <Table data-testid="table">
            <Table.Head data-testid="head">
              <Table.Row data-testid="row">
                <Table.HeaderCell data-testid="headerCell">
                  Browser
                </Table.HeaderCell>
              </Table.Row>
            </Table.Head>
            <Table.Body data-testid="body">
              <Table.Row>
                <Table.Cell data-testid="cell">Cell</Table.Cell>
              </Table.Row>
            </Table.Body>
            <Table.Foot data-testid="foot" />
          </Table>,
        );
      });

      it.each([
        ["Table", "table"],
        ["Table.Head", "head"],
        ["Table.Body", "body"],
        ["Table.Foot", "foot"],
        ["Table.Row", "row"],
        ["Table.HeaderCell", "headerCell"],
        ["Table.Cell", "cell"],
      ])("for the %s component", (_component, testId) => {
        expect(screen.getByTestId(testId)).toHaveClass("mobius");
      });
    });

    it("uses table component class names", () => {
      render(
        <Table data-testid="test">
          <Table.Head>
            <Table.Row>
              <Table.HeaderCell>Browser</Table.HeaderCell>
              <Table.HeaderCell>Number of unique visitors</Table.HeaderCell>
              <Table.HeaderCell>Header</Table.HeaderCell>
              <Table.HeaderCell>Header</Table.HeaderCell>
              <Table.HeaderCell>Header</Table.HeaderCell>
            </Table.Row>
          </Table.Head>
          <Table.Body>
            <Table.Row>
              <Table.HeaderCell>Cell</Table.HeaderCell>
              <Table.Cell>Cell</Table.Cell>
              <Table.Cell>Cell</Table.Cell>
              <Table.Cell>Cell</Table.Cell>
              <Table.Cell>Cell</Table.Cell>
            </Table.Row>
          </Table.Body>
          <Table.Foot />
        </Table>,
      );
      // Table and head
      expect(screen.getByTestId("test")).toHaveClass(TABLE_CLASS_NAME);
      const head = screen.getByTestId("test").firstChild;
      expect(head).toHaveClass(TABLE_HEAD_CLASS_NAME);
      expect(head?.firstChild).toHaveClass(TABLE_ROW_CLASS_NAME);
      expect(head?.firstChild?.firstChild).toHaveClass(
        TABLE_HEAD_CELL_CLASS_NAME,
      );
      // Body
      const body = screen.getByTestId("test").childNodes[1];
      expect(body).toHaveClass(TABLE_BODY_CLASS_NAME);
      expect(body?.firstChild).toHaveClass(TABLE_ROW_CLASS_NAME);
      expect(body?.firstChild?.firstChild).toHaveClass(
        TABLE_HEAD_CELL_CLASS_NAME,
      );
      // Foot
      const foot = screen.getByTestId("test").childNodes[2];
      expect(foot).toHaveClass(TABLE_FOOT_CLASS_NAME);
    });

    it("includes custom class name", () => {
      render(
        <Table data-testid="test" className="my-table">
          <Table.Head className="my-header">
            <Table.Row className="my-header-row">
              <Table.HeaderCell className="my-header-cell">
                Browser
              </Table.HeaderCell>
              <Table.HeaderCell>Number of unique visitors</Table.HeaderCell>
              <Table.HeaderCell>Header</Table.HeaderCell>
              <Table.HeaderCell>Header</Table.HeaderCell>
              <Table.HeaderCell>Header</Table.HeaderCell>
            </Table.Row>
          </Table.Head>
          <Table.Body className="my-body">
            <Table.Row className="my-body-row">
              <Table.HeaderCell className="my-body-cell">Cell</Table.HeaderCell>
              <Table.Cell>Cell</Table.Cell>
              <Table.Cell>Cell</Table.Cell>
              <Table.Cell>Cell</Table.Cell>
              <Table.Cell>Cell</Table.Cell>
            </Table.Row>
          </Table.Body>
          <Table.Foot className="my-footer" />
        </Table>,
      );
      // Table and head
      expect(screen.getByTestId("test")).toHaveClass(TABLE_CLASS_NAME);
      expect(screen.getByTestId("test")).toHaveClass("my-table");
      const head = screen.getByTestId("test").firstChild;
      expect(head).toHaveClass(TABLE_HEAD_CLASS_NAME);
      expect(head).toHaveClass("my-header");
      expect(head?.firstChild).toHaveClass(TABLE_ROW_CLASS_NAME);
      expect(head?.firstChild).toHaveClass("my-header-row");
      expect(head?.firstChild?.firstChild).toHaveClass(
        TABLE_HEAD_CELL_CLASS_NAME,
      );
      expect(head?.firstChild?.firstChild).toHaveClass("my-header-cell");
      // Body
      const body = screen.getByTestId("test").childNodes[1];
      expect(body).toHaveClass(TABLE_BODY_CLASS_NAME);
      expect(body).toHaveClass("my-body");
      expect(body?.firstChild).toHaveClass(TABLE_ROW_CLASS_NAME);
      expect(body?.firstChild).toHaveClass("my-body-row");
      expect(body?.firstChild?.firstChild).toHaveClass(
        TABLE_HEAD_CELL_CLASS_NAME,
      );
      expect(body?.firstChild?.firstChild).toHaveClass("my-body-cell");
      // Foot
      const foot = screen.getByTestId("test").childNodes[2];
      expect(foot).toHaveClass(TABLE_FOOT_CLASS_NAME);
      expect(foot).toHaveClass("my-footer");
    });
  });

  describe("semantic HTML structure", () => {
    it("Table renders a <table> element", () => {
      const { container } = render(<Table />);
      expect(container.firstChild?.nodeName).toBe("TABLE");
    });

    it("Head renders a <thead> element", () => {
      render(
        <Table>
          <Table.Head data-testid="head">
            <Table.Row>
              <Table.HeaderCell>Header</Table.HeaderCell>
            </Table.Row>
          </Table.Head>
        </Table>,
      );
      expect(screen.getByTestId("head").nodeName).toBe("THEAD");
    });

    it("Body renders a <tbody> element", () => {
      render(
        <Table>
          <Table.Body data-testid="body">
            <Table.Row>
              <Table.Cell>Cell</Table.Cell>
            </Table.Row>
          </Table.Body>
        </Table>,
      );
      expect(screen.getByTestId("body").nodeName).toBe("TBODY");
    });

    it("Foot renders a <tfoot> element", () => {
      render(
        <Table>
          <Table.Foot data-testid="foot" />
        </Table>,
      );
      expect(screen.getByTestId("foot").nodeName).toBe("TFOOT");
    });

    it("Row renders a <tr> element", () => {
      render(
        <Table>
          <Table.Body>
            <Table.Row data-testid="row">
              <Table.Cell>Cell</Table.Cell>
            </Table.Row>
          </Table.Body>
        </Table>,
      );
      expect(screen.getByTestId("row").nodeName).toBe("TR");
    });

    it("Cell renders a <td> element", () => {
      render(
        <Table>
          <Table.Body>
            <Table.Row>
              <Table.Cell data-testid="cell">Cell</Table.Cell>
            </Table.Row>
          </Table.Body>
        </Table>,
      );
      expect(screen.getByTestId("cell").nodeName).toBe("TD");
    });

    it("HeaderCell renders a <th> element", () => {
      render(
        <Table>
          <Table.Head>
            <Table.Row>
              <Table.HeaderCell data-testid="header-cell">
                Header
              </Table.HeaderCell>
            </Table.Row>
          </Table.Head>
        </Table>,
      );
      expect(screen.getByTestId("header-cell").nodeName).toBe("TH");
    });

    it("HeaderCell applies scope='col' by default", () => {
      render(
        <Table>
          <Table.Head>
            <Table.Row>
              <Table.HeaderCell data-testid="header-cell">
                Header
              </Table.HeaderCell>
            </Table.Row>
          </Table.Head>
        </Table>,
      );
      expect(screen.getByTestId("header-cell")).toHaveAttribute("scope", "col");
    });
  });

  describe("forwards ref", () => {
    it("Table forwards ref", () => {
      const ref = createRef<HTMLTableElement>();
      render(<Table ref={ref} />);
      expect(ref.current).toBeInstanceOf(HTMLTableElement);
    });

    it("Table.Head forwards ref", () => {
      const ref = createRef<HTMLTableSectionElement>();
      render(
        <Table>
          <Table.Head ref={ref} />
        </Table>,
      );
      expect(ref.current).toBeInstanceOf(HTMLTableSectionElement);
    });

    it("Table.Body forwards ref", () => {
      const ref = createRef<HTMLTableSectionElement>();
      render(
        <Table>
          <Table.Body ref={ref} />
        </Table>,
      );
      expect(ref.current).toBeInstanceOf(HTMLTableSectionElement);
    });

    it("Table.Foot forwards ref", () => {
      const ref = createRef<HTMLTableSectionElement>();
      render(
        <Table>
          <Table.Foot ref={ref} />
        </Table>,
      );
      expect(ref.current).toBeInstanceOf(HTMLTableSectionElement);
    });

    it("Table.Row forwards ref", () => {
      const ref = createRef<HTMLTableRowElement>();
      render(
        <Table>
          <Table.Body>
            <Table.Row ref={ref} />
          </Table.Body>
        </Table>,
      );
      expect(ref.current).toBeInstanceOf(HTMLTableRowElement);
    });

    it("Table.Cell forwards ref", () => {
      const ref = createRef<HTMLTableCellElement>();
      render(
        <Table>
          <Table.Body>
            <Table.Row>
              <Table.Cell ref={ref}>Cell</Table.Cell>
            </Table.Row>
          </Table.Body>
        </Table>,
      );
      expect(ref.current).toBeInstanceOf(HTMLTableCellElement);
    });

    it("Table.HeaderCell forwards ref", () => {
      const ref = createRef<HTMLTableCellElement>();
      render(
        <Table>
          <Table.Head>
            <Table.Row>
              <Table.HeaderCell ref={ref}>Header</Table.HeaderCell>
            </Table.Row>
          </Table.Head>
        </Table>,
      );
      expect(ref.current).toBeInstanceOf(HTMLTableCellElement);
    });
  });
});
