diff --git a/README.md b/README.md index d387c7679..fab1c990c 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,9 @@ - [x] 지금 인기 있는 영화 목록 20개를 보여준다. - [x] 제일 먼저 있는 영화를 배너로 띄운다. - [x] 더보기 버튼을 누르면 20개의 영화 목록을 추가로 불러온다. +- [x] 더보기 버튼을 무한 스크롤로 대체한다. - [x] 불러올 영화 목록이 없으면 더보기 버튼이 사라진다. +- [x] 영화 데이터를 받아오기 전에 스켈레톤 UI를 먼저 띄운다. ## 검색 시 UI @@ -17,18 +19,32 @@ - [x] 해당 검색어에 대한 영화 목록 20개를 보여준다. - [x] 검색어를 검색 제목에 띄운다. - [x] 더보기 버튼을 누르면 20개의 검색과 관련된 영화 목록을 추가로 불러온다. +- [x] 더보기 버튼을 무한 스크롤로 대체한다. - [x] 불러올 영화 목록이 없으면 더보기 버튼이 사라진다. - [x] 검색 결과가 없으면 `검색 결과가 없습니다.` 라는 문구를 띄운다. +- [x] 영화 데이터를 받아오기 전에 스켈레톤 UI를 먼저 띄운다. + +## 모달 UI + +- [x] 영화 항목을 선택하면 모달 UI를 띄운다. +- [x] 선택한 영화의 상세 정보 api를 호출한다. +- [x] 해당 영화에 대한 상세 정보를 보여준다. +- [x] 별점을 누르면 해당 별점에 맞게 나의 점수가 저장된다. +- [x] x 를 누르면 모달이 닫힌다. +- [x] esc 를 누르면 모달이 닫힌다. +- [x] 모달 바깥을 누르면 모달이 닫힌다. +- [x] 영화 데이터를 받아오기 전에 스켈레톤 UI를 먼저 띄운다. ## 예외 처리 - [x] 기본 UI에서 영화 정보를 불러오지 못했다면 `영화 정보를 불러오지 못했습니다. 다시 시도해주세요.` 라는 문구를 띄운다. - [x] 영화 이미지를 불러오지 못했다면 `No Image` 이미지를 띄운다. - [x] 검색 시, 영화 정보를 불러오지 못했다면 `영화 검색 결과를 불러오지 못했습니다. 다시 시도해주세요.` 라는 문구를 띄운다. +- [x] 영화 상세 정보를 불러올 시, 정보를 불러오지 못했다면 에러 UI를 출력한다. ## 공통 -- [x] 영화 데이터를 받아오기 전에 스켈레톤 UI를 먼저 띄운다. +- [ ] pc, 태블릿, 휴대폰에서 사용가능하도록 반응형 UI를 만든다. ## API 명세 diff --git a/cypress/e2e/home.cy.ts b/cypress/e2e/home.cy.ts index fef89e954..10a1281df 100644 --- a/cypress/e2e/home.cy.ts +++ b/cypress/e2e/home.cy.ts @@ -1,104 +1,95 @@ describe("홈 화면 테스트", () => { beforeEach(() => { - cy.intercept("GET", "**/movie/popular?language=ko-KR®ion=KR&page=1").as( - "getMovies", - ); - cy.visit("http://localhost:5173"); - }); + cy.intercept("GET", "**/movie/popular?language=ko-KR®ion=KR&page=1", { + statusCode: 200, + body: { + page: 1, + results: Array.from({ length: 20 }, (_, index) => ({ + id: index + 1, + title: `영화 ${index + 1}`, + poster_path: `/poster-${index + 1}.jpg`, + vote_average: 7.5, + })), + total_pages: 2, + total_results: 40, + }, + }).as("page1"); - it("API 호출 확인", () => { - cy.wait("@getMovies").its("response.body.results").should("be.an", "array"); + cy.visit("http://localhost:5173"); }); - it("배너 안의 요소를 확인", () => { - cy.wait("@getMovies") - .its("response.body.results") - .then((results) => { - cy.get(".title").should("contain", results[0].title); - cy.get(".rate-value").should("contain", results[0].vote_average); - cy.get(".background-container") - .invoke("css", "background-image") - .should("include", results[0].poster_path); - }); + it("초기 진입 시 영화 목록이 렌더된다.", () => { + cy.wait("@page1"); + cy.get(".thumbnail-item").should("have.length", 20); }); - it("리스트 안의 요소를 확인", () => { - cy.wait("@getMovies") - .its("response.body.results") - .then((results) => { - cy.get(".thumbnail").each(($el, index) => { - cy.wrap($el) - .should("have.attr", "src") - .and("include", results[index].poster_path); - }); + it("초기 진입 시 베너가 렌더된다.", () => { + cy.wait("@page1"); - cy.get(".item-rate").each(($el, index) => { - cy.wrap($el).should("contain", results[index].vote_average); - }); - - cy.get(".item-title").each(($el, index) => { - cy.wrap($el).should("contain", results[index].title); - }); - }); + cy.get(".top-rated-movie").should("be.visible"); + cy.get(".title").should("contain", "영화 1"); + cy.get(".rate-value").should("contain", "7.5"); }); - it("더보기 버튼이 있는지 확인", () => { - cy.get(".thumbnail-add-button").should("exist"); + it("배너의 자세히 보기 버튼 클릭 시 모달이 열린다.", () => { + cy.wait("@page1"); + + cy.get(".top-rated-movie .detail").click(); + cy.get(".modal-background").should("have.class", "active"); + cy.get(".modal").should("be.visible"); }); }); -describe("더보기 버튼 테스트", () => { +describe("영화 리스트 기능 테스트", () => { beforeEach(() => { - cy.intercept("GET", "**/movie/popular?language=ko-KR®ion=KR&page=1").as( - "page1", - ); - cy.intercept("GET", "**/movie/popular?language=ko-KR®ion=KR&page=2").as( - "page2", - ); + cy.intercept("GET", "**/movie/popular?language=ko-KR®ion=KR&page=1", { + statusCode: 200, + body: { + page: 1, + results: Array.from({ length: 20 }, (_, index) => ({ + id: index + 1, + title: `영화 ${index + 1}`, + poster_path: `/poster-${index + 1}.jpg`, + vote_average: 7.5, + })), + total_pages: 2, + total_results: 40, + }, + }).as("page1"); + + cy.intercept("GET", "**/movie/popular?language=ko-KR®ion=KR&page=2", { + statusCode: 200, + body: { + page: 2, + results: Array.from({ length: 20 }, (_, index) => ({ + id: index + 21, + title: `영화 ${index + 21}`, + poster_path: `/poster-${index + 21}.jpg`, + vote_average: 8.1, + })), + total_pages: 2, + total_results: 40, + }, + }).as("page2"); cy.visit("http://localhost:5173"); }); - it("버튼 클릭 시 API 호출 확인", () => { - cy.get(".thumbnail-add-button").click(); + it("영화 카드 클릭 시 모달이 열린다.", () => { + cy.wait("@page1"); - cy.wait("@page2").its("response.body.results").should("be.an", "array"); + cy.get(".item").first().click(); + cy.get(".modal-background").should("have.class", "active"); + cy.get(".modal").should("be.visible"); }); - it("리스트 안의 요소를 확인", () => { - let page1Results: Movies[]; - - cy.wait("@page1") - .its("response.body.results") - .then((results1) => { - page1Results = results1; - }); - - cy.get(".thumbnail-add-button").click(); - - cy.wait("@page2") - .its("response.body.results") - .then((results2) => { - const allResults = [...page1Results, ...results2]; - - cy.get(".thumbnail").should("have.length", allResults.length); - - cy.get(".thumbnail").each(($el, index) => { - cy.wrap($el) - .should("have.attr", "src") - .and("include", allResults[index].poster_path); - }); + it("무한 스크롤 시 다음 페이지가 붙는다.", () => { + cy.wait("@page1"); + cy.get(".thumbnail-item").should("have.length", 20); - cy.get(".item-title").each(($el, index) => { - cy.wrap($el).should("contain", allResults[index].title); - }); + cy.get("#sentinel").scrollIntoView(); + cy.wait("@page2"); - cy.get(".item-rate").each(($el, index) => { - cy.wrap($el).should( - "contain", - String(allResults[index].vote_average), - ); - }); - }); + cy.get(".thumbnail-item").should("have.length", 40); }); }); diff --git a/cypress/e2e/modal.cy.ts b/cypress/e2e/modal.cy.ts new file mode 100644 index 000000000..13a6b73ff --- /dev/null +++ b/cypress/e2e/modal.cy.ts @@ -0,0 +1,75 @@ +describe("모달 테스트", () => { + beforeEach(() => { + cy.intercept("GET", "**/movie/popular?language=ko-KR®ion=KR&page=1", { + statusCode: 200, + body: { + page: 1, + results: Array.from({ length: 20 }, (_, index) => ({ + id: index + 1, + title: `영화 ${index + 1}`, + poster_path: `/poster-${index + 1}.jpg`, + vote_average: 7.5, + })), + total_pages: 2, + total_results: 40, + }, + }).as("page1"); + + cy.intercept("GET", "**/movie/1?language=ko-KR®ion=KR", { + statusCode: 200, + body: { + id: 1, + title: "영화 1", + poster_path: "/poster-1.jpg", + vote_average: 7.5, + release_date: "2024-01-01", + genres: [ + { id: 1, name: "액션" }, + { id: 2, name: "모험" }, + ], + overview: "영화 1의 줄거리입니다.", + }, + }).as("movieDetail"); + + cy.visit("http://localhost:5173"); + cy.wait("@page1"); + cy.get(".top-rated-movie .detail").click(); + cy.wait("@movieDetail"); + }); + + it("모달이 열리면 상세정보가 렌더된다.", () => { + cy.get(".modal-background").should("have.class", "active"); + cy.get(".modal").should("be.visible"); + cy.get(".modal .modal-title-section h2").should("contain", "영화 1"); + cy.get(".modal .detail").should("contain", "영화 1의 줄거리입니다."); + }); + + it("x 버튼을 누르면 모달이 닫힌다.", () => { + cy.get(".close-modal").click(); + cy.get(".modal-background").should("not.have.class", "active"); + cy.get(".modal").should("not.be.visible"); + }); + + it("esc 버튼을 누르면 모달이 닫힌다.", () => { + cy.get("body").type("{esc}"); + cy.get(".modal-background").should("not.have.class", "active"); + cy.get(".modal").should("not.be.visible"); + }); + + it("모달 바깥을 누르면 모달이 닫힌다.", () => { + cy.get(".modal-background").click("topLeft"); + cy.get(".modal-background").should("not.have.class", "active"); + cy.get(".modal").should("not.be.visible"); + }); + + it("처음 모달이 열리면 별점 초기값이 보인다.", () => { + cy.get(".myrate-comment").should("contain", "별점을 입력해주세요"); + cy.get(".myrate-score").should("contain", "(0/10)"); + }); + + it("3번째 별을 클릭하면 별점이 반영된다.", () => { + cy.get(".myrate-stars img").eq(2).click(); + cy.get(".myrate-comment").should("contain", "보통이에요"); + cy.get(".myrate-score").should("contain", "(6/10)"); + }); +}); diff --git a/cypress/e2e/search.cy.ts b/cypress/e2e/search.cy.ts index 5763af5d6..e04070615 100644 --- a/cypress/e2e/search.cy.ts +++ b/cypress/e2e/search.cy.ts @@ -1,42 +1,70 @@ describe("검색 데이터 있을 때 테스트", () => { beforeEach("검색어를 입력하고 검색 버튼을 클릭한다.", () => { - cy.intercept("GET", "**/search/movie*").as("searchMovies"); + cy.intercept("GET", "**/search/movie*", { + statusCode: 200, + body: { + page: 1, + results: [ + { + id: 101, + title: "해리 포터와 마법사의 돌", + poster_path: "/harry1.jpg", + vote_average: 7.8, + }, + { + id: 102, + title: "해리 포터와 비밀의 방", + poster_path: "/harry2.jpg", + vote_average: 7.6, + }, + { + id: 103, + title: "해리 포터와 아즈카반의 죄수", + poster_path: "/harry3.jpg", + vote_average: 8.0, + }, + ], + total_pages: 1, + total_results: 3, + }, + }).as("searchMovies"); + cy.visit("http://localhost:5173"); cy.get(".search-input").type("해리포터"); cy.get(".search-button").click(); }); - it("검색 버튼을 클릭하면 API가 호출된다", () => { - cy.wait("@searchMovies") - .its("response.body.results") - .should("be.an", "array"); + it("검색 버튼을 클릭하면 검색 결과가 렌더된다.", () => { + cy.wait("@searchMovies"); + cy.get(".thumbnail-item").should("have.length", 3); + }); + + it("검색한 입력값이 제목에 보인다.", () => { + cy.wait("@searchMovies"); + cy.get(".thumbnail-title").should("contain", "해리포터"); }); - it("리스트 안의 요소를 확인한다.", () => { - cy.wait("@searchMovies") - .its("response.body.results") - .then((results) => { - cy.get(".thumbnail").each(($el, index) => { - cy.wrap($el) - .should("have.attr", "src") - .and("include", results[index].poster_path); - }); - - cy.get(".item-rate").each(($el, index) => { - cy.wrap($el).should("contain", results[index].vote_average); - }); - - cy.get(".item-title").each(($el, index) => { - cy.wrap($el).should("contain", results[index].title); - }); - }); + it("영화 카드 클릭 시 모달이 열린다.", () => { + cy.wait("@searchMovies"); + + cy.get(".item").first().click(); + cy.get(".modal").should("be.visible"); }); }); describe("검색 데이터 없을 때 테스트", () => { it("검색 데이터가 없으면 '검색 결과가 없습니다.'라는 문구를 띄운다.", () => { - cy.intercept("GET", "**/search/movie*").as("searchMovies"); + cy.intercept("GET", "**/search/movie*", { + statusCode: 200, + body: { + page: 1, + results: [], + total_pages: 0, + total_results: 0, + }, + }).as("emptySearchMovies"); + cy.visit("http://localhost:5173"); cy.get(".search-input").type("asdfdas"); @@ -47,16 +75,3 @@ describe("검색 데이터 없을 때 테스트", () => { .and("contain", "검색 결과가 없습니다."); }); }); - -describe("검색 데이터가 전부 출력되었을 때 더보기 버튼 사라지는 테스트", () => { - beforeEach("검색어를 입력하고 검색 버튼을 클릭한다.", () => { - cy.visit("http://localhost:5173"); - - cy.get(".search-input").type("해리포터"); - cy.get(".search-button").click(); - }); - - it("검색 데이터가 마지막 데이터면 더보기 버튼이 사라진다.", () => { - cy.get(".thumbnail-add-button").should("not.be.visible"); - }); -}); diff --git a/index.html b/index.html index bd566ea99..ad74dfa77 100644 --- a/index.html +++ b/index.html @@ -51,7 +51,8 @@
