Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Tests

on:
pull_request:
branches: [main]
push:
branches: [main]

jobs:
test:
name: Unit & Integration Tests
runs-on: ubuntu-latest

steps:
- name: Checkout repo
uses: actions/checkout@v4

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm

- name: Install dependencies
run: npm ci --force

- name: Run tests
run: npm run test:run

- name: Run coverage
run: npm run test:coverage
22 changes: 22 additions & 0 deletions app/components/_layouts/background-blur/background-blur.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { render } from "@testing-library/react";
import BackgroundBlur from "./index";

describe("BackgroundBlur", () => {
it("renders two blur elements", () => {
const { container } = render(<BackgroundBlur />);
const blurElements = container.querySelectorAll("[aria-hidden='true']");
expect(blurElements).toHaveLength(2);
});

it("has blur-3xl class on containers", () => {
const { container } = render(<BackgroundBlur />);
const elements = container.querySelectorAll(".blur-3xl");
expect(elements).toHaveLength(2);
});

it("renders gradient divs inside", () => {
const { container } = render(<BackgroundBlur />);
const gradients = container.querySelectorAll(".bg-linear-to-tr");
expect(gradients).toHaveLength(2);
});
});
24 changes: 24 additions & 0 deletions app/components/_layouts/wave/wave.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { render } from "@testing-library/react";
import Wave from "./index";

describe("Wave", () => {
it("renders bottom wave as SVG", () => {
const { container } = render(<Wave position="bottom" />);
expect(container.querySelector("svg")).toBeInTheDocument();
});

it("renders top wave with wrapper div", () => {
const { container } = render(<Wave position="top" />);
expect(container.querySelector(".relative")).toBeInTheDocument();
expect(container.querySelector("svg")).toBeInTheDocument();
});

it("renders different SVG paths for top and bottom", () => {
const { container: bottomContainer } = render(<Wave position="bottom" />);
const { container: topContainer } = render(<Wave position="top" />);

const bottomPath = bottomContainer.querySelector("path")?.getAttribute("d");
const topPath = topContainer.querySelector("path")?.getAttribute("d");
expect(bottomPath).not.toBe(topPath);
});
});
34 changes: 34 additions & 0 deletions app/components/features/button-whatsapp/whatsapp.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { render, screen } from "@testing-library/react";
import WhatsButton from "./index";

describe("WhatsButton", () => {
it("renders a link to WhatsApp", () => {
render(<WhatsButton />);
const link = screen.getByRole("link");
expect(link).toHaveAttribute("href", expect.stringContaining("wa.me"));
});

it("opens in a new tab", () => {
render(<WhatsButton />);
expect(screen.getByRole("link")).toHaveAttribute("target", "_blank");
});

it("has accessible label", () => {
render(<WhatsButton />);
expect(
screen.getByLabelText("Fale conosco pelo WhatsApp"),
).toBeInTheDocument();
});

it("renders without fixed positioning when onlyWrapper", () => {
const { container } = render(<WhatsButton onlyWrapper />);
const link = container.querySelector("a");
expect(link?.className).not.toContain("fixed");
});

it("renders with fixed positioning by default", () => {
const { container } = render(<WhatsButton />);
const link = container.querySelector("a");
expect(link?.className).toContain("fixed");
});
});
32 changes: 32 additions & 0 deletions app/components/features/error-handling/500/error-500.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { screen } from "@testing-library/react";
import { renderWithRouter } from "~/test/render-with-router";
import { Error500 } from "./index";

const mockError = { message: "Test error", stack: "Error stack trace" };

