Skip to content

Commit

Permalink
Merge pull request #32 from saneci/feature/book_search_page
Browse files Browse the repository at this point in the history
Book search page
  • Loading branch information
saneci authored Feb 9, 2024
2 parents b916c08 + 8e78217 commit 518d493
Show file tree
Hide file tree
Showing 8 changed files with 116 additions and 15 deletions.
13 changes: 10 additions & 3 deletions src/main/java/ru/saneci/booklibrary/controller/BookController.java
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,11 @@ public String addNewBook(@ModelAttribute("book") @Valid Book book, BindingResult
return REDIRECT_TO_BOOKS;
}

@GetMapping
@GetMapping({"", "/search"})
public String getAllBooks(@RequestParam(value = "page", defaultValue = "0") int page,
@RequestParam(value = "size", defaultValue = "10") int size,
@RequestParam(value = "sort_by_year", defaultValue = "false") boolean sortByYear, Model model) {
@RequestParam(value = "sort_by_year", defaultValue = "false") boolean sortByYear,
@RequestParam(value = "title", defaultValue = "") String title, Model model) {
log.debug("getAllBooks: start processing");
Page<Book> bookPage = bookService.findAll(page, size, sortByYear);

Expand All @@ -85,7 +86,13 @@ public String getAllBooks(@RequestParam(value = "page", defaultValue = "0") int
model.addAttribute("pageNumbers", TemplateUtil.generatePageNumbers(bookPage));
}

model.addAttribute("bookList", bookPage.getContent());
if (title.isEmpty()) {
model.addAttribute("bookList", bookPage.getContent());
} else {
model.addAttribute("bookList", bookService.findAll(title));
model.addAttribute("bookTitle", title);
}

model.addAttribute("size", size);
model.addAttribute("sortByYear", sortByYear);
log.debug("getAllBooks: finish processing");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import ru.saneci.booklibrary.domain.Book;
import ru.saneci.booklibrary.domain.Person;

import java.util.List;
import java.util.Optional;

@Repository
Expand All @@ -18,4 +19,6 @@ public interface BookRepository extends JpaRepository<Book, Long> {
@Modifying
@Query("update Book b set b.person = ?1 where b.id = ?2")
int updatePersonId(Person person, Long bookId);

List<Book> findAllByTitleContainsIgnoreCase(String title);
}
5 changes: 5 additions & 0 deletions src/main/java/ru/saneci/booklibrary/service/BookService.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import ru.saneci.booklibrary.repository.BookRepository;
import ru.saneci.booklibrary.repository.PersonRepository;

import java.util.List;
import java.util.Optional;

@Service
Expand All @@ -27,6 +28,10 @@ public BookService(BookRepository bookRepository, PersonRepository personReposit
this.personRepository = personRepository;
}

public List<Book> findAll(String title) {
return bookRepository.findAllByTitleContainsIgnoreCase(title);
}

public Page<Book> findAll(int page, int size, boolean sortByYear) {
return sortByYear
? bookRepository.findAll(PageRequest.of(page, size, Sort.by(Direction.ASC, "publishYear")))
Expand Down
35 changes: 33 additions & 2 deletions src/main/webapp/WEB-INF/views/books/list.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,39 @@
</ol>
</section>
<section id="buttonBlock" class="mb-3 gap-2 d-lg-flex justify-content-md-between">
<div th:replace="~{fragments/pagination :: itemsPerPage(@{/books})}"></div>
<a class="btn btn-outline-success" role="button" href="/books/new">Добавить новую книгу</a>
<svg xmlns="http://www.w3.org/2000/svg" class="d-none">
<symbol th:replace="~{fragments/svg :: #search}"></symbol>
<symbol th:replace="~{fragments/svg :: #plus}"></symbol>
</svg>
<div class="row justify-content-lg-start">
<div th:replace="~{fragments/pagination :: itemsPerPage(@{/books})}"></div>
</div>
<div class="row justify-content-lg-end">
<div class="col-auto pe-1">
<form class="row g-2" th:method="GET" th:action="@{/books/search(title=${bookTitle})}">
<div class="col-auto">
<label for="bookSearch" class="visually-hidden">Book name</label>
<input id="bookSearch" class="form-control" type="search" placeholder="Название книги" name="title"
th:value="${bookTitle}"/>
</div>
<div class="col-auto">
<button class="btn btn-outline-secondary form-control" type="submit">
<svg class="bi" width="16" height="16">
<use xlink:href="#search"></use>
</svg>
</button>
</div>
</form>
</div>
<div class="col-auto ps-1">
<a class="btn btn-outline-success" role="button" href="/books/new" data-bs-toggle="tooltip"
data-bs-title="Нажмите для добавления новой книги">
<svg class="bi" width="16" height="16">
<use xlink:href="#plus"></use>
</svg>
</a>
</div>
</div>
</section>
<section id="bookList" class="table-responsive">
<svg xmlns="http://www.w3.org/2000/svg" class="d-none">
Expand Down
1 change: 1 addition & 0 deletions src/main/webapp/WEB-INF/views/fragments/main.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"
integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL"
crossorigin="anonymous"></script>
<script src="../../../resources/js/main.js" th:href="@{/resources/js/main.js}"></script>
</body>
</html>
6 changes: 6 additions & 0 deletions src/main/webapp/WEB-INF/views/fragments/svg.html
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@
<symbol id="sortDesc" viewBox="0 0 16 16">
<path d="M3.5 2.5a.5.5 0 0 0-1 0v8.793l-1.146-1.147a.5.5 0 0 0-.708.708l2 1.999.007.007a.497.497 0 0 0 .7-.006l2-2a.5.5 0 0 0-.707-.708L3.5 11.293zm3.5 1a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5M7.5 6a.5.5 0 0 0 0 1h5a.5.5 0 0 0 0-1zm0 3a.5.5 0 0 0 0 1h3a.5.5 0 0 0 0-1zm0 3a.5.5 0 0 0 0 1h1a.5.5 0 0 0 0-1z"/>
</symbol>
<symbol id="search" viewBox="0 0 16 16">
<path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001q.044.06.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1 1 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0"/>
</symbol>
<symbol id="plus" viewBox="0 1 16 16">
<path d="M8 2a.5.5 0 0 1 .5.5v5h5a.5.5 0 0 1 0 1h-5v5a.5.5 0 0 1-1 0v-5h-5a.5.5 0 0 1 0-1h5v-5A.5.5 0 0 1 8 2"/>
</symbol>
</svg>
</body>
</html>
15 changes: 15 additions & 0 deletions src/main/webapp/resources/js/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*!
* JavaScript for Bootstrap's docs (https://getbootstrap.com/)
* Copyright 2011-2023 The Bootstrap Authors
* Licensed under the Creative Commons Attribution 3.0 Unported License.
* For details, see https://creativecommons.org/licenses/by/3.0/.
*/

(() => {
'use strict'

document.querySelectorAll('[data-bs-toggle="tooltip"]')
.forEach(tooltip => {
new bootstrap.Tooltip(tooltip)
})
})()
Original file line number Diff line number Diff line change
Expand Up @@ -41,20 +41,26 @@ void whenAddNewBook_thenRedirectToBookAllEndpoint() throws Exception {
.andExpect(redirectedUrl("/books"));
}

// -------------------------------------------------- getAllBooks --------------------------------------------------

@Test
void whenGetAllBooks_thenBookListViewShouldContainRightLayout() throws Exception {
mockMvc.perform(get("/books"))
.andExpect(status().isOk())
.andExpect(view().name("books/list"))
.andExpect(xpath("/html/head/title").string("Список книг - SB Library"))
.andExpect(xpath("//*[@id='breadcrumb']/nav/ol/li[1]/a/span").string("Главная"))
.andExpect(xpath("//*[@id='breadcrumb']/nav/ol/li[2]").string("Список книг"))
.andExpect(xpath("//*[@id='buttonBlock']/div/ul/li[1]/span").string("Строк в таблице"))
.andExpect(xpath("//*[@id='buttonBlock']/div/ul/li[2]/a").number(5d))
.andExpect(xpath("//*[@id='buttonBlock']/div/ul/li[3]/a").number(10d))
.andExpect(xpath("//*[@id='buttonBlock']/div/ul/li[4]/a").number(20d))
.andExpect(xpath("//*[@id='buttonBlock']/a").string("Добавить новую книгу"))
.andExpect(xpath("//*[@id='bookList']/table").exists());
.andExpectAll(
view().name("books/list"),
xpath("/html/head/title").string("Список книг - SB Library"),
xpath("//*[@id='breadcrumb']/nav/ol/li[1]/a/span").string("Главная"),
xpath("//*[@id='breadcrumb']/nav/ol/li[2]").string("Список книг"),
xpath("//*[@id='buttonBlock']/div[1]/div/ul/li[1]/span").string("Строк в таблице"),
xpath("//*[@id='buttonBlock']/div[1]/div/ul/li[2]/a").number(5d),
xpath("//*[@id='buttonBlock']/div[1]/div/ul/li[3]/a").number(10d),
xpath("//*[@id='buttonBlock']/div[1]/div/ul/li[4]/a").number(20d),
xpath("//*[@id='buttonBlock']/div[2]/div[1]/form/div[1]/input").exists(),
xpath("//*[@id='buttonBlock']/div[2]/div[1]/form/div[2]/button/svg/use/@href").string("#search"),
xpath("//*[@id='buttonBlock']/div[2]/div[2]/a/svg/use/@href").string("#plus"),
xpath("//*[@id='bookList']/table").exists()
);
}

@Test
Expand Down Expand Up @@ -161,6 +167,23 @@ void whenGetAllBooksWithSortByYearParamEqualsToFalse_thenReturnRowsSortedByYearI
);
}

