Alex 2 éve
szülő
commit
602a2580e4

+ 31 - 0
.idea/inspectionProfiles/Project_Default.xml

@@ -34,5 +34,36 @@
       </option>
       <option name="myCustomValuesEnabled" value="true" />
     </inspection_tool>
+    <inspection_tool class="HttpUrlsUsage" enabled="true" level="WEAK WARNING" enabled_by_default="true">
+      <option name="ignoredUrls">
+        <list>
+          <option value="http://localhost" />
+          <option value="http://127.0.0.1" />
+          <option value="http://0.0.0.0" />
+          <option value="http://www.w3.org/" />
+          <option value="http://json-schema.org/draft" />
+          <option value="http://java.sun.com/" />
+          <option value="http://xmlns.jcp.org/" />
+          <option value="http://javafx.com/javafx/" />
+          <option value="http://javafx.com/fxml" />
+          <option value="http://maven.apache.org/xsd/" />
+          <option value="http://maven.apache.org/POM/" />
+          <option value="http://www.springframework.org/schema/" />
+          <option value="http://www.springframework.org/tags" />
+          <option value="http://www.springframework.org/security/tags" />
+          <option value="http://www.thymeleaf.org" />
+          <option value="http://www.jboss.org/j2ee/schema/" />
+          <option value="http://www.jboss.com/xml/ns/" />
+          <option value="http://www.ibm.com/webservices/xsd" />
+          <option value="http://activemq.apache.org/schema/" />
+          <option value="http://schema.cloudfoundry.org/spring/" />
+          <option value="http://schemas.xmlsoap.org/" />
+          <option value="http://cxf.apache.org/schemas/" />
+          <option value="http://primefaces.org/ui" />
+          <option value="http://tiles.apache.org/" />
+          <option value="http://shop-roles.asmer.fs.a-level.com.ua" />
+        </list>
+      </option>
+    </inspection_tool>
   </profile>
 </component>

+ 18 - 20
src/App.js

@@ -1,4 +1,4 @@
-import {Router, Route} from 'react-router-dom';
+import {Router} from 'react-router-dom';
 import createHistory from "history/createBrowserHistory";
 import MainPage from "./pages/MainPage";
 import Page404 from "./pages/404Page";
@@ -14,44 +14,42 @@ import {store} from "./reducers";
 import ProfilePage from "./pages/ProfilePage";
 import CatalogPage from "./pages/CatalogPage";
 import Header from "./components/Header";
-import Footer from "./components/Footer";
+import {Footer} from "./components/Footer";
 import ProductPage from "./pages/ProductPage";
 import WishListPage from "./pages/WishListPage";
 import SearchPage from "./pages/SearchPage";
 import MyOrdersPage from "./pages/MyOrdersPage";
 import CartPage from "./pages/CartPage";
-import {CPRoute} from "./components/CPRoute";
+import {CPRoute, CRRoute} from "./components/CPRoute";
 import AdminPage from "./pages/AdminPage/AdminPage";
 
 const history = createHistory();
 
