Skip to content

Commit

Permalink
Merge pull request #30 from Blockchain-Country/userdb
Browse files Browse the repository at this point in the history
added user login and db backend
  • Loading branch information
Blockchain-Country authored Dec 18, 2024
2 parents e4849e6 + 1e661f9 commit dcae305
Show file tree
Hide file tree
Showing 12 changed files with 233 additions and 75 deletions.
16 changes: 15 additions & 1 deletion src/App.js
Original file line number Diff line number Diff line change
@@ -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 (
<BrowserRouter
future={{
Expand Down
24 changes: 17 additions & 7 deletions src/App.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { screen, fireEvent, within } from '@testing-library/react'
import { screen, fireEvent, within, act } from '@testing-library/react'
import { setup } from './setupTests'
import { createStore } from './redux/store'
import App from './App'
Expand Down Expand Up @@ -31,11 +31,11 @@ function submitNewBook(bookTitle, bookAuthor) {
}

describe('App Component Tests', () => {
//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')
Expand All @@ -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')
Expand All @@ -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')
Expand All @@ -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)

Expand Down
4 changes: 2 additions & 2 deletions src/api/services/searchBookService.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
16 changes: 12 additions & 4 deletions src/components/BookListSection/BookListSection.jsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -18,19 +20,25 @@ 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!"))
}
}
}

const toggleFavoriteBook = (id) => {
dispatch(toggleFavorite(id))
dispatch(syncToggleFavorite(id))
}

const filteredBooksArr = books.filter((book) => {
Expand Down
81 changes: 43 additions & 38 deletions src/components/BookListSection/BookListSection.test.js
Original file line number Diff line number Diff line change
@@ -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', () => {
Expand Down Expand Up @@ -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: [
{
Expand All @@ -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()
})
})
12 changes: 9 additions & 3 deletions src/components/filterSection/FilterSection.test.js
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -9,15 +9,21 @@ 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')
filterbyAuthorsInput = screen.getByTestId('filter_author_input')
clearAllFiltersBtn = screen.getByTestId('filter_clear_btn')
})

afterEach(() => {
document.body.innerHTML = ''
})

test('Should render TitleFilter input', () => {
expect(filterByTitleInput).toBeInTheDocument()
expect(filterByTitleInput).toBeEnabled()
Expand Down
4 changes: 2 additions & 2 deletions src/components/manualAddBookSection/ManualAddBookSection.jsx
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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!'))
}
Expand Down
12 changes: 9 additions & 3 deletions src/components/manualAddBookSection/ManualAddBookSection.test.js
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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')
Expand All @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'] {
Expand Down
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -61,14 +61,13 @@ const SearchBookModal = ({ isOpen, onClose, searchResults }) => {
? 'Authors:'
: 'Author:'}
</strong>{' '}
{book.authors || 'Unknown Authors'}
{book.authors}
</p>
<p>
<strong>Published Year:</strong> {book.publishedDate || 'N/A'}
<strong>Published Year:</strong> {book.publishedDate}
</p>
<p>
<strong>Description:</strong>{' '}
{book.description || 'No description available'}
<strong>Description:</strong> {book.description}
</p>
<div data-testid="modal_add_book_wrapper">
<Button
Expand Down
Loading

0 comments on commit dcae305

Please sign in to comment.