Skip to content

Commit

Permalink
Merge pull request #10 from Blockchain-Country/loadingspinner
Browse files Browse the repository at this point in the history
Added loading spinner and filter for exiting books in the list
  • Loading branch information
Blockchain-Country authored Oct 31, 2024
2 parents 5466c4e + adaab7f commit a6d020c
Show file tree
Hide file tree
Showing 16 changed files with 148 additions and 120 deletions.
14 changes: 7 additions & 7 deletions src/App.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import App from './App'
let header, manualBookForm_Component, bookFilter_Component, bookList_Component

// BookForm elements:
let titleInput, authorInput, submitBookBtn
let titleInput, authorsInput, submitBookBtn

// BookFilter elements:
let filterByTitleInput, filterbyAuthorInput, clearAllFiltersBtn
let filterByTitleInput, filterbyAuthorsInput, clearAllFiltersBtn

const bookTitleName = 'BookTitle1'
const bookAuthorName = 'BookAuthor1'
Expand All @@ -23,7 +23,7 @@ function resetStore() {

function submitNewBook(bookTitle, bookAuthor) {
fireEvent.change(titleInput, { target: { value: bookTitle } })
fireEvent.change(authorInput, { target: { value: bookAuthor } })
fireEvent.change(authorsInput, { target: { value: bookAuthor } })
fireEvent.click(submitBookBtn)
}

Expand Down Expand Up @@ -66,12 +66,12 @@ describe('App functional Tests', () => {

// ManualBookForm elements:
titleInput = screen.getByTestId('manualBookForm_titleInput')
authorInput = screen.getByTestId('manualBookForm_aurthorInput')
authorsInput = screen.getByTestId('manualBookForm_authorsInput')
submitBookBtn = screen.getByTestId('manualBookForm_submitBtn')
bookList_Component = screen.getByTestId('bookList_component')
// BookFilter elements:
filterByTitleInput = screen.getByTestId('filterByTitle_input')
filterbyAuthorInput = screen.getByTestId('filterByAuthor_input')
filterbyAuthorsInput = screen.getByTestId('filterByAuthors_input')
clearAllFiltersBtn = screen.getByTestId('clearAllFilters_btn')
})

Expand Down Expand Up @@ -108,7 +108,7 @@ describe('App functional Tests', () => {
expect(filteredTitleEl).toBeInTheDocument()
})

test('Should Submit two books then filter by Author', () => {
test('Should Submit two books then filter by Authors', () => {
submitNewBook(bookTitleName, bookAuthorName)
submitNewBook('BookTitle2', 'BookAuthor2')

Expand All @@ -117,7 +117,7 @@ describe('App functional Tests', () => {
expect(bookItems.length).toEqual(2)

// Apply AuthorFilter
fireEvent.change(filterbyAuthorInput, {
fireEvent.change(filterbyAuthorsInput, {
target: { value: bookAuthorName },
})

Expand Down
30 changes: 19 additions & 11 deletions src/components/bookFilters/BookFilter.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,35 @@ import { useDispatch, useSelector } from 'react-redux'
import './BookFilter.css'
import {
setTitleFilter,
setAuthorFilter,
setAuthorsFilter,
selectTitleFilter,
selectAuthorFilter,
selectAuthorsFilter,
resetAllFilters,
} from '../../redux/slices/filterSlice'

const BookFilter = () => {
const dispatch = useDispatch()
const filterTitle = useSelector(selectTitleFilter)
const filterAuthor = useSelector(selectAuthorFilter)
const filterAuthors = useSelector(selectAuthorsFilter)

const handleTitleFilter = (e) => {
dispatch(setTitleFilter(e.target.value))
const value = e.target.value
if (value.trim() || value === '') {
dispatch(setTitleFilter(value))
}
}

const handleAuthorFilter = (e) => {
dispatch(setAuthorFilter(e.target.value))
const handleAuthorsFilter = (e) => {
const value = e.target.value
if (value.trim() || value === '') {
dispatch(setAuthorsFilter(value))
}
}

const handleResetFilters = () => {
dispatch(resetAllFilters())
if (filterTitle || filterAuthors) {
dispatch(resetAllFilters())
}
}

return (
Expand All @@ -40,10 +48,10 @@ const BookFilter = () => {
<div className="filter-group">
<input
type="text"
placeholder="Filter by author..."
value={filterAuthor}
onChange={handleAuthorFilter}
data-testid="filterByAuthor_input"
placeholder="Filter by author(s)..."
value={filterAuthors}
onChange={handleAuthorsFilter}
data-testid="filterByAuthors_input"
></input>
</div>
<button onClick={handleResetFilters} data-testid="clearAllFilters_btn">
Expand Down
18 changes: 9 additions & 9 deletions src/components/bookFilters/BookFilters.test.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import { fireEvent, within, screen } from '@testing-library/react'
import { fireEvent, screen } from '@testing-library/react'
import { setup } from '../../setupTests'
import store from '../../redux/store'
import App from '../../App'

const titleFilterStr = 'title'
const authorFilterStr = 'author'
const authorsFilterStr = 'authors'

let filterByTitleInput, filterbyAuthorInput, clearAllFiltersBtn
let filterByTitleInput, filterbyAuthorsInput, clearAllFiltersBtn

describe('BookFilter Component Tests', () => {
beforeEach(() => {
setup(App, store)

// BookFilter elements:
filterByTitleInput = screen.getByTestId('filterByTitle_input')
filterbyAuthorInput = screen.getByTestId('filterByAuthor_input')
filterbyAuthorsInput = screen.getByTestId('filterByAuthors_input')
clearAllFiltersBtn = screen.getByTestId('clearAllFilters_btn')
})

Expand All @@ -29,15 +29,15 @@ describe('BookFilter Component Tests', () => {
})

test('Should render AuthorFilter input', () => {
expect(filterbyAuthorInput).toBeInTheDocument()
expect(filterbyAuthorInput).toBeEnabled()
expect(filterbyAuthorsInput).toBeInTheDocument()
expect(filterbyAuthorsInput).toBeEnabled()
})

test('Should accept inputs to AuthorFilter', () => {
fireEvent.change(filterbyAuthorInput, {
target: { value: authorFilterStr },
fireEvent.change(filterbyAuthorsInput, {
target: { value: authorsFilterStr },
})
expect(filterbyAuthorInput.value).toBe(authorFilterStr)
expect(filterbyAuthorsInput.value).toBe(authorsFilterStr)
})

test('Should render ClearAllFilters btn', () => {
Expand Down
8 changes: 4 additions & 4 deletions src/components/bookList/BookList.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ import {
} from '../../redux/slices/booksSlice'
import {
selectTitleFilter,
selectAuthorFilter,
selectAuthorsFilter,
} from '../../redux/slices/filterSlice'

const BookList = () => {
const books = useSelector(selectBook)
const dispatch = useDispatch()
const titleFilter = useSelector(selectTitleFilter)
const authorFilter = useSelector(selectAuthorFilter)
const authorsFilter = useSelector(selectAuthorsFilter)

const handleDeleteBook = (id) => {
books.forEach((book) => {
Expand All @@ -33,7 +33,7 @@ const BookList = () => {
const filteredBooksArr = books.filter((book) => {
return (
book.title.toLowerCase().includes(titleFilter.toLowerCase()) &&
book.author.toLowerCase().includes(authorFilter.toLowerCase())
book.authors.toLowerCase().includes(authorsFilter.toLowerCase())
)
})

Expand All @@ -53,7 +53,7 @@ const BookList = () => {
<span>{book.title}</span>
<span>
{'" '}
by <strong>{book.author}</strong>
by <strong>{book.authors}</strong>
</span>
</div>
<div className="book-actions">
Expand Down
6 changes: 3 additions & 3 deletions src/components/bookList/BookList.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ describe('BookList Component Tests', () => {
test('Should display a book in the list when there is one book', () => {
// Create the mocked store with one book in the preloaded state
const mockedStore = createStore({
books: [{ title: bookTitleName, author: bookAuthorName, id: bookId }],
books: [{ title: bookTitleName, authors: bookAuthorName, id: bookId }],
})

// Render the BookList component with the updated store
Expand All @@ -42,7 +42,7 @@ describe('BookList Component Tests', () => {

test('Should delete a book from the list', () => {
const mockedStore = createStore({
books: [{ title: bookTitleName, author: bookAuthorName, id: bookId }],
books: [{ title: bookTitleName, authors: bookAuthorName, id: bookId }],
})

const { container } = setup(BookList, mockedStore)
Expand All @@ -61,7 +61,7 @@ describe('BookList Component Tests', () => {
books: [
{
title: bookTitleName,
author: bookAuthorName,
authors: bookAuthorName,
id: bookId,
isFavorite: false,
},
Expand Down
15 changes: 0 additions & 15 deletions src/components/manualBookForm/ManualBookForm.css
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,3 @@
.book-form button:hover {
background-color: #0056b3;
}

.book-form .spinner {
animation: spinner 1s infinite linear;
margin-left: 10px;
}

@keyframes spinner {
from {
transform: rotate(0deg);
}

to {
transform: rotate(360deg);
}
}
36 changes: 23 additions & 13 deletions src/components/manualBookForm/ManualBookForm.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
import { useState } from 'react'
import { useDispatch } from 'react-redux'
import './ManualBookForm.css'
import { addBook } from '../../redux/slices/booksSlice'
import { useDispatch, useSelector } from 'react-redux'
import { addBook, selectBook } from '../../redux/slices/booksSlice'
import createBook from '../../utils/createBook'
import './ManualBookForm.css'

const BookForm = () => {
const dispatch = useDispatch()

const [title, setTitle] = useState('')
const [author, setAuthor] = useState('')
const [authors, setAuthors] = useState('')

const books = useSelector(selectBook)

const handleSubmit = (e) => {
e.preventDefault()
if (title && author) {
dispatch(addBook(createBook({ title, author })))
setTitle('')
setAuthor('')
if (title.trim() && authors.trim()) {
const filteredBooks = books.find(
(existedBook) =>
existedBook.title.toLowerCase() === title.toLowerCase() &&
existedBook.authors.toLowerCase() === authors.toLowerCase()
)
if (!filteredBooks) {
dispatch(addBook(createBook({ title, authors })))
}
}
setTitle('')
setAuthors('')
}

return (
Expand All @@ -38,13 +48,13 @@ const BookForm = () => {
></input>
</div>
<div>
<label>Author: </label>
<label>Author(s): </label>
<input
type="text"
placeholder="Enter book author..."
value={author}
onChange={(e) => setAuthor(e.target.value)}
data-testid="manualBookForm_aurthorInput"
placeholder="Enter book author(s)..."
value={authors}
onChange={(e) => setAuthors(e.target.value)}
data-testid="manualBookForm_authorsInput"
></input>
</div>
<button type="submit" data-testid="manualBookForm_submitBtn">
Expand Down
16 changes: 8 additions & 8 deletions src/components/manualBookForm/ManualBookForm.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ import App from '../../App'
const bookTitleName = 'Test Book Title'
const bookAuthorName = 'Test Book Author'

let titleInput, authorInput, submitBookBtn, bookListComponent
let titleInput, authorsInput, submitBookBtn, bookListComponent

describe('ManualBookForm Component Tests', () => {
beforeEach(() => {
setup(App, store)

// ManualBookForm elements:
titleInput = screen.getByTestId('manualBookForm_titleInput')
authorInput = screen.getByTestId('manualBookForm_aurthorInput')
authorsInput = screen.getByTestId('manualBookForm_authorsInput')
submitBookBtn = screen.getByTestId('manualBookForm_submitBtn')
bookListComponent = screen.getByTestId('bookList_component')
})
Expand All @@ -29,13 +29,13 @@ describe('ManualBookForm Component Tests', () => {
expect(titleInput.value).toBe(bookTitleName)
})

test('Should render Author input', () => {
expect(authorInput).toBeEnabled()
expect(authorInput).toBeInTheDocument()
test('Should render Authors input', () => {
expect(authorsInput).toBeEnabled()
expect(authorsInput).toBeInTheDocument()
})

test('Should accept inputs to Author', () => {
fireEvent.change(authorInput, { target: { value: bookAuthorName } })
expect(authorInput.value).toBe(bookAuthorName)
test('Should accept inputs to Authors', () => {
fireEvent.change(authorsInput, { target: { value: bookAuthorName } })
expect(authorsInput.value).toBe(bookAuthorName)
})
})
24 changes: 15 additions & 9 deletions src/components/modals/SearchBookModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,23 @@ const BookSearchModal = ({ isOpen, onClose, booksFoundList }) => {
const books = useSelector(selectBook)

if (!isOpen) return null

if (!booksFoundList || booksFoundList.length === 0)
return <p>No bookFound details available</p>
return <p>No books found, try again!</p>

const filteredBooksFoundList = booksFoundList.filter(
(bookFound) => !books.some((book) => book.title === bookFound.title)
const filteredAddedBooks = booksFoundList.filter(
(bookFound) =>
!books.some(
(book) =>
book.title.toLowerCase() === bookFound.title.toLowerCase() &&
book.authors &&
bookFound.authors &&
book.authors.toLowerCase() === bookFound.authors.toLowerCase()
)
)

const handleAddBook = (bookFound) => {
dispatch(
addBook(createBook({ title: bookFound.title, author: bookFound.authors }))
)
dispatch(addBook(createBook(bookFound)))
}

return (
Expand All @@ -32,8 +38,8 @@ const BookSearchModal = ({ isOpen, onClose, booksFoundList }) => {
</button>
</div>
<div className="modal-body">
{filteredBooksFoundList.map((bookFound, index) => (
<div key={bookFound.id} className="bookFound-item">
{filteredAddedBooks.map((bookFound, _) => (
<div key={bookFound.bookId} className="bookFound-item">
<h2>{bookFound.title}</h2>
<div>
{bookFound.image ? (
Expand All @@ -48,7 +54,7 @@ const BookSearchModal = ({ isOpen, onClose, booksFoundList }) => {
? 'Authors:'
: 'Author:'}
</strong>{' '}
{bookFound.authors ? bookFound.authors : 'Unknown Author'}
{bookFound.authors ? bookFound.authors : 'Unknown Authors'}
</p>
<p>
<strong>Published Year:</strong> {bookFound.publishedDate}
Expand Down
2 changes: 1 addition & 1 deletion src/components/searchBookForm/SearchBookForm.css
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@

.book-form-api .spinner {
animation: spinner 1s infinite linear;
margin-left: 10px;
padding: 1px 14.5px;
}

@keyframes spinner {
Expand Down
Loading

0 comments on commit a6d020c

Please sign in to comment.