#16 board : article list page - search(with paging, sort)

This commit is contained in:
haerong22
2022-08-17 03:16:11 +09:00
parent 21b9c4c3f0
commit 03f4e83b0b
5 changed files with 73 additions and 12 deletions

View File

@@ -38,6 +38,7 @@ public class ArticleController {
map.addAttribute("articles", articles);
map.addAttribute("paginationBarNumbers", barNumbers);
map.addAttribute("searchTypes", SearchType.values());
return "articles/index";
}

View File

@@ -1,5 +1,19 @@
package com.example.board.domain.type;
import lombok.Getter;
public enum SearchType {
TITLE, CONTENT, ID, NICKNAME, HASHTAG
TITLE("제목"),
CONTENT("본문"),
ID("유저 ID"),
NICKNAME("닉네임"),
HASHTAG("해시태그")
;
@Getter
private final String description;
SearchType(String description) {
this.description = description;
}
}

View File

@@ -23,13 +23,13 @@
<div class="row">
<div class="card card-margin search-form">
<div class="card-body p-0">
<form id="card search-form">
<form action="/articles" method="get">
<div class="row">
<div class="col-12">
<div class="row no-gutters">
<div class="col-lg-3 col-md-3 col-sm-12 p-0">
<label for="search-type" hidden>검색 유형</label>
<select class="form-control" id="search-type">
<select class="form-control" id="search-type" name="searchType">
<option>제목</option>
<option>본문</option>
<option>id</option>
@@ -39,7 +39,7 @@
</div>
<div class="col-lg-8 col-md-6 col-sm-12 p-0">
<label for="search-value" hidden>검색어</label>
<input type="text" placeholder="검색어..." class="form-control" id="search-value" name="search-value">
<input type="text" placeholder="검색어..." class="form-control" id="search-value" name="searchValue">
</div>
<div class="col-lg-1 col-md-3 col-sm-12 p-0">
<button type="submit" class="btn btn-base">

View File

@@ -4,23 +4,42 @@
<attr sel="#footer" th:replace="footer :: footer" />
<attr sel="main" th:object="${articles}">
<attr sel="#search-type" th:remove="all-but-first">
<attr sel="option[0]"
th:each="searchType : ${searchTypes}"
th:value="${searchType.name}"
th:text="${searchType.description}"
th:selected="${param.searchType != null && (param.searchType.toString == searchType.name)}"
/>
</attr>
<attr sel="#search-value" th:value="${param.searchValue}" />
<attr sel="#article-table">
<attr sel="thead/tr">
<attr sel="th.title/a" th:text="'제목'" th:href="@{/articles(
page=${articles.number},
sort='title' + (*{sort.getOrderFor('title')} != null ? (*{sort.getOrderFor('title').direction.name} != 'DESC' ? ',desc' : '') : '')
sort='title' + (*{sort.getOrderFor('title')} != null ? (*{sort.getOrderFor('title').direction.name} != 'DESC' ? ',desc' : '') : ''),
searchType=${param.searchType},
searchValue=${param.searchValue}
)}"/>
<attr sel="th.hashtag/a" th:text="'해시태그'" th:href="@{/articles(
page=${articles.number},
sort='hashtag' + (*{sort.getOrderFor('hashtag')} != null ? (*{sort.getOrderFor('hashtag').direction.name} != 'DESC' ? ',desc' : '') : '')
sort='hashtag' + (*{sort.getOrderFor('hashtag')} != null ? (*{sort.getOrderFor('hashtag').direction.name} != 'DESC' ? ',desc' : '') : ''),
searchType=${param.searchType},
searchValue=${param.searchValue}
)}"/>
<attr sel="th.user-id/a" th:text="'작성자'" th:href="@{/articles(
page=${articles.number},
sort='userAccount.userId' + (*{sort.getOrderFor('userAccount.userId')} != null ? (*{sort.getOrderFor('userAccount.userId').direction.name} != 'DESC' ? ',desc' : '') : '')
sort='userAccount.userId' + (*{sort.getOrderFor('userAccount.userId')} != null ? (*{sort.getOrderFor('userAccount.userId').direction.name} != 'DESC' ? ',desc' : '') : ''),
searchType=${param.searchType},
searchValue=${param.searchValue}
)}"/>
<attr sel="th.created-at/a" th:text="'작성일'" th:href="@{/articles(
page=${articles.number},
sort='createdAt' + (*{sort.getOrderFor('createdAt')} != null ? (*{sort.getOrderFor('createdAt').direction.name} != 'DESC' ? ',desc' : '') : '')
sort='createdAt' + (*{sort.getOrderFor('createdAt')} != null ? (*{sort.getOrderFor('createdAt').direction.name} != 'DESC' ? ',desc' : '') : ''),
searchType=${param.searchType},
searchValue=${param.searchValue}
)}"/>
</attr>
@@ -37,19 +56,19 @@
<attr sel="#pagination">
<attr sel="li[0]/a"
th:text="'previous'"
th:href="@{/articles(page=${articles.number - 1})}"
th:href="@{/articles(page=${articles.number - 1}, searchType=${param.searchType}, searchValue=${param.searchValue})}"
th:class="'page-link' + (${articles.number} <= 0 ? ' disabled' : '')"
/>
<attr sel="li[1]" th:class="page-item" th:each="pageNumber : ${paginationBarNumbers}">
<attr sel="a"
th:text="${pageNumber + 1}"
th:href="@{/articles(page=${pageNumber})}"
th:href="@{/articles(page=${pageNumber}, searchType=${param.searchType}, searchValue=${param.searchValue})}"
th:class="'page-link' + (${pageNumber} == ${articles.number} ? ' disabled' : '')"
/>
</attr>
<attr sel="li[2]/a"
th:text="'next'"
th:href="@{/articles(page=${articles.number + 1})}"
th:href="@{/articles(page=${articles.number + 1}, searchType=${param.searchType}, searchValue=${param.searchValue})}"
th:class="'page-link' + (${articles.number} >= ${articles.totalPages - 1} ? ' disabled' : '')"
/>
</attr>

