From 1b58f437c769678fee5d0624b82542fb090df0b6 Mon Sep 17 00:00:00 2001
From: Gurikov Maxim <maximgurikoff@gmail.com>
Date: Sun, 8 Dec 2024 17:46:35 +0500
Subject: [PATCH 1/5] fixes

---
 src/Pages/login-page/login-page.tsx           |  7 ++---
 src/api/api.ts                                | 26 +++++++++++++++++--
 src/components/reviews/reviews-list.tsx       |  6 +++--
 src/components/reviews/reviews.tsx            |  9 ++++---
 .../current-offer/current-offer.selectors.ts  |  8 +++---
 5 files changed, 43 insertions(+), 13 deletions(-)

diff --git a/src/Pages/login-page/login-page.tsx b/src/Pages/login-page/login-page.tsx
index 321229a..7482064 100644
--- a/src/Pages/login-page/login-page.tsx
+++ b/src/Pages/login-page/login-page.tsx
@@ -25,7 +25,6 @@ export function LoginPage(): React.JSX.Element {
     loginInfo.email &&
     validateEmail(loginInfo.email) &&
     loginInfo.password &&
-    loginInfo.password.length > 3 &&
     loginInfo.password.match(/[a-zA-z]/g) &&
     loginInfo.password.match(/[0-9]/g);
   return (
@@ -47,7 +46,8 @@ export function LoginPage(): React.JSX.Element {
                     name="email"
                     placeholder="Email"
                     onChange={(event) =>
-                      setLoginInfo({ ...loginInfo, email: event.target.value })}
+                      setLoginInfo({ ...loginInfo, email: event.target.value })
+                    }
                     required
                   />
                 </div>
@@ -62,7 +62,8 @@ export function LoginPage(): React.JSX.Element {
                       setLoginInfo({
                         ...loginInfo,
                         password: event.target.value,
-                      })}
+                      })
+                    }
                     required
                   />
                 </div>
diff --git a/src/api/api.ts b/src/api/api.ts
index 2713287..1ac4a1b 100644
--- a/src/api/api.ts
+++ b/src/api/api.ts
@@ -1,8 +1,13 @@
-import axios, { AxiosInstance, InternalAxiosRequestConfig } from 'axios';
+import axios, {
+  AxiosError,
+  AxiosInstance,
+  InternalAxiosRequestConfig,
+} from 'axios';
 import { getToken } from '../utils/token-utils.ts';
+import { toast } from 'react-toastify';
 
 const BACKEND_URL = 'https://14.design.htmlacademy.pro/six-cities';