-function App() {
+export const App = () => {
   return (
       <Router history={history}>
           <Provider store={store}>
               <Header/>
               <Switch>
-                  <Route path="/" component={MainPage} exact/>
-                  <Route path="/catalog" component={CatalogPage} />
-                  <Route path="/good" component={ProductPage} />
-                  <Route path="/about-us" component={AboutUsPage} />
-                  <Route path="/our-team" component={OurTeamPage} />
-                  <Route path="/faq" component={FAQPage} />
-                  <Route path="/contact" component={ContactPage} />
-                  <Route path="/my-account" component={MyAccountPage} />
-                  <Route path="/privacy-policy" component={PrivacyPolicy} />
-                  <Route path="/search" component={SearchPage} />
-                  <Route path="/basket" component={CartPage} />
-                  <Route path="/wish-list" component={WishListPage} />
-                  <Route path="/my-orders" component={MyOrdersPage} />
+                  <CRRoute path="/" component={MainPage} exact/>
+                  <CRRoute path="/catalog" component={CatalogPage} />
+                  <CRRoute path="/good" component={ProductPage} />
+                  <CRRoute path="/about-us" component={AboutUsPage} />
+                  <CRRoute path="/our-team" component={OurTeamPage} />
+                  <CRRoute path="/faq" component={FAQPage} />
+                  <CRRoute path="/contact" component={ContactPage} />
+                  <CRRoute path="/my-account" component={MyAccountPage} />
+                  <CRRoute path="/privacy-policy" component={PrivacyPolicy} />
+                  <CRRoute path="/search" component={SearchPage} />
+                  <CRRoute path="/basket" component={CartPage} />
+                  <CRRoute path="/wish-list" component={WishListPage} />
+                  <CRRoute path="/my-orders" component={MyOrdersPage} />
                   <CPRoute roles={["user", "admin"]} path="/profile" fallback='/my-account' component={ProfilePage} />
                   <CPRoute roles={["admin"]} path="/admin" fallback='/my-account' component={AdminPage} />
-                  <Route path="*" component={Page404} />
+                  <CRRoute path="*" component={Page404} />
               </Switch>
               <Footer/>
           </Provider>
       </Router>
   )
 }
-
-export default App;

+ 0 - 8
src/App.test.js

@@ -1,8 +0,0 @@
-import { render, screen } from '@testing-library/react';
-import App from './App';
-
-test('renders learn react link', () => {
-  render(<App />);
-  const linkElement = screen.getByText(/learn react/i);
-  expect(linkElement).toBeInTheDocument();
-});

+ 3 - 3
src/components/CPRoute.jsx

@@ -2,15 +2,15 @@ import {connect} from "react-redux";
 import {Redirect} from "react-router-dom";
 import Route from "react-router-dom/es/Route";
 
-const RRoute = ({action, component:Component,...routeProps}) => {
+const RRoute = ({action, component: Component, ...routeProps}) => {
     const WrapperComponent = (componentProps) => {
         action(componentProps.match)
         return <Component {...componentProps}/>
     }
     return <Route {...routeProps} component={WrapperComponent}/>
 }
+export const CRRoute = connect(null, {action: match => ({type: 'ROUTE', match})})(RRoute)
 
-const CRRoute = connect(null, {action: match => ({type: 'ROUTE', match})})(RRoute)
 const ProtectedRoute =({ fallback='/',
                          roles=['admin'],
                          auth,
@@ -33,4 +33,4 @@ const ProtectedRoute =({ fallback='/',
     }
     return <CRRoute {...routeProps} component={WrapperComponent}/>
 }
-export const CPRoute = connect(state=>({auth: state.auth}))(ProtectedRoute)
+export const CPRoute = connect(state => ({auth: state.auth}))(ProtectedRoute)

+ 5 - 4
src/components/Footer.jsx

@@ -1,4 +1,4 @@
-import {Container, Typography, Link, Grid, Box, useMediaQuery} from "@mui/material";
+import {Container, Typography, Link, Grid, Box, useMediaQuery, makeStyles} from "@mui/material";
 import LocalPhoneIcon from '@mui/icons-material/LocalPhone';
 import LocationOnIcon from '@mui/icons-material/LocationOn';
 import EmailIcon from '@mui/icons-material/Email';
@@ -54,7 +54,7 @@ const Contact = ({Icon, text, link}) => {
     )
 }
 
-const Footer = ({contact=contactDefault, linksSocial=linksSocialDefault}) => {
+export const Footer = ({contact=contactDefault, linksSocial=linksSocialDefault}) => {
     const matches = useMediaQuery('(max-width:899px)');
     const matches2 = useMediaQuery('(max-width:450px)');
     return (
@@ -110,7 +110,9 @@ const Footer = ({contact=contactDefault, linksSocial=linksSocialDefault}) => {
                             maxWidth="275px"
                             lineHeight="1.7em"
                         >
-                            Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid, animi consectetur, dicta dolore doloremque eum exercitationem illum incidunt inventore ipsam itaque, modi odio odit quo sed sint suscipit vitae voluptates.
+                            Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid, animi consectetur, dicta
+                            dolore doloremque eum exercitationem illum incidunt inventore ipsam itaque, modi odio odit
+                            quo sed sint suscipit vitae voluptates.
                         </Typography>
                     </Grid>
                 </Grid>
@@ -140,4 +142,3 @@ const Footer = ({contact=contactDefault, linksSocial=linksSocialDefault}) => {
         </footer>
     )
 }
-export default Footer;

+ 2 - 1
src/index.js

@@ -1,9 +1,10 @@
 import React from 'react';
 import ReactDOM from 'react-dom';
 import './index.css';
-import App from './App';
+import {App} from "./App";
 import reportWebVitals from './reportWebVitals';
 
+
 ReactDOM.render(
   <React.StrictMode>
     <App />

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 0 - 1
src/logo.svg


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 80 - 20
src/pages/AboutUsPage.jsx


+ 5 - 3
src/pages/AdminPage/AdminPage.jsx

@@ -29,6 +29,8 @@ import {CategoryAside} from "../CatalogPage";
 import {CFindGoodEdit, CGoodEdit, CSearchPage, FindGoodEdit} from "./GoodTab";
 import MyOrdersPage from "../MyOrdersPage";
 import {Route} from "react-router-dom";
+import {CClients} from "./ClientsTab";
+import {CCategoryEdit} from "./CateforyTab";
 
 const defaultTabs = [{icon: PersonIcon, text: 'clients'}, {icon: CategoryIcon, text: 'categories'}, {icon: AutoAwesomeIcon, text: 'products'}]
 
@@ -85,7 +87,7 @@ const SelectBlock = ({Block, FindBlock}) => {
                 </BottomNavigation>
             </Box>
             {value === 'create' ?
-                <Block variant={value}/> : <FindBlock variant={value}/>
+                <Block/> : <FindBlock/>
             }
         </>
     )
@@ -125,10 +127,10 @@ const FullWidthTabs = () => {
                 onChangeIndex={handleChangeIndex}
             >
                 <TabPanel value={value} index={0} dir={theme.direction}>
-                    {/*<CClients/>*/}
+                    <CClients/>
                 </TabPanel>
                 <TabPanel value={value} index={1} dir={theme.direction}>
-                    {/*<SelectBlock Block={CCategoryEdit} FindBlock={CFindGoodEdit}/>*/}
+                    <SelectBlock Block={CCategoryEdit} FindBlock={CFindGoodEdit}/>
                 </TabPanel>
                 <TabPanel value={value} index={2} dir={theme.direction}>
                     <SelectBlock Block={CGoodEdit} FindBlock={CFindGoodEdit}/>

+ 110 - 77
src/pages/AdminPage/GoodTab.jsx

@@ -7,12 +7,8 @@ import Typography from "@mui/material/Typography";
 import {
     Button,
     CircularProgress, Container,
-    FormControl,
     Grid, IconButton,
     InputAdornment,
-    InputLabel,
-    MenuItem,
-    Select,
     TextField
 } from "@mui/material";
 import {connect} from "react-redux";
@@ -29,8 +25,8 @@ import SearchIcon from "@material-ui/icons/Search";
 import imgNotFound from "../../img/catalog/imgNotFound.png";
 import {actionSearchRemove} from "../../reducers/SearchReducer";
 
-
-const GoodEdit = ({entity={images: [], categories: []}, onSave, onFileDrop, fileStatus, variant='create', categoryState, actionRootCat, goodCount, goods, actionClear, result}) => {
+const GoodEdit = ({entity={images: [], categories: []}, onSave, onFileDrop, fileStatus,
+                      categoryState, actionRootCat, goodCount, goods, actionClear, result}) => {
     const [state, setState] = useState(entity)
 
     const {getRootProps, getInputProps, isDragActive} = useDropzone({accept: 'image/*', onDrop: acceptedFiles => {
@@ -39,22 +35,26 @@ const GoodEdit = ({entity={images: [], categories: []}, onSave, onFileDrop, file
             })
         }})
     const SortableItem = sortableElement(({value}) => {
-        return <div key={value?._id} style={{display: 'inline-flex', borderRadius: 2,border: '1px solid #eaeaea',marginBottom: 8, marginRight: 8, width: 200, height: 200, padding: 4, boxSizing: 'border-box'}}>
-            <div style={{display: 'flex', justifyContent: 'center', minWidth: 0, overflow: 'hidden'}}>
-                {value?.url ?
-                    <img src={backURL+ '/' + value?.url} style={{display: 'block', width: 'auto', height: '100%', objectFit: 'cover', objectPosition: 'center center'}} alt={value.name}/>
-                    :
-                    <Box sx={{ display: 'flex' }}>
-                        <CircularProgress />
-                    </Box>
-                }
-            </div>
-        </div>
+        return (
+            <Box key={value?._id} sx={{display: 'flex', justifyContent: 'center',  borderRadius: 2, border: '1px solid #eaeaea', marginBottom: 2, width: 200, height: 200, padding: '5px', boxSizing: 'border-box'}}>
+                <Box sx={{display: 'flex', justifyContent: 'center', minWidth: 0, overflow: 'hidden'}}>
+                    {value?.url ?
+                        <img src={backURL+ '/' + value.url} style={{display: 'block', width: 'auto', height: '100%', objectFit: 'cover', objectPosition: 'center center'}} alt={value.name}/>
+                        :
+                        <Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center'}}>
+                            <CircularProgress />
+                        </Box>
+                    }
+                </Box>
+            </Box>
+        )
     });
     const SortableContainer = sortableContainer(({children}) => {
-        return <aside style={{display:'flex', justifyContent: 'space-between', flexWrap: 'wrap'}}>
-            {children}
-        </aside>
+        return (
+            <aside style={{display:'flex', justifyContent: 'space-between', flexWrap: 'wrap'}}>
+                {children}
+            </aside>
+        )
     })
     const onSortEnd = ({oldIndex, newIndex}) => {
         setState(({images}) => ({
@@ -68,8 +68,8 @@ const GoodEdit = ({entity={images: [], categories: []}, onSave, onFileDrop, file
     }
     const handleOnSave = () => {
         let query = {...state}
-        state.images.length > 0 ? query.images = state.images.map(item => {return {'_id': item['_id']}}) : delete query.images
-        state.categories.length > 0 ? query.categories = state.categories.map(item => {return {'_id': item['_id'], 'name': item['name']}}) : delete query.categories
+        state.images?.length > 0 ? query.images = state.images.map(item => {return {'_id': item['_id']}}) : delete query.images
+        state.categories?.length > 0 ? query.categories = state.categories.map(item => {return {'_id': item['_id'], 'name': item['name']}}) : delete query.categories
         onSave(query)
         goodCount()
     }
@@ -82,7 +82,9 @@ const GoodEdit = ({entity={images: [], categories: []}, onSave, onFileDrop, file
         if(!categoryState || Object.entries(categoryState).length === 0) actionRootCat()
         if(!goods) goodCount()
         if(fileStatus?.status === 'RESOLVED'){
-            setState({...state, images: [...state.images, fileStatus?.payload]})
+            state.images?.length > 0 ?
+                setState({...state, images: [...state.images, fileStatus?.payload]}) :
+                setState({...state, images: [fileStatus?.payload]})
         }
     },[categoryState, goods, fileStatus])
 
@@ -98,8 +100,8 @@ const GoodEdit = ({entity={images: [], categories: []}, onSave, onFileDrop, file
                             <Typography variant='body1' textAlign='center' color='#616161' marginBottom='20px'>Drag 'n' drop image files here, or click to select file</Typography>
                         }
                         <SortableContainer axis="xy" onSortEnd={onSortEnd}>
-                            {state.images.length > 0 && state.images.map((value, index) => (
-                                <SortableItem key={`item-${value?._id}`} index={index} value={value} />
+                            {state.images?.length > 0 && state.images.map((value, index) => (
+                                <SortableItem key={`item-${value?._id || index}`} index={index} value={value} />
                             ))}
                         </SortableContainer>
                     </Box>
@@ -109,6 +111,26 @@ const GoodEdit = ({entity={images: [], categories: []}, onSave, onFileDrop, file
                         </Grid>
                         <Grid item xs={5.5}>
                             {categoryState &&
+                                state.categories?.length > 0 ?
+                                <Autocomplete
+                                    multiple
+                                    id="tags-standard"
+                                    options={Object.values(categoryState)}
+                                    defaultValue={state.categories}
+                                    onChange={(event, newValue) => {
+                                        setState({...state, categories: [...newValue]})
+                                    }}
+                                    getOptionLabel={(option) => option?.name || 'no name'}
+                                    key={option => option?.id}
+                                    renderInput={(params) => (
+                                        <TextField
+                                            {...params}
+                                            variant="standard"
+                                            label="Select categories"
+                                            placeholder="categories"
+                                        />
+                                    )}
+                                />:
                                 <Autocomplete
                                     multiple
                                     id="tags-standard"
@@ -116,7 +138,7 @@ const GoodEdit = ({entity={images: [], categories: []}, onSave, onFileDrop, file
                                     onChange={(event, newValue) => {
                                         setState({...state, categories: [...newValue]})
                                     }}
-                                    getOptionLabel={(option) => state?.categories ? [...state.categories] : option?.name || 'no name'}
+                                    getOptionLabel={(option) => option?.name || 'no name'}
                                     key={option => option?.id}
                                     renderInput={(params) => (
                                         <TextField
@@ -180,7 +202,7 @@ const GoodEdit = ({entity={images: [], categories: []}, onSave, onFileDrop, file
                         <Box display='flex' alignItems='center' flexDirection='column'>
                             <Typography variant='h5' letterSpacing='2px' textAlign='center' color='#616161' marginBottom='20px'>Product successfully created!</Typography>
                             <CheckCircleOutlineIcon sx={{marginBottom: '20px'}}/>
-                            <Link to={`/good/${result?.payload?._id}`} style={{color:'#616161', marginBottom:'20px'}}>
+                            <Link to={`/good/${result.payload._id}`} style={{color:'#616161', marginBottom:'20px'}}>
                                 <Typography variant='h5' letterSpacing='2px' textAlign='center' color='#616161'>View results</Typography>
                             </Link>
                             <Button variant='outlined' onClick={handleFullClear}>Add more</Button>
@@ -199,62 +221,72 @@ const GoodEdit = ({entity={images: [], categories: []}, onSave, onFileDrop, file
         </>
     )
 }
-export const CGoodEdit = connect(state => ({fileStatus: state.promise['uploadFile'], categoryState: state.category, goods: state.promise['goodCount'], result: state.promise['goodUpsert']}), {actionRootCat: actionFullRootCats, onSave: actionGoodUpsert, goodCount:  actionGoodCount, onFileDrop: actionUploadFile, actionClear: actionClearPromise})(GoodEdit)
+
+export const CGoodEdit = connect(state => ({fileStatus: state.promise['uploadFile'],
+    categoryState: state.category, goods: state.promise['goodCount'], result: state.promise['goodUpsert']}),
+    {actionRootCat: actionFullRootCats, onSave: actionGoodUpsert, goodCount:  actionGoodCount,
+        onFileDrop: actionUploadFile, actionClear: actionClearPromise})(GoodEdit)
 
 const ItemFound = ({item:{_id, name, price, images, description, categories}}) => {
     let [state, setState] = useState(false)
 
     return (
         !state ?
-            <Button style={{textDecoration: 'none', display: 'flex', alignItems: 'center', marginBottom: '30px'}} onClick={() => setState(true)}>
-                <Box width='60px' height='60px' borderRadius='10px' overflow='hidden' marginRight='60px'
-                     position='relative'>
-                    <img style={{
-                        position: 'absolute',
-                        top: '0',
-                        left: '0',
-                        width: '100%',
-                        height: '100%',
-                        objectFit: 'cover'
-                    }} src={images && Array.isArray(images) && images[0]?.url ? backURL + '/' + images[0].url : imgNotFound}
-                         alt={name}/>
-                </Box>
-                <Box sx={{
-                    display: 'flex',
-                    flexDirection: 'column',
-                    justifyContent: 'space-between',
-                    alignItems: 'flex-start'
-                }}>
-                    <Typography
-                        color='#000'
-                        letterSpacing='1px'
-                        fontFamily='sarif'
-                        fontWeight='600'
-                        variant='h6'
-                    >
-                        {name}
-                    </Typography>
-                    <Typography
-                        letterSpacing='1px'
-                        variant='body1'
-                        fontWeight='300'
-                        color='#616161'
-                        margin='10px 0'
-                    >
-                        {description?.length > 60 ? 'Lorem ipsum dolor sit amet, consectetur adipisicing elit.' : description}
-                    </Typography>
-                    <Typography
-                        color='#000'
-                        letterSpacing='1px'
-                        variant='body1'
-                        fontWeight='600'
-                    >
-                        ${parseFloat(price).toFixed(2)}
-                    </Typography>
+            <Button fullWidth sx={{display: 'flex', justifyContent:'flex-start'}} onClick={() => setState(true)}>
+                <Box style={{display: 'flex', alignItems: 'center', marginBottom: '30px'}}>
+                    <Box width='60px' height='60px' borderRadius='10px' overflow='hidden' marginRight='60px'
+                         position='relative'>
+                        <img style={{
+                            position: 'absolute',
+                            top: '0',
+                            left: '0',
+                            width: '100%',
+                            height: '100%',
+                            objectFit: 'cover'
+                        }} src={images && Array.isArray(images) && images[0]?.url ? backURL + '/' + images[0].url : imgNotFound}
+                             alt={name}/>
+                    </Box>
+                    <Box sx={{
+                        display: 'flex',
+                        flexDirection: 'column',
+                        justifyContent: 'space-between',
+                        alignItems: 'flex-start'
+                    }}>
+                        <Typography
+                            color='#000'
+                            letterSpacing='1px'
+                            fontFamily='sarif'
+                            fontWeight='600'
+                            variant='h6'
+                        >
+                            {name || 'no name'}
+                        </Typography>
+                        <Typography
+                            letterSpacing='1px'
+                            variant='body1'
+                            fontWeight='300'
+                            color='#616161'
+                            margin='10px 0'
+                            sx={{textTransform: 'capitalize'}}
+                        >
+                            {description?.length > 60 ? 'Lorem ipsum dolor sit amet, consectetur adipisicing elit.' : description}
+                        </Typography>
+                        <Typography
+                            color='#000'
+                            letterSpacing='1px'
+                            variant='body1'
+                            fontWeight='600'
+                        >
+                            ${parseFloat(price).toFixed(2)}
+                        </Typography>
+                    </Box>
                 </Box>
             </Button>
         :
-            <CGoodEdit entity={{_id, name, price, images, description, categories}}/>
+            <Box sx={{marginBottom: '30px', border: '1px solid #616161', borderRadius: '10px', padding: '30px 20px'}}>
+                <CGoodEdit entity={{_id, name, price, images, description, categories}}/>
+                <Button variant='outlined' sx={{marginTop: '30px'}} fullWidth onClick={() => setState(false)}>Cansel</Button>
+            </Box>
     )
 }
 const NotFound = () => {
@@ -275,7 +307,7 @@ const FindGoodEdit = ({searchResult, onSearch, onSearchRemove}) => {
 
     return (
         <>
-            <Container maxWidth="sm">
+            <Container maxWidth="md">
                 <Typography
                     variant='h5'
                     fontFamily='sarif'
@@ -306,12 +338,13 @@ const FindGoodEdit = ({searchResult, onSearch, onSearchRemove}) => {
                 />
                 {(value !== '' && click) && (searchResult?.searchResult ?
                         Object.values(searchResult.searchResult).length > 0  ?
-                            Object.values(searchResult.searchResult).map(item => <ItemFound item={item}/>) : <NotFound/> :
+                            Object.values(searchResult.searchResult).map(item => <ItemFound key={item?._id} item={item}/>) : <NotFound/> :
                         <CircularProgress color="inherit"/>
                 )}
             </Container>
         </>
     )
 }
-export const CFindGoodEdit = connect(state=>({searchResult: state.search}), {onSearch: actionFullGoodFind, onSearchRemove: actionSearchRemove})(FindGoodEdit)
 
+export const CFindGoodEdit = connect(state=>({searchResult: state.search}),
+    {onSearch: actionFullGoodFind, onSearchRemove: actionSearchRemove})(FindGoodEdit)

+ 2 - 6
src/pages/MainPage.jsx

@@ -1,13 +1,9 @@
+import {Redirect} from "react-router-dom";
 
 const MainPage = () => {
     return (
         <>
-            <main>
-                <article>
-                    Main page
-                    <br/><br/><br/><br/><br/><br/><br/><br/>
-                </article>
-            </main>
+            <Redirect to={'/catalog'}/>
         </>
     )
 }

+ 0 - 5
src/setupTests.js

@@ -1,5 +0,0 @@
-// jest-dom adds custom jest matchers for asserting on DOM nodes.
-// allows you to do things like:
-// expect(element).toHaveTextContent(/react/i)
-// learn more: https://github.com/testing-library/jest-dom
-import '@testing-library/jest-dom';