|
1 | | -import { describe, it } from "vitest"; |
| 1 | +import { render, screen } from "@testing-library/react"; |
| 2 | +import { useNavigation } from "react-router"; |
| 3 | +import { describe, expect, it, vi } from "vitest"; |
| 4 | + |
| 5 | +import { createTestProduct } from "@/lib/utils.tests"; |
| 6 | +import type { Product as ProductType } from "@/models/product.model"; |
| 7 | + |
| 8 | +import Product from "."; |
| 9 | + |
| 10 | +import type { Route } from "./+types"; |
| 11 | + |
| 12 | +// Helper function to create a test navigation object |
| 13 | +const createTestNavigation = (overrides = {}) => ({ |
| 14 | + state: "idle", |
| 15 | + location: undefined, |
| 16 | + formMethod: undefined, |
| 17 | + formAction: undefined, |
| 18 | + formEncType: undefined, |
| 19 | + formData: undefined, |
| 20 | + json: undefined, |
| 21 | + text: undefined, |
| 22 | + ...overrides, |
| 23 | +}); |
| 24 | + |
| 25 | +// Mock de react-router |
| 26 | +vi.mock("react-router", () => ({ |
| 27 | + Form: vi.fn(({ children }) => <form>{children}</form>), |
| 28 | + useNavigation: vi.fn(() => createTestNavigation()), |
| 29 | + Link: vi.fn(({ children, ...props }) => <a {...props}>{children}</a>), |
| 30 | +})); |
| 31 | + |
| 32 | +const createTestProps = ( |
| 33 | + productData: Partial<ProductType> = {} |
| 34 | +): Route.ComponentProps => ({ |
| 35 | + loaderData: { product: createTestProduct(productData) }, |
| 36 | + params: vi.fn() as any, |
| 37 | + matches: vi.fn() as any, |
| 38 | +}); |
2 | 39 |
|
3 | 40 | describe("Product Component", () => { |
4 | 41 | describe("Rendering with valid product data", () => { |
5 | | - // Crear un mock de producto de prueba con todos los campos necesarios |
6 | | - // const mockProduct: Product = { id: 1, title: "Test Product", price: 99.99, ... } |
7 | | - |
8 | 42 | it("should render product title correctly", () => { |
9 | | - // 1. Renderizar el componente Product con mockProduct usando render() |
10 | | - // 2. Buscar el elemento h1 que contiene el título del producto |
11 | | - // 3. Verificar que el texto coincida con mockProduct.title usando expect().toHaveTextContent() |
| 43 | + // Step 1: Setup - Create test props |
| 44 | + const props = createTestProps({ title: "Awesome Product" }); |
| 45 | + // Step 2: Mock - Component mocks already set up above |
| 46 | + // Step 3: Call - Render component |
| 47 | + render(<Product {...props} />); |
| 48 | + // Step 4: Verify - Check title is rendered correctly |
| 49 | + const titleElement = screen.getByRole("heading", { level: 1 }); |
| 50 | + expect(titleElement).toHaveTextContent("Awesome Product"); |
12 | 51 | }); |
13 | 52 |
|
14 | 53 | it("should render product price with dollar sign", () => { |
15 | | - // 1. Renderizar el componente Product con mockProduct |
16 | | - // 2. Buscar el elemento que muestra el precio (probablemente un <p> con el precio) |
17 | | - // 3. Verificar que el texto incluya el símbolo $ y el precio correcto |
18 | | - // Tip: usar toHaveTextContent() con el formato "$99.99" |
| 54 | + // Step 1: Setup - Create test props |
| 55 | + const props = createTestProps({ price: 150.99 }); |
| 56 | + // Step 2: Mock - Component mocks already set up above |
| 57 | + // Step 3: Call - Render component |
| 58 | + render(<Product {...props} />); |
| 59 | + // Step 4: Verify - Check price is rendered correctly |
| 60 | + expect(screen.queryByText("$150.99")).toBeInTheDocument(); |
19 | 61 | }); |
20 | 62 |
|
21 | 63 | it("should render product description", () => { |
22 | | - // 1. Renderizar el componente Product con mockProduct |
23 | | - // 2. Buscar el párrafo que contiene la descripción del producto |
24 | | - // 3. Verificar que el texto coincida con mockProduct.description |
25 | | - // Nota: considerar el caso donde description puede ser null |
| 64 | + // Step 1: Setup - Create test props |
| 65 | + const props = createTestProps({ |
| 66 | + description: "Amazing product", |
| 67 | + }); |
| 68 | + // Step 2: Mock - Component mocks already set up above |
| 69 | + // Step 3: Call - Render component |
| 70 | + render(<Product {...props} />); |
| 71 | + // Step 4: Verify - Check description is rendered |
| 72 | + expect(screen.queryByText("Amazing product")).toBeInTheDocument(); |
26 | 73 | }); |
27 | 74 |
|
28 | 75 | it("should render product image with correct src and alt attributes", () => { |
29 | | - // 1. Renderizar el componente Product con mockProduct |
30 | | - // 2. Buscar la imagen usando getByRole('img') o getByAltText() |
31 | | - // 3. Verificar que el atributo src coincida con mockProduct.imgSrc usando toHaveAttribute() |
32 | | - // 4. Verificar que el atributo alt coincida con mockProduct.title |
| 76 | + // Step 1: Setup - Create test props |
| 77 | + const props = createTestProps({ |
| 78 | + imgSrc: "/test-image.jpg", |
| 79 | + alt: "Test Product", |
| 80 | + }); |
| 81 | + // Step 2: Mock - Component mocks already set up above |
| 82 | + // Step 3: Call - Render component |
| 83 | + render(<Product {...props} />); |
| 84 | + // Step 4: Verify - Check image attributes |
| 85 | + const image = screen.getByRole("img"); |
| 86 | + expect(image).toHaveAttribute("src", "/test-image.jpg"); |
| 87 | + expect(image).toHaveAttribute("alt", "Test Product"); |
33 | 88 | }); |
34 | 89 |
|
35 | 90 | it("should render all product features as list items", () => { |
36 | | - // 1. Renderizar el componente Product con mockProduct que tenga un array de features |
37 | | - // 2. Buscar todos los elementos <li> dentro de la lista de características |
38 | | - // 3. Verificar que el número de elementos li coincida con mockProduct.features.length |
39 | | - // 4. Verificar que cada feature aparezca en el DOM usando getAllByText() o similar |
| 91 | + // Step 1: Setup - Create test props |
| 92 | + const features = ["Feature 1", "Feature 2", "Feature 3"]; |
| 93 | + const props = createTestProps({ features }); |
| 94 | + // Step 2: Mock - Component mocks already set up above |
| 95 | + // Step 3: Call - Render component |
| 96 | + render(<Product {...props} />); |
| 97 | + // Step 4: Verify - Check features are rendered |
| 98 | + features.forEach((feature) => { |
| 99 | + expect(screen.queryByText(feature)).toBeInTheDocument(); |
| 100 | + }); |
40 | 101 | }); |
41 | 102 |
|
42 | 103 | it('should render "Agregar al Carrito" button', () => { |
43 | | - // 1. Renderizar el componente Product con mockProduct |
44 | | - // 2. Buscar el botón usando getByRole('button') o getByText('Agregar al Carrito') |
45 | | - // 3. Verificar que el botón esté presente en el documento usando toBeInTheDocument() |
46 | | - // 4. Verificar que el botón tenga el valor correcto (productId) usando toHaveAttribute() |
| 104 | + // Step 1: Setup - Create test props |
| 105 | + const props = createTestProps(); |
| 106 | + // Step 2: Mock - Component mocks already set up above |
| 107 | + // Step 3: Call - Render component |
| 108 | + render(<Product {...props} />); |
| 109 | + // Step 4: Verify - Check button is present |
| 110 | + expect( |
| 111 | + screen.queryByRole("button", { name: "Agregar al Carrito" }) |
| 112 | + ).toBeInTheDocument(); |
47 | 113 | }); |
48 | 114 | }); |
49 | 115 |
|
50 | 116 | describe("Form interactions", () => { |
51 | | - it("should include hidden redirectTo input with correct value"); |
52 | | - it("should include productId as button value"); |
53 | | - it("should disable button when cart is loading"); |
| 117 | + it("should include hidden redirectTo input with correct value", () => { |
| 118 | + // Step 1: Setup |
| 119 | + const productId = 123; |
| 120 | + const props = createTestProps({ id: productId }); |
| 121 | + // Step 2: Mock - Component mocks already set up above |
| 122 | + // Step 3: Call |
| 123 | + render(<Product {...props} />); |
| 124 | + // Step 4: Verify |
| 125 | + const redirectInput = screen.queryByDisplayValue( |
| 126 | + `/products/${productId}` |
| 127 | + ); |
| 128 | + expect(redirectInput).toBeInTheDocument(); |
| 129 | + }); |
| 130 | + |
| 131 | + it("should disable button when cart is loading", () => { |
| 132 | + // Step 1: Setup |
| 133 | + const props = createTestProps(); |
| 134 | + const expectedNavigation = createTestNavigation({ state: "submitting" }); |
| 135 | + // Step 2: Mock - Override navigation state to simulate loading |
| 136 | + vi.mocked(useNavigation).mockReturnValue(expectedNavigation as any); |
| 137 | + // Step 3: Call |
| 138 | + render(<Product {...props} />); |
| 139 | + // Step 4: Verify |
| 140 | + const button = screen.getByRole("button"); |
| 141 | + expect(button).toBeDisabled(); |
| 142 | + expect(button).toHaveTextContent("Agregando..."); |
| 143 | + }); |
54 | 144 | }); |
55 | 145 |
|
56 | 146 | describe("Error handling", () => { |
57 | | - it("should render NotFound component when product is not provided"); |
| 147 | + it("should render NotFound component when product is not provided", () => { |
| 148 | + // Step 1: Setup - Create props without product |
| 149 | + const props = createTestProps(); |
| 150 | + props.loaderData.product = undefined; |
| 151 | + |
| 152 | + // Step 2: Mock - Mock NotFound component |
| 153 | + // vi.mock("../not-found", () => ({ |
| 154 | + // default: () => <div data-testid="not-found">Not Found Page</div>, |
| 155 | + // })); |
| 156 | + // Step 3: Call |
| 157 | + render(<Product {...props} />); |
| 158 | + // Step 4: Verify |
| 159 | + expect(screen.getByTestId("not-found")).toBeInTheDocument(); |
| 160 | + }); |
58 | 161 | }); |
59 | 162 | }); |
0 commit comments