Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[1단계 - 영화 목록 불러오기] 야미(이다인) 미션 제출합니다. #5

Merged
merged 35 commits into from
Mar 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
5db1c55
docs: 기능 목록과 요구 사항 작성
feb-dain Mar 14, 2023
281a479
chore: 폴더 정리 및 프리티어 적용
feb-dain Mar 14, 2023
fbc7b98
fix: 이미지 업로드 문제 해결
feb-dain Mar 14, 2023
82760c2
feat: 헤더 컴포넌트화
feb-dain Mar 14, 2023
904e6c0
feat: 영화 목록 컴포넌트화
feb-dain Mar 14, 2023
509f9f0
style: export 방법 변경
feb-dain Mar 14, 2023
cbd6e3d
feat: 영화 아이템 컴포넌트화
feb-dain Mar 14, 2023
440d6fe
feat: API 요청 및 API 키 은닉화
feb-dain Mar 14, 2023
c866b88
feat: API에서 가져온 정보 렌더링
feb-dain Mar 14, 2023
3947f75
feat: 더보기 클릭시 영화 목록 추가 및 렌더링
feb-dain Mar 14, 2023
f77159e
refactor: 영화 목록 관련 함수 분리
feb-dain Mar 14, 2023
8cec080
feat: 인기순으로 영화 정렬
feb-dain Mar 15, 2023
c936bdd
fix: 이미지 불러오기 문제 해결
feb-dain Mar 15, 2023
7197252
feat: 검색 기능 구현 및 정렬 기능 삭제
feb-dain Mar 15, 2023
cfa5193
feat: 목록 이름 대신 검색 결과 표시하는 기능 추가
feb-dain Mar 15, 2023
658cc76
feat: 별점을 소수점 1자리까지만 표시는
feb-dain Mar 15, 2023
b362bed
feat: 로고 클릭시 처음 화면으로 돌아가는 기능 구현
feb-dain Mar 15, 2023
ae6383e
feat: 요청한 API 상태 코드에 따라 오류 메시지 출력
feb-dain Mar 15, 2023
17a4af6
feat: 검색 결과가 없을 경우 결과 없음 안내 페이지 렌더링
feb-dain Mar 15, 2023
eb55b7c
fix: 페이지 초기화 문제 해결
feb-dain Mar 15, 2023
1efa111
refactor: any 타입 제거
feb-dain Mar 15, 2023
e0a59bb
refactor: movie API와 movieStore 분리
feb-dain Mar 16, 2023
95c26c3
feat: 스켈레톤 로딩 기능 추가 및 빈 이미지일 경우 대체 이미지 추가
feb-dain Mar 16, 2023
beea5fc
test: e2e 테스트 진행
feb-dain Mar 16, 2023
f639cfd
docs: README 작성
feb-dain Mar 16, 2023
2e96a14
chore: 필요 없는 파일 삭제
feb-dain Mar 16, 2023
3bc416d
comment: 불필요한 주석 삭제
feb-dain Mar 19, 2023
d85a393
refactor: `I- prefix` 삭제 및 타입 선언 방식 변경
feb-dain Mar 19, 2023
cf49660
style: css 수정 및 a 태그 삭제
feb-dain Mar 19, 2023
ae1e790
remove: 이벤트 리스너 유틸 삭제"
feb-dain Mar 19, 2023
58c0c35
refactor: movieApi 필드 `camelCase`로 변경
feb-dain Mar 19, 2023
46d29a8
refactor: `new URL`, `new URLSearchParams` 사용 및 상수화
feb-dain Mar 19, 2023
4834c40
refactor: 함수명 변경
feb-dain Mar 19, 2023
623f7d6
refactor: 역할 분리 및 에러 핸들러 개선
feb-dain Mar 20, 2023
cd080ed
test: 테스트 코드 수정 및 추가
feb-dain Mar 20, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
node_modules
.env
dist
107 changes: 105 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,106 @@
# javascript-movie-review
# 🎬

