Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
aff9bde
docs: 2단계 요구사항 작성
jebiyeon02 Apr 7, 2026
8afc143
feat: 영화 상보기 구현
jebiyeon02 Apr 7, 2026
f9175d4
refactor: 영화 id 추가
jebiyeon02 Apr 7, 2026
e31a537
refactor: 미사용 태그 제거
jebiyeon02 Apr 7, 2026
a067fb1
feat: 요청 파라미터의 지역을 한국으로 설정
jebiyeon02 Apr 8, 2026
b51762d
feat: 배너에서 자세히 보기 클릭시 영화 상세 정보를 띄움
jebiyeon02 Apr 8, 2026
19daf40
chore: 평균 평점을 소숫점 1자리까지 표시
jebiyeon02 Apr 8, 2026
53460c9
docs: 배너 상세보기 요구사항 추가
jebiyeon02 Apr 8, 2026
dcb58c5
feat: 영화 상세정보에서 별점을 매김
jebiyeon02 Apr 8, 2026
78f8287
chore: API 파라미터 수정
jebiyeon02 Apr 9, 2026
410554b
feat: 무한 스크롤 방식으로 변경
jebiyeon02 Apr 9, 2026
0f09f50
feat: 반응형 UI 구현
jebiyeon02 Apr 9, 2026
8e830c1
chore: 모달 UI 수정
jebiyeon02 Apr 9, 2026
ddd6492
refactor: 모달창을 dialog 태그로 변경
jebiyeon02 Apr 9, 2026
8d6a258
fix: 검색 시 section과 footer의 UI가 파괴되었던 문제 해결
jebiyeon02 Apr 9, 2026
b47dc98
refactor: 메인 로고를 div에서 button으로 변경해 접근성 개선
jebiyeon02 Apr 9, 2026
f4075df
refactor: 영화 포스터 내부를 button 태그로 변경해 접근성 개선
jebiyeon02 Apr 9, 2026
f45f130
feat: 모달 활성화 시 배경 스크롤 방지
jebiyeon02 Apr 9, 2026
8017394
feat: 모달 외부 클릭 시 모달 닫힘
jebiyeon02 Apr 9, 2026
a11bcc9
chore: 검색 시 section 상단 margin 값 증가
jebiyeon02 Apr 9, 2026
1674da4
refactor: 리뷰 숫자 색상 변경 및 관련 값 상수화
jebiyeon02 Apr 9, 2026
90ab8fa
chore: 주석 제거
jebiyeon02 Apr 10, 2026
17bf0d5
refactor: 별점 등록 코드 단순화
jebiyeon02 Apr 10, 2026
335d514
docs: E2E 테스트 시나리오 추가 작성
jebiyeon02 Apr 10, 2026
00fbd2d
test: E2E 테스트 작성
jebiyeon02 Apr 10, 2026
253704f
test: 영화 상세정보 fetch 테스트 작성
jebiyeon02 Apr 10, 2026
694fac2
chore: 템플릿 폴더 삭제
jebiyeon02 Apr 10, 2026
5691ddf
feat: 영화 썸네일 포커스 시 outline대신 크기 확대
jebiyeon02 Apr 10, 2026
de13d76
chore: 미사용 이벤트 객체 제거
jebiyeon02 Apr 10, 2026
174b780
feat: 최고 평가 영화 별도 API 사용
jebiyeon02 Apr 14, 2026
53784ba
refactor: 불필요한 비동기 처리 삭제
jebiyeon02 Apr 14, 2026
72e4b4d
refactor: setRating메서드 제거 및 ratingValue를 number로 관리
jebiyeon02 Apr 14, 2026
2bfff07
refactor: movieId를 Number로 관리
jebiyeon02 Apr 14, 2026
b40b7ba
refactor: 컨트롤러 1개를 화면별 컨트롤러로 분리
jebiyeon02 Apr 14, 2026
2f34842
feat: scroll 이벤트 대신 intersectionObserver 사용
jebiyeon02 Apr 14, 2026
ff39f9f
chore: bind메서드명 수정
jebiyeon02 Apr 14, 2026
fa1f0b6
fix: overlay가 제대로 적용되지 않던 문제 해결
jebiyeon02 Apr 14, 2026
7d22598
chore: 주석 제거
jebiyeon02 Apr 14, 2026
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
69 changes: 53 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,48 @@