describe("Error500", () => {
it("renders 500 status code", () => {
renderWithRouter(() => <Error500 error={mockError} />);
expect(screen.getByText("500")).toBeInTheDocument();
});

it("renders error heading", () => {
renderWithRouter(() => <Error500 error={mockError} />);
expect(screen.getByText("Ops...")).toBeInTheDocument();
});

it("renders description text", () => {
renderWithRouter(() => <Error500 error={mockError} />);
expect(screen.getByText("Alguma coisa deu errada...")).toBeInTheDocument();
});

it("renders link back to home", () => {
renderWithRouter(() => <Error500 error={mockError} />);
expect(screen.getByText(/Voltar para a Home/)).toBeInTheDocument();
});

it("shows error details in non-production", () => {
renderWithRouter(() => <Error500 error={mockError} />);
expect(screen.getByText(/"Test error"/)).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { screen } from "@testing-library/react";
import { renderWithRouter } from "~/test/render-with-router";
import NotFound from "./index";

vi.mock("~/components/_layouts/public-env", () => ({
getPublicEnv: (key: string) => (key === "NODE_ENV" ? "test" : undefined),
}));

describe("NotFound", () => {
it("renders 404 status code", () => {
renderWithRouter(() => <NotFound />);
expect(screen.getByText("404")).toBeInTheDocument();
});

it("renders page heading", () => {
renderWithRouter(() => <NotFound />);
expect(screen.getByText("Página não encontrada")).toBeInTheDocument();
});

it("renders description", () => {
renderWithRouter(() => <NotFound />);
expect(
screen.getByText("Desculpe, não encontramos nada por aqui..."),
).toBeInTheDocument();
});

it("renders link to home", () => {
renderWithRouter(() => <NotFound />);
const link = screen.getByText(/Voltar para a Home/);
expect(link.closest("a")).toHaveAttribute("href", "/");
});

it("shows error details in non-production", () => {
renderWithRouter(() => <NotFound error="some error info" />);
expect(screen.getByText(/"some error info"/)).toBeInTheDocument();
});
});
46 changes: 46 additions & 0 deletions app/components/features/form/input/input.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { render, screen } from "@testing-library/react";
import Input from "./index";

describe("Input", () => {
it("renders label text", () => {
render(<Input name="email" id="email" label="E-mail" />);
expect(screen.getByText("E-mail")).toBeInTheDocument();
});

it("renders input with correct attributes", () => {
render(<Input name="email" id="email" label="E-mail" type="email" />);
const input = screen.getByLabelText("E-mail");
expect(input).toHaveAttribute("type", "email");
expect(input).toHaveAttribute("name", "email");
expect(input).toHaveAttribute("id", "email");
});

it("defaults to text type", () => {
render(<Input name="name" id="name" label="Nome" />);
expect(screen.getByLabelText("Nome")).toHaveAttribute("type", "text");
});

it("supports disabled state", () => {
render(<Input name="name" id="name" label="Nome" disabled />);
expect(screen.getByLabelText("Nome")).toBeDisabled();
});

it("supports placeholder", () => {
render(
<Input
name="name"
id="name"
label="Nome"
placeholder="Digite seu nome"
/>,
);
expect(screen.getByPlaceholderText("Digite seu nome")).toBeInTheDocument();
});

it("passes through additional props", () => {
render(
<Input name="name" id="name" label="Nome" data-testid="custom-input" />,
);
expect(screen.getByTestId("custom-input")).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { render, screen } from "@testing-library/react";
import LoadingButton from "./index";

describe("LoadingButton", () => {
it("renders children text in idle state", () => {
render(<LoadingButton status="idle">Enviar</LoadingButton>);
expect(screen.getByText("Enviar")).toBeVisible();
});

it("is enabled in idle state", () => {
render(<LoadingButton status="idle">Enviar</LoadingButton>);
expect(screen.getByRole("button")).not.toBeDisabled();
});

it("is disabled when loading", () => {
render(<LoadingButton status="loading">Enviar</LoadingButton>);
expect(screen.getByRole("button")).toBeDisabled();
});

it("is disabled when submitting", () => {
render(<LoadingButton status="submitting">Enviar</LoadingButton>);
expect(screen.getByRole("button")).toBeDisabled();
});

it("shows spinner during loading", () => {
const { container } = render(
<LoadingButton status="loading">Enviar</LoadingButton>,
);
expect(container.querySelector(".animate-spin")).toBeInTheDocument();
});

it("shows spinner during submitting", () => {
const { container } = render(
<LoadingButton status="submitting">Enviar</LoadingButton>,
);
expect(container.querySelector(".animate-spin")).toBeInTheDocument();
});

it("hides children text when loading", () => {
render(<LoadingButton status="loading">Enviar</LoadingButton>);
expect(screen.getByText("Enviar")).toHaveClass("invisible");
});

it("is disabled on successful submission", () => {
render(
<LoadingButton status="idle" isSuccessfulSubmission>
Enviar
</LoadingButton>,
);
expect(screen.getByRole("button")).toBeDisabled();
});

it("hides children on successful submission", () => {
render(
<LoadingButton status="idle" isSuccessfulSubmission>
Enviar
</LoadingButton>,
);
expect(screen.getByText("Enviar")).toHaveClass("invisible");
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { screen } from "@testing-library/react";
import { renderWithRouter } from "~/test/render-with-router";
import LinkToLoginWithRedirect from "./index";

describe("LinkToLoginWithRedirect", () => {
it("renders children", () => {
renderWithRouter(
() => <LinkToLoginWithRedirect>Login</LinkToLoginWithRedirect>,
{ path: "/workshops/react" },
);
expect(screen.getByText("Login")).toBeInTheDocument();
});

it("links to /login with current path as redirectTo", () => {
renderWithRouter(
() => <LinkToLoginWithRedirect>Login</LinkToLoginWithRedirect>,
{ path: "/workshops/react" },
);
expect(screen.getByRole("link")).toHaveAttribute(
"href",
"/login?redirectTo=/workshops/react",
);
});

it("redirects to /dashboard when on root path", () => {
renderWithRouter(
() => <LinkToLoginWithRedirect>Login</LinkToLoginWithRedirect>,
{ path: "/" },
);
expect(screen.getByRole("link")).toHaveAttribute(
"href",
"/login?redirectTo=/dashboard",
);
});

it("uses custom redirectPath when provided", () => {
renderWithRouter(
() => (
<LinkToLoginWithRedirect redirectPath="/custom">
Login
</LinkToLoginWithRedirect>
),
{ path: "/somewhere" },
);
expect(screen.getByRole("link")).toHaveAttribute(
"href",
"/login?redirectTo=/custom",
);
});

it("redirects to / then /dashboard when on password-reset page", () => {
renderWithRouter(
() => <LinkToLoginWithRedirect>Login</LinkToLoginWithRedirect>,
{ path: "/password-reset/abc123" },
);
expect(screen.getByRole("link")).toHaveAttribute(
"href",
"/login?redirectTo=/dashboard",
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { render } from "@testing-library/react";
import { FirstPlace, SecondPlace, ThirdPlace } from "./index";

describe("Ranking Badges", () => {
it("renders FirstPlace SVG with amber fill", () => {
const { container } = render(<FirstPlace />);
const svg = container.querySelector("svg");
expect(svg).toBeInTheDocument();
expect(container.querySelector(".fill-amber-400")).toBeInTheDocument();
});

it("renders SecondPlace SVG with slate fill", () => {
const { container } = render(<SecondPlace />);
const svg = container.querySelector("svg");
expect(svg).toBeInTheDocument();
expect(container.querySelector(".fill-slate-400")).toBeInTheDocument();
});

it("renders ThirdPlace SVG with amber-700 fill", () => {
const { container } = render(<ThirdPlace />);
const svg = container.querySelector("svg");
expect(svg).toBeInTheDocument();
expect(container.querySelector(".fill-amber-700")).toBeInTheDocument();
});

it("passes through additional props", () => {
const { container } = render(<FirstPlace data-testid="badge" />);
expect(
container.querySelector("[data-testid='badge']"),
).toBeInTheDocument();
});
});
Loading
Loading