diff --git a/README.md b/README.md index ecb708d..64c5097 100644 --- a/README.md +++ b/README.md @@ -94,5 +94,5 @@ To run all back-end unit and integration tests (full code coverage): ``` 2. Run the tests: ```bash - + mvn test ``` diff --git a/backend/src/main/java/com/inventory/backend/config/WebConfig.java b/backend/src/main/java/com/inventory/backend/config/WebConfig.java new file mode 100644 index 0000000..e0145f0 --- /dev/null +++ b/backend/src/main/java/com/inventory/backend/config/WebConfig.java @@ -0,0 +1,16 @@ +package com.inventory.backend.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.*; + +@Configuration +public class WebConfig implements WebMvcConfigurer{ + @Override + public void addCorsMappings(CorsRegistry registry){ + registry.addMapping("/**") + .allowedOrigins("http://localhost:8080") + .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") + .allowedHeaders("*") + .allowCredentials(true); + } +} diff --git a/backend/src/main/java/com/inventory/backend/controller/ProductController.java b/backend/src/main/java/com/inventory/backend/controller/ProductController.java index bd4f766..f1a8b82 100644 --- a/backend/src/main/java/com/inventory/backend/controller/ProductController.java +++ b/backend/src/main/java/com/inventory/backend/controller/ProductController.java @@ -24,7 +24,7 @@ public ProductController(ProductService service) { @GetMapping public ProductPage getProducts( @RequestParam Optional name, - @RequestParam Optional> category, + @RequestParam Optional category, @RequestParam Optional availability, @RequestParam Optional sortBy, @RequestParam Optional sortBy2, @@ -33,10 +33,10 @@ public ProductPage getProducts( @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size ){ - Set categorySet = category.map(HashSet::new).orElse(null); + //Set categorySet = category.map(HashSet::new).orElse(null); return service.getFilteredProductsPage( name, - Optional.ofNullable(categorySet), + category, availability, sortBy, sortBy2, diff --git a/backend/src/main/java/com/inventory/backend/repository/ProductRepository.java b/backend/src/main/java/com/inventory/backend/repository/ProductRepository.java index d61cd93..8ac8a14 100644 --- a/backend/src/main/java/com/inventory/backend/repository/ProductRepository.java +++ b/backend/src/main/java/com/inventory/backend/repository/ProductRepository.java @@ -37,12 +37,12 @@ public void clear() { public List findByNameOrCategoryOrAvailability( Optional nameFilter, - Optional> categoryFilter, + Optional categoryFilter, Optional availability ) { return products.values().stream() .filter(p -> nameFilter.map(name -> p.getName().toLowerCase().contains(name.toLowerCase())).orElse(true)) - .filter(p -> categoryFilter.map(categories -> categories.contains(p.getCategory())).orElse(true)) + .filter(p -> categoryFilter.filter(cat -> !cat.isBlank()).map(cat -> cat.equalsIgnoreCase(p.getCategory())).orElse(true)) .filter(p -> availability.map(avail -> avail == p.isInStock()).orElse(true)) .collect(Collectors.toList()); } diff --git a/backend/src/main/java/com/inventory/backend/service/ProductService.java b/backend/src/main/java/com/inventory/backend/service/ProductService.java index 23ac353..04e8d1c 100644 --- a/backend/src/main/java/com/inventory/backend/service/ProductService.java +++ b/backend/src/main/java/com/inventory/backend/service/ProductService.java @@ -21,7 +21,7 @@ public ProductService(ProductRepository repository) { public List getFilteredAndSortedProducts( Optional nameFilter, - Optional> categoryFilter, + Optional categoryFilter, Optional availability, Optional sortBy, Optional sortBy2, @@ -45,7 +45,7 @@ public List getFilteredAndSortedProducts( } public ProductPage getFilteredProductsPage( Optional nameFilter, - Optional> categoryFilter, + Optional categoryFilter, Optional availability, Optional sortBy, Optional sortBy2, diff --git a/backend/src/test/java/com/inventory/backend/model/InventoryMetricsTest.java b/backend/src/test/java/com/inventory/backend/model/InventoryMetricsTest.java new file mode 100644 index 0000000..d415d11 --- /dev/null +++ b/backend/src/test/java/com/inventory/backend/model/InventoryMetricsTest.java @@ -0,0 +1,4 @@ +package com.inventory.backend.model; + +public class InventoryMetricsTest { +} diff --git a/backend/src/test/java/com/inventory/backend/service/ProductServiceTest.java b/backend/src/test/java/com/inventory/backend/service/ProductServiceTest.java index 6f80f6a..507be27 100644 --- a/backend/src/test/java/com/inventory/backend/service/ProductServiceTest.java +++ b/backend/src/test/java/com/inventory/backend/service/ProductServiceTest.java @@ -2,10 +2,15 @@ import com.inventory.backend.dto.ProductDTO; import com.inventory.backend.model.Product; +import com.inventory.backend.model.InventoryMetrics; import com.inventory.backend.repository.ProductRepository; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.Validation; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import java.util.Optional; +import java.util.Set; import java.util.UUID; import static org.junit.jupiter.api.Assertions.*; @@ -52,4 +57,74 @@ void markOutOfStock_shouldSetQuantityToZero() { Product updated = repository.findById(id).orElseThrow(); assertEquals(0, updated.getQuantityInStock()); } + + @Test + void shouldCalculateMetricsByCategory() { + ProductDTO dto = new ProductDTO(); + dto.setName("Mouse"); + dto.setCategory("Electronics"); + dto.setUnitPrice(10.0); + dto.setQuantityInStock(5); + service.createProduct(dto); + + ProductDTO dto2 = new ProductDTO(); + dto2.setName("book"); + dto2.setCategory("Books"); + dto2.setUnitPrice(20.0); + dto2.setQuantityInStock(10); + service.createProduct(dto2); + + InventoryMetrics metrics = service.getMetrics(); + assertEquals(15, metrics.getTotalInStock()); + assertTrue(metrics.getByCategory().containsKey("Books")); + assertTrue(metrics.getByCategory().containsKey("Electronics")); + } + + @Test + void shouldFailValidationOnInvalidDTO() { + ProductDTO dto = new ProductDTO(); + dto.setName(""); + dto.setCategory(""); + dto.setUnitPrice(-10.0); + dto.setQuantityInStock(-2); + + Set> violations = Validation.buildDefaultValidatorFactory() + .getValidator() + .validate(dto); + + assertEquals(4, violations.size()); + } + + @Test + void shouldSortByNameAndStock() { + ProductDTO dto1 = new ProductDTO(); + dto1.setName("AAA"); + dto1.setCategory("Cat1"); + dto1.setUnitPrice(10.0); + dto1.setQuantityInStock(5); + + ProductDTO dto2 = new ProductDTO(); + dto2.setName("AAA"); + dto2.setCategory("Cat2"); + dto2.setUnitPrice(20.0); + dto2.setQuantityInStock(2); + + service.createProduct(dto1); + service.createProduct(dto2); + + var result = service.getFilteredAndSortedProducts( + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.of("name"), + Optional.of("quantityInStock"), + true, + false, + 0, + 10 + ); + + assertEquals(5, result.get(0).getQuantityInStock()); + assertEquals(2, result.get(1).getQuantityInStock()); + } } diff --git a/frontend/jest.config.js b/frontend/jest.config.js new file mode 100644 index 0000000..fb8e741 --- /dev/null +++ b/frontend/jest.config.js @@ -0,0 +1,15 @@ +/** @type {import('ts-jest').JestConfigWithTsJest} */ +module.exports = { + preset: "ts-jest", + testEnvironment: "jsdom", + transform: { + "^.+\\.(ts|tsx)$": "ts-jest" + }, + transformIgnorePatterns: [ + "/node_modules/(?!(axios|react-icons)/)" + ], + moduleNameMapper: { + "\\.(css|less|scss|sass)$": "identity-obj-proxy" + }, + setupFilesAfterEnv: ["/src/setupTests.ts"] +}; \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 729f7f9..c11655e 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -16,28 +16,19 @@ "@types/node": "^16.18.126", "@types/react": "^19.1.7", "@types/react-dom": "^19.1.6", -<<<<<<< HEAD - "autoprefixer": "^10.4.21", - "axios": "^1.10.0", - "postcss": "^8.5.6", -======= - "axios": "^1.10.0", ->>>>>>> fullstack + "axios": "^0.27.2", "react": "^19.1.0", "react-dom": "^19.1.0", + "react-icons": "^5.5.0", "react-scripts": "5.0.1", "tailwindcss": "^4.1.10", "typescript": "^4.9.5", "web-vitals": "^2.1.4" }, "devDependencies": { -<<<<<<< HEAD - "@types/uuid": "^10.0.0" -======= "autoprefixer": "^10.4.21", "postcss": "^8.5.6", "tailwindcss": "^3.4.17" ->>>>>>> fullstack } }, "node_modules/@adobe/css-tools": { @@ -3949,13 +3940,6 @@ "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", "license": "MIT" }, - "node_modules/@types/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/ws": { "version": "8.18.1", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", @@ -4951,14 +4935,13 @@ } }, "node_modules/axios": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.10.0.tgz", - "integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", + "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" + "follow-redirects": "^1.14.9", + "form-data": "^4.0.0" } }, "node_modules/axios/node_modules/form-data": { @@ -13730,12 +13713,6 @@ "node": ">= 0.10" } }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT" - }, "node_modules/psl": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", @@ -14012,6 +13989,15 @@ "integrity": "sha512-SN/U6Ytxf1QGkw/9ve5Y+NxBbZM6Ht95tuXNMKs8EJyFa/Vy/+Co3stop3KBHARfn/giv+Lj1uUnTfOJ3moFEQ==", "license": "MIT" }, + "node_modules/react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -14100,55 +14086,6 @@ } } }, - "node_modules/react-scripts/node_modules/lilconfig": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", - "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antonk52" - } - }, - "node_modules/react-scripts/node_modules/tailwindcss": { - "version": "3.4.17", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", - "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", - "license": "MIT", - "dependencies": { - "@alloc/quick-lru": "^5.2.0", - "arg": "^5.0.2", - "chokidar": "^3.6.0", - "didyoumean": "^1.2.2", - "dlv": "^1.1.3", - "fast-glob": "^3.3.2", - "glob-parent": "^6.0.2", - "is-glob": "^4.0.3", - "jiti": "^1.21.6", - "lilconfig": "^3.1.3", - "micromatch": "^4.0.8", - "normalize-path": "^3.0.0", - "object-hash": "^3.0.0", - "picocolors": "^1.1.1", - "postcss": "^8.4.47", - "postcss-import": "^15.1.0", - "postcss-js": "^4.0.1", - "postcss-load-config": "^4.0.2", - "postcss-nested": "^6.2.0", - "postcss-selector-parser": "^6.1.2", - "resolve": "^1.22.8", - "sucrase": "^3.35.0" - }, - "bin": { - "tailwind": "lib/cli.js", - "tailwindcss": "lib/cli.js" - }, - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -16015,10 +15952,53 @@ "license": "MIT" }, "node_modules/tailwindcss": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.10.tgz", - "integrity": "sha512-P3nr6WkvKV/ONsTzj6Gb57sWPMX29EPNPopo7+FcpkQaNsrNpZ1pv8QmrYI2RqEKD7mlGqLnGovlcYnBK0IqUA==", - "license": "MIT" + "version": "3.4.17", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", + "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.6", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tailwindcss/node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } }, "node_modules/tapable": { "version": "2.2.2", diff --git a/frontend/package.json b/frontend/package.json index 4db3087..dffa5af 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,15 +12,10 @@ "@types/node": "^16.18.126", "@types/react": "^19.1.7", "@types/react-dom": "^19.1.6", -<<<<<<< HEAD - "autoprefixer": "^10.4.21", - "axios": "^1.10.0", - "postcss": "^8.5.6", -======= - "axios": "^1.10.0", ->>>>>>> fullstack + "axios": "^0.27.2", "react": "^19.1.0", "react-dom": "^19.1.0", + "react-icons": "^5.5.0", "react-scripts": "5.0.1", "tailwindcss": "^4.1.10", "typescript": "^4.9.5", @@ -65,12 +60,8 @@ ] }, "devDependencies": { -<<<<<<< HEAD - "@types/uuid": "^10.0.0" -======= "autoprefixer": "^10.4.21", "postcss": "^8.5.6", "tailwindcss": "^3.4.17" ->>>>>>> fullstack } } diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx index 2a68616..33510ff 100644 --- a/frontend/src/App.test.tsx +++ b/frontend/src/App.test.tsx @@ -2,8 +2,9 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import App from './App'; -test('renders learn react link', () => { - render(); - const linkElement = screen.getByText(/learn react/i); +test('renders product list page by default', () => { + render( + ); + const linkElement = screen.getByText(/Inventory/i); expect(linkElement).toBeInTheDocument(); }); diff --git a/frontend/src/components/ProductFilters.tsx b/frontend/src/components/ProductFilters.tsx index 86ffb28..35b82da 100644 --- a/frontend/src/components/ProductFilters.tsx +++ b/frontend/src/components/ProductFilters.tsx @@ -1,5 +1,5 @@ import React, {useState, useEffect, useCallback} from "react"; -import { getCategories } from "../services/productService"; +import { useCategoryContext } from "../context/CategoryContext"; interface Props { filters: any; @@ -8,35 +8,21 @@ interface Props { const ProductFilters: React.FC = ({filters, setFilters}) => { const [name, setName] = useState(filters.name || ""); - const [category, setCategory] = useState(filters.category || []); + const [category, setCategory] = useState(filters.category || ""); const [availability, setAvailability] = useState(filters.availability || ""); - const [allCategories, setAllCategories] = useState ([]); - - useEffect(() => { - getCategories().then(setAllCategories).catch(err => { - console.error("Error getting categories"); - setAllCategories([]); - }); - }, []); + const {categories: allCategories} = useCategoryContext(); const handleApply = useCallback( () => { setFilters((prevFilters: any) =>({ ...prevFilters, name, - category, + category: category === "" ? "" : category, availability: availability === "" ? "" : availability === "in", page: 0, })); }, [setFilters, name, category, availability]); - const toggleCategory = (value: string) => { - setCategory(prev => - prev.includes(value) - ? prev.filter(c => c !== value) - : [...prev, value] - ); - }; useEffect(() => { handleApply(); @@ -55,17 +41,15 @@ const ProductFilters: React.FC = ({filters, setFilters}) => {
-
- {allCategories.map(cat => ( - +
diff --git a/frontend/src/components/ProductFormModal.tsx b/frontend/src/components/ProductFormModal.tsx index e7041ae..fa8014d 100644 --- a/frontend/src/components/ProductFormModal.tsx +++ b/frontend/src/components/ProductFormModal.tsx @@ -1,6 +1,7 @@ import React, {useEffect, useState} from "react"; import { Product, ProductDTO } from "../types/Product"; import { createProduct, updateProduct } from "../services/productService"; +import { useCategoryContext } from "../context/CategoryContext"; interface Props{ isOpen: boolean; @@ -20,6 +21,8 @@ const ProductFormModal: React.FC = ({isOpen, onClose, onSucces, initialDa const [errors, setErrors] = useState>({}); + const {refreshCategories} = useCategoryContext(); + useEffect(() => { if(initialData) { setForm({ @@ -67,6 +70,7 @@ const ProductFormModal: React.FC = ({isOpen, onClose, onSucces, initialDa } else { await createProduct(form); } + await refreshCategories(); onSucces(); onClose(); }catch (error) { diff --git a/frontend/src/components/ProductTable.tsx b/frontend/src/components/ProductTable.tsx index 01b6753..fc00f67 100644 --- a/frontend/src/components/ProductTable.tsx +++ b/frontend/src/components/ProductTable.tsx @@ -10,6 +10,7 @@ interface Props{ } const ProductTable: React.FC = ({products, filters, setFilters, onEdit}) => { + const handleSort = (field: string, secondary = false) => { if(secondary) { setFilters({ ...filters, sortBy2: field, asc2: !filters.asc2}); @@ -18,6 +19,13 @@ const ProductTable: React.FC = ({products, filters, setFilters, onEdit}) } }; + const renderSortIcon = (field: string) => { + if(filters.sortBy === field) { + return filters.asc ? "↑" : "↓"; + } + return "⇅"; + }; + const toggleStock = async (product: Product) => { if(product.quantityInStock === 0) { await markInStock(product.id); @@ -56,11 +64,11 @@ const ProductTable: React.FC = ({products, filters, setFilters, onEdit}) - handleSort("name")} className="cursor-pointer">Name - handleSort("category")} className="cursor-pointer">Category - handleSort("unitPrice")} className="cursor-pointer">Price - handleSort("quantityInStock")} className="cursor-pointer">Stock - handleSort("expirationDate")} className="cursor-pointer">Expiration date + handleSort("name")} className="cursor-pointer">Name {renderSortIcon("name")} + handleSort("category")} className="cursor-pointer">Category {renderSortIcon("category")} + handleSort("unitPrice")} className="cursor-pointer">Price {renderSortIcon("unitPrice")} + handleSort("quantityInStock")} className="cursor-pointer">Stock {renderSortIcon("quantityInStock")} + handleSort("expirationDate")} className="cursor-pointer">Expiration date {renderSortIcon("expirationDate")} Actions @@ -73,14 +81,15 @@ const ProductTable: React.FC = ({products, filters, setFilters, onEdit}) toggleStock(p)}/> + onChange={() => toggleStock(p)} + className="ml-4 mr-6"/> {p.name} - {p.category} - ${p.unitPrice.toFixed(2)} + {p.category} + ${p.unitPrice.toFixed(2)} {p.quantityInStock} - {p.expirationDate ?? "-"} - + {p.expirationDate ?? "-"} +
diff --git a/frontend/src/services/productService.ts b/frontend/src/services/productService.ts index 18c9c77..00e9bbd 100644 --- a/frontend/src/services/productService.ts +++ b/frontend/src/services/productService.ts @@ -25,7 +25,7 @@ export const markOutOfStock = async (id: string): Promise => { }; export const markInStock = async (id: string, quantity: number = 10): Promise => { - await api.post(`/products/${id}/instock?defaultQuantity=${quantity}`); + await api.put(`/products/${id}/instock?defaultQuantity=${quantity}`); }; export const getMetrics = async (): Promise => { diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index a273b0c..7435e60 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -22,5 +22,5 @@ }, "include": [ "src" - ] +, "jest.config.js" ] }