View File

@@ -1,6 +1,7 @@
package com.example.board.controller;
import com.example.board.config.SecurityConfig;
import com.example.board.domain.type.SearchType;
import com.example.board.dto.ArticleWithCommentsDto;
import com.example.board.dto.UserAccountDto;
import com.example.board.service.ArticleService;
@@ -65,9 +66,35 @@ class ArticleControllerTest {
then(paginationService).should().getPaginationBarNumbers(anyInt(), anyInt());
}
@DisplayName("[view][GET] 게시글 리스트(게시판) 페이지 - 검색어와 함께 호출")
@Test
public void givenSearchKeyword_whenSearchingArticlesView_thenReturnsArticlesView() throws Exception {
// given
SearchType searchType = SearchType.TITLE;
String searchValue = "title";
given(articleService.searchArticles(eq(searchType), eq(searchValue), any(Pageable.class))).willReturn(Page.empty());
given(paginationService.getPaginationBarNumbers(anyInt(), anyInt())).willReturn(List.of(0, 1, 2, 3 ,4));
// when & then
mockMvc.perform(
get("/articles")
.queryParam("searchType", searchType.name())
.queryParam("searchValue", searchValue)
)
.andExpect(status().isOk())
.andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML))
.andExpect(view().name("articles/index"))
.andExpect(model().attributeExists("articles"))
.andExpect(model().attributeExists("searchTypes"))
;
then(articleService).should().searchArticles(eq(searchType), eq(searchValue), any(Pageable.class));
then(paginationService).should().getPaginationBarNumbers(anyInt(), anyInt());
}
@DisplayName("[view][GET] 게시글 리스트 (게시판) 페이지 - 페이징, 정렬 기능")
@Test
void givenPagingAndSortingParams_whenSearchingArticlesPage_thenReturnsArticlesPage() throws Exception {
void givenPagingAndSortingParams_whenSearchingArticlesPage_thenReturnsArticlesView() throws Exception {
// Given
String sortName = "title";
String direction = "desc";