FE 레벨1 영화 리뷰 미션

2단계 - 상세 정보 & UI/UX 개선하기

# 요구사항 목록

## 1. 검색
## 1. 인기 영화 목록

- 인기 영화를 최대 20개 가져온다.
- **무한스크롤 방식으로 추가 영화를 최대 20개씩 가져온다.**
- **더이상 가져올 영화가 없으면 스크롤이 끝난다.**
- 스켈레톤 UI를 표시한다.

## 2. 배너

- 가장 인기 있는 영화의 정보를 띄운다.
- 자세히 보기 클릭 시 상세 정보가 표시된다.

## 3. 검색

- 검색어를 입력받아 검색한다.
- 검색어에 해당하는 영화를 10개 가져온다.
- 더보기를 누르면 10개를 추가로 가져온다.
- 해당하는 모든 영화를 가져왔을 때 더 보기 버튼을 숨긴다.
- 검색어에 해당하는 영화가 없을 경우, "검색 결과가 없습니다"
- 검색어에 해당하는 영화를 최대 20개 가져온다.
- **무한스크롤 방식으로 추가 영화를 최대 20개씩 가져온다.**
- **더이상 가져올 영화가 없으면 스크롤이 끝난다.**
- 검색어에 해당하는 영화가 없을 경우, "검색 결과가 없습니다"를 표시한다.
- 검색 시 스켈레톤 UI를 표시한다.

## 2. 영화 목록
## 4. 영화 상세정보

- 영화 20개를 가져온다.
- 더보기를 누르면 20개를 추가로 가져온다.
- 모든 영화를 가져왔을 때 더 보기 버튼을 숨긴다.
- 검색 시 스켈레톤 UI를 표시한다.
- 영화 포스터 클릭시 해당 영화의 상세 정보를 확인할 수 있는 모달이 표시된다.
- 데스크톱/태블릿/모바일에 모두 대응 가능하도록 반응형 UI로 구현한다.
- 포스터, 제목, 장르, 평균 별점, 내 별점, 줄거리를 표시한다.
- 모달은 ESC, 모달 외부 클릭, 모달 내부 닫기버튼을 통해 닫을 수 있다.

## 배너
## 5. 별점

- 가장 인기 있는 영화의 정보를 띄운다.
- 영화 상세정보 내부에서 별을 클릭하여 별점을 매긴다.
- 별은 총 5개, 개당 2점이고 1점 단위는 고려하지 않는다.
- 별점별 나타나는 텍스트
- 2점: 최악이예요
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

평점 문구 오탈자가 있습니다.

