diff --git a/src/App.js b/src/App.js
index 045add0..5b4693a 100644
--- a/src/App.js
+++ b/src/App.js
@@ -1,16 +1,30 @@
+import { useEffect } from 'react'
+import { useDispatch } from 'react-redux'
+import { onAuthStateChanged } from 'firebase/auth'
import { BrowserRouter, Routes, Route } from 'react-router-dom'
import FilterSection from './components/filterSection/FilterSection'
import ManualAddBookSection from './components/manualAddBookSection/ManualAddBookSection'
import SearchBookSection from './components/searchBookSection/SearchBookSection'
+import RandomBookSection from './components/randomBookSection/RandomBookSection'
import BookListSection from './components/BookListSection/BookListSection'
import Header from './components/header/Header'
import Login from './components/user/login/Login'
import Signup from './components/user/signup/Signup'
import Error from './components/error/Error'
+import { syncLoadBook } from './redux/slices/booksSlice'
+import { auth } from './services/firebaseConfig'
import './App.css'
-import RandomBookSection from './components/randomBookSection/RandomBookSection'
function App() {
+ const dispatch = useDispatch()
+
+ useEffect(() => {
+ const unsubscribe = onAuthStateChanged(auth, (user) => {
+ dispatch(syncLoadBook())
+ })
+ return () => unsubscribe()
+ }, [dispatch])
+
return (
{
- //App main component elements:
-
- beforeEach(() => {
+ beforeEach(async () => {
resetStore()
- setup(App, store)
+ await act(async () => {
+ setup(App, store)
+ })
//main components elements:
header = screen.getByTestId('header_container')
@@ -44,6 +44,10 @@ describe('App Component Tests', () => {
bookListSection_component = screen.getByTestId('bookList_section')
})
+ afterEach(() => {
+ jest.clearAllMocks()
+ })
+
test('Should render the Header to be in the DOM and should contain text "My Books Storage"', async () => {
expect(header).toBeInTheDocument()
expect(header).toHaveTextContent('My Books Storage')
@@ -63,9 +67,11 @@ describe('App Component Tests', () => {
})
describe('App functional Tests', () => {
- beforeEach(() => {
+ beforeEach(async () => {
resetStore()
- setup(App, store)
+ await act(async () => {
+ setup(App, store)
+ })
// ManualAddBookSection form elements:
titleInput = screen.getByTestId('manualAddBook_input_title')
@@ -78,6 +84,10 @@ describe('App functional Tests', () => {
clearAllFiltersBtn = screen.getByTestId('filter_clear_btn')
})
+ afterEach(() => {
+ jest.clearAllMocks()
+ })
+
test('Should Submit a new book to the BookListSection', () => {
submitNewBook(bookTitleName, bookAuthorName)
diff --git a/src/api/services/searchBookService.js b/src/api/services/searchBookService.js
index 7db62b6..425a1a9 100644
--- a/src/api/services/searchBookService.js
+++ b/src/api/services/searchBookService.js
@@ -26,9 +26,9 @@ export async function searchBookService(query) {
title: item.volumeInfo.title,
image: item.volumeInfo.imageLinks?.thumbnail,
authors: item.volumeInfo.authors?.join(', ') || 'Unknown Author',
- publishedDate: item.volumeInfo.publishedDate,
+ publishedDate: item.volumeInfo.publishedDate || 'N/A',
language: item.volumeInfo.language,
- description: item.volumeInfo.description,
+ description: item.volumeInfo.description || 'No description available',
bookId: item.id,
}))
} else {
diff --git a/src/components/BookListSection/BookListSection.jsx b/src/components/BookListSection/BookListSection.jsx
index 5b1a75f..1589b4e 100644
--- a/src/components/BookListSection/BookListSection.jsx
+++ b/src/components/BookListSection/BookListSection.jsx
@@ -1,11 +1,13 @@
+import { useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { setError } from '../../redux/slices/errorSlice'
import Book from './book/Book'
import './BookListSection.css'
import {
selectBook,
- deleteBook,
- toggleFavorite,
+ syncLoadBook,
+ syncDeleteBook,
+ syncToggleFavorite,
} from '../../redux/slices/booksSlice'
import {
selectTitleFilter,
@@ -18,11 +20,17 @@ const BookListSection = ({ 'data-testid': testId }) => {
const titleFilter = useSelector(selectTitleFilter)
const authorsFilter = useSelector(selectAuthorsFilter)
+ useEffect(() => {
+ if (process.env.NODE_ENV !== 'test') {
+ dispatch(syncLoadBook())
+ }
+ }, [dispatch])
+
const handleDeleteBook = (id) => {
const book = books.find((book) => book.id === id)
if (book) {
if (!book.isFavorite) {
- dispatch(deleteBook(id))
+ dispatch(syncDeleteBook(id))
} else {
dispatch(setError("Can't Delete Favorite Book!"))
}
@@ -30,7 +38,7 @@ const BookListSection = ({ 'data-testid': testId }) => {
}
const toggleFavoriteBook = (id) => {
- dispatch(toggleFavorite(id))
+ dispatch(syncToggleFavorite(id))
}
const filteredBooksArr = books.filter((book) => {
diff --git a/src/components/BookListSection/BookListSection.test.js b/src/components/BookListSection/BookListSection.test.js
index 0f7139c..eb54323 100644
--- a/src/components/BookListSection/BookListSection.test.js
+++ b/src/components/BookListSection/BookListSection.test.js
@@ -1,25 +1,39 @@
-import { within, fireEvent } from '@testing-library/react'
+import { within, fireEvent, screen, act } from '@testing-library/react'
import { v4 as uuidv4 } from 'uuid'
import { setup } from '../../setupTests'
+import store from '../../redux/store'
import BookListSection from './BookListSection'
import { createStore } from '../../redux/store'
+import App from '../../App'
+let bookListComponent, noBooksMessage
const bookTitleName = 'Test Book Title'
const bookAuthorName = 'Test Book Author'
const bookId = uuidv4()
describe('BookList Component Tests', () => {
- test('Should display "No books in my list..." when books list is empty', () => {
- // Create the mocked store with no books in the preloaded state
- const mockedStore = createStore({
- books: [],
+ beforeEach(async () => {
+ await act(async () => {
+ setup(App, store)
})
- // Render the BookList component with the mock store
- const { container } = setup(BookListSection, mockedStore)
+ // Locate BookListSection elements
+ bookListComponent = screen.getByTestId('bookList_section')
+ noBooksMessage = screen.queryByTestId('bookList_emptyMsg')
+ })
- const noBooksSign = within(container).getByTestId('bookList_emptyMsg')
- expect(noBooksSign).toBeInTheDocument()
+ afterEach(() => {
+ document.body.innerHTML = ''
+ })
+
+ test('Should render the BookListSection component', () => {
+ expect(bookListComponent).toBeInTheDocument()
+ expect(screen.getByText('My Book List')).toBeInTheDocument()
+ })
+
+ test('Should display "No books in my list..." when books list is empty', () => {
+ expect(noBooksMessage).toBeInTheDocument()
+ expect(noBooksMessage.textContent).toBe('No books in my list...')
})
test('Should display a book in the list when there is one book', () => {
@@ -58,7 +72,7 @@ describe('BookList Component Tests', () => {
expect(noBooksSign).toBeInTheDocument()
})
- test('Should not able to delete Favorite book', () => {
+ test('Should toggle favorite and delete functionality', () => {
const mockedStore = createStore({
books: [
{
@@ -72,41 +86,32 @@ describe('BookList Component Tests', () => {
const { container } = setup(BookListSection, mockedStore)
- const bookItems = within(container).getByTestId(
- `bookList_item id=${bookId}`
- )
- let isFavoriteFalse = within(bookItems).getByTestId(
+ const bookItem = within(container).getByTestId(`bookList_item id=${bookId}`)
+ const favoriteToggle = within(bookItem).getByTestId(
'book_favorite_toggle_false'
)
- expect(isFavoriteFalse).toBeInTheDocument()
- fireEvent.click(isFavoriteFalse)
- let isFavoriteTrue = within(bookItems).getByTestId(
- 'book_favorite_toggle_true'
- )
- expect(isFavoriteTrue).toBeInTheDocument()
+ // Toggle favorite on
+ fireEvent.click(favoriteToggle)
+ expect(
+ within(bookItem).getByTestId('book_favorite_toggle_true')
+ ).toBeInTheDocument()
- //try to delete toggled book:
- let deleteBookBtn = within(bookItems).getByTestId('delete_book_btn')
+ // Try deleting the book
+ const deleteBookBtn = within(bookItem).getByTestId('delete_book_btn')
fireEvent.click(deleteBookBtn)
- expect(bookItems).toBeInTheDocument()
+ expect(bookItem).toBeInTheDocument()
- //untoggle favorite:
- isFavoriteTrue = within(bookItems).getByTestId('book_favorite_toggle_true')
- fireEvent.click(isFavoriteTrue)
- isFavoriteFalse = within(bookItems).getByTestId(
- 'book_favorite_toggle_false'
- )
- expect(isFavoriteFalse).toBeInTheDocument()
+ // Untoggle favorite
+ fireEvent.click(within(bookItem).getByTestId('book_favorite_toggle_true'))
+ expect(
+ within(bookItem).getByTestId('book_favorite_toggle_false')
+ ).toBeInTheDocument()
- //delete book:
- deleteBookBtn = within(bookItems).getByTestId('delete_book_btn')
+ // Delete book
fireEvent.click(deleteBookBtn)
-
- //assert if the book deleted from the DOM:
- const deletedBookItem = within(container).queryByTestId(
- `bookList_item id=${bookId}`
- )
- expect(deletedBookItem).toBeNull()
+ expect(
+ within(container).queryByTestId(`bookList_item id=${bookId}`)
+ ).toBeNull()
})
})
diff --git a/src/components/filterSection/FilterSection.test.js b/src/components/filterSection/FilterSection.test.js
index a726f4d..9ff5c3e 100644
--- a/src/components/filterSection/FilterSection.test.js
+++ b/src/components/filterSection/FilterSection.test.js
@@ -1,4 +1,4 @@
-import { fireEvent, screen } from '@testing-library/react'
+import { fireEvent, screen, act } from '@testing-library/react'
import { setup } from '../../setupTests'
import store from '../../redux/store'
import App from '../../App'
@@ -9,8 +9,10 @@ const authorsFilterStr = 'authors'
let filterByTitleInput, filterbyAuthorsInput, clearAllFiltersBtn
describe('FilterSection Component Tests', () => {
- beforeEach(() => {
- setup(App, store)
+ beforeEach(async () => {
+ await act(async () => {
+ setup(App, store)
+ })
// FilterSection elements:
filterByTitleInput = screen.getByTestId('filter_title_input')
@@ -18,6 +20,10 @@ describe('FilterSection Component Tests', () => {
clearAllFiltersBtn = screen.getByTestId('filter_clear_btn')
})
+ afterEach(() => {
+ document.body.innerHTML = ''
+ })
+
test('Should render TitleFilter input', () => {
expect(filterByTitleInput).toBeInTheDocument()
expect(filterByTitleInput).toBeEnabled()
diff --git a/src/components/manualAddBookSection/ManualAddBookSection.jsx b/src/components/manualAddBookSection/ManualAddBookSection.jsx
index f8aa248..5efbc3a 100644
--- a/src/components/manualAddBookSection/ManualAddBookSection.jsx
+++ b/src/components/manualAddBookSection/ManualAddBookSection.jsx
@@ -1,6 +1,6 @@
import { useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
-import { addBook, selectBook } from '../../redux/slices/booksSlice'
+import { selectBook, syncAddBook } from '../../redux/slices/booksSlice'
import createBook from '../../utils/createBook'
import './ManualAddBookSection.css'
import { setError } from '../../redux/slices/errorSlice'
@@ -25,7 +25,7 @@ const ManualAddBookSection = ({ 'data-testid': testId }) => {
e.preventDefault()
if (title.trim() && authors.trim()) {
if (!filterExistingBooks()) {
- dispatch(addBook(createBook({ title, authors })))
+ dispatch(syncAddBook(createBook({ title, authors })))
} else {
dispatch(setError('The book is already in the List!'))
}
diff --git a/src/components/manualAddBookSection/ManualAddBookSection.test.js b/src/components/manualAddBookSection/ManualAddBookSection.test.js
index 92594ca..6d2dd50 100644
--- a/src/components/manualAddBookSection/ManualAddBookSection.test.js
+++ b/src/components/manualAddBookSection/ManualAddBookSection.test.js
@@ -1,4 +1,4 @@
-import { fireEvent, screen } from '@testing-library/react'
+import { fireEvent, screen, act } from '@testing-library/react'
import { setup } from '../../setupTests'
import store from '../../redux/store'
import App from '../../App'
@@ -9,8 +9,10 @@ const bookAuthorName = 'Test Book Author'
let titleInput, authorsInput, submitBookBtn, bookListComponent
describe('ManualAddBookSection Component Tests', () => {
- beforeEach(() => {
- setup(App, store)
+ beforeEach(async () => {
+ await act(async () => {
+ setup(App, store)
+ })
// ManualAddBookSection elements:
titleInput = screen.getByTestId('manualAddBook_input_title')
@@ -19,6 +21,10 @@ describe('ManualAddBookSection Component Tests', () => {
bookListComponent = screen.getByTestId('bookList_section')
})
+ afterEach(() => {
+ document.body.innerHTML = ''
+ })
+
test('Should render Title input', () => {
expect(titleInput).toBeEnabled()
expect(titleInput).toBeInTheDocument()
diff --git a/src/components/searchBookSection/searchBookModal/SearchBookModal.css b/src/components/searchBookSection/searchBookModal/SearchBookModal.css
index 823cc57..f7726ff 100644
--- a/src/components/searchBookSection/searchBookModal/SearchBookModal.css
+++ b/src/components/searchBookSection/searchBookModal/SearchBookModal.css
@@ -23,14 +23,15 @@ div[data-testid='modal_book_content'] {
div[data-testid='book_left_content'] {
display: flex;
- align-items: center;
+ align-items: flex-start;
justify-content: center;
width: 30%;
}
img[data-testid='modal_book_img'] {
- min-width: 50%;
- height: 100%;
+ min-width: 70%;
+ max-height: 100%;
+ padding-top: 3rem;
}
div[data-testid='book_right_content'] {
diff --git a/src/components/searchBookSection/searchBookModal/SearchBookModal.jsx b/src/components/searchBookSection/searchBookModal/SearchBookModal.jsx
index 74a8680..691542a 100644
--- a/src/components/searchBookSection/searchBookModal/SearchBookModal.jsx
+++ b/src/components/searchBookSection/searchBookModal/SearchBookModal.jsx
@@ -1,6 +1,6 @@
import { useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
-import { addBook, selectBook } from '../../../redux/slices/booksSlice'
+import { selectBook, syncAddBook } from '../../../redux/slices/booksSlice'
import createBook from '../../../utils/createBook'
import Button from '../../common/button/Button'
import Modal from '../../common/modal/Modal'
@@ -28,7 +28,7 @@ const SearchBookModal = ({ isOpen, onClose, searchResults }) => {
}, [onClose, filteredSearchResults])
const handleAddBook = (bookFound) => {
- dispatch(addBook(createBook(bookFound)))
+ dispatch(syncAddBook(createBook(bookFound)))
}
if (!searchResults || searchResults.length === 0)
@@ -61,14 +61,13 @@ const SearchBookModal = ({ isOpen, onClose, searchResults }) => {
? 'Authors:'
: 'Author:'}
{' '}
- {book.authors || 'Unknown Authors'}
+ {book.authors}
- Published Year: {book.publishedDate || 'N/A'}
+ Published Year: {book.publishedDate}
- Description:{' '}
- {book.description || 'No description available'}
+ Description: {book.description}