@Test
@Sql(scripts = "/sql/book_controller/create-twelve-books.sql")
void whenGetAllBooksWithTitleParam_thenBookListViewShouldContainMatchingEntities() throws Exception {
mockMvc.perform(get("/books/search").queryParam("title", "1"))
.andExpect(status().isOk())
.andExpectAll(
xpath("//*[@id='buttonBlock']/div[2]/div[1]/form/@action").string("/books/search?title=1"),
xpath("//*[@id='buttonBlock']/div[2]/div[1]/form/div[1]/input/@value").string("1"),
xpath("//*[@id='bookList']/table/tbody/tr[1]/td[1]").string("Test Book 1"),
xpath("//*[@id='bookList']/table/tbody/tr[2]/td[1]").string("Test Book 10"),
xpath("//*[@id='bookList']/table/tbody/tr[3]/td[1]").string("Test Book 11"),
xpath("//*[@id='bookList']/table/tbody/tr[4]/td[1]").string("Test Book 12")
);
}

// -------------------------------------------------- getBookById --------------------------------------------------

@Test
void whenGetBookById_thenBookItemViewShouldContainRightLayout() throws Exception {
mockMvc.perform(get("/books/{id}", 1))
Expand All @@ -174,6 +197,8 @@ void whenGetBookById_thenBookItemViewShouldContainRightLayout() throws Exception
.andExpect(xpath("//*[@id='bookInfo']/div[@id='deleteConfirmationModal']").exists());
}

