Skip to content

Commit beca6d2

Browse files
authored
Merge pull request #199 from codeableorg/testing
Testing
2 parents 29afe26 + f8ac0ca commit beca6d2

20 files changed

+2503
-19
lines changed

package-lock.json

Lines changed: 1226 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
"migrate:dev": "tsx src/db/scripts/migrate.ts",
1717
"reset-db:dev": "npm run drop-db:dev && npm run create-db:dev && npm run migrate:dev && npm run seed:dev",
1818
"seed": "node dist/db/scripts/seed.js",
19-
"seed:dev": "tsx src/db/scripts/seed.ts"
19+
"seed:dev": "tsx src/db/scripts/seed.ts",
20+
"test": "vitest"
2021
},
2122
"dependencies": {
2223
"@hookform/resolvers": "^4.1.3",
@@ -48,6 +49,9 @@
4849
"devDependencies": {
4950
"@eslint/js": "^9.15.0",
5051
"@react-router/dev": "^7.5.1",
52+
"@testing-library/dom": "^10.4.0",
53+
"@testing-library/jest-dom": "^6.6.3",
54+
"@testing-library/react": "^16.3.0",
5155
"@types/jsonwebtoken": "^9.0.9",
5256
"@types/node": "^22.10.2",
5357
"@types/pg": "^8.15.2",
@@ -61,14 +65,17 @@
6165
"eslint-plugin-import": "^2.31.0",
6266
"eslint-plugin-react-hooks": "^5.0.0",
6367
"eslint-plugin-react-refresh": "^0.4.14",
68+
"global-jsdom": "^26.0.0",
6469
"globals": "^15.12.0",
70+
"jsdom": "^26.1.0",
6571
"postcss": "^8.5.3",
6672
"react-router-devtools": "^1.1.10",
6773
"tailwindcss": "^3.4.17",
6874
"tsx": "^4.19.4",
6975
"typescript": "~5.6.2",
7076
"typescript-eslint": "^8.15.0",
7177
"vite": "^6.0.1",
72-
"vite-tsconfig-paths": "^5.1.4"
78+
"vite-tsconfig-paths": "^5.1.4",
79+
"vitest": "^3.2.1"
7380
}
7481
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { render, screen } from "@testing-library/react";
2+
import { describe, it, expect } from "vitest";
3+
4+
import { Button } from "@/components/ui";
5+
6+
describe("Button Component", () => {
7+
it("render correctly", () => {
8+
render(<Button>Click me</Button>);
9+
10+
const button = screen.queryByRole("button", {
11+
name: /click me/i,
12+
});
13+
14+
expect(button).toBeInTheDocument();
15+
});
16+
17+
it("render secondary variant correctly", () => {
18+
render(<Button variant="secondary">Click me</Button>);
19+
20+
const button = screen.queryByRole("button", {
21+
name: /click me/i,
22+
});
23+
24+
expect(button).toHaveClass(
25+
"bg-secondary",
26+
"text-secondary-foreground",
27+
"hover:bg-secondary-hover"
28+
);
29+
});
30+
});

src/lib/utils.tests.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { vi } from "vitest";
2+
3+
import type { Category } from "@/models/category.model";
4+
import type { Product } from "@/models/product.model";
5+
import type { User } from "@/models/user.model";
6+
7+
import type { Session } from "react-router";
8+
9+
type TestRequestConfig = {
10+
url?: string;
11+
headers?: HeadersInit;
12+
};
13+
14+
// Helper functions for creating commonly used test objects
15+
export const createTestUser = (overrides?: Partial<User>): User => ({
16+
id: 1,
17+
email: "",
18+
name: null,
19+
password: null,
20+
isGuest: false,
21+
createdAt: new Date().toISOString(),
22+
updatedAt: new Date().toISOString(),
23+
...overrides,
24+
});
25+
26+
export const createTestRequest = (overrides?: TestRequestConfig): Request => {
27+
const defaultConfig: TestRequestConfig = {
28+
url: "http://localhost/test",
29+
headers: { Cookie: "session=mock-session-id" },
30+
};
31+
32+
const config = { ...defaultConfig, ...overrides };
33+
return new Request(config.url!, {
34+
headers: { ...defaultConfig.headers, ...config.headers },
35+
});
36+
};
37+
38+
export const createMockSession = (userId: number | null): Session => ({
39+
id: "mock-session-id",
40+
data: {},
41+
has: vi.fn(),
42+
get: vi.fn().mockReturnValue(userId), // Default userId in session
43+
set: vi.fn(),
44+
flash: vi.fn(),
45+
unset: vi.fn(),
46+
});
47+
48+
export const createTestProduct = (overrides?: Partial<Product>): Product => ({
49+
id: 1,
50+
title: "Test Product",
51+
imgSrc: "/test-image.jpg",
52+
alt: "Test alt text",
53+
price: 100,
54+
description: "Test description",
55+
categoryId: 1,
56+
isOnSale: false,
57+
features: ["Feature 1", "Feature 2"],
58+
createdAt: new Date().toISOString(),
59+
updatedAt: new Date().toISOString(),
60+
...overrides,
61+
});
62+
63+
export const createTestCategory = (
64+
overrides?: Partial<Category>
65+
): Category => ({
66+
id: 1,
67+
title: "Polos",
68+
slug: "polos",
69+
imgSrc: "/images/polos.jpg",
70+
alt: "Colección de polos para programadores",
71+
description: "Explora nuestra colección de polos para programadores",
72+
createdAt: new Date().toISOString(),
73+
updatedAt: new Date().toISOString(),
74+
...overrides,
75+
});

src/routes/not-found/index.tsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,16 @@ import { Button, Container, Section } from "@/components/ui";
55
export default function NotFound() {
66
return (
77
<Container>
8-
<Section className='flex justify-center items-center'>
9-
<div className='text-center'>
10-
<p className='text-base font-semibold text-accent-foreground'>404</p>
11-
<h1 className='text-4xl leading-9 font-bold tracking-tight text-foreground mt-4 sm:text-6xl sm:leading-none'>Página no encontrada</h1>
12-
<p className='text-lg font-medium text-muted-foreground mt-6 sm:text-xl leading-none'>
8+
<Section className="flex justify-center items-center">
9+
<div className="text-center" data-testid="not-found">
10+
<p className="text-base font-semibold text-accent-foreground">404</p>
11+
<h1 className="text-4xl leading-9 font-bold tracking-tight text-foreground mt-4 sm:text-6xl sm:leading-none">
12+
Página no encontrada
13+
</h1>
14+
<p className="text-lg font-medium text-muted-foreground mt-6 sm:text-xl leading-none">
1315
No pudimos encontrar la página que estás buscando.
1416
</p>
15-
<Button className='mt-10' asChild size="xl">
17+
<Button className="mt-10" asChild size="xl">
1618
<Link to="/">Regresar al inicio</Link>
1719
</Button>
1820
</div>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { describe, expect, it } from "vitest";
2+
3+
import { loader } from ".";
4+
5+
describe("OrderConfirmation loader", () => {
6+
// Helper function to create loader arguments
7+
const createLoaderArgs = (orderId: string) => ({
8+
params: { orderId },
9+
request: new Request(`http://localhost/order-confirmation/${orderId}`),
10+
context: {},
11+
});
12+
13+
it("should return orderId from params", async () => {
14+
// Step 1: Setup - Create test data
15+
const testOrderId = "testOrderId-123"; // Example order ID
16+
17+
// Step 2: Mock - Not needed as loader has no dependencies
18+
19+
// Step 3: Call service function
20+
const result = await loader(createLoaderArgs(testOrderId));
21+
22+
// Step 4: Verify expected behavior
23+
expect(result).toEqual({
24+
orderId: testOrderId,
25+
});
26+
});
27+
});
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { render, screen } from "@testing-library/react";
2+
import { describe, expect, it, vi } from "vitest";
3+
4+
import OrderConfirmation from ".";
5+
import type { Route } from "./+types";
6+
7+
// Creates minimal test props for OrderConfirmation component
8+
const createTestProps = (orderId = "test-123"): Route.ComponentProps => ({
9+
loaderData: { orderId },
10+
params: vi.fn() as any,
11+
matches: vi.fn() as any,
12+
});
13+
14+
describe("OrderConfirmation", () => {
15+
describe("Success Messages Display", () => {
16+
it("should display all success messages correctly", () => {
17+
// Step 1: Setup - Create test props
18+
const props = createTestProps();
19+
// Step 2: Mock
20+
// Step 3: Call - Render component
21+
render(<OrderConfirmation {...props} />);
22+
// Step 4: Verify - Check all success messages
23+
const expectedMessages = [
24+
"¡Muchas gracias por tu compra!",
25+
"Tu orden está en camino",
26+
"Llegaremos a la puerta de tu domicilio lo antes posible",
27+
];
28+
expectedMessages.forEach((message) => {
29+
expect(screen.queryByText(message)).toBeInTheDocument();
30+
});
31+
});
32+
});
33+
34+
describe("Order Tracking Information", () => {
35+
it("should display correct tracking code section", () => {
36+
// Step 1: Setup - Create test props with a specific order ID
37+
const testOrderId = "order-456";
38+
const props = createTestProps(testOrderId);
39+
// Step 2: Mock
40+
// Step 3: Call - Render component
41+
render(<OrderConfirmation {...props} />);
42+
// Step 4: Verify - Check tracking code section
43+
const trackingCodeLabel = screen.queryByText("Código de seguimiento");
44+
expect(trackingCodeLabel).toBeInTheDocument();
45+
46+
const trackingCode = screen.queryByText(testOrderId);
47+
expect(trackingCode).toBeInTheDocument();
48+
});
49+
});
50+
});
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { describe, expect, it, vi } from "vitest";
2+
3+
import { createTestProduct } from "@/lib/utils.tests";
4+
import * as productService from "@/services/product.service";
5+
6+
import { loader } from ".";
7+
8+
// Mock the product service
9+
vi.mock("@/services/product.service", () => ({
10+
getProductById: vi.fn(), // mock function
11+
}));
12+
13+
const mockGetProductById = vi.mocked(productService.getProductById);
14+
15+
describe("Product loader", () => {
16+
const createLoaderArgs = (id: string) => ({
17+
params: { id },
18+
request: new Request(`http://localhost/products/${id}`),
19+
context: {},
20+
});
21+
22+
it("returns a product when it exists", async () => {
23+
const mockProduct = createTestProduct();
24+
25+
mockGetProductById.mockResolvedValue(mockProduct);
26+
27+
const result = await loader(createLoaderArgs("1"));
28+
29+
expect(result.product).toBeDefined();
30+
expect(result.product).toEqual(mockProduct);
31+
expect(mockGetProductById).toHaveBeenCalledWith(1);
32+
});
33+
34+
it("returns empty object when product does not exist", async () => {
35+
mockGetProductById.mockRejectedValue(new Error("Product not found"));
36+
37+
const result = await loader(createLoaderArgs("999"));
38+
39+
expect(result).toEqual({});
40+
expect(mockGetProductById).toHaveBeenCalledWith(999);
41+
});
42+
43+
it("handles invalid product id", async () => {
44+
mockGetProductById.mockRejectedValue(new Error("Invalid ID"));
45+
46+
const result = await loader(createLoaderArgs("invalid"));
47+
48+
expect(result).toEqual({});
49+
expect(mockGetProductById).toHaveBeenCalledWith(NaN);
50+
});
51+
});

0 commit comments

Comments
 (0)