diff --git a/restapi/products/Product.ts b/restapi/products/Product.ts index 22aafeac..763c735d 100644 --- a/restapi/products/Product.ts +++ b/restapi/products/Product.ts @@ -32,6 +32,11 @@ export const product = new Schema( type: String, required: false, }, + category: { + type:String, + required: true, + enum: ['Clothes', 'Decoration', 'Electronics', 'Miscellaneous'] + } }, { versionKey: false, diff --git a/restapi/products/ProductController.ts b/restapi/products/ProductController.ts index 4672a410..58e281bc 100644 --- a/restapi/products/ProductController.ts +++ b/restapi/products/ProductController.ts @@ -84,3 +84,31 @@ export const updateProduct: RequestHandler = async (req, res) => { res.status(403).json(); } }; + +export const filterAndOrderBy: RequestHandler = async (req, res) => { + let mode = req.params.mode; + const category = req.params.category; + let products; + + if ( + category !== "Clothes" && + category !== "Decoration" && + category !== "Electronics" && + category !== "Miscellaneous" + ) { + if (mode !== "asc" && mode !== "desc") { + products = await productModel.find(); + } else { + products = await productModel.find().sort({ price: mode }); + } + } else { + if (mode !== "asc" && mode !== "desc") { + products = await productModel.find({ category: category }); + } else { + products = await productModel + .find({ category: category }) + .sort({ price: mode }); + } + } + return res.json(products); +}; diff --git a/restapi/products/ProductRoutes.ts b/restapi/products/ProductRoutes.ts index 9c1256cb..ccd6567b 100644 --- a/restapi/products/ProductRoutes.ts +++ b/restapi/products/ProductRoutes.ts @@ -1,7 +1,7 @@ -import express, { Request, Response, Router } from "express"; +import express, { Router } from "express"; +import multer from "../utils/multer"; import * as ProdctController from "./ProductController"; -import multer from "../utils/multer"; const api: Router = express.Router(); @@ -9,6 +9,8 @@ api.get("/products", ProdctController.getProducts); api.get("/products/findByCode/:code", ProdctController.getProduct); +api.get('/products/filter&order/:category&:mode', ProdctController.filterAndOrderBy) + api.post("/products", multer.single("image"), ProdctController.createProduct); api.post("/products/delete/:code", ProdctController.deleteProduct); diff --git a/restapi/products/product.http b/restapi/products/product.http index 4bcfacde..125dfe74 100644 --- a/restapi/products/product.http +++ b/restapi/products/product.http @@ -3,7 +3,7 @@ GET {{url}}/products/list ### -GET {{url}}/products/findByCode/1 +GET {{url}}/products/findByCode/0007 ### POST {{url}}/products/create @@ -21,9 +21,15 @@ Content-Type: application/json DELETE {{url}}/products/delete/1 ### -POST {{url}}/products/update/1347 +POST {{url}}/products/update/0045 Content-Type: application/json { - "name": "Camiseta del Sporting chacho" + "category": "Miscellaneous" } + +### +GET {{url}}/products/order/desc + +### +GET {{url}}/products/filter&order/Electronics&asc diff --git a/restapi/tests/orders.test.ts b/restapi/tests/orders.test.ts index 531cffb0..d4bafd5c 100644 --- a/restapi/tests/orders.test.ts +++ b/restapi/tests/orders.test.ts @@ -71,6 +71,7 @@ describe("orders", () => { "Do you wanna show your friends that you are THE GREATEST IMPOSTER? Then this shirt is for you!", stock: 1, image: "0001.png", + category: "Clothes", }, ], date: new Date(), diff --git a/restapi/tests/products.test.ts b/restapi/tests/products.test.ts index fc0637b4..b7266c78 100644 --- a/restapi/tests/products.test.ts +++ b/restapi/tests/products.test.ts @@ -71,6 +71,7 @@ describe("prodcuts", () => { name: "Super SUS T-Shirt", price: 9.5, image: "0001.png", + category: "Clothes", }) ); }); @@ -91,6 +92,7 @@ describe("prodcuts", () => { price: 0.99, description: "Another test product", stock: 0, + category: "Clothes", }); expect(response.statusCode).toBe(403); }); @@ -110,6 +112,7 @@ describe("prodcuts", () => { price: 0.99, description: "Another test product", stock: 0, + category: "Clothes", }); expect(response.statusCode).toBe(200); expect(response.body.name).toBe("testProduct"); @@ -128,6 +131,7 @@ describe("prodcuts", () => { price: 0.99, description: "A failure insert test product", stock: 0, + category: "Clothes", }); expect(response.statusCode).toBe(409); }); @@ -144,6 +148,23 @@ describe("prodcuts", () => { expect(response.statusCode).toBe(412); }); + it("Can't create a product with incorrect or missing category", async () => { + let userToken = await getToken(); + const response: Response = await request(app) + .post("/products") + .set("token", userToken) + .set("email", "test") + .send({ + code: uuidv4(), + name: "testFailProduct", + price: 0.99, + description: "A failure insert test product", + stock: 0, + category: "Nothing", + }); + expect(response.statusCode).toBe(412); + }); + it("Can update a product correctly", async () => { let userToken = await getToken(); const response: Response = await request(app) @@ -151,23 +172,25 @@ describe("prodcuts", () => { .set("token", userToken) .set("email", "test") .send({ - stock: 1000, + stock: 10, }); expect(response.statusCode).toBe(200); - expect(response.body.stock).toBe(1000); + expect(response.body.stock).toBe(10); }); it("Can't update a product without being admin or manager", async () => { - const token: Response = await request(app).post("/users/requestToken/").send({ - email: "test1", - password: "test", - }); + const token: Response = await request(app) + .post("/users/requestToken/") + .send({ + email: "test1", + password: "test", + }); const response: Response = await request(app) .post("/products/update/" + productCode) .set("token", token.body) .set("email", "test1") .send({ - stock: 1000, + stock: 10, }); expect(response.statusCode).toBe(403); }); @@ -176,7 +199,7 @@ describe("prodcuts", () => { const response: Response = await request(app) .post("/products/update/" + productCode) .send({ - stock: 1000, + stock: 10, }); expect(response.statusCode).toBe(403); }); @@ -197,6 +220,53 @@ describe("prodcuts", () => { .send(); expect(response.statusCode).toBe(403); }); + + /* + Testing filtering and ordering products + */ + it("Can filter by category", async () => { + const response: Response = await request(app) + .get("/products/filter&order/Electronics&A") + .send(); + expect(response.statusCode).toBe(200); + expect(response.body).toEqual( + expect.arrayContaining([ + expect.objectContaining({ category: "Electronics" }), + ]) + ); + }); + + it("Can filter by category and order by price", async () => { + const response: Response = await request(app) + .get("/products/filter&order/Electronics&asc") + .send(); + expect(response.statusCode).toBe(200); + expect(response.body).toEqual( + expect.arrayContaining([ + expect.objectContaining({ category: "Electronics" }), + ]) + ); + expect(response.body[0].price).toBe(20.99); + }); + + it("Can order by price", async () => { + const response: Response = await request(app) + .get("/products/filter&order/Category&asc") + .send(); + expect(response.statusCode).toBe(200); + expect(response.body[0].price).toBe(1.99); + }); + + it("Check normal order when 'wrong' input", async () => { + const response: Response = await request(app) + .get("/products/filter&order/a&a") + .send(); + expect(response.statusCode).toBe(200); + expect(response.body[0].price).toBe(12.95); + expect(response.body[0].code).toBe("1234"); + expect(response.body[1].price).toBe(9.5); + expect(response.body[1].code).toBe("0001"); + }); }); async function getToken() { diff --git a/restapi/utils/PDFHelper.ts b/restapi/utils/PDFHelper.ts index 86104829..96c6db5f 100644 --- a/restapi/utils/PDFHelper.ts +++ b/restapi/utils/PDFHelper.ts @@ -1,5 +1,5 @@ import { orderModel } from "../orders/Order"; -import { userModel } from "../users/User"; +import { sendInvoiceEmail } from "./emailSender"; const fs = require("fs"); const PDFGenerator = require("pdfkit"); @@ -11,7 +11,7 @@ export const createPDF = async (code: string) => { const orderFound = await orderModel.findOne({ orderCode: code, }); - const user = await userModel.findOne({ email: orderFound.userEmail }); + /*const user = await userModel.findOne({ email: orderFound.userEmail }); const invoiceData = { addresses: { shipping: { @@ -29,27 +29,17 @@ export const createPDF = async (code: string) => { }; const ig = new InvoiceGenerator(invoiceData); - ig.generate(); - - /*var html = fs.readFileSync(process.cwd() + "/utils/template.html", "utf-8"); + ig.generate();*/ + var html = fs.readFileSync(process.cwd() + "/utils/template.html", "utf-8"); const parse = require("node-html-parser").parse; - const options: CreateOptions = { - format: "A4", - orientation: "portrait", - }; - - const orderFound = await orderModel.findOne({ - orderCode: code, - }); - const root = parse(html); const body = root.querySelector("body"); let aux = '
' + '
' + '