// --------------------------------------------- assignBookToTheReader ---------------------------------------------

@Test
@Sql(scripts = "/sql/book_controller/create-book-with-linked-person.sql")
void whenAssignBookToTheReader_thenRedirectToGetBookByIdEndpoint() throws Exception {
Expand All @@ -194,6 +219,8 @@ void whenAssignBookToTheReader_thenBookShouldBeLinkedToThePerson() throws Except
.string("Test Book, Test Author, 1234"));
}

// ------------------------------------------- releaseBookFromTheReader --------------------------------------------

@Test
@Sql(scripts = "/sql/book_controller/create-book-with-linked-person.sql")
void whenReleaseBookFromTheReader_thenRedirectToGetBookByIdEndpoint() throws Exception {
Expand All @@ -214,6 +241,8 @@ void whenReleaseBookFromTheReader_thenBookShouldBeUnlinkedToThePerson() throws E
.string("Человек пока не взял не одной книги"));
}

// ---------------------------------------------- getBookUpdatingView ----------------------------------------------

@Test
void whenGetBookUpdatingView_thenUpdateViewShouldContainRightLayout() throws Exception {
mockMvc.perform(get("/books/{id}/edit", 1))
Expand All @@ -228,6 +257,8 @@ void whenGetBookUpdatingView_thenUpdateViewShouldContainRightLayout() throws Exc
.andExpect(xpath("//*[@id='mainContent']/div/form/div").nodeCount(3));
}

// --------------------------------------------------- updateBook --------------------------------------------------

@Test
@Sql(scripts = "/sql/book_controller/create-book-with-linked-person.sql")
void whenUpdateBook_thenRedirectToBookAllEndpoint() throws Exception {
Expand Down Expand Up @@ -258,6 +289,8 @@ void whenUpdateBook_thenBookShouldBeUpdated() throws Exception {
.string("Test Book Updated, Test Author Updated, 2000"));
}

// --------------------------------------------------- deleteBook --------------------------------------------------

@Test
void whenDeleteBook_thenRedirectToBookAllEndpoint() throws Exception {
mockMvc.perform(delete("/books/{id}", 1))
Expand Down

0 comments on commit 518d493

Please sign in to comment.