diff --git a/src/frontend/src/features/addNote/lib/searchProductsByName.test.ts b/src/frontend/src/features/addNote/lib/searchProductsByName.test.ts new file mode 100644 index 000000000..0d207c5ff --- /dev/null +++ b/src/frontend/src/features/addNote/lib/searchProductsByName.test.ts @@ -0,0 +1,28 @@ +import { type ProductSelectOption } from '@/entities/product'; +import { searchProductsByName } from './searchProductsByName'; + +type GivenProducts = 'cheese' | 'cheesecake' | 'chocolateCake' | 'creamCheese'; + +const given: Record = { + cheese: { id: 1, name: 'Cheese', defaultQuantity: 120 }, + cheesecake: { id: 2, name: 'Cheesecake', defaultQuantity: 50 }, + chocolateCake: { id: 3, name: 'Chocolate cake', defaultQuantity: 60 }, + creamCheese: { id: 4, name: 'Cream cheese', defaultQuantity: 100 }, +}; + +const sourceProducts = Object.values(given); + +test.each<[string, ProductSelectOption[]]>([ + ['che', [given.cheese, given.cheesecake, given.creamCheese]], + ['cream', [given.creamCheese]], + ['Cake ', [given.cheesecake, given.chocolateCake]], +])('should find products by search query: "%s"', (query, foundProducts) => { + expect(searchProductsByName(sourceProducts, query)).toStrictEqual(foundProducts); +}); + +test.each(['', ' ', 'ch', 'sadsfafds'])( + 'should not find any products by search query: "%s"', + query => { + expect(searchProductsByName(sourceProducts, query)).toStrictEqual([]); + }, +); diff --git a/src/frontend/src/features/addNote/lib/searchProductsByName.ts b/src/frontend/src/features/addNote/lib/searchProductsByName.ts new file mode 100644 index 000000000..caf08ba94 --- /dev/null +++ b/src/frontend/src/features/addNote/lib/searchProductsByName.ts @@ -0,0 +1,18 @@ +import { type ProductSelectOption } from '@/entities/product'; + +const QUERY_LENGTH_THRESHOLD = 3; + +export const searchProductsByName = ( + sourceProducts: ProductSelectOption[], + query: string, +): ProductSelectOption[] => { + const queryTrimmed = query.trim(); + + if (queryTrimmed.length < QUERY_LENGTH_THRESHOLD) { + return []; + } + + return sourceProducts.filter(product => + product.name.trim().toLowerCase().includes(queryTrimmed.toLowerCase()), + ); +}; diff --git a/src/frontend/src/features/addNote/ui/SearchProducts.tsx b/src/frontend/src/features/addNote/ui/SearchProducts.tsx index 346652805..4b51bdfc1 100644 --- a/src/frontend/src/features/addNote/ui/SearchProducts.tsx +++ b/src/frontend/src/features/addNote/ui/SearchProducts.tsx @@ -4,6 +4,7 @@ import { useState, type FC, useEffect } from 'react'; import { useForm } from 'react-hook-form'; import { useDebounce } from 'use-debounce'; import { type ProductSelectOption, productApi } from '@/entities/product'; +import { searchProductsByName } from '../lib/searchProductsByName'; import { FoundProductsList } from './FoundProductsList'; import { UploadImageButton } from './UploadImageButton'; @@ -12,10 +13,8 @@ interface FormValues { } const DEBOUNCE_QUERY_DELAY = 300; -const DEBOUNCE_QUERY_LENGTH_THRESHOLD = 3; const EMPTY_PRODUCTS: ProductSelectOption[] = []; -// TODO: add tests export const SearchProducts: FC = () => { const { register, watch } = useForm({ mode: 'onChange', @@ -33,17 +32,10 @@ export const SearchProducts: FC = () => { const [foundProducts, setFoundProducts] = useState([]); useEffect(() => { - if (debouncedQuery.trim().length >= DEBOUNCE_QUERY_LENGTH_THRESHOLD) { - setFoundProducts( - products.filter(product => - product.name.trim().toLowerCase().includes(debouncedQuery.trim().toLowerCase()), - ), - ); - } else { - setFoundProducts([]); - } + setFoundProducts(searchProductsByName(products, debouncedQuery)); }, [products, debouncedQuery]); + // TODO: add auto focuses return ( <>