FE 5기 레벨1 영화관 미션
## FE 5기 레벨1 영화관 미션

![스크린샷(91)](https://user-images.githubusercontent.com/108778921/225553741-43297097-5333-4167-8364-e336aed2e832.png)

<br>

### 🧑‍🤝‍🧑 페어 (페어 프로그래밍으로 개발)

<table>
<tr>
<td align="center" width="120px">
<a href="https://github.com/feb-dain" target="_blank">
<img src="https://avatars.githubusercontent.com/u/108778921?v=4" alt="야미(이다인) 프로필" />
</a>
</td>
<td align="center" width="120px">
<a href="https://github.com/gabrielyoon7" target="_blank">
<img src="https://avatars.githubusercontent.com/u/69189073?v=4" alt="가브리엘(윤주현) 프로필" />
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/feb-dain" target="_blank">
야미(이다인)
</a>
</td>
<td align="center">
<a href="https://github.com/gabrielyoon7" target="_blank">
가브리엘(윤주현)
</a>
</td>
</tr>
</table>

<br>

### 📍 학습 목표

- 이번 미션의 주요 목표는 웹 프론트엔드에서의 비동기에 대해 이해하고,
- API 통신을 처리할 때 고려해야 하는 다양한 문제를 직접 경험해보면서 해결 방법을 고민해보는 것입니다
- API 연동을 위한 테스트 환경 경험
- 실제 동작하는 API를 통한 비동기 통신
- UX 경험 개선을 위한 더 보기(페이징) 구현
- 역할과 책임에 따라 관심사를 분리할 수 있는 마지막 기회

<br>

### 📝 실행 방법

- <a href="https://feb-dain.github.io/javascript-/javascript-movie-review">앱 바로 실행하기</a>

- 터미널에서 npm 설치(`npm install`) 후 `npm run start` 커맨드로 앱을 실행할 수 있다.

<br>
<br>

### 🎯 기능 목록

- 🎬 영화 목록 조회 (인기순)
- 영화 목록의 1페이지를 불러오며 더보기 버튼을 누르면 그 다음의 영화 목록을 불러 올 수 있다.
- 단, 페이지 끝에 도달한 경우에는 더보기 버튼을 화면에 출력하지 않는다.
- ⚠️ 인기순은 TMDB에서 제공하는 API의 속성 이름을 나타내는 것이므로 별도로 받은 데이터를 정렬하지 않습니다.
- figma 시안과는 달리 20개씩 영화 목록을 보여주면 됩니다.
- 영화 목록 아이템에 대한 Skeleton UI를 구현한다.
- Skeleton UI는 템플릿으로 제공되는 파일 이외로 자유롭게 구현할 수 있다.

<br>

- 🔎 검색
- 영화 검색 API를 이용하여 내가 보고 싶은 영화를 검색할 수 있다.
- 엔터키를 눌러 검색할 수 있다
- 검색 버튼을 클릭하여 검색할 수 있다
- 영화 목록 조회와 같이 검색한 결과에 한해 정보를 보여주는 화면의 요구사항은 동일하다

<br>

- ⚠️ 오류
- 오류가 발생하는 경우에는 사용자를 위한 오류 메시지를 띄워 준다.
- 어떤 오류를 대응해야 하고, 어떤 UI로 보여줄 것인지는 자율적으로 결정한다.

<br>
<br>

### ✅ 프로그래밍 요구 사항

**이전 미션의 프로그래밍 요구 사항은 기본으로 포함한다.**

- API key를 공개된 저장소에 포함하지 않는다.
- 비동기 통신에서 실패할 경우를 대비한다.
- 비동기 통신에서 일어날 수 있는 다양한 상황을 고려해 본다.
- 비동기 호출을 포함한 사용자 기능 플로우를 선정하고 기능을 포함하여 E2E 테스트를 작성한다.
- 특정한 패턴에 사고를 끼워 맞추지 않고 단지 역할과 책임에 따라 관심사를 분리한다.
- 어떠한 관점에서 역할과 책임에 따라 관심사를 분리하였는지 리뷰어에게 설명할 수 있어야 한다.
- 도메인 영역을 TypeScript를 사용해 구현한다. (UI 영역은 선택)
- any를 사용하지 않는다.
- API에서 응답한 데이터의 규격을 문자열 그대로 활용하지 않고 도메인 객체를 만들어 활용한다.

<br>
<br>

---

<a href="https://github.com/woowacourse">@woowacourse</a>
5 changes: 4 additions & 1 deletion babel.config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
module.exports = {
presets: [["@babel/preset-env", { targets: { node: "current" } }], "@babel/preset-typescript"],
presets: [
["@babel/preset-env", { targets: { node: "current" } }],
"@babel/preset-typescript",
],
};
9 changes: 9 additions & 0 deletions cypress.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { defineConfig } from "cypress";

export default defineConfig({
e2e: {
setupNodeEvents(on, config) {
// implement node event listeners here
},
},
});
99 changes: 99 additions & 0 deletions cypress/e2e/movie.spec.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { API, PATH } from "../../src/constants";
const { URL: API_URL } = API;
const { POPULAR_MOVIE } = PATH;

describe("영화관 앱 테스트.", () => {
const buildMovieUrl = (endpoint: string) => {
const url = new URL(endpoint, API_URL);

return new RegExp(`^${url}`);
};

const interceptor = (url: () => RegExp, fixture: string) => {
return [
{
method: "GET",
url: url(),
},
{
fixture: fixture,
},
];
};

beforeEach(() => {
const [movieUrl, popularMovies] = interceptor(
() => buildMovieUrl(POPULAR_MOVIE),
"popular-movies.json"
);

cy.intercept(movieUrl, popularMovies).as("getPopularMovies");

const [page2, popularMoviesIn2Page] = interceptor(
() => /&page=2/,
"popular-movie-page2.json"
);

cy.intercept(page2, popularMoviesIn2Page).as("getPopularMoviePage2");

const [MovieSearchUrl, searchedMovies] = interceptor(
() => buildMovieUrl("search/movie"),
"searched-movies.json"
);

cy.intercept(MovieSearchUrl, searchedMovies).as("getSearchedMovies");

cy.visit("http://localhost:8080/");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

});

it("API 요청시 성공적으로 응답한다.", () => {
cy.wait("@getPopularMovies").then((interception) => {
const state = interception.response?.statusCode;
const movieItems = interception.response?.body.results;

expect(state).to.equal(200);
expect(movieItems.length).to.equal(20);
});
});

it("더보기를 클릭하면 영화 목록이 20개씩 추가된다.", () => {
cy.wait("@getPopularMovies");

cy.get("#more-button").click();

cy.wait("@getPopularMoviePage2");

cy.get(".item-list").children().should("have.length", "40");

cy.fixture("popular-movie-page2.json").then((movieInfo) => {
movieInfo.results.forEach((movie) => {
cy.get(".item-list > li").should("contain.text", movie.title);
});
});
});

it("키워드를 검색하면 해당 키워드가 포함된 영화 목록을 보여준다.", () => {
cy.get("input[name='search-bar']").type("강아지");
cy.get("#search-bar").submit();

cy.wait("@getSearchedMovies");

cy.get(".item-list > li").each((li: HTMLElement) => {
expect(li).to.contain.text("강아지");
});

cy.fixture("searched-movies.json").then((movieInfo) => {
movieInfo.results.forEach((movie) => {
cy.get(".item-list > li").should("contain.text", movie.title);
});
});
});

it("로고를 클릭하면 처음 화면으로 이동한다.", () => {
cy.get("#logo").click();

cy.wait("@getPopularMovies");

cy.get(".item-view > h2").should("contain.text", "지금 인기 있는 영화");
});
});
Loading