Line 42의 최악이예요는 실제 UI/상수 표현과 맞추려면 최악이에요로 통일하는 편이 좋습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` at line 42, Replace the misspelled rating phrase "최악이예요" with the
correct wording "최악이에요" so the README's rating text matches the UI/constants;
search for the exact string "최악이예요" and update it to "최악이에요" wherever it appears
(e.g., the rating line currently showing "2점: 최악이예요").

- 4점: 별로예요
- 6점: 보통이에요
- 8점: 재미있어요
- 10점: 명작이에요

# 테스트 명세

Expand All @@ -37,16 +58,32 @@ FE 레벨1 영화 리뷰 미션

1. 배너에 첫 번째 인기 영화의 정보가 표시된다.
2. 최초 진입 시 인기 영화 최대 20개가 표시된다.
3. 더 보기 버튼을 누르면 최대 20개가 추가된다.
4. 더 이상 보여 줄 영화가 없으면 더 보기 버튼이 사라진다.
3. 스크롤을 내리면 무한 스크롤 방식으로 영화가 추가로 최대 20개씩 표시된다.
4. 더 이상 보여 줄 영화가 영화를 가져오지 않는다.

### 검색 시

1. `인사이드`를 검색하면 제목에 인사이드가 포함된 영화들이 최대 20개 표시된다.
2. 더 보기 버튼을 누르면 최대 20개가 추가된다.
3. 더 이상 보여줄 영화가 없으면 더 보기 버튼이 사라진다.
2. 스크롤을 내리면 무한 스크롤 방식으로 영화가 추가로 최대 20개씩 표시된다.
3. 더 이상 보여 줄 영화가 영화를 가져오지 않는다.
4. 검색 결과가 없으면 `검색 결과가 없습니다.`를 아이콘과 함께 표시된다.
5. 로고를 누르면 메인 화면으로 돌아온다.

### 배너 클릭 시

1. 자세히 보기 버튼을 눌러 영화 상세정보 모달창을 띄운다.

### 영화 포스터 클릭 시

1. 영화 상세정보 모달창이 표시된다.
2. ESC 키를 입력 또는 모달 외부 화면 클릭으로 모달창을 닫을 수 있다.
3. 모달 활성화 시 배경화면 스크롤이 비활성화 된다.

### 별점 등록 시

1. 영화 상세정보 모달이 활성화 되었을 때 별점이 없다면 별점 없음이 표시된다.
2. 1점을 등록하면 해당하는 텍스트와 함께 별 1개만 채워진다.
3. 2점을 등록하면 해당하는 텍스트와 함께 별 2개만 채워진다.
4. 3점을 등록하면 해당하는 텍스트와 함께 별 3개만 채워진다.
5. 4점을 등록하면 해당하는 텍스트와 함께 별 4개만 채워진다.
6. 5점을 등록하면 해당하는 텍스트와 함께 별 5개만 채워진다.
Comment on lines +85 to +89
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

점수 단위 표현이 앞선 규칙과 충돌합니다.

Line 40에서 “별 1개 = 2점”으로 정의했는데, Line 85-89는 “1점/2점/3점...”처럼 읽혀 요구사항 해석이 엇갈릴 수 있습니다. 시나리오 문구를 별 개수 기준 또는 2/4/6/8/10 점수 기준으로 맞춰 주세요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` around lines 85 - 89, The README contains a conflicting
score-to-star mapping: the earlier rule stating "별 1개 = 2점" conflicts with the
later bullets that list "1점/2점/3점/4점/5점". Update the scenario bullets to use the
same scale as the earlier rule—either convert the bullets to star-count phrasing
("별 1개", "별 2개", ... in place of "1점", "2점", ...) or convert them to the
2/4/6/8/10 point values that correspond to "별 1개 = 2점"; ensure the text around
the scoring examples (the list items currently showing 1~5) matches the selected
scheme throughout the README so the mapping is unambiguous.

52 changes: 52 additions & 0 deletions README_step1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# javascript-movie-review

FE 레벨1 영화 리뷰 미션

# 요구사항 목록

## 1. 검색

- 검색어를 입력받아 검색한다.
- 검색어에 해당하는 영화를 10개 가져온다.
- 더보기를 누르면 10개를 추가로 가져온다.
- 해당하는 모든 영화를 가져왔을 때 더 보기 버튼을 숨긴다.
- 검색어에 해당하는 영화가 없을 경우, "검색 결과가 없습니다"
- 검색 시 스켈레톤 UI를 표시한다.

## 2. 영화 목록

- 영화 20개를 가져온다.
- 더보기를 누르면 20개를 추가로 가져온다.
- 모든 영화를 가져왔을 때 더 보기 버튼을 숨긴다.
- 검색 시 스켈레톤 UI를 표시한다.

## 배너

- 가장 인기 있는 영화의 정보를 띄운다.

# 테스트 명세

