-
Notifications
You must be signed in to change notification settings - Fork 88
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[1단계 - 영화 목록 불러오기] 윤생(이윤성) 미션 제출합니다. (#6)
* docs: 기능 요구 사항 작성 Co-authored-by: 2yunseong <2yunseong@users.noreply.github.com> * feat: 컴포넌트 분리 구현 * feat: 이미지 밑 스타일 임포트 * feat: 인기 영화 데이터 fetch 후 render 구현 * feat: 인기 영화 더보기 기능 구현 * feat: 영화 검색 기능 구현 * feat: API 키 ignore * refactor: 컴포넌트 폴더 분리 * feat: 영화 API 페이지 상태관리 * refactor: 중복되는 api 호출 로직 모듈화 * refactor: 중복되는 렌더링 로직 모듈화 * refactor: initialRender 메서드 변경 * feat: 에러에 따른 렌더링 구현 * feat: 에러 컴포넌트 구현 * test: 영화 리뷰 미션 e2e 테스트 작성 Co-authored-by: 2yunseong <2yunseong@users.noreply.github.com> * refactor: Movie List Deprecated 된 코드 제거 * refactor: console.log 제거 * refactor: MoreButton 컴포넌트 코드 가독성 개선 * refactor: 코드 상수화 * refactor: event 위임 로직 if문 switch문으로 변경 * refactor: Movie list HTML 구조 변경 * refactor: header HTML 구조 변경 * refactor: Movie List Title HTML 구조 변경 * refactor: App 내의 불필요한 document 선언 제거 * refactor: Skeleton 불필요한 li태그 제거 * refactor: App 내부 변수명 통일 * refactor: App 불필요한 필드 제거 * refactor: List Method 네이밍 변경 * refactor: API 처리 로직 구조 변경 * feat: 로고 클릭시 input value 초기화 * refactor: url 생성 책임 분리 * refactor: App 더보기 버튼 액션 변경 메소드 분리 * refactor: App 컴포넌트 코드 가독성 개선 * refactor: TITLE 상수화 객체 타입 단언 * refactor: 컴포넌트 전체적 가독성 개선 * refactor: constants 디렉토리 변경 * docs: README.md 작성 * refactor: 사용되지 않는 매개변수 선언 제거 * fix: html 마크업 수정 * refactor: cypress 불필요한 파일 제거 * refactor: 웹 접근성 개선 * refactor: 마크업 구조 변경에 따른 테스트 수정 * chore: 기술에 대한 정보를 제공하는 주석 추가 * refactor: early return / 모호한 네이밍 수정 * test: 검색창 공백 입력 시/ 입력이 비어있을 시 테스트 추가 * fix: 공백을 입력 시 검색을 하는 오류 수정 * test: 테스트 명 수정 * feat: 오류 메세지 페이지에 CTA 버튼 추가 * refactor: fetch 재사용화를 위한 책임 분리 * fix: totalPage 상태값 갱신하지 않은 오류 해결 * test: 엘리먼트 선택자 변경 * refactor: custom component 네이밍 변경 --------- Co-authored-by: SangwonKang <72087183+ksone02@users.noreply.github.com> Co-authored-by: 2yunseong <2yunseong@users.noreply.github.com>
- Loading branch information
1 parent
965a557
commit 3f9c0b0
Showing
27 changed files
with
1,219 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,3 @@ | ||
node_modules | ||
|
||
src/constants/key.js |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,45 @@ | ||
# javascript-movie-review | ||
|
||
FE 5기 레벨1 영화관 미션 | ||
|
||
## 페어프로그래밍 룰 | ||
|
||
1. 드라이버가 주도 네비게이터가 조언 | ||
2. 드라이버 변경시간 30분 | ||
3. 네비게이터는 집중을 잃지 않는다. | ||
4. 2시간마다 15분 휴식 | ||
5. 모르는 부분 / 논의가 필요한 부분 나오면 타이머 중단 | ||
6. 화장실이나 급한 사정은 유도리 있게 | ||
|
||
## 컴포넌트를 분리하려는 목적 | ||
|
||
1. 재사용성이 좋아진다. | ||
- 문제 요구사항을 분석했을 때, 재사용 되는 요소들이 많았다고 생각이 들었다.(더보기 버튼, 영화 목록 아이템, 페이지 타이틀) | ||
- 또한, 앱의 기능이 확장 되었을 때, 재사용 하려 만든 요소를 이용해 쉽게 기능을 확장할 수 있다고 판단이 들었다. | ||
2. 가독성이 좋아진다. | ||
- 파일을 컴포넌트 단위로 나누고, 컴포넌트를 UI, 기능별로 나누어 한 파일에 한 UI, 기능 단위의 로직만 들어가게 되어 개발자 입장에서 보기 좋은 프로젝트가 된다. | ||
3. 독립성이 향상된다. | ||
- 컴포넌트로 나누게 되면 각각의 다른 컴포넌트(기능)들의 간섭을 덜 받게 되어 로직의 안정성이 높아진다. | ||
|
||
## 개발 환경 | ||
|
||
- node v16.14.0 | ||
- npm 8.3.1 | ||
|
||
## API Key 설정 방법 | ||
|
||
- API 키를 숨기기 위해 레퍼지토리에서 숨김 처리 해두었습니다. | ||
|
||
1. src/constants 디렉토리 하위에 key.js 생성 | ||
2. 다음의 내용 입력 | ||
|
||
```javascript | ||
export const API_KEY = "사용할 API 키"; | ||
``` | ||
|
||
## e2e 테스트 방법 | ||
|
||
1. npm run start로 local server 구동(포트 8081 고정) | ||
2. npm run test-e2e로 cypress 구동 | ||
3. E2E-Testing -> Start E2E Testing in Chrome 클릭 | ||
4. cypress 창이 뜨면 movie-e2e.cy.ts 클릭 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { defineConfig } from "cypress"; | ||
|
||
export default defineConfig({ | ||
e2e: { | ||
setupNodeEvents(on, config) { | ||
// implement node event listeners here | ||
}, | ||
supportFile: false, | ||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
describe("인기 영화 목록 페이지", () => { | ||
beforeEach(() => { | ||
cy.visit("http://localhost:8081/"); | ||
cy.viewport(1920, 1080); | ||
}); | ||
|
||
it("초기 접속 시에 인기 영화목록 상위 20개가 화면에 출력된다.", () => { | ||
cy.get("movie-list-page").find("movie-item").should("exist"); | ||
cy.get("movie-list-page").children().should("have.length", 20); | ||
}); | ||
|
||
it("헤더 로고 클릭 시 처음 인기 영화목록이 출력된다.", () => { | ||
cy.get("img[data-action='popular']").click(); | ||
|
||
cy.get("movie-list-page").find("movie-item").should("exist"); | ||
cy.get("movie-list-page").should("exist"); | ||
}); | ||
}); | ||
|
||
describe("인기 영화 목록 더보기", () => { | ||
it("사용자가 마지막 페이지에 도달했을 때, 더보기 버튼이 화면에서 보이지 않아야 한다.", () => { | ||
cy.intercept( | ||
{ | ||
method: "GET", | ||
url: /^https:\/\/api.themoviedb.org\/3\/movie\/popular*/, | ||
}, | ||
{ fixture: "movie-popular.json" } | ||
).as("getPopularMovies"); | ||
|
||
cy.visit("http://localhost:8081/"); | ||
cy.viewport(1920, 1080); | ||
|
||
cy.wait("@getPopularMovies").then(() => { | ||
cy.get("button[data-action='more_popular']").should("not.be.visible"); | ||
}); | ||
}); | ||
it("인기 영화 페이지에서 더보기 버튼 클릭시 영화 목록이 추가된다.", () => { | ||
cy.visit("http://localhost:8081/"); | ||
cy.viewport(1920, 1080); | ||
|
||
cy.get("button[data-action='more_popular']").click(); | ||
|
||
cy.get("movie-list-page").find("movie-item").should("exist"); | ||
cy.get("movie-list-page").should("have.length", 2); | ||
}); | ||
}); | ||
|
||
describe("검색 목록 확인", () => { | ||
it("검색창에 해리포터를 입력하고 검색버튼 클릭시, 제목에 '해리포터'가 포함된 영화 목록이 화면에 나온다.", () => { | ||
cy.visit("http://localhost:8081/"); | ||
cy.viewport(1920, 1080); | ||
|
||
cy.get(".search-box").find("input").type("해리포터"); | ||
cy.get("button[data-action='search']") | ||
.click() | ||
.then(() => { | ||
cy.get(".item-title").each(() => { | ||
cy.get("h3.item-title").contains(/해\s*리\s*포\s*터/); | ||
}); | ||
}); | ||
}); | ||
|
||
it("검색창에 해리포터를 입력하고 엔터키를 누르면, 제목에 '해리포터'가 포함된 영화 목록이 화면에 나타난다.", () => { | ||
cy.visit("http://localhost:8081/"); | ||
cy.viewport(1920, 1080); | ||
|
||
cy.get(".search-box") | ||
.find("input") | ||
.type("해리포터{enter}") | ||
.then(() => { | ||
cy.get(".item-title").each(() => { | ||
cy.get("h3.item-title").contains(/해\s*리\s*포\s*터/); | ||
}); | ||
}); | ||
}); | ||
|
||
it("검색창에 검색결과가 없는 검색어를 입력한 후 검색하는데, 결과가 존재하지 않을 시 검색결과 없음 페이지가 화면에 나타난다.", () => { | ||
cy.visit("http://localhost:8081/"); | ||
cy.viewport(1920, 1080); | ||
|
||
cy.get(".search-box") | ||
.find("input") | ||
.type("sdmnfbikrdnfivjdnfodmfhfidff{enter}") | ||
.then(() => { | ||
cy.get(".no-result-title").contains("검색 결과를 찾지 못하였습니다."); | ||
}); | ||
}); | ||
|
||
it("검색창에 입력을 하지 않고 엔터키를 누르면 사용자에게 검색어가 없다는 경고창이 화면에 나온다.", () => { | ||
cy.visit("http://localhost:8081/"); | ||
cy.viewport(1920, 1080); | ||
|
||
const stub = cy.stub(); | ||
|
||
cy.on("window:alert", stub); | ||
|
||
cy.get(".search-box") | ||
.find("input") | ||
.type("{enter}") | ||
.then(() => { | ||
expect(stub.getCall(0)).to.be.calledWith("검색어를 입력해주세요."); | ||
}); | ||
}); | ||
|
||
it("검색창에 입력을 하지 않고 검색버튼을 클릭하면 사용자에게 검색어가 없다는 경고창이 화면에 나온다.", () => { | ||
cy.visit("http://localhost:8081/"); | ||
cy.viewport(1920, 1080); | ||
|
||
const stub = cy.stub(); | ||
|
||
cy.on("window:alert", stub); | ||
|
||
cy.get(".search-button") | ||
.click() | ||
.then(() => { | ||
expect(stub.getCall(0)).to.be.calledWith("검색어를 입력해주세요."); | ||
}); | ||
}); | ||
|
||
it("검색창에 공백만을 입력하고 검색버튼을 클릭하면 사용자에게 검색어가 없다는 경고창이 화면에 나온다.", () => { | ||
cy.visit("http://localhost:8081/"); | ||
cy.viewport(1920, 1080); | ||
|
||
const stub = cy.stub(); | ||
cy.on("window:alert", stub); | ||
|
||
cy.get(".search-box").find("input").type(" "); | ||
cy.get(".search-button") | ||
.click() | ||
.then(() => { | ||
expect(stub.getCall(0)).to.be.calledWith("검색어를 입력해주세요."); | ||
}); | ||
}); | ||
|
||
it("검색창에 공백만을 입력하고 엔터키를 누르면 사용자에게 검색어가 없다는 경고창이 화면에 나온다.", () => { | ||
cy.visit("http://localhost:8081/"); | ||
cy.viewport(1920, 1080); | ||
|
||
const stub = cy.stub(); | ||
cy.on("window:alert", stub); | ||
|
||
cy.get(".search-box") | ||
.find("input") | ||
.type(" {enter}") | ||
.then(() => { | ||
expect(stub.getCall(0)).to.be.calledWith("검색어를 입력해주세요."); | ||
}); | ||
}); | ||
}); | ||
|
||
describe("검색목록 더보기 확인", () => { | ||
it("검색결과 페이지에서 더보기 버튼 클릭시 검색목록이 추가된다. ", () => { | ||
cy.visit("http://localhost:8081/"); | ||
cy.viewport(1920, 1080); | ||
|
||
cy.get(".search-box") | ||
.find("input") | ||
.type("전쟁{enter}") | ||
.then(() => { | ||
cy.get("button[data-action='more_search']").click(); | ||
|
||
cy.get("movie-list-page").find("movie-item").should("exist"); | ||
cy.get("movie-list-page").should("have.length", 2); | ||
}) | ||
.then(() => { | ||
cy.get("h3.item-title").each(() => { | ||
cy.get("h3.item-title").contains(/전\s*쟁/); | ||
}); | ||
}); | ||
}); | ||
}); | ||
|
||
describe("네트워크 에러 확인", () => { | ||
it("네트워크 접속 오류 시 에러 페이지가 화면에 나타난다.", () => { | ||
cy.intercept( | ||
{ | ||
method: "GET", | ||
url: /^https:\/\/api.themoviedb.org\/3\/movie\/popular*/, | ||
}, | ||
{ statusCode: 401 } | ||
).as("getErrorData"); | ||
|
||
cy.visit("http://localhost:8081/"); | ||
cy.viewport(1920, 1080); | ||
|
||
cy.wait("@getErrorData").then(() => { | ||
cy.get(".error-title").contains("오류"); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.