changePage 함수가 비동기로 페이지를 변경한다.
통과하지 않는 테스트
it("changePage 함수가 비동기로 페이지를 변경한다.", async () => {
// changePage 함수가 Promise를 반환하도록 가정
const secondPageButton = document.getElementById("page-button-2");
await app.changePage(2); // 비동기 함수 호출과 완료를 기다림
expect(app.page).toBe(2); // 페이지 상태가 올바르게 변경되었는지 확인
expect(secondPageButton.classList.contains("current-page")).toBe(true); // 버튼의 클래스가 바뀌었는지 확인
});
이 부분을 수정하기 위해 changePage 구현 코드를 봤다.
//app.js
renderPagination() {
if (document.getElementById("pagination-container") == null) {
this.createPagination();
}
if (!this.articlesData) return;
const pageNumbersWrapper = document.getElementById("page-numbers-wrapper");
pageNumbersWrapper.innerHTML = "";
let startPage = this.page > 3 ? this.page - 3 : 1;
const lastPage =
startPage + 4 <= this.articlesData.totalPages
? startPage + 4
: this.articlesData.totalPages;
if (lastPage == this.articlesData.totalPages) {
startPage = lastPage - 4;
}
for (let curtPage = startPage; curtPage <= lastPage; curtPage++) {
const pageButton = document.createElement("button");
pageButton.innerHTML = curtPage;
pageNumbersWrapper.appendChild(pageButton);
pageButton.id = `page-button-${curtPage}`;
pageButton.addEventListener("click", () => this.changePage(curtPage));
if (curtPage == this.page) {
pageButton.classList.add("current-page");
}
}
}
async changePage(targetPage) {
this.page = targetPage;
await this.getArticles(this.page);
this.renderTable();
this.renderPagination();
}
우선 app.js 코드만 보았을 때에는 문제를 느끼지 못했다.
실제 실행했을 때에도 클릭했을 때 클래스가 잘 바뀐다.
이번 테스트는 테스트 코드의 문제임을 확인했다.
이 테스트가 필요한가? -> O
이 테스트가 돌아가도록 테스트 코드를 수정하기로 했다.
우선 secondPageButton의 class를 한번 확인해보았다.
오잉..? secondPageButton의 클래스가 아예없다.
그럼 secondPageButton 자체는 있는지 확인했다.
button에 id는 있지만 class는 없다.
그렇다면 이건 단순히 async await 한 이후에 DOM 업데이트가 되기 이전에 검사해서 생긴 문제로 판단하였다.
따라서 MutationObserver
를 활용하여 수정했다.
it("changePage 함수가 비동기로 페이지를 변경한다.", async () => {
// changePage 함수가 Promise를 반환하도록 가정
const secondPageButton = document.getElementById("page-button-2");
await app.changePage(2); // 비동기 함수 호출과 완료를 기다림
// DOM 업데이트 를 기다리기 위한 MutationObserver
const observer = new MutationObserver(() => {
if (secondPageButton.classList.contains("current-page")) {
expect(app.page).toBe(2);
observer.disconnect();
}
});
observer.observe(secondPageButton, {
attributes: true,
attributeFilter: ["class"],
});
});
결과는 통과!
이미 선택된 버튼은 클릭할 수 없어야 한다.
it("이미 선택된 버튼은 클릭할 수 없어야 한다.", async () => {
const secondPageButton = document.getElementById("page-button-2");
secondPageButton.click();
secondPageButton.click();
// 페이지 교체 함수는 한번만 실행되었는지 검사
expect(mockChangePage).toHaveBeenCalledTimes(1);
});
이 테스트를 통과하기 위해 js먼저 수정하였다.
const pageButton = document.createElement("button");
pageButton.innerHTML = curtPage;
pageNumbersWrapper.appendChild(pageButton);
pageButton.id = `page-button-${curtPage}`;
pageButton.addEventListener("click", () => this.changePage(curtPage));
if (curtPage == this.page) {
pageButton.classList.add("current-page");
pageButton.disabled = true; // << 추가! 현재 선택된 페이지 버튼일 경우 disabled 추가
}
하지만 결과는 여전히 실패..!
여기서 클릭이 내가 생각한 클릭과 다른가?해서 알아봤다. 하지만 그렇지는 않은 것 같다.
아 혹시 아까 발생했던 그 UI 업데이트와 같은 맥락인가? 해서 MutationObserver를 똑같이 적용해봤다.
it("이미 선택된 버튼은 클릭할 수 없어야 한다.", async () => {
const secondPageButton = document.getElementById("page-button-2");
secondPageButton.click();
const observer = new MutationObserver(() => {
secondPageButton.click();
// 페이지 교체 함수는 한번만 실행되었는지 검사
expect(mockChangePage).toHaveBeenCalledTimes(1);
observer.disconnect();
});
observer.observe(secondPageButton, {
attributes: true,
attributeFilter: ["disabled"],
});
});
통과!
버튼 클릭 후 dom update가 미처 되기도 전에 또 클릭해서 발생한 일이었다.
앞으로 테스트코드 작성시 유의해야겠다.
마지막 페이지일 경우 다음, 마지막 페이지 버튼이 비활성화되어야 한다.
it("마지막 페이지일 경우 다음, 마지막 페이지 버튼이 비활성화 되어야 한다.", async () => {
const nextPageButton = document.getElementById("go-next-page-button");
const lastPageButton = document.getElementById("go-last-page-button");
// 페이지 상태 설정
app.changePage(app.articlesData.totalPages);
// 페이지네이션 렌더링 함수 실행
app.renderPagination();
const observer = new MutationObserver(() => {
//nextPageButton이 disabled 상태인지 확인
expect(nextPageButton.disabled).toBe(true);
// lastPageButton이 disabled 상태인지 확인
expect(lastPageButton.disabled).toBe(true);
observer.disconnect();
});
observer.observe(nextPageButton, {
attributes: true,
attributeFilter: ["disabled"],
});
});
우선 테스트케이스부터 확실하게 수정하고 app.js에도 예외처리를 추가했다.
renderPagination() {
if (document.getElementById("pagination-container") == null) {
this.createPagination();
}
const nextPageButton = document.getElementById("go-next-page-button");
const lastPageButton = document.getElementById("go-last-page-button");
const prevPageButton = document.getElementById("go-prev-page-button");
const firstPageButton = document.getElementById("go-first-page-button");
if (!this.articlesData) return;
const pageNumbersWrapper = document.getElementById("page-numbers-wrapper");
pageNumbersWrapper.innerHTML = "";
let startPage = this.page > 3 ? this.page - 3 : 1;
const lastPage =
startPage + 4 <= this.articlesData.totalPages
? startPage + 4
: this.articlesData.totalPages;
if (lastPage == this.articlesData.totalPages) {
startPage = lastPage - 4;
}
for (let curtPage = startPage; curtPage <= lastPage; curtPage++) {
const pageButton = document.createElement("button");
pageButton.innerHTML = curtPage;
pageNumbersWrapper.appendChild(pageButton);
pageButton.id = `page-button-${curtPage}`;
pageButton.addEventListener("click", () => this.changePage(curtPage));
if (curtPage == this.page) {
pageButton.classList.add("current-page");
}
}
if (this.page == 1) {
prevPageButton.disabled = true;
firstPageButton.disabled = true;
} else {
prevPageButton.disabled = false;
firstPageButton.disabled = false;
}
if (this.page == lastPage) {
nextPageButton.disabled = true;
lastPageButton.disabled = true;
} else {
nextPageButton.disabled = false;
lastPageButton.disabled = false;
}
}
하는김에 마지막이 아닌 첫번째 부분 예외처리도 함께 진행했다.
결과는 통과!
현재 첫번째 페이지일 경우 이전 페이지, 첫번째 페이지 가기 버튼이 비활성화 되어야 한다.
it("현재 첫번째 페이지일 경우 이전 페이지, 첫번째 페이지 가기 버튼이 비활성화 되어야 한다.", () => {
const prevPageButton = document.getElementById("go-prev-page-button");
const firstPageButton = document.getElementById("go-first-page-button");
app.renderPagination();
// 페이지 상태 설정
app.articlesData.totalPages = 2; // 총 페이지 수
app.page = 1; // 현재 페이지가 첫번째 페이지
// 페이지네이션 렌더링 함수 실행
app.renderPagination();
const observer = new MutationObserver(() => {
// prevPageButton이 disabled 상태인지 확인
expect(prevPageButton.disabled).toBe(true);
// firstPageButton이 disabled 상태인지 확인
expect(firstPageButton.disabled).toBe(true);
observer.disconnect();
done();
});
observer.observe(prevPageButton, {
attributes: true,
attributeFilter: ["disabled"],
});
});
이것 역시 아까 구현한 것 덕분에 잘 테스트코드만 수정하니 잘 동작한다.
내일은 아직 테스트코드에도 추가 안한 화살표 버튼으로 페이지 이동하는 테스트 코드와 구현을 해볼 예정이다
오늘의 배운점
테스트 코드 작성시, dom이 바로 바뀌지 않는다는 것을 감안하여 MutationOberver나 setTimeout으로 DOM변경을 기다린 후 테스트하자!
오늘의 결과
'Study > Frontend' 카테고리의 다른 글
완벽한 페이지네이션 바닥부터 개발하기 EP4 : 클로저를 활용한 캐싱 기능 추가 (0) | 2024.06.27 |
---|---|
완벽한 페이지네이션 바닥부터 개발하기 EP3 : 리팩토링 (0) | 2024.06.24 |
완벽한 페이지네이션 바닥부터 개발하기 EP1 : 개발환경 구축하기 (1) | 2024.06.19 |
2024년에는 라이브러리를 위해 어떤 모듈 번들러를 선택해야 할까? (0) | 2024.04.12 |
TDD 테스트 주도 개발 방법론 (1) | 2023.10.28 |