## 단위 테스트

1. 인기 있는 영화 목록을 가져온다.
2. 검색어에 맞는 영화만 가져온다.

## E2E 시나리오

### 최초 진입 시

1. 배너에 첫 번째 인기 영화의 정보가 표시된다.
2. 최초 진입 시 인기 영화 최대 20개가 표시된다.
3. 더 보기 버튼을 누르면 최대 20개가 추가된다.
4. 더 이상 보여 줄 영화가 없으면 더 보기 버튼이 사라진다.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

문장 내 공백이 비정상적으로 들어가 있습니다.

Line 41의 더 이상 보여 줄은 문서 가독성을 해치므로 공백 정리가 필요합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README_step1.md` at line 41, 문서의 "더 이상 보여                                    
줄 영화가 없으면 더 보기 버튼이 사라진다." 문장에 불필요한 연속 공백이 들어가 있으니 해당 문자열(더 이상 보여                
줄)을 찾아 연속 공백을 단일 공백 또는 적절한 문장 형태("더 이상 보여줄")로 정리하고 문장 전체를 "더 이상 보여줄 영화가 없으면 더 보기
버튼이 사라진다."처럼 자연스럽게 수정해 README_step1.md 내 동일 표현이 다른 곳에 있으면 동일하게 공백을 정규화하세요.


### 검색 시

1. `인사이드`를 검색하면 제목에 인사이드가 포함된 영화들이 최대 20개 표시된다.
2. 더 보기 버튼을 누르면 최대 20개가 추가된다.
3. 더 이상 보여줄 영화가 없으면 더 보기 버튼이 사라진다.
4. 검색 결과가 없으면 `검색 결과가 없습니다.`를 아이콘과 함께 표시된다.
5. 로고를 누르면 메인 화면으로 돌아온다.



50 changes: 50 additions & 0 deletions __tests__/movie.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
fetchPopularMovies,
fetchSearchedMovies,
} from "../src/api/fetchMovies.ts";
import { fetchMovieDetail } from "../src/api/fetchMovieDetail.ts";

describe("영화 목록 테스트", () => {
beforeEach(() => {
Expand Down Expand Up @@ -109,3 +110,52 @@ describe("영화 목록 API 에러 테스트", () => {
await expect(fetchPopularMovies(1)).rejects.toThrow("Network Error");
});
});

describe("영화 상세정보 테스트", () => {
beforeEach(() => {
vi.stubGlobal("fetch", vi.fn());
});

it("영화 상세정보를 가져온다.", async () => {
// given
const mockDetailData = {
id: 1,
poster_path: "/detail.jpg",
title: "인사이드 아웃 2",
release_date: "2024-06-12",
genres: [
{ id: 16, name: "애니메이션" },
{ id: 35, name: "코미디" },
],
vote_average: 8.3,
overview: "새로운 감정들과 함께하는 이야기",
};

vi.mocked(fetch).mockResolvedValue({
ok: true,
json: async () => mockDetailData,
} as Response);

// when
const result = await fetchMovieDetail(1);

// then
expect(result.id).toBe(1);
expect(result.title).toBe("인사이드 아웃 2");
expect(result.genres).toHaveLength(2);
expect(result.overview).toBe("새로운 감정들과 함께하는 이야기");
});

it("영화 상세정보 요청 실패 시 정의된 에러 메시지를 던진다.", async () => {
// given
vi.mocked(fetch).mockResolvedValue({
ok: false,
status: 404,
} as Response);

// when & then
await expect(fetchMovieDetail(1)).rejects.toThrow(
"영화 상세 정보를 불러오는 데 실패했습니다.",
);
});
});
64 changes: 36 additions & 28 deletions cypress/e2e/main.cy.ts
Original file line number Diff line number Diff line change
@@ -1,79 +1,87 @@
describe("메인 화면 최초 진입 시나리오 테스트", () => {
// TMDB API 응답 형식을 생성하는 헬퍼 함수
const mockMovies = (
count: number,
titlePrefix: string,
page: number,
totalPages: number,
) => ({
results: Array.from({ length: count }, (_, i) => ({
id: i + (page - 1) * 20,
title: `${titlePrefix} ${i + 1}`,
id: 1000 + i + (page - 1) * 20,
title: `${titlePrefix} ${i + 1 + (page - 1) * 20}`,
poster_path: "/63In39uCc7769Y0667vCInth6Uv.jpg",
vote_average: 8.5,
})),
page: page,
page,
total_pages: totalPages,
});

const visitMainPage = () => {
cy.visit("http://localhost:5173/");
};

const scrollToBottom = () => {
cy.scrollTo("bottom");
cy.wait(300);
};

beforeEach(() => {
// 1페이지 호출 모킹 (20개 응답, 총 2페이지가 있다고 가정)
cy.intercept(
"GET",
"**/movie/popular?*page=1*",
mockMovies(20, "인기 영화", 1, 2),
).as("getPopularP1");
cy.intercept("GET", "**/movie/popular?*page=1*", (req) => {
req.reply({
delay: 300,
body: mockMovies(20, "인기 영화", 1, 2),
});
}).as("getPopularP1");

// 2페이지 호출 모킹 (마지막 페이지)
cy.intercept(
"GET",
"**/movie/popular?*page=2*",
mockMovies(20, "인기 영화", 2, 2),
).as("getPopularP2");

cy.visit("http://localhost:5173/");
});

it("1. 배너에 첫 번째 인기 영화의 정보가 표시된다.", () => {
visitMainPage();
cy.wait("@getPopularP1");

// 리스트의 첫 번째 영화 제목을 가져와서 배너(.title)와 비교
cy.get(".thumbnail-list li:first-child", { timeout: 10000 })
cy.get(".thumbnail-list li:first-child")
.find("strong")
.invoke("text")
.then((firstMovieTitle) => {
cy.get(".title").invoke("text").should("eq", firstMovieTitle);
cy.get(".title").should("have.text", firstMovieTitle);
});
});

it("2. 최초 진입 시 인기 영화 최대 20개가 표시된다.", () => {
cy.wait("@getPopularP1");
visitMainPage();

// 리스트 아이템 개수 확인
cy.get(".movie-skeleton").should("have.length.at.least", 1);
cy.wait("@getPopularP1");
cy.get(".movie-skeleton").should("not.exist");
cy.get(".thumbnail-list li").should("have.length", 20);
});

it("3. 더 보기 버튼을 누르면 최대 20개가 추가된다.", () => {
it("3. 스크롤을 내리면 무한 스크롤 방식으로 영화가 최대 20개씩 추가된다.", () => {
visitMainPage();
cy.wait("@getPopularP1");

// 초기 20개 확인 후 버튼 클릭
cy.get(".thumbnail-list li").should("have.length", 20);
cy.get(".more-button").click();

scrollToBottom();
cy.wait("@getPopularP2");

// 추가되어 총 40개가 되었는지 확인
cy.get(".thumbnail-list li").should("have.length", 40);
});

it("4. 더 이상 보여줄 영화가 없으면 더 보기 버튼이 사라진다.", () => {
it("4. 더 이상 보여 줄 영화가 없으면 영화를 가져오지 않는다.", () => {
visitMainPage();
cy.wait("@getPopularP1");

// 마지막 페이지(2페이지)를 불러오도록 버튼 클릭
cy.get(".more-button").click();
scrollToBottom();
cy.wait("@getPopularP2");
cy.get(".thumbnail-list li").should("have.length", 40);

scrollToBottom();

// 소스 코드 로직(nowPage === totalPages)에 따라 버튼이 숨겨져야 함
cy.get(".more-button").should("not.be.visible");
cy.get("@getPopularP2.all").should("have.length", 1);
cy.get(".thumbnail-list li").should("have.length", 40);
});
});
Loading