DeDe

' + - '
aswdedeen2a@gmail.com
' + + '
aswdedeen2a@gmail.com


' + "
" + "
" + "
" + @@ -146,12 +136,5 @@ export const createPDF = async (code: string) => { body.insertAdjacentHTML("beforeend", aux); - pdf - .create(root.toString(), options) - .toFile("./pdf/" + orderFound.orderCode + ".pdf", function (err, res) { - if (err) console.log(err); - else { - sendInvoiceEmail(orderFound.userEmail, orderFound.orderCode); - } - });*/ + sendInvoiceEmail(orderFound.userEmail, orderFound.orderCode, root.toString()); }; diff --git a/restapi/utils/emailSender.ts b/restapi/utils/emailSender.ts index 0fa4f7e6..49ee0923 100644 --- a/restapi/utils/emailSender.ts +++ b/restapi/utils/emailSender.ts @@ -1,4 +1,3 @@ - const nodemailer = require("nodemailer"); const { v4: uuidv4 } = require("uuid"); const fs = require("fs"); @@ -12,10 +11,11 @@ sgMail.setApiKey( export const sendInvoiceEmail: Function = ( email: string, - orderCode: string + orderCode: string, + message: string ) => { - const pathToAttachment = `${__dirname}/pdf/` + orderCode + ".pdf"; - const attachment = fs.readFileSync(pathToAttachment).toString("base64"); + /*const pathToAttachment = `${__dirname}/pdf/` + orderCode + ".pdf"; + const attachment = fs.readFileSync(pathToAttachment).toString("base64");*/ const mailOptions = { to: email, @@ -23,20 +23,24 @@ export const sendInvoiceEmail: Function = ( subject: "DeDe Order Invoice", html: "

