|
1 | | -import { render, screen } from "@testing-library/react"; |
2 | | -import { useNavigation } from "react-router"; |
| 1 | +import { render, screen, fireEvent } from "@testing-library/react"; |
3 | 2 | import { describe, expect, it, vi } from "vitest"; |
4 | 3 |
|
5 | | -import { createTestProduct } from "@/lib/utils.tests"; |
6 | | -import type { Product as ProductType } from "@/models/product.model"; |
| 4 | +import { createTestProduct, createTestVariantAttributeValue } from "@/lib/utils.tests"; |
| 5 | +import type { Product as ProductModel, VariantAttributeValueWithNumber } from "@/models/product.model"; |
7 | 6 |
|
8 | 7 | import Product from "."; |
9 | 8 |
|
10 | 9 | import type { Route } from "./+types"; |
11 | 10 |
|
12 | | -// Helper function to create a test navigation object |
13 | | -const createTestNavigation = (overrides = {}) => ({ |
14 | | - state: "idle" as const, |
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 | 11 | // 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 | | -})); |
| 12 | +vi.mock("react-router", () => { |
| 13 | + const actual = vi.importActual("react-router"); // mantener los demás exports reales |
| 14 | + return { |
| 15 | + ...actual, |
| 16 | + Form: vi.fn(({ children }) => <form>{children}</form>), |
| 17 | + useNavigation: vi.fn(() => ({ state: "idle" } )), |
| 18 | + useSearchParams: vi.fn(() => [new URLSearchParams(), vi.fn()]), |
| 19 | + Link: vi.fn(({ children, ...props }) => <a {...props}>{children}</a>), |
| 20 | + }; |
| 21 | +}); |
31 | 22 |
|
32 | 23 | const createTestProps = ( |
33 | | - productData: Partial<ProductType> = {} |
| 24 | + productData: Partial<ProductModel> = {} |
34 | 25 | ): Route.ComponentProps => ({ |
35 | 26 | loaderData: { product: createTestProduct(productData) }, |
36 | 27 | params: { id: "123" }, |
37 | | - // Hack to satisfy type requirements |
38 | 28 | matches: [] as unknown as Route.ComponentProps["matches"], |
39 | 29 | }); |
40 | 30 |
|
41 | 31 | describe("Product Component", () => { |
42 | 32 | describe("Rendering with valid product data", () => { |
43 | 33 | it("should render product title correctly", () => { |
44 | | - // Step 1: Setup - Create test props |
45 | 34 | const props = createTestProps({ title: "Awesome Product" }); |
46 | | - // Step 2: Mock - Component mocks already set up above |
47 | | - // Step 3: Call - Render component |
48 | 35 | render(<Product {...props} />); |
49 | | - // Step 4: Verify - Check title is rendered correctly |
50 | | - const titleElement = screen.getByRole("heading", { level: 1 }); |
51 | | - expect(titleElement).toHaveTextContent("Awesome Product"); |
| 36 | + expect(screen.getByRole("heading", { level: 1 })).toHaveTextContent("Awesome Product"); |
52 | 37 | }); |
53 | 38 |
|
54 | 39 | it("should render product price with correct currency", () => { |
55 | | - // Step 1: Setup - Create test props |
56 | | - const props = createTestProps({ price: 150.99 }); |
57 | | - // Step 2: Mock - Component mocks already set up above |
58 | | - // Step 3: Call - Render component |
| 40 | + const props = createTestProps({ |
| 41 | + categoryId: 1, // Para que el componente muestre variantes |
| 42 | + variantAttributeValues: [ |
| 43 | + createTestVariantAttributeValue({ id: 1, value: "S", price: 100 }), |
| 44 | + createTestVariantAttributeValue({ id: 2, value: "M", price: 120 }), |
| 45 | + ], |
| 46 | +}); |
59 | 47 | render(<Product {...props} />); |
60 | | - // Step 4: Verify - Check price is rendered correctly |
61 | | - expect(screen.queryByText("S/150.99")).toBeInTheDocument(); |
| 48 | + expect(screen.getByText("S/100.00")).toBeInTheDocument(); |
62 | 49 | }); |
63 | 50 |
|
64 | 51 | it("should render product description", () => { |
65 | | - // Step 1: Setup - Create test props |
66 | | - const props = createTestProps({ |
67 | | - description: "Amazing product", |
68 | | - }); |
69 | | - // Step 2: Mock - Component mocks already set up above |
70 | | - // Step 3: Call - Render component |
| 52 | + const props = createTestProps({ description: "Amazing product" }); |
71 | 53 | render(<Product {...props} />); |
72 | | - // Step 4: Verify - Check description is rendered |
73 | | - expect(screen.queryByText("Amazing product")).toBeInTheDocument(); |
| 54 | + expect(screen.getByText("Amazing product")).toBeInTheDocument(); |
74 | 55 | }); |
75 | 56 |
|
76 | | - it("should render product image with correct src and alt attributes", () => { |
77 | | - // Step 1: Setup - Create test props |
| 57 | + it("should render product image with correct src and alt", () => { |
78 | 58 | const props = createTestProps({ |
79 | 59 | imgSrc: "/test-image.jpg", |
80 | 60 | alt: "Test Product", |
81 | 61 | }); |
82 | | - // Step 2: Mock - Component mocks already set up above |
83 | | - // Step 3: Call - Render component |
84 | 62 | render(<Product {...props} />); |
85 | | - // Step 4: Verify - Check image attributes |
86 | 63 | const image = screen.getByRole("img"); |
87 | 64 | expect(image).toHaveAttribute("src", "/test-image.jpg"); |
88 | 65 | expect(image).toHaveAttribute("alt", "Test Product"); |
89 | 66 | }); |
90 | 67 |
|
91 | 68 | it("should render all product features as list items", () => { |
92 | | - // Step 1: Setup - Create test props |
93 | 69 | const features = ["Feature 1", "Feature 2", "Feature 3"]; |
94 | 70 | const props = createTestProps({ features }); |
95 | | - // Step 2: Mock - Component mocks already set up above |
96 | | - // Step 3: Call - Render component |
97 | 71 | render(<Product {...props} />); |
98 | | - // Step 4: Verify - Check features are rendered |
99 | 72 | features.forEach((feature) => { |
100 | | - expect(screen.queryByText(feature)).toBeInTheDocument(); |
| 73 | + expect(screen.getByText(feature)).toBeInTheDocument(); |
101 | 74 | }); |
102 | 75 | }); |
103 | 76 |
|
104 | 77 | it('should render "Agregar al Carrito" button', () => { |
105 | | - // Step 1: Setup - Create test props |
106 | 78 | const props = createTestProps(); |
107 | | - // Step 2: Mock - Component mocks already set up above |
108 | | - // Step 3: Call - Render component |
109 | 79 | render(<Product {...props} />); |
110 | | - // Step 4: Verify - Check button is present |
111 | | - expect( |
112 | | - screen.queryByRole("button", { name: "Agregar al Carrito" }) |
113 | | - ).toBeInTheDocument(); |
| 80 | + expect(screen.getByRole("button", { name: "Agregar al Carrito" })).toBeInTheDocument(); |
114 | 81 | }); |
115 | | - }); |
116 | 82 |
|
117 | | - describe("Form interactions", () => { |
118 | | - it("should include hidden redirectTo input with correct value", () => { |
119 | | - // Step 1: Setup |
120 | | - const productId = 123; |
121 | | - const props = createTestProps({ id: productId }); |
122 | | - // Step 2: Mock - Component mocks already set up above |
123 | | - // Step 3: Call |
124 | | - render(<Product {...props} />); |
125 | | - // Step 4: Verify |
126 | | - const redirectInput = screen.queryByDisplayValue( |
127 | | - `/products/${productId}` |
128 | | - ); |
129 | | - expect(redirectInput).toBeInTheDocument(); |
130 | | - }); |
| 83 | + it("should render variants and update price when variant is selected", () => { |
| 84 | + const props = createTestProps({ |
| 85 | + categoryId: 1, |
| 86 | + variantAttributeValues: [ |
| 87 | + { |
| 88 | + id: 1, |
| 89 | + attributeId: 1, |
| 90 | + productId: 1, |
| 91 | + value: "S", |
| 92 | + price: 100, |
| 93 | + createdAt: new Date(), |
| 94 | + updatedAt: new Date(), |
| 95 | + variantAttribute: { id: 1, name: "Talla" }, |
| 96 | + }, |
| 97 | + { |
| 98 | + id: 2, |
| 99 | + attributeId: 1, |
| 100 | + productId: 1, |
| 101 | + value: "M", |
| 102 | + price: 120, |
| 103 | + createdAt: new Date(), |
| 104 | + updatedAt: new Date(), |
| 105 | + variantAttribute: { id: 1, name: "Talla" }, |
| 106 | + }, |
| 107 | + ] as VariantAttributeValueWithNumber[], |
| 108 | + }); |
131 | 109 |
|
132 | | - it("should disable button when cart is loading", () => { |
133 | | - // Step 1: Setup |
134 | | - const props = createTestProps(); |
135 | | - const expectedNavigation = createTestNavigation({ state: "submitting" }); |
136 | | - // Step 2: Mock - Override navigation state to simulate loading |
137 | | - vi.mocked(useNavigation).mockReturnValue(expectedNavigation); |
138 | | - // Step 3: Call |
139 | 110 | render(<Product {...props} />); |
140 | | - // Step 4: Verify |
141 | | - const button = screen.getByRole("button"); |
142 | | - expect(button).toBeDisabled(); |
143 | | - expect(button).toHaveTextContent("Agregando..."); |
144 | | - }); |
145 | | - }); |
146 | 111 |
|
147 | | - describe("Error handling", () => { |
148 | | - it("should render NotFound component when product is not provided", () => { |
149 | | - // Step 1: Setup - Create props without product |
150 | | - const props = createTestProps(); |
151 | | - props.loaderData.product = undefined; |
| 112 | + const smallBtn = screen.getByRole("button", { name: "S" }); |
| 113 | + const mediumBtn = screen.getByRole("button", { name: "M" }); |
| 114 | + expect(smallBtn).toBeInTheDocument(); |
| 115 | + expect(mediumBtn).toBeInTheDocument(); |
152 | 116 |
|
153 | | - // Step 2: Mock - Mock NotFound component |
154 | | - // vi.mock("../not-found", () => ({ |
155 | | - // default: () => <div data-testid="not-found">Not Found Page</div>, |
156 | | - // })); |
157 | | - // Step 3: Call |
158 | | - render(<Product {...props} />); |
159 | | - // Step 4: Verify |
160 | | - expect(screen.getByTestId("not-found")).toBeInTheDocument(); |
| 117 | + expect(screen.getByText("S/100.00")).toBeInTheDocument(); |
| 118 | + |
| 119 | + fireEvent.click(mediumBtn); |
| 120 | + expect(screen.getByText("S/120.00")).toBeInTheDocument(); |
161 | 121 | }); |
162 | 122 | }); |
163 | 123 | }); |
0 commit comments