-const REQUEST_TIMEOUT = 5000;
+const REQUEST_TIMEOUT = 3000;
 
 export const createAPI = (): AxiosInstance => {
   const api = axios.create({
@@ -20,5 +25,22 @@ export const createAPI = (): AxiosInstance => {
     return config;
   });
 
+  api.interceptors.response.use(
+    (response) => response,
+    (error: AxiosError) => {
+      if (
+        error &&
+        (error.code === 'ECONNABORTED' || error.code === 'ERR_NETWORK')
+      ) {
+        toast.error(
+          'Сервер недоступен, проверте подключение к интернету или повторите попытку позже',
+          {
+            toastId: 'server-unreachable',
+          },
+        );
+      }
+    },
+  );
+
   return api;
 };
diff --git a/src/components/reviews/reviews-list.tsx b/src/components/reviews/reviews-list.tsx
index 8f463fa..e6c4853 100644
--- a/src/components/reviews/reviews-list.tsx
+++ b/src/components/reviews/reviews-list.tsx
@@ -1,16 +1,18 @@
 import { Review } from '../../dataTypes/review.ts';
 import { ReviewComponent } from './review-component.tsx';
+import { useAppSelector } from '../../store/store.ts';
+import { getReviewsCount } from '../../store/current-offer/current-offer.selectors.ts';
 
 interface ReviewsListProps {
   reviews: Review[];
 }
 
 export function ReviewsList({ reviews }: ReviewsListProps): React.JSX.Element {
+  const reviewsCount = useAppSelector(getReviewsCount);
   return (
     <>
       <h2 className="reviews__title">
-        Reviews &middot;{' '}
-        <span className="reviews__amount">{reviews.length}</span>
+        Reviews &middot; <span className="reviews__amount">{reviewsCount}</span>
       </h2>
       <ul className="reviews__list">
         {reviews.map((review: Review) => (
diff --git a/src/components/reviews/reviews.tsx b/src/components/reviews/reviews.tsx
index d613ce3..1bdc9eb 100644
--- a/src/components/reviews/reviews.tsx
+++ b/src/components/reviews/reviews.tsx
@@ -4,6 +4,7 @@ import { Review } from '../../dataTypes/review.ts';
 import { useAppSelector } from '../../store/store.ts';
 import { useMemo } from 'react';
 import { getIsAuthorized } from '../../store/user/user.selectors.ts';
+import { MAX_REVIEWS_COUNT } from '../../consts/reviews.ts';
 
 interface ReviewsProps {
   reviews: Review[];
@@ -12,9 +13,11 @@ interface ReviewsProps {
 export function Reviews({ reviews }: ReviewsProps): React.JSX.Element {
   const sortedReviews = useMemo(
     () =>
-      reviews.toSorted(
-        (a, b) => new Date(b.date).getTime() - new Date(a.date).getTime(),
-      ),
+      reviews
+        .toSorted(
+          (a, b) => new Date(b.date).getTime() - new Date(a.date).getTime(),
+        )
+        .slice(0, MAX_REVIEWS_COUNT),
     [reviews],
   );
   const isAuthorized = useAppSelector(getIsAuthorized);
diff --git a/src/store/current-offer/current-offer.selectors.ts b/src/store/current-offer/current-offer.selectors.ts
index 36c7d10..bd74d1e 100644
--- a/src/store/current-offer/current-offer.selectors.ts
+++ b/src/store/current-offer/current-offer.selectors.ts
@@ -1,7 +1,6 @@
 import { State } from '../../dataTypes/store-types.ts';
 import { NameSpaces } from '../../dataTypes/enums/name-spaces.ts';
 import { MAX_NEARBY_OFFERS } from '../../consts/offers.ts';
-import { MAX_REVIEWS_COUNT } from '../../consts/reviews.ts';
 import { createSelector } from '@reduxjs/toolkit';
 import { Offer } from '../../dataTypes/offer.ts';
 import { Review } from '../../dataTypes/review.ts';
@@ -12,9 +11,12 @@ export const getNearbyOffers = createSelector(
   [(state: State) => state[NameSpaces.CurrentOffer].nearbyOffers],
   (offers: Offer[]) => offers.slice(0, MAX_NEARBY_OFFERS),
 );
-export const getCurrentReviews = createSelector(
+export const getCurrentReviews = (state: State) =>
+  state[NameSpaces.CurrentOffer].currentReviews;
+
+export const getReviewsCount = createSelector(
   [(state: State) => state[NameSpaces.CurrentOffer].currentReviews],
-  (reviews: Review[]) => reviews.slice(0, MAX_REVIEWS_COUNT),
+  (reviews: Review[]) => reviews.length,
 );
 export const getReviewPostingStatus = (state: State) =>
   state[NameSpaces.CurrentOffer].reviewPostingStatus;

From 7c12e24c3e7a3f908027a9ad248ed5be095e23bc Mon Sep 17 00:00:00 2001
From: Gurikov Maxim <maximgurikoff@gmail.com>
Date: Tue, 10 Dec 2024 23:30:02 +0500
Subject: [PATCH 2/5] added test and naming fixes

---
 .../login-page/login-page-right-section.tsx   |  4 +-
 src/Pages/not-found-page/not-found-page.tsx   |  8 +-
 src/Pages/offer-page/offer-page.tsx           |  4 +-
 src/components/app.tsx                        | 14 ++--
 src/components/authorization-wrapper.tsx      |  4 +-
 src/components/bookmark-button.tsx            |  4 +-
 src/components/layout/footer.tsx              |  4 +-
 src/components/layout/header.tsx              |  6 +-
 src/components/layout/user-info.tsx           |  6 +-
 src/components/offer/offer-card.tsx           |  8 +-
 src/components/reviews/review-form.tsx        |  9 ++-
 .../enums/{api-routes.ts => api-route.ts}     |  2 +-
 .../enums/{app-routes.ts => app-route.ts}     |  2 +-
 .../enums/{name-spaces.ts => name-space.ts}   |  2 +-
 src/mocks/mock-detailed-offer.ts              | 36 +++++++++
 src/mocks/mock-offers.ts                      | 43 ++++++++++
 src/mocks/mock-review.ts                      | 21 +++++
 src/mocks/mock-user.ts                        | 20 +++++
 src/setupTests.ts                             |  2 +-
 src/store/async-actions.ts                    | 22 ++---
 .../current-offer/current-offer.selectors.ts  | 20 ++---
 .../current-offer/current-offer.slice.test.ts | 80 +++++++++++++++++++
 .../current-offer/current-offer.slice.ts      |  6 +-
 .../current-offers.selectors.test.ts          | 48 +++++++++++
 src/store/offers/offers.selector.test.ts      | 37 +++++++++
 src/store/offers/offers.selectors.ts          | 16 ++--
 src/store/offers/offers.slice.test.ts         | 57 +++++++++++++
 src/store/offers/offers.slice.ts              |  4 +-
 src/store/store.ts                            |  8 +-
 src/store/user/user-slice.ts                  |  4 +-
 src/store/user/user.selector.test.ts          |  0
 src/store/user/user.selectors.ts              | 10 ++-
 src/store/user/user.slice.test.ts             |  0
 33 files changed, 431 insertions(+), 80 deletions(-)
 rename src/dataTypes/enums/{api-routes.ts => api-route.ts} (81%)
 rename src/dataTypes/enums/{app-routes.ts => app-route.ts} (83%)
 rename src/dataTypes/enums/{name-spaces.ts => name-space.ts} (74%)
 create mode 100644 src/mocks/mock-detailed-offer.ts
 create mode 100644 src/mocks/mock-offers.ts
 create mode 100644 src/mocks/mock-review.ts
 create mode 100644 src/mocks/mock-user.ts
 create mode 100644 src/store/current-offer/current-offer.slice.test.ts
 create mode 100644 src/store/current-offer/current-offers.selectors.test.ts
 create mode 100644 src/store/offers/offers.selector.test.ts
 create mode 100644 src/store/offers/offers.slice.test.ts
 create mode 100644 src/store/user/user.selector.test.ts
 create mode 100644 src/store/user/user.slice.test.ts

diff --git a/src/Pages/login-page/login-page-right-section.tsx b/src/Pages/login-page/login-page-right-section.tsx
index 4379bde..a17bcaa 100644
--- a/src/Pages/login-page/login-page-right-section.tsx
+++ b/src/Pages/login-page/login-page-right-section.tsx
@@ -1,5 +1,5 @@
 import { Link } from 'react-router-dom';
-import { AppRoutes } from '../../dataTypes/enums/app-routes.ts';
+import { AppRoute } from '../../dataTypes/enums/app-route.ts';
 import { changeCity } from '../../store/offers/offers.slice.ts';
 import { CITIES } from '../../consts/cities.ts';
 import { useAppDispatch } from '../../store/store.ts';
@@ -13,7 +13,7 @@ function LoginPageRightSectionImpl() {
       <div className="locations__item">
         <Link
           className="locations__item-link"
-          to={AppRoutes.MainPage}
+          to={AppRoute.MainPage}
           onClick={() => dispatch(changeCity(city))}
         >
           <span>{city.name}</span>
diff --git a/src/Pages/not-found-page/not-found-page.tsx b/src/Pages/not-found-page/not-found-page.tsx
index 05e617a..1e4f606 100644
--- a/src/Pages/not-found-page/not-found-page.tsx
+++ b/src/Pages/not-found-page/not-found-page.tsx
@@ -1,19 +1,19 @@
 import { Link } from 'react-router-dom';
 import { Helmet } from 'react-helmet-async';
-import {AppRoutes} from '../../dataTypes/enums/app-routes.ts';
+import { AppRoute } from '../../dataTypes/enums/app-route.ts';
 
 export function NotFoundPage(): React.JSX.Element {
   return (
-    <main className='not-found-page'>
+    <main className="not-found-page">
       <Helmet>
         <title>404 - not found</title>
       </Helmet>
       <h1>404 - Page Not Found</h1>
-      <p className='not-found-page__title'>
+      <p className="not-found-page__title">
         The page you are looking for might have been removed or is temporarily
         unavailable.
       </p>
-      <Link to={AppRoutes.MainPage} className='not-found-page__link'>
+      <Link to={AppRoute.MainPage} className="not-found-page__link">
         back to main page
       </Link>
     </main>
diff --git a/src/Pages/offer-page/offer-page.tsx b/src/Pages/offer-page/offer-page.tsx
index 0b2d93b..b1a6907 100644
--- a/src/Pages/offer-page/offer-page.tsx
+++ b/src/Pages/offer-page/offer-page.tsx
@@ -13,7 +13,7 @@ import { OfferGallery } from '../../components/offer/offer-gallery.tsx';
 import { BookmarkButton } from '../../components/bookmark-button.tsx';
 import { store, useAppSelector } from '../../store/store.ts';
 import { Spinner } from '../../components/spinner/Spinner.tsx';
-import { AppRoutes } from '../../dataTypes/enums/app-routes.ts';
+import { AppRoute } from '../../dataTypes/enums/app-route.ts';
 import {
   fetchNearbyOffers,
   fetchOffer,
@@ -38,7 +38,7 @@ export function OfferPage(): React.JSX.Element {
   const currentOffer = useAppSelector(getCurrentOffer);
   const currentReviews = useAppSelector(getCurrentReviews);
   if (currentOffer === undefined) {
-    return <Navigate to={AppRoutes.NotFoundPage} />;
+    return <Navigate to={AppRoute.NotFoundPage} />;
   }
   return (
     <div className="page">
diff --git a/src/components/app.tsx b/src/components/app.tsx
index f875bc1..cafc1e3 100644
--- a/src/components/app.tsx
+++ b/src/components/app.tsx
@@ -8,7 +8,7 @@ import {
   AuthorizationWrapperForAuthorizedOnly,
   AuthorizationWrapperForUnauthorizedOnly,
 } from './authorization-wrapper.tsx';
-import { AppRoutes } from '../dataTypes/enums/app-routes.ts';
+import { AppRoute } from '../dataTypes/enums/app-route.ts';
 import { HelmetProvider } from 'react-helmet-async';
 import { Provider } from 'react-redux';
 import { store } from '../store/store.ts';
@@ -19,28 +19,28 @@ export function App(): React.JSX.Element {
       <HelmetProvider>
         <BrowserRouter>
           <Routes>
-            <Route path={AppRoutes.MainPage} element={<MainPage />} />
+            <Route path={AppRoute.MainPage} element={<MainPage />} />
             <Route
-              path={AppRoutes.Login}
+              path={AppRoute.Login}
               element={
                 <AuthorizationWrapperForUnauthorizedOnly
-                  fallbackUrl={AppRoutes.MainPage}
+                  fallbackUrl={AppRoute.MainPage}
                 >
                   <LoginPage />
                 </AuthorizationWrapperForUnauthorizedOnly>
               }
             />
             <Route
-              path={AppRoutes.Favorites}
+              path={AppRoute.Favorites}
               element={
                 <AuthorizationWrapperForAuthorizedOnly
-                  fallbackUrl={AppRoutes.Login}
+                  fallbackUrl={AppRoute.Login}
                 >
                   <FavoritesPage />
                 </AuthorizationWrapperForAuthorizedOnly>
               }
             />
-            <Route path={`${AppRoutes.Offer}/:id`} element={<OfferPage />} />
+            <Route path={`${AppRoute.Offer}/:id`} element={<OfferPage />} />
             <Route path="*" element={<NotFoundPage />} />
           </Routes>
         </BrowserRouter>
diff --git a/src/components/authorization-wrapper.tsx b/src/components/authorization-wrapper.tsx
index 02c40aa..7347046 100644
--- a/src/components/authorization-wrapper.tsx
+++ b/src/components/authorization-wrapper.tsx
@@ -1,11 +1,11 @@
 import { Navigate } from 'react-router-dom';
 import { useAppSelector } from '../store/store.ts';
-import { AppRoutes } from '../dataTypes/enums/app-routes.ts';
+import { AppRoute } from '../dataTypes/enums/app-route.ts';
 import { getIsAuthorized } from '../store/user/user.selectors.ts';
 
 interface AuthorizationWrapperProps {
   children: React.JSX.Element;
-  fallbackUrl: AppRoutes;
+  fallbackUrl: AppRoute;
 }
 
 export function AuthorizationWrapperForAuthorizedOnly({
diff --git a/src/components/bookmark-button.tsx b/src/components/bookmark-button.tsx
index 7fb2314..226ddf6 100644
--- a/src/components/bookmark-button.tsx
+++ b/src/components/bookmark-button.tsx
@@ -4,7 +4,7 @@ import { bookmarkOffer } from '../store/async-actions.ts';
 import { Offer } from '../dataTypes/offer.ts';
 import { getIsAuthorized } from '../store/user/user.selectors.ts';
 import { useNavigate } from 'react-router-dom';
-import { AppRoutes } from '../dataTypes/enums/app-routes.ts';
+import { AppRoute } from '../dataTypes/enums/app-route.ts';
 
 interface BookmarkButtonProps {
   size: 'big' | 'small';
@@ -45,7 +45,7 @@ export function BookmarkButton({
           );
           setIsFavoriteReactive(!isFavoriteReactive);
         } else {
-          navigate(AppRoutes.Login);
+          navigate(AppRoute.Login);
         }
       }}
     >
diff --git a/src/components/layout/footer.tsx b/src/components/layout/footer.tsx
index dd9d65a..f9b93da 100644
--- a/src/components/layout/footer.tsx
+++ b/src/components/layout/footer.tsx
@@ -1,11 +1,11 @@
 import { Link } from 'react-router-dom';
-import { AppRoutes } from '../../dataTypes/enums/app-routes.ts';
+import { AppRoute } from '../../dataTypes/enums/app-route.ts';
 import { memo } from 'react';
 
 function FooterImpl() {
   return (
     <footer className="footer container">
-      <Link className="footer__logo-link" to={AppRoutes.MainPage}>
+      <Link className="footer__logo-link" to={AppRoute.MainPage}>
         <img
           className="footer__logo"
           src="img/logo.svg"
diff --git a/src/components/layout/header.tsx b/src/components/layout/header.tsx
index 5c49f9e..a33fc12 100644
--- a/src/components/layout/header.tsx
+++ b/src/components/layout/header.tsx
@@ -1,5 +1,5 @@
 import { Link } from 'react-router-dom';
-import { AppRoutes } from '../../dataTypes/enums/app-routes.ts';
+import { AppRoute } from '../../dataTypes/enums/app-route.ts';
 import { useAppSelector } from '../../store/store.ts';
 import { memo } from 'react';
 import { UserInfo } from './user-info.tsx';
@@ -16,7 +16,7 @@ function HeaderImpl({ dontShowUserInfo }: HeaderProps) {
       <div className="container">
         <div className="header__wrapper">
           <div className="header__left">
-            <Link className="header__logo-link" to={AppRoutes.MainPage}>
+            <Link className="header__logo-link" to={AppRoute.MainPage}>
               <img
                 className="header__logo"
                 src="img/logo.svg"
@@ -35,7 +35,7 @@ function HeaderImpl({ dontShowUserInfo }: HeaderProps) {
                   <li className="header__nav-item user">
                     <Link
                       className="header__nav-link header__nav-link--profile"
-                      to={AppRoutes.Login}
+                      to={AppRoute.Login}
                     >
                       <div className="header__avatar-wrapper user__avatar-wrapper"></div>
                       <span className="header__login">Sign in</span>
diff --git a/src/components/layout/user-info.tsx b/src/components/layout/user-info.tsx
index acdacac..f265a76 100644
--- a/src/components/layout/user-info.tsx
+++ b/src/components/layout/user-info.tsx
@@ -1,5 +1,5 @@
 import { Link, useNavigate } from 'react-router-dom';
-import { AppRoutes } from '../../dataTypes/enums/app-routes.ts';
+import { AppRoute } from '../../dataTypes/enums/app-route.ts';
 import { useAppDispatch, useAppSelector } from '../../store/store.ts';
 import { logout } from '../../store/async-actions.ts';
 import { memo } from 'react';
@@ -15,7 +15,7 @@ function UserInfoImpl() {
   const handleLogout = () => {
     dispatch(logout());
     dispatch(setFavoriteOffers([]));
-    navigate(AppRoutes.MainPage);
+    navigate(AppRoute.MainPage);
   };
   return (
     <nav className="header__nav">
@@ -23,7 +23,7 @@ function UserInfoImpl() {
         <li className="header__nav-item user">
           <Link
             className="header__nav-link header__nav-link--profile"
-            to={AppRoutes.Favorites}
+            to={AppRoute.Favorites}
           >
             <div className="header__avatar-wrapper user__avatar-wrapper">
               {userInfo?.avatarUrl && (
diff --git a/src/components/offer/offer-card.tsx b/src/components/offer/offer-card.tsx
index f7c9f4f..71c150c 100644
--- a/src/components/offer/offer-card.tsx
+++ b/src/components/offer/offer-card.tsx
@@ -1,5 +1,5 @@
 import { Link } from 'react-router-dom';
-import { AppRoutes } from '../../dataTypes/enums/app-routes.ts';
+import { AppRoute } from '../../dataTypes/enums/app-route.ts';
 import cn from 'classnames';
 import { Rating } from '../rating.tsx';
 import { BookmarkButton } from '../bookmark-button.tsx';
@@ -38,7 +38,7 @@ export function OfferCardImpl({
       onMouseLeave={handleMouseLeave}
       className={cn(
         'place-card',
-        { 'cities__card': isOnMainPage },
+        { cities__card: isOnMainPage },
         { 'near-places__card': !isOnMainPage },
       )}
     >
@@ -54,7 +54,7 @@ export function OfferCardImpl({
           { 'near-places__image-wrapper': !isOnMainPage },
         )}
       >
-        <Link to={`${AppRoutes.Offer}/${id}`}>
+        <Link to={`${AppRoute.Offer}/${id}`}>
           <img
             className="place-card__image"
             src={previewImage}
@@ -79,7 +79,7 @@ export function OfferCardImpl({
         </div>
         <Rating rating={rating} usePlace="place-card" />
         <h2 className="place-card__name">
-          <Link to={`${AppRoutes.Offer}/${id}`}>{title}</Link>
+          <Link to={`${AppRoute.Offer}/${id}`}>{title}</Link>
         </h2>
         <p className="place-card__type">{capitalize(type)}</p>
       </div>
diff --git a/src/components/reviews/review-form.tsx b/src/components/reviews/review-form.tsx
index 9827ad3..4c2df7b 100644
--- a/src/components/reviews/review-form.tsx
+++ b/src/components/reviews/review-form.tsx
@@ -24,9 +24,13 @@ export function ReviewForm(): React.JSX.Element {
   const offerId = useAppSelector(getCurrentOffer)!.id;
   const reviewPostingStatus = useAppSelector(getReviewPostingStatus);
   useEffect(() => {
-    if (reviewPostingStatus === ReviewStatus.Success) {
+    let isMounted = true;
+    if (isMounted && reviewPostingStatus === ReviewStatus.Success) {
       setReview({ comment: '', rating: undefined });
     }
+    return () => {
+      isMounted = false;
+    };
   }, [reviewPostingStatus]);
   const onRatingChange: React.ChangeEventHandler<HTMLInputElement> = (
     event,
@@ -166,8 +170,7 @@ export function ReviewForm(): React.JSX.Element {
         value={review?.comment || ''}
         onChange={onCommentChange}
         disabled={reviewPostingStatus === ReviewStatus.Pending}
-      >
-      </textarea>
+      ></textarea>
       <div className="reviews__button-wrapper">
         <p className="reviews__help">
           To submit review please make sure to set{' '}
diff --git a/src/dataTypes/enums/api-routes.ts b/src/dataTypes/enums/api-route.ts
similarity index 81%
rename from src/dataTypes/enums/api-routes.ts
rename to src/dataTypes/enums/api-route.ts
index 4b9b4bc..d6dcb13 100644
--- a/src/dataTypes/enums/api-routes.ts
+++ b/src/dataTypes/enums/api-route.ts
@@ -1,4 +1,4 @@
-export enum ApiRoutes {
+export enum ApiRoute {
   Offers = '/offers',
   Login = '/login',
   Logout = '/logout',
diff --git a/src/dataTypes/enums/app-routes.ts b/src/dataTypes/enums/app-route.ts
similarity index 83%
rename from src/dataTypes/enums/app-routes.ts
rename to src/dataTypes/enums/app-route.ts
index fafd2cf..d192e16 100644
--- a/src/dataTypes/enums/app-routes.ts
+++ b/src/dataTypes/enums/app-route.ts
@@ -1,4 +1,4 @@
-export enum AppRoutes {
+export enum AppRoute {
   MainPage = '/',
   Login = '/login',
   Offer = '/offer',
diff --git a/src/dataTypes/enums/name-spaces.ts b/src/dataTypes/enums/name-space.ts
similarity index 74%
rename from src/dataTypes/enums/name-spaces.ts
rename to src/dataTypes/enums/name-space.ts
index 3047c6d..baf8d7e 100644
--- a/src/dataTypes/enums/name-spaces.ts
+++ b/src/dataTypes/enums/name-space.ts
@@ -1,4 +1,4 @@
-export enum NameSpaces {
+export enum NameSpace {
   Offers = 'Offers',
   CurrentOffer = 'CurrentOffer',
   User = 'User',
diff --git a/src/mocks/mock-detailed-offer.ts b/src/mocks/mock-detailed-offer.ts
new file mode 100644
index 0000000..4fc0963
--- /dev/null
+++ b/src/mocks/mock-detailed-offer.ts
@@ -0,0 +1,36 @@
+import * as faker from 'faker';
+import { getMockCity, getMockLocation } from './mock-offers.ts';
+import { DetailedOffer } from '../dataTypes/detailed-offer.ts';
+import { RoomType } from '../dataTypes/enums/room-type.ts';
+
+export function getMockDetailedOffer(): DetailedOffer {
+  return {
+    id: faker.datatype.uuid(),
+    title: faker.commerce.productName(),
+    type: RoomType.Hotel,
+    price: faker.datatype.number({ min: 100, max: 500 }),
+    city: getMockCity(),
+    location: getMockLocation(),
+    isFavorite: faker.datatype.boolean(),
+    isPremium: faker.datatype.boolean(),
+    rating: faker.datatype.number({ min: 1, max: 5, precision: 0.1 }),
+    description: faker.lorem.paragraph(),
+    bedrooms: faker.datatype.number({ min: 1, max: 5 }),
+    goods: [
+      faker.commerce.product(),
+      faker.commerce.product(),
+      faker.commerce.product(),
+    ],
+    host: {
+      name: faker.name.findName(),
+      avatarUrl: faker.image.avatar(),
+      isPro: faker.datatype.boolean(),
+    },
+    images: [
+      faker.image.imageUrl(),
+      faker.image.imageUrl(),
+      faker.image.imageUrl(),
+    ],
+    maxAdults: faker.datatype.number({ min: 1, max: 10 }),
+  };
+}
diff --git a/src/mocks/mock-offers.ts b/src/mocks/mock-offers.ts
new file mode 100644
index 0000000..16ab7c9
--- /dev/null
+++ b/src/mocks/mock-offers.ts
@@ -0,0 +1,43 @@
+import * as faker from 'faker';
+import { Location } from '../dataTypes/location.ts';
+import { RoomType } from '../dataTypes/enums/room-type.ts';
+import { City } from '../dataTypes/city.ts';
+import { Offer } from '../dataTypes/offer.ts';
+
+export function getMockLocation(): Location {
+  return {
+    latitude: Math.random() * 10 + 50,
+    longitude: Math.random() + 4,
+    zoom: faker.datatype.number({ min: 8, max: 15 }),
+  };
+}
+
+export function getMockCity(): City {
+  return {
+    name: 'Amsterdam',
+    location: getMockLocation(),
+  };
+}
+
+export function getMockOffers(count: number): Offer[] {
+  const list = [];
+
+  for (let i = 0; i < count; i++) {
+    const offer = {
+      id: faker.datatype.uuid(),
+      title: faker.commerce.productName(),
+      type: RoomType.Apartment,
+      price: faker.datatype.number({ min: 100, max: 500 }),
+      city: getMockCity(),
+      location: getMockLocation(),
+      isFavorite: faker.datatype.boolean(),
+      isPremium: faker.datatype.boolean(),
+      rating: faker.datatype.number({ min: 1, max: 5, precision: 0.1 }),
+      previewImage: faker.image.imageUrl(),
+    };
+
+    list.push(offer);
+  }
+
+  return list;
+}
diff --git a/src/mocks/mock-review.ts b/src/mocks/mock-review.ts
new file mode 100644
index 0000000..760a72a
--- /dev/null
+++ b/src/mocks/mock-review.ts
@@ -0,0 +1,21 @@
+import * as faker from 'faker';
+import { getMockUser } from './mock-user.ts';
+import { Review } from '../dataTypes/review.ts';
+
+export function getMockReview(): Review {
+  return {
+    id: faker.datatype.uuid(),
+    date: faker.date.recent().toDateString(),
+    user: getMockUser(),
+    comment: faker.lorem.sentence(),
+    rating: faker.datatype.number({ min: 1, max: 5 }),
+  };
+}
+
+export function getMockReviews(count: number): Review[] {
+  const reviews = [];
+  for (let i = 0; i < count; i++) {
+    reviews.push(getMockReview());
+  }
+  return reviews;
+}
diff --git a/src/mocks/mock-user.ts b/src/mocks/mock-user.ts
new file mode 100644
index 0000000..5c575af
--- /dev/null
+++ b/src/mocks/mock-user.ts
@@ -0,0 +1,20 @@
+import * as faker from 'faker';
+import { AuthInfo, User } from '../dataTypes/user.ts';
+
+export function getMockUser(): User {
+  return {
+    name: faker.name.findName(),
+    avatarUrl: faker.image.avatar(),
+    isPro: faker.datatype.boolean(),
+  };
+}
+
+export function getMockAuthInfo(): AuthInfo {
+  return {
+    name: faker.name.findName(),
+    avatarUrl: faker.image.avatar(),
+    isPro: faker.datatype.boolean(),
+    email: faker.internet.email(),
+    token: faker.random.alphaNumeric(16),
+  };
+}
diff --git a/src/setupTests.ts b/src/setupTests.ts
index b210af5..43eb80c 100644
--- a/src/setupTests.ts
+++ b/src/setupTests.ts
@@ -1,4 +1,4 @@
-import matchers from '@testing-library/jest-dom/matchers';
 import { expect } from 'vitest';
+import * as matchers from '@testing-library/jest-dom/matchers';
 
 expect.extend(matchers);
diff --git a/src/store/async-actions.ts b/src/store/async-actions.ts
index 6070dab..0e1e9f7 100644
--- a/src/store/async-actions.ts
+++ b/src/store/async-actions.ts
@@ -2,7 +2,7 @@ import { createAsyncThunk } from '@reduxjs/toolkit';
 import { AppDispatch, State } from '../dataTypes/store-types.ts';
 import axios, { AxiosError, AxiosInstance } from 'axios';
 import { Offer } from '../dataTypes/offer.ts';
-import { ApiRoutes } from '../dataTypes/enums/api-routes.ts';
+import { ApiRoute } from '../dataTypes/enums/api-route.ts';
 import { DetailedOffer } from '../dataTypes/detailed-offer.ts';
 import { AuthInfo, LoginInfo } from '../dataTypes/user.ts';
 import { AuthorizationStatus } from '../dataTypes/enums/authorization-status.ts';
@@ -29,7 +29,7 @@ export const fetchOffers = createAsyncThunk<
     extra: AxiosInstance;
   }
 >('data/fetchOffers', async (_arg, { dispatch, extra: api }) => {
-  const { data } = await api.get<Offer[]>(ApiRoutes.Offers);
+  const { data } = await api.get<Offer[]>(ApiRoute.Offers);
   dispatch(setOffers(data));
 });
 
@@ -43,7 +43,7 @@ export const fetchOffer = createAsyncThunk<
   }
 >('data/fetchOffer', async (id, { dispatch, extra: api }) => {
   try {
-    const { data } = await api.get<DetailedOffer>(`${ApiRoutes.Offers}/${id}`);
+    const { data } = await api.get<DetailedOffer>(`${ApiRoute.Offers}/${id}`);
     dispatch(setCurrentOffer(data));
   } catch (err) {
     const error = err as Error | AxiosError;
@@ -66,7 +66,7 @@ export const fetchNearbyOffers = createAsyncThunk<
     extra: AxiosInstance;
   }
 >('data/fetchNearbyOffers', async (id, { dispatch, extra: api }) => {
-  const { data } = await api.get<Offer[]>(`${ApiRoutes.Offers}/${id}/nearby`);
+  const { data } = await api.get<Offer[]>(`${ApiRoute.Offers}/${id}/nearby`);
   dispatch(setNearbyOffers(data));
 });
 
@@ -79,7 +79,7 @@ export const fetchReviews = createAsyncThunk<
     extra: AxiosInstance;
   }
 >('review/fetchReviews', async (offerId, { dispatch, extra: api }) => {
-  const { data } = await api.get<Review[]>(`${ApiRoutes.Comments}/${offerId}`);
+  const { data } = await api.get<Review[]>(`${ApiRoute.Comments}/${offerId}`);
   dispatch(setCurrentReviews(data));
 });
 
@@ -93,7 +93,7 @@ export const postReview = createAsyncThunk<
   }
 >('review/postReview', async (info, { dispatch, extra: api }) => {
   try {
-    const response = await api.post(`${ApiRoutes.Comments}/${info.offerId}`, {
+    const response = await api.post(`${ApiRoute.Comments}/${info.offerId}`, {
       comment: info.comment,
       rating: info.rating,
     });
@@ -148,7 +148,7 @@ export const fetchFavoriteOffers = createAsyncThunk<
     extra: AxiosInstance;
   }
 >('offers/fetchFavorites', async (_arg, { dispatch, extra: api }) => {
-  const response = await api.get<Offer[]>(ApiRoutes.Favorites);
+  const response = await api.get<Offer[]>(ApiRoute.Favorites);
   if (response.status === 200) {
     dispatch(setFavoriteOffers(response.data));
   }
@@ -164,7 +164,7 @@ export const bookmarkOffer = createAsyncThunk<
   }
 >('review/fetchReviews', async (info, { dispatch, extra: api }) => {
   const response = await api.post(
-    `${ApiRoutes.Favorites}/${info.offerId}/${+info.status}`,
+    `${ApiRoute.Favorites}/${info.offerId}/${+info.status}`,
   );
   if (response.status === 201 || response.status === 200) {
     dispatch(fetchFavoriteOffers());
@@ -181,7 +181,7 @@ export const login = createAsyncThunk<
   }
 >('auth/login', async (loginInfo, { dispatch, extra: api }) => {
   try {
-    const response = await api.post<AuthInfo>(ApiRoutes.Login, loginInfo);
+    const response = await api.post<AuthInfo>(ApiRoute.Login, loginInfo);
     if (response.status === 200 || response.status === 201) {
       dispatch(setAuthorizationStatus(AuthorizationStatus.Authorized));
       saveToken(response.data.token);
@@ -210,7 +210,7 @@ export const checkAuthorization = createAsyncThunk<
   }
 >('auth/checkAuthorization', async (_arg, { dispatch, extra: api }) => {
   try {
-    const response = await api.get<AuthInfo>(ApiRoutes.Login);
+    const response = await api.get<AuthInfo>(ApiRoute.Login);
     if (response.status === 200 || response.status === 201) {
       dispatch(setAuthorizationStatus(AuthorizationStatus.Authorized));
       dispatch(setUserInfo(response.data));
@@ -237,6 +237,6 @@ export const logout = createAsyncThunk<
     extra: AxiosInstance;
   }
 >('auth/logout', async (_arg, { dispatch, extra: api }) => {
-  await api.delete(ApiRoutes.Logout);
+  await api.delete(ApiRoute.Logout);
   dispatch(setAuthorizationStatus(AuthorizationStatus.Unauthorized));
 });
diff --git a/src/store/current-offer/current-offer.selectors.ts b/src/store/current-offer/current-offer.selectors.ts
index bd74d1e..735ab74 100644
--- a/src/store/current-offer/current-offer.selectors.ts
+++ b/src/store/current-offer/current-offer.selectors.ts
@@ -1,22 +1,24 @@
 import { State } from '../../dataTypes/store-types.ts';
-import { NameSpaces } from '../../dataTypes/enums/name-spaces.ts';
+import { NameSpace } from '../../dataTypes/enums/name-space.ts';
 import { MAX_NEARBY_OFFERS } from '../../consts/offers.ts';
 import { createSelector } from '@reduxjs/toolkit';
 import { Offer } from '../../dataTypes/offer.ts';
 import { Review } from '../../dataTypes/review.ts';
 
-export const getCurrentOffer = (state: State) =>
-  state[NameSpaces.CurrentOffer].currentOffer;
+type CurrentOfferState = Pick<State, NameSpace.CurrentOffer>;
+
+export const getCurrentOffer = (state: CurrentOfferState) =>
+  state[NameSpace.CurrentOffer].currentOffer;
 export const getNearbyOffers = createSelector(
-  [(state: State) => state[NameSpaces.CurrentOffer].nearbyOffers],
+  [(state: CurrentOfferState) => state[NameSpace.CurrentOffer].nearbyOffers],
   (offers: Offer[]) => offers.slice(0, MAX_NEARBY_OFFERS),
 );
-export const getCurrentReviews = (state: State) =>
-  state[NameSpaces.CurrentOffer].currentReviews;
+export const getCurrentReviews = (state: CurrentOfferState) =>
+  state[NameSpace.CurrentOffer].currentReviews;
 
 export const getReviewsCount = createSelector(
-  [(state: State) => state[NameSpaces.CurrentOffer].currentReviews],
+  [(state: CurrentOfferState) => state[NameSpace.CurrentOffer].currentReviews],
   (reviews: Review[]) => reviews.length,
 );
-export const getReviewPostingStatus = (state: State) =>
-  state[NameSpaces.CurrentOffer].reviewPostingStatus;
+export const getReviewPostingStatus = (state: CurrentOfferState) =>
+  state[NameSpace.CurrentOffer].reviewPostingStatus;
diff --git a/src/store/current-offer/current-offer.slice.test.ts b/src/store/current-offer/current-offer.slice.test.ts
new file mode 100644
index 0000000..ad14d39
--- /dev/null
+++ b/src/store/current-offer/current-offer.slice.test.ts
@@ -0,0 +1,80 @@
+import { ReviewStatus } from '../../dataTypes/enums/review-status.ts';
+import {
+  currentOfferSlice,
+  setCurrentOffer,
+  setCurrentReviews,
+  setNearbyOffers,
+  setReviewPostingStatus,
+} from './current-offer.slice.ts';
+import { getMockDetailedOffer } from '../../mocks/mock-detailed-offer.ts';
+import { describe, expect, it } from 'vitest';
+import { getMockOffers } from '../../mocks/mock-offers.ts';
+import { getMockReviews } from '../../mocks/mock-review.ts';
+
+describe('current offer slice tests', () => {
+  const initialState = {
+    currentOffer: null,
+    nearbyOffers: [],
+    currentReviews: [],
+    reviewPostingStatus: ReviewStatus.Success,
+  };
+  it('should return initial state with empty action', () => {
+    const emptyAction = { type: '' };
+
+    const result = currentOfferSlice.reducer(initialState, emptyAction);
+
+    expect(result).toEqual(initialState);
+  });
+
+  it('should return default initial state with empty action', () => {
+    const emptyAction = { type: '' };
+
+    const result = currentOfferSlice.reducer(undefined, emptyAction);
+
+    expect(result).toEqual(initialState);
+  });
+
+  it('should set currentOffer', () => {
+    const detailedOffer = getMockDetailedOffer();
+
+    const newState = currentOfferSlice.reducer(
+      initialState,
+      setCurrentOffer(detailedOffer),
+    );
+
+    expect(newState.currentOffer).toEqual(detailedOffer);
+  });
+
+  it('should set nearbyOffers', () => {
+    const nearbyOffers = getMockOffers(2);
+
+    const newState = currentOfferSlice.reducer(
+      initialState,
+      setNearbyOffers(nearbyOffers),
+    );
+
+    expect(newState.nearbyOffers).toEqual(nearbyOffers);
+  });
+
+  it('should set currentReviews', () => {
+    const reviews = getMockReviews(3);
+
+    const newState = currentOfferSlice.reducer(
+      initialState,
+      setCurrentReviews(reviews),
+    );
+
+    expect(newState.currentReviews).toEqual(reviews);
+  });
+
+  it('should set reviewPostingStatus', () => {
+    const status = ReviewStatus.Success;
+
+    const newState = currentOfferSlice.reducer(
+      initialState,
+      setReviewPostingStatus(status),
+    );
+
+    expect(newState.reviewPostingStatus).toEqual(status);
+  });
+});
diff --git a/src/store/current-offer/current-offer.slice.ts b/src/store/current-offer/current-offer.slice.ts
index 04a9ad5..9638322 100644
--- a/src/store/current-offer/current-offer.slice.ts
+++ b/src/store/current-offer/current-offer.slice.ts
@@ -1,10 +1,10 @@
 import { Offer } from '../../dataTypes/offer.ts';
 import { createSlice, PayloadAction } from '@reduxjs/toolkit';
-import { NameSpaces } from '../../dataTypes/enums/name-spaces.ts';
-import { Nullable } from 'vitest';
+import { NameSpace } from '../../dataTypes/enums/name-space.ts';
 import { Review } from '../../dataTypes/review.ts';
 import { DetailedOffer } from '../../dataTypes/detailed-offer.ts';
 import { ReviewStatus } from '../../dataTypes/enums/review-status.ts';
+import { Nullable } from 'vitest';
 
 type CurrentOfferInitialState = {
   currentOffer: Nullable<DetailedOffer>;
@@ -21,7 +21,7 @@ const initialState: CurrentOfferInitialState = {
 };
 
 export const currentOfferSlice = createSlice({
-  name: NameSpaces.CurrentOffer,
+  name: NameSpace.CurrentOffer,
   initialState,
   reducers: {
     setCurrentOffer: (
diff --git a/src/store/current-offer/current-offers.selectors.test.ts b/src/store/current-offer/current-offers.selectors.test.ts
new file mode 100644
index 0000000..8f3eab7
--- /dev/null
+++ b/src/store/current-offer/current-offers.selectors.test.ts
@@ -0,0 +1,48 @@
+import { describe } from 'vitest';
+import {
+  getCurrentOffer,
+  getCurrentReviews,
+  getNearbyOffers,
+  getReviewPostingStatus,
+  getReviewsCount,
+} from './current-offer.selectors.ts';
+import { ReviewStatus } from '../../dataTypes/enums/review-status.ts';
+import { getMockDetailedOffer } from '../../mocks/mock-detailed-offer.ts';
+import { getMockOffers } from '../../mocks/mock-offers.ts';
+import { getMockReviews } from '../../mocks/mock-review.ts';
+import { NameSpace } from '../../dataTypes/enums/name-space.ts';
+
+describe('Current offer selectors test', () => {
+  const state = {
+    [NameSpace.CurrentOffer]: {
+      currentOffer: getMockDetailedOffer(),
+      nearbyOffers: getMockOffers(3),
+      currentReviews: getMockReviews(2),
+      reviewPostingStatus: ReviewStatus.Success,
+    },
+  };
+  it('should return current offer', () => {
+    const result = getCurrentOffer(state);
+    expect(result).toEqual(state[NameSpace.CurrentOffer].currentOffer);
+  });
+
+  it('should return nearby offers', () => {
+    const result = getNearbyOffers(state);
+    expect(result).toEqual(state[NameSpace.CurrentOffer].nearbyOffers);
+  });
+
+  it('should return current reviews', () => {
+    const result = getCurrentReviews(state);
+    expect(result).toEqual(state[NameSpace.CurrentOffer].currentReviews);
+  });
+
+  it('should return number of reviews', () => {
+    const result = getReviewsCount(state);
+    expect(result).toBe(2);
+  });
+
+  it('should return review posting status', () => {
+    const result = getReviewPostingStatus(state);
+    expect(result).toBe(state[NameSpace.CurrentOffer].reviewPostingStatus);
+  });
+});
diff --git a/src/store/offers/offers.selector.test.ts b/src/store/offers/offers.selector.test.ts
new file mode 100644
index 0000000..d9db6d5
--- /dev/null
+++ b/src/store/offers/offers.selector.test.ts
@@ -0,0 +1,37 @@
+import {
+  getFavoritesOffers,
+  getCity,
+  getSortedOffers,
+} from './offers.selectors.ts';
+import { NameSpace } from '../../dataTypes/enums/name-space.ts';
+import { AMSTERDAM } from '../../consts/cities.ts';
+import { Offer } from '../../dataTypes/offer.ts';
+import { getMockOffers } from '../../mocks/mock-offers.ts';
+
+describe('offers selectors test', () => {
+  const state = {
+    [NameSpace.Offers]: {
+      city: AMSTERDAM,
+      offers: getMockOffers(3),
+      sorting: (offers: Offer[]) =>
+        offers.toSorted((a, b) => a.price - b.price),
+      favoritesOffers: getMockOffers(4),
+    },
+  };
+
+  it('should return favorite offers', () => {
+    const result = getFavoritesOffers(state);
+    expect(result).toEqual(state[NameSpace.Offers].favoritesOffers);
+  });
+
+  it('should return city', () => {
+    const result = getCity(state);
+    expect(result).toEqual(AMSTERDAM);
+  });
+
+  it('should return sorted offers for the city', () => {
+    const sortedOffers = getSortedOffers(state);
+    expect(sortedOffers[0].price).toBeLessThan(sortedOffers[1].price);
+    expect(sortedOffers[1].price).toBeLessThan(sortedOffers[2].price);
+  });
+});
diff --git a/src/store/offers/offers.selectors.ts b/src/store/offers/offers.selectors.ts
index 204833e..275e7ef 100644
--- a/src/store/offers/offers.selectors.ts
+++ b/src/store/offers/offers.selectors.ts
@@ -1,18 +1,20 @@
 import { State } from '../../dataTypes/store-types.ts';
-import { NameSpaces } from '../../dataTypes/enums/name-spaces.ts';
+import { NameSpace } from '../../dataTypes/enums/name-space.ts';
 import { createSelector } from '@reduxjs/toolkit';
 import { Offer } from '../../dataTypes/offer.ts';
 import { City } from '../../dataTypes/city.ts';
 import { SortOffers } from '../../dataTypes/sort-offers.ts';
 
-export const getFavoritesOffers = (state: State) =>
-  state[NameSpaces.Offers].favoritesOffers;
-export const getCity = (state: State) => state[NameSpaces.Offers].city;
+type OffersState = Pick<State, NameSpace.Offers>;
+
+export const getFavoritesOffers = (state: OffersState) =>
+  state[NameSpace.Offers].favoritesOffers;
+export const getCity = (state: OffersState) => state[NameSpace.Offers].city;
 export const getSortedOffers = createSelector(
   [
-    (state: State) => state[NameSpaces.Offers].offers,
-    (state: State) => state[NameSpaces.Offers].city,
-    (state: State) => state[NameSpaces.Offers].sorting,
+    (state: OffersState) => state[NameSpace.Offers].offers,
+    (state: OffersState) => state[NameSpace.Offers].city,
+    (state: OffersState) => state[NameSpace.Offers].sorting,
   ],
   (offers: Offer[], city: City, sort: SortOffers) =>
     sort(offers.filter((offer: Offer) => offer.city.name === city.name)),
diff --git a/src/store/offers/offers.slice.test.ts b/src/store/offers/offers.slice.test.ts
new file mode 100644
index 0000000..f78149f
--- /dev/null
+++ b/src/store/offers/offers.slice.test.ts
@@ -0,0 +1,57 @@
+import {
+  offersSlice,
+  changeCity,
+  setOffers,
+  setSorting,
+  setFavoriteOffers,
+} from './offers.slice.ts';
+import { Offer } from '../../dataTypes/offer.ts';
+import { AMSTERDAM, PARIS } from '../../consts/cities.ts';
+import { getMockOffers } from '../../mocks/mock-offers.ts';
+import { expect, it } from 'vitest';
+
+describe('offers slice test', () => {
+  const initialState = {
+    city: PARIS,
+    offers: [],
+    sorting: (offers: Offer[]): Offer[] => offers,
+    favoritesOffers: [],
+  };
+  it('should return initial state with empty action', () => {
+    const emptyAction = { type: '' };
+
+    const result = offersSlice.reducer(initialState, emptyAction);
+
+    expect(result).toEqual(initialState);
+  });
+
+  it('should change city', () => {
+    const newState = offersSlice.reducer(initialState, changeCity(AMSTERDAM));
+    expect(newState.city).toEqual(AMSTERDAM);
+  });
+
+  it('should set offers', () => {
+    const newOffers = getMockOffers(3);
+    const newState = offersSlice.reducer(initialState, setOffers(newOffers));
+    expect(newState.offers).toEqual(newOffers);
+  });
+
+  it('should set sorting function', () => {
+    const sortingFunction = (offers: Offer[]) =>
+      offers.sort((a, b) => a.price - b.price);
+    const newState = offersSlice.reducer(
+      initialState,
+      setSorting(sortingFunction),
+    );
+    expect(newState.sorting).toEqual(sortingFunction);
+  });
+
+  it('should set favorite offers', () => {
+    const favoriteOffers = getMockOffers(3);
+    const newState = offersSlice.reducer(
+      initialState,
+      setFavoriteOffers(favoriteOffers),
+    );
+    expect(newState.favoritesOffers).toEqual(favoriteOffers);
+  });
+});
diff --git a/src/store/offers/offers.slice.ts b/src/store/offers/offers.slice.ts
index a822b7c..26cd284 100644
--- a/src/store/offers/offers.slice.ts
+++ b/src/store/offers/offers.slice.ts
@@ -3,7 +3,7 @@ import { Offer } from '../../dataTypes/offer.ts';
 import { City } from '../../dataTypes/city.ts';
 import { SortOffers } from '../../dataTypes/sort-offers.ts';
 import { createSlice, PayloadAction } from '@reduxjs/toolkit';
-import { NameSpaces } from '../../dataTypes/enums/name-spaces.ts';
+import { NameSpace } from '../../dataTypes/enums/name-space.ts';
 type OffersInitialState = {
   city: City;
   offers: Offer[];
@@ -19,7 +19,7 @@ const initialState: OffersInitialState = {
 };
 
 export const offersSlice = createSlice({
-  name: NameSpaces.Offers,
+  name: NameSpace.Offers,
   initialState,
   reducers: {
     changeCity: (state, action: PayloadAction<City>) => {
diff --git a/src/store/store.ts b/src/store/store.ts
index 8de9388..8b6d392 100644
--- a/src/store/store.ts
+++ b/src/store/store.ts
@@ -2,7 +2,7 @@ import { combineReducers, configureStore } from '@reduxjs/toolkit';
 import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
 import { AppDispatch, State } from '../dataTypes/store-types.ts';
 import { createAPI } from '../api/api.ts';
-import { NameSpaces } from '../dataTypes/enums/name-spaces.ts';
+import { NameSpace } from '../dataTypes/enums/name-space.ts';
 import { offersSlice } from './offers/offers.slice.ts';
 import { userSlice } from './user/user-slice.ts';
 import { currentOfferSlice } from './current-offer/current-offer.slice.ts';
@@ -10,9 +10,9 @@ import { currentOfferSlice } from './current-offer/current-offer.slice.ts';
 export const api = createAPI();
 
 const reducer = combineReducers({
-  [NameSpaces.Offers]: offersSlice.reducer,
-  [NameSpaces.CurrentOffer]: currentOfferSlice.reducer,
-  [NameSpaces.User]: userSlice.reducer,
+  [NameSpace.Offers]: offersSlice.reducer,
+  [NameSpace.CurrentOffer]: currentOfferSlice.reducer,
+  [NameSpace.User]: userSlice.reducer,
 });
 
 export const store = configureStore({
diff --git a/src/store/user/user-slice.ts b/src/store/user/user-slice.ts
index dd88c91..6e847ce 100644
--- a/src/store/user/user-slice.ts
+++ b/src/store/user/user-slice.ts
@@ -1,5 +1,5 @@
 import { createSlice, PayloadAction } from '@reduxjs/toolkit';
-import { NameSpaces } from '../../dataTypes/enums/name-spaces.ts';
+import { NameSpace } from '../../dataTypes/enums/name-space.ts';
 import { AuthorizationStatus } from '../../dataTypes/enums/authorization-status.ts';
 import { AuthInfo } from '../../dataTypes/user.ts';
 
@@ -14,7 +14,7 @@ const initialState: UserInitialState = {
 };
 
 export const userSlice = createSlice({
-  name: NameSpaces.User,
+  name: NameSpace.User,
   initialState,
   reducers: {
     setAuthorizationStatus: (
diff --git a/src/store/user/user.selector.test.ts b/src/store/user/user.selector.test.ts
new file mode 100644
index 0000000..e69de29
diff --git a/src/store/user/user.selectors.ts b/src/store/user/user.selectors.ts
index 3c1b9f3..c6014a6 100644
--- a/src/store/user/user.selectors.ts
+++ b/src/store/user/user.selectors.ts
@@ -1,7 +1,9 @@
 import { State } from '../../dataTypes/store-types.ts';
-import { NameSpaces } from '../../dataTypes/enums/name-spaces.ts';
+import { NameSpace } from '../../dataTypes/enums/name-space.ts';
 import { AuthorizationStatus } from '../../dataTypes/enums/authorization-status.ts';
 
-export const getIsAuthorized = (state: State) =>
-  state[NameSpaces.User].authorizationStatus === AuthorizationStatus.Authorized;
-export const getUserInfo = (state: State) => state[NameSpaces.User].userInfo;
+type UserState = Pick<State, NameSpace.User>;
+
+export const getIsAuthorized = (state: UserState) =>
+  state[NameSpace.User].authorizationStatus === AuthorizationStatus.Authorized;
+export const getUserInfo = (state: UserState) => state[NameSpace.User].userInfo;
diff --git a/src/store/user/user.slice.test.ts b/src/store/user/user.slice.test.ts
new file mode 100644
index 0000000..e69de29

From 1599c3b1b4757c40efe7cc963a0e05ab79fa36e5 Mon Sep 17 00:00:00 2001
From: Gurikov Maxim <maximgurikoff@gmail.com>
Date: Tue, 10 Dec 2024 23:30:22 +0500
Subject: [PATCH 3/5] fixes

---
 src/store/user/user.selector.test.ts | 23 ++++++++++++++
 src/store/user/user.slice.test.ts    | 47 ++++++++++++++++++++++++++++
 2 files changed, 70 insertions(+)

diff --git a/src/store/user/user.selector.test.ts b/src/store/user/user.selector.test.ts
index e69de29..2cd8fb8 100644
--- a/src/store/user/user.selector.test.ts
+++ b/src/store/user/user.selector.test.ts
@@ -0,0 +1,23 @@
+import { getIsAuthorized, getUserInfo } from './user.selectors.ts';
+import { NameSpace } from '../../dataTypes/enums/name-space.ts';
+import { getMockAuthInfo } from '../../mocks/mock-user.ts';
+import { AuthorizationStatus } from '../../dataTypes/enums/authorization-status.ts';
+
+describe('user selectors test', () => {
+  const state = {
+    [NameSpace.User]: {
+      authorizationStatus: AuthorizationStatus.Authorized,
+      userInfo: getMockAuthInfo(),
+    },
+  };
+
+  it('should return true if user is authorized', () => {
+    const result = getIsAuthorized(state);
+    expect(result).toBe(true);
+  });
+
+  it('should return user info', () => {
+    const result = getUserInfo(state);
+    expect(result).toEqual(state[NameSpace.User].userInfo);
+  });
+});
diff --git a/src/store/user/user.slice.test.ts b/src/store/user/user.slice.test.ts
index e69de29..0051404 100644
--- a/src/store/user/user.slice.test.ts
+++ b/src/store/user/user.slice.test.ts
@@ -0,0 +1,47 @@
+import {
+  setAuthorizationStatus,
+  setUserInfo,
+  userSlice,
+} from './user-slice.ts';
+import { AuthorizationStatus } from '../../dataTypes/enums/authorization-status.ts';
+import { getMockAuthInfo } from '../../mocks/mock-user.ts';
+import { expect, it } from 'vitest';
+import { currentOfferSlice } from '../current-offer/current-offer.slice.ts';
+
+describe('user slice test', () => {
+  const initialState = {
+    authorizationStatus: AuthorizationStatus.Unknown,
+    userInfo: null,
+  };
+  it('should return initial state with empty action', () => {
+    const emptyAction = { type: '' };
+
+    const result = userSlice.reducer(initialState, emptyAction);
+
+    expect(result).toEqual(initialState);
+  });
+
+  it('should return default initial state with empty action', () => {
+    const emptyAction = { type: '' };
+
+    const result = userSlice.reducer(undefined, emptyAction);
+
+    expect(result).toEqual(initialState);
+  });
+
+  it('should set authorization status', () => {
+    const newState = userSlice.reducer(
+      initialState,
+      setAuthorizationStatus(AuthorizationStatus.Authorized),
+    );
+    expect(newState.authorizationStatus).toEqual(
+      AuthorizationStatus.Authorized,
+    );
+  });
+
+  it('should set user info', () => {
+    const userInfo = getMockAuthInfo();
+    const newState = userSlice.reducer(initialState, setUserInfo(userInfo));
+    expect(newState.userInfo).toEqual(userInfo);
+  });
+});

From 1a91503b73ba0a3a5a9a4f50f701fec88e664504 Mon Sep 17 00:00:00 2001
From: Gurikov Maxim <maximgurikoff@gmail.com>
Date: Wed, 11 Dec 2024 20:26:48 +0500
Subject: [PATCH 4/5] fix

---
 src/store/offers/offers.slice.test.ts | 10 ++++++++++
 src/store/user/user.slice.test.ts     |  1 -
 2 files changed, 10 insertions(+), 1 deletion(-)

diff --git a/src/store/offers/offers.slice.test.ts b/src/store/offers/offers.slice.test.ts
index f78149f..550a438 100644
--- a/src/store/offers/offers.slice.test.ts
+++ b/src/store/offers/offers.slice.test.ts
@@ -25,6 +25,16 @@ describe('offers slice test', () => {
     expect(result).toEqual(initialState);
   });
 
+  it('should return default initial state with empty action', () => {
+    const emptyAction = { type: '' };
+
+    const result = offersSlice.reducer(undefined, emptyAction);
+
+    expect(result.city.name).toEqual(initialState.city.name);
+    expect(result.offers).toEqual([]);
+    expect(result.favoritesOffers).toEqual([]);
+  });
+
   it('should change city', () => {
     const newState = offersSlice.reducer(initialState, changeCity(AMSTERDAM));
     expect(newState.city).toEqual(AMSTERDAM);
diff --git a/src/store/user/user.slice.test.ts b/src/store/user/user.slice.test.ts
index 0051404..b16fb7a 100644
--- a/src/store/user/user.slice.test.ts
+++ b/src/store/user/user.slice.test.ts
@@ -6,7 +6,6 @@
 import { AuthorizationStatus } from '../../dataTypes/enums/authorization-status.ts';
 import { getMockAuthInfo } from '../../mocks/mock-user.ts';
 import { expect, it } from 'vitest';
-import { currentOfferSlice } from '../current-offer/current-offer.slice.ts';
 
 describe('user slice test', () => {
   const initialState = {

From 4c03d8b69d23c0a26855c21f320fc73ef1ddea07 Mon Sep 17 00:00:00 2001
From: Gurikov Maxim <maximgurikoff@gmail.com>
Date: Wed, 11 Dec 2024 20:55:55 +0500
Subject: [PATCH 5/5] more fixes

---
 src/Pages/login-page/login-page.tsx         |  6 ++----
 src/components/offer/offer-card.tsx         |  2 +-
 src/components/reviews/review-component.tsx |  3 ++-
 src/components/reviews/review-form.tsx      |  3 ++-
 src/consts/cities.ts                        |  2 +-
 src/utils/date-utils.ts                     | 22 +++++++++++++++++++++
 6 files changed, 30 insertions(+), 8 deletions(-)
 create mode 100644 src/utils/date-utils.ts

diff --git a/src/Pages/login-page/login-page.tsx b/src/Pages/login-page/login-page.tsx
index 7482064..26a5633 100644
--- a/src/Pages/login-page/login-page.tsx
+++ b/src/Pages/login-page/login-page.tsx
@@ -46,8 +46,7 @@ export function LoginPage(): React.JSX.Element {
                     name="email"
                     placeholder="Email"
                     onChange={(event) =>
-                      setLoginInfo({ ...loginInfo, email: event.target.value })
-                    }
+                      setLoginInfo({ ...loginInfo, email: event.target.value })}
                     required
                   />
                 </div>
@@ -62,8 +61,7 @@ export function LoginPage(): React.JSX.Element {
                       setLoginInfo({
                         ...loginInfo,
                         password: event.target.value,
-                      })
-                    }
+                      })}
                     required
                   />
                 </div>
diff --git a/src/components/offer/offer-card.tsx b/src/components/offer/offer-card.tsx
index 71c150c..5950e6b 100644
--- a/src/components/offer/offer-card.tsx
+++ b/src/components/offer/offer-card.tsx
@@ -38,7 +38,7 @@ export function OfferCardImpl({
       onMouseLeave={handleMouseLeave}
       className={cn(
         'place-card',
-        { cities__card: isOnMainPage },
+        { 'cities__card': isOnMainPage },
         { 'near-places__card': !isOnMainPage },
       )}
     >
diff --git a/src/components/reviews/review-component.tsx b/src/components/reviews/review-component.tsx
index 1e72ef5..32d3263 100644
--- a/src/components/reviews/review-component.tsx
+++ b/src/components/reviews/review-component.tsx
@@ -1,5 +1,6 @@
 import { getFirstName } from '../../utils/username-utils.ts';
 import { Rating } from '../rating.tsx';
+import { formatDate } from '../../utils/date-utils.ts';
 
 interface ReviewProps {
   comment: string;
@@ -34,7 +35,7 @@ export function ReviewComponent({
         <Rating rating={rating} usePlace="reviews" />
         <p className="reviews__text">{comment}</p>
         <time className="reviews__time" dateTime={date.toDateString()}>
-          {date.toLocaleDateString('en-US', {})}
+          {formatDate(date)}
         </time>
       </div>
     </li>
diff --git a/src/components/reviews/review-form.tsx b/src/components/reviews/review-form.tsx
index 4c2df7b..a527aca 100644
--- a/src/components/reviews/review-form.tsx
+++ b/src/components/reviews/review-form.tsx
@@ -170,7 +170,8 @@ export function ReviewForm(): React.JSX.Element {
         value={review?.comment || ''}
         onChange={onCommentChange}
         disabled={reviewPostingStatus === ReviewStatus.Pending}
-      ></textarea>
+      >
+      </textarea>
       <div className="reviews__button-wrapper">
         <p className="reviews__help">
           To submit review please make sure to set{' '}
diff --git a/src/consts/cities.ts b/src/consts/cities.ts
index dfee7ec..f28474b 100644
--- a/src/consts/cities.ts
+++ b/src/consts/cities.ts
@@ -61,4 +61,4 @@ export const CITIES: City[] = [
   AMSTERDAM,
   HAMBURG,
   DUSSELDORF,
-];
+] as const;
diff --git a/src/utils/date-utils.ts b/src/utils/date-utils.ts
new file mode 100644
index 0000000..5ab91a0
--- /dev/null
+++ b/src/utils/date-utils.ts
@@ -0,0 +1,22 @@
+export function formatDate(dateString: Date): string {
+  const monthsInEnglish = [
+    'January',
+    'February',
+    'March',
+    'April',
+    'May',
+    'June',
+    'July',
+    'August',
+    'September',
+    'October',
+    'November',
+    'December',
+  ];
+
+  const date = new Date(dateString);
+  const month = monthsInEnglish[date.getMonth()];
+  const year = date.getFullYear();
+
+  return `${month} ${year}`;
+}