Thank you for trusting in DeDe and buying with us.

Here you have the receipt of your purchase." + - "

We hope to see you soon :)

", - attachments: [ + "

We hope to see you soon :)






" + + message, + /*attachments: [ { content: attachment, filename: orderCode + ".pdf", type: "application/pdf", disposition: "attachment", }, - ], + ],*/ }; sgMail.send(mailOptions); }; -export const sendVerificationEmail: Function = async (email: string, uniqueString: string) => { +export const sendVerificationEmail: Function = async ( + email: string, + uniqueString: string +) => { const currentUrl = "http://localhost:5000"; const mailOptions = { diff --git a/webapp/src/api/api.ts b/webapp/src/api/api.ts index 07d96049..97510551 100644 --- a/webapp/src/api/api.ts +++ b/webapp/src/api/api.ts @@ -1,7 +1,9 @@ -import { Order, Review, User, Product } from "../shared/shareddtypes"; +import { Order, Product, Review, User } from "../shared/shareddtypes"; const apiEndPoint = process.env.REACT_APP_API_URI || "http://localhost:5000"; +// USERS + export async function addUser(user: User): Promise { let response = await fetch(apiEndPoint + "/users", { method: "POST", @@ -44,6 +46,8 @@ export async function getUser(userEmail: String): Promise { return response.json(); } +// PRODUCTS + export async function getProducts(): Promise { let response = await fetch(apiEndPoint + "/products/"); return response.json(); @@ -56,35 +60,6 @@ export async function getProduct(productCode: string): Promise { return response.json(); } -export async function getPlaces( - x: number, - y: number, - radiusMeters: number, - maxResults: number -): Promise { - const url = - "https://api.geoapify.com/v2/places?categories=commercial&filter=circle:" + - x + - "," + - y + - "," + - radiusMeters + - "&bias=proximity:" + - x + - "," + - y + - "&limit=" + - maxResults + - "&apiKey=" + - process.env.REACT_APP_GEOAPIFY_KEY; - - let places; - await fetch(url, { - method: "GET", - }).then((response) => (places = response.json())); - return places; -} - export async function updateProduct(product: Product) { await fetch(apiEndPoint + "/products/update/" + product.code, { method: "POST", @@ -133,6 +108,20 @@ export async function deleteProduct(code: string) { }); } +// Mode must be desc or asc. If not default order +// Category must be Clothes, Decoration, Elecrtonics or Miscellaneous. If not all categories +export async function filterProductsByCategory( + category: string, + mode: string +): Promise { + let response = await fetch( + apiEndPoint + "/products/filter&order/" + category + "&" + mode + ); + return response.json(); +} + +// ORDERS + export async function createOrder(body: any) { await fetch(apiEndPoint + "/orders", { method: "POST", @@ -182,6 +171,8 @@ export async function getOrders(): Promise { return response.json(); } +// REVIEWS + export async function getReviewsByCode(code: string): Promise { let response = await fetch(apiEndPoint + "/reviews/listByCode/" + code); return response.json(); @@ -215,3 +206,34 @@ export async function addReview(review: Review): Promise { return true; } else return false; } + +// PLACES + +export async function getPlaces( + x: number, + y: number, + radiusMeters: number, + maxResults: number +): Promise { + const url = + "https://api.geoapify.com/v2/places?categories=commercial&filter=circle:" + + x + + "," + + y + + "," + + radiusMeters + + "&bias=proximity:" + + x + + "," + + y + + "&limit=" + + maxResults + + "&apiKey=" + + process.env.REACT_APP_GEOAPIFY_KEY; + + let places; + await fetch(url, { + method: "GET", + }).then((response) => (places = response.json())); + return places; +} diff --git a/webapp/src/components/checkout/Checkout.tsx b/webapp/src/components/checkout/Checkout.tsx index 5db9b03d..2769a806 100644 --- a/webapp/src/components/checkout/Checkout.tsx +++ b/webapp/src/components/checkout/Checkout.tsx @@ -1,21 +1,21 @@ -import * as React from "react"; - import Container from "@mui/material/Container"; import Paper from "@mui/material/Paper"; -import Stepper from "@mui/material/Stepper"; import Step from "@mui/material/Step"; import StepLabel from "@mui/material/StepLabel"; +import Stepper from "@mui/material/Stepper"; import Typography from "@mui/material/Typography"; - +import * as React from "react"; import { updateProduct } from "../../api/api"; -import { CartItem } from "../../shared/shareddtypes"; import { saveOrder } from "../../helpers/ShoppingCartHelper"; - -import ShippingAddress from "./ShippingAddress"; -import ShippingMethod from "./ShippingMethod"; -import Review from "./Review"; +import { CartItem } from "../../shared/shareddtypes"; import Billing from "./Billing"; import OrderConfirmation from "./OrderConfirmation"; +import Review from "./Review"; +import ShippingAddress from "./ShippingAddress"; +import ShippingMethod from "./ShippingMethod"; + + + function getSteps() { return ["Address", "Shipping method", "Review", "Billing", "Confirm"]; @@ -59,7 +59,7 @@ export default function Checkout(props: any) { props.productsCart, costs, props.userEmail, - "Get address not implemented yet" + address ); props.deleteCart(); }; diff --git a/webapp/src/components/dashboard/products/DeleteProduct.tsx b/webapp/src/components/dashboard/products/DeleteProduct.tsx index 0fe25b4c..ce4f93eb 100644 --- a/webapp/src/components/dashboard/products/DeleteProduct.tsx +++ b/webapp/src/components/dashboard/products/DeleteProduct.tsx @@ -1,23 +1,20 @@ -import React, { useState } from "react"; - import { - Box, - Card, + Alert, Box, Button, Card, Container, MenuItem, Paper, Snackbar, Stack, styled, - TextField, - Alert, - Button, + TextField } from "@mui/material"; - +import React, { useState } from "react"; import { deleteProduct } from "../../../api/api"; import { checkImageExists } from "../../../helpers/ImageHelper"; import { NotificationType, Product } from "../../../shared/shareddtypes"; + + const DEF_IMAGE: string = require("../../../images/not-found.png"); const Img = styled("img")({ @@ -39,6 +36,7 @@ export default function DeleteProduct(props: DeleteProductProps): JSX.Element { const [description, setDescription] = useState(""); const [stock, setStock] = useState(""); const [price, setPrice] = useState(""); + const [category, setCategory] = useState(""); const [dialogOpen, setDialogOpen] = useState(0); const [image, setImage] = useState(DEF_IMAGE); const [notification, setNotification] = useState({ @@ -54,6 +52,7 @@ export default function DeleteProduct(props: DeleteProductProps): JSX.Element { setCode(p.code); setName(p.name); setDescription(p.description); + setCategory(p.category) setPrice(p.price.toString()); setStock(p.stock.toString()); setImage(checkImageExists(p.image)); @@ -87,6 +86,7 @@ export default function DeleteProduct(props: DeleteProductProps): JSX.Element { setCode(""); setName(""); setDescription(""); + setCategory("") setStock(""); setPrice(""); setImage(DEF_IMAGE); @@ -114,7 +114,7 @@ export default function DeleteProduct(props: DeleteProductProps): JSX.Element { > {products.map((product) => ( - {product.name + " (" + product.description + ")"} + {product.code + " - " + product.name} ))} @@ -163,6 +163,18 @@ export default function DeleteProduct(props: DeleteProductProps): JSX.Element { variant="outlined" /> + setCategory(event.target.value)} + /> + (DEF_IMAGE); const getCode = async () => { @@ -76,6 +76,7 @@ export default function UploadImage(props: UploadProductProps): JSX.Element { setDescription(""); setStock(""); setPrice(""); + setCategory(""); }; const checkFields = () => { @@ -99,6 +100,7 @@ export default function UploadImage(props: UploadProductProps): JSX.Element { description: description, price: Number(price), stock: Number(stock), + category: category, }); if (created) { emptyFields(); @@ -184,6 +186,32 @@ export default function UploadImage(props: UploadProductProps): JSX.Element { onChange={(event) => setDescription(event.target.value)} /> + setCategory(event.target.value)} + > + + Clothes + + + Decoration + + + Electronics + + + Miscellaneous + + + { - item.product.stock = item.amount; - orderProducts.push(item.product); + let p: Product = { + code: item.product.code, + name: item.product.name, + description: item.product.description, + price: item.product.price, + stock: item.amount, + image: item.product.image, + category: item.product.category + }; + orderProducts.push(p); }); let order: Order = { diff --git a/webapp/src/shared/shareddtypes.ts b/webapp/src/shared/shareddtypes.ts index cb973ef0..17ae53da 100644 --- a/webapp/src/shared/shareddtypes.ts +++ b/webapp/src/shared/shareddtypes.ts @@ -16,6 +16,14 @@ export type Product = { price: number; stock: number; image: string; + category: string; +}; + +export const ProductCategories = { + Clothes: "Clothes", + Decoration: "Decoration", + Electronics: "Electronics", + Misccellaneous: "Miscellaneous", }; export type NotificationType = { diff --git a/webapp/src/tests/cart/ShoppingCart.test.tsx b/webapp/src/tests/cart/ShoppingCart.test.tsx index 9ed394cd..e1ea2d0e 100644 --- a/webapp/src/tests/cart/ShoppingCart.test.tsx +++ b/webapp/src/tests/cart/ShoppingCart.test.tsx @@ -1,7 +1,7 @@ import { render } from "@testing-library/react"; +import { BrowserRouter as Router } from "react-router-dom"; import ShoppingCart from "../../components/cart/ShoppingCart"; import { CartItem } from "../../shared/shareddtypes"; -import { BrowserRouter as Router } from "react-router-dom"; //Test for the ShoppingCart component, receives a list of cart items and it is rendered properly. test("A list of two cart items is rendered", async () => { @@ -14,6 +14,7 @@ test("A list of two cart items is rendered", async () => { price: 10, stock: 20, image: "", + category: 'Electronics' }, amount: 1, }, @@ -25,6 +26,7 @@ test("A list of two cart items is rendered", async () => { price: 15, stock: 10, image: "", + category: 'Clothes' }, amount: 2, }, diff --git a/webapp/src/tests/cart/ShoppingCartTable.test.tsx b/webapp/src/tests/cart/ShoppingCartTable.test.tsx index ddb9b1f2..0aab086b 100644 --- a/webapp/src/tests/cart/ShoppingCartTable.test.tsx +++ b/webapp/src/tests/cart/ShoppingCartTable.test.tsx @@ -13,6 +13,7 @@ test("A list of two cart items is rendered", async () => { price: 10, stock: 20, image: "", + category: 'Electronics' }, amount: 1, }, @@ -24,6 +25,7 @@ test("A list of two cart items is rendered", async () => { price: 15, stock: 10, image: "", + category: 'Clothes' }, amount: 2, }, @@ -73,6 +75,7 @@ test("A list of one cart item with amount 0 is not rendered", async () => { price: 10, stock: 20, image: "", + category: 'Electronics' }, amount: 0, }, @@ -102,6 +105,7 @@ test("The increment button is disabled when the amount is equal or higher to the price: 10, stock: 20, image: "", + category: 'Electronics' }, amount: 20, }, @@ -130,6 +134,7 @@ test("Increment and decrement buttons work well.", async () => { price: 10, stock: 20, image: "", + category: 'Electronics' }, amount: 1, }, diff --git a/webapp/src/tests/products/ProductBox.test.tsx b/webapp/src/tests/products/ProductBox.test.tsx index 5e176ea5..6facf9d5 100644 --- a/webapp/src/tests/products/ProductBox.test.tsx +++ b/webapp/src/tests/products/ProductBox.test.tsx @@ -1,9 +1,8 @@ -import React from "react"; - import { fireEvent, render } from "@testing-library/react"; -import ProductBox from "../../components/products/ProductBox"; -import { Product, CartItem } from "../../shared/shareddtypes"; import { BrowserRouter as Router } from "react-router-dom"; +import ProductBox from "../../components/products/ProductBox"; +import { CartItem, Product } from "../../shared/shareddtypes"; + test("A product is rendered", async () => { const product: Product = { @@ -13,6 +12,7 @@ test("A product is rendered", async () => { price: 10, stock: 20, image: "", + category: 'Electronics' }; const { getByText, container } = render( @@ -41,6 +41,7 @@ test("A product is added to the cart", async () => { price: 10, stock: 20, image: "", + category: 'Electronics' }; const cart: CartItem[] = []; @@ -69,6 +70,7 @@ test("URL to product details works", async () => { price: 10, stock: 20, image: "", + category: 'Electronics' }; const cart: CartItem[] = []; diff --git a/webapp/src/tests/products/ProductDetails.test.tsx b/webapp/src/tests/products/ProductDetails.test.tsx index 9eb6d2d9..7750d5ef 100644 --- a/webapp/src/tests/products/ProductDetails.test.tsx +++ b/webapp/src/tests/products/ProductDetails.test.tsx @@ -1,6 +1,6 @@ -import { fireEvent, prettyDOM, render } from "@testing-library/react"; +import { fireEvent, render } from "@testing-library/react"; import ProductDetails from "../../components/products/ProductDetails"; -import { Product, CartItem } from "../../shared/shareddtypes"; +import { Product } from "../../shared/shareddtypes"; //Test that the product details page renders correctly test("Renders product details page", () => { @@ -11,6 +11,7 @@ test("Renders product details page", () => { price: 9.99, stock: 15, image: "", + category: 'Electronics' }; const { getByText, getAllByText } = render( @@ -38,6 +39,7 @@ test("Adds product to cart", () => { price: 9.99, stock: 15, image: "", + category: 'Electronics' }; const onAdd = jest.fn(); diff --git a/webapp/src/tests/products/ProductList.test.tsx b/webapp/src/tests/products/ProductList.test.tsx index 41aaa987..f6770cc3 100644 --- a/webapp/src/tests/products/ProductList.test.tsx +++ b/webapp/src/tests/products/ProductList.test.tsx @@ -1,9 +1,8 @@ -import React from "react"; - -import { fireEvent, render } from "@testing-library/react"; +import { render } from "@testing-library/react"; +import { BrowserRouter as Router } from "react-router-dom"; import ProductList from "../../components/products/ProductList"; import { Product } from "../../shared/shareddtypes"; -import { BrowserRouter as Router } from "react-router-dom"; + test("A list of products is rendered", async () => { const products: Product[] = [ @@ -14,6 +13,7 @@ test("A list of products is rendered", async () => { price: 10, stock: 20, image: "", + category: 'Electronics' }, { code: "9998", @@ -22,6 +22,7 @@ test("A list of products is rendered", async () => { price: 15, stock: 10, image: "", + category: 'Electronics' }, ];