Skip to content
Open
Show file tree
Hide file tree
Changes from 51 commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
72aff64
docs: 커밋 테스트
goodsmell Mar 31, 2026
62261f0
docs: 요구사항 및 테스트 명세 작성
goodsmell Mar 31, 2026
c9a1238
feat: 인기 영화 20개를 가져온다.
goodsmell Mar 31, 2026
4be2b05
chore: env파일 무시
goodsmell Mar 31, 2026
06fccf8
feat: 인기 영화 목록 렌더링
goodsmell Mar 31, 2026
b9ff268
feat: 검색창 UI 구현
goodsmell Mar 31, 2026
4cee69e
feat: 배너 설정
goodsmell Mar 31, 2026
8214f4f
feat: 더보기 기능 구현
goodsmell Mar 31, 2026
864a72c
feat: 검색 기능 구현
goodsmell Apr 1, 2026
3b71dee
feat: 더보기 숨기기 기능 구현
goodsmell Apr 1, 2026
ac83534
feat: 스켈레톤UI 구현
goodsmell Apr 1, 2026
7683de0
fix: 검색 결과 없을 때 UI 중복되는 오류 해결
goodsmell Apr 2, 2026
2fa44b0
docs: E2E 시나리오 작성
goodsmell Apr 2, 2026
7090a41
chore: 스타일 파일 이동
goodsmell Apr 2, 2026
bc6fb20
test: 검색 시나리오 테스트 구현
goodsmell Apr 2, 2026
d8d57ec
test: 메인 시나리오 테스트 구현
goodsmell Apr 2, 2026
fba0e4d
feat: api 요청 시 더 보기 비활성 구현
goodsmell Apr 2, 2026
90c441e
test: api 요청 예외 테스트
goodsmell Apr 2, 2026
d6fe4d0
refector: 파일구조 분리
goodsmell Apr 2, 2026
3a177a6
chore: 배포 파이프라인 설정
goodsmell Apr 2, 2026
5824970
fix: 빌드 오류 해결
goodsmell Apr 2, 2026
130ab9c
refactor: main.ts - try-catch-finally 패턴 적용
goodsmell Apr 6, 2026
e4bf5e4
refactor: moreButton.ts - try-catch-finally 패턴 적용 및 중복 로직 제거
goodsmell Apr 6, 2026
d58f4d1
refatcore: searchForm.ts - try-catch-finally 패턴 적용
goodsmell Apr 6, 2026
cbb8693
fix: 검색 시 페이지 상태 초기화
goodsmell Apr 6, 2026
c1aada5
fix: 검색 결과에 따라 더보기 버튼 표시 처리
goodsmell Apr 6, 2026
a26637b
fix: response.json() 파싱 실패 처리를 위한 try/catch 추가
goodsmell Apr 7, 2026
692c2c2
refactor: non-null assertion(!) 제거 및 null 체크 추가
goodsmell Apr 7, 2026
beffa5a
fix: 영화 이미지 없을 경우 fallback image 사용
goodsmell Apr 7, 2026
d764db5
refactor: bindEvent 역할 분리
goodsmell Apr 7, 2026
ca9f91a
refactor: 상태 관리를 DOM 기준에서 store 기준으로 개선
goodsmell Apr 7, 2026
b45c77f
refactor: 중복 랜더링 제거
goodsmell Apr 7, 2026
689ac3b
refactor: main.ts 구조 개선 및 역할 분리
goodsmell Apr 7, 2026
443f5b1
refactor: searchForm DOM 요소를 역할별 객체로 묶어 관리하도록 개선
goodsmell Apr 7, 2026
2ca9882
refactor: SearchForm의 검색처리 로직을 메서드로 분리
goodsmell Apr 7, 2026
b772bb4
refactor: MoreButton에 더보기 버튼 표시 로직을 위임
goodsmell Apr 7, 2026
cbeb870
chore: 파일 구조 분리
goodsmell Apr 7, 2026
a614dc4
fix: 리스트 초기화 없이 append되어 영화 목록이 누적되는 문제 수정
goodsmell Apr 7, 2026
12affa2
refactor: 영화 API 요청 및 응답 처리 로직 공통화
goodsmell Apr 7, 2026
0eec055
fix: api 링크 지역 및 언어 설정
goodsmell Apr 8, 2026
8dd1f01
docs: step2 요구사항 목록 작성
goodsmell Apr 9, 2026
ce95c96
feat: 영화 상세 정보 모달 구현
goodsmell Apr 10, 2026
11041d0
feat: 별점 입력 기능 구현
goodsmell Apr 12, 2026
25e3908
feat:무한 스크롤 구현 및 더보기 버튼 삭제
goodsmell Apr 12, 2026
bb64502
fix: 모달 열리면 스크롤 방어
goodsmell Apr 12, 2026
7eef935
feat: 모달 즉시 열기 및 로딩 상태 추가
goodsmell Apr 12, 2026
7a378a9
style: 반응형 적용
goodsmell Apr 13, 2026
7752223
style: section title 워딩 변경
goodsmell Apr 13, 2026
cf9c908
test: 반응형, 모달, 무한스크롤을 e2e에 적용
goodsmell Apr 13, 2026
6c2eacb
fix: RATING_LABELS 워딩 변경
goodsmell Apr 13, 2026
b06cdfb
fix: 모달 이미지 렌더링 문제 수정 및 fallback 처리 추가
goodsmell Apr 13, 2026
492cb85
Merge remote-tracking branch 'upstream/goodsmell' into step-2
goodsmell Apr 13, 2026
4076b17
refactor: 클래스 인스턴스 변수 3개 이하로 제한
goodsmell Apr 13, 2026
b26003f
docs: 요구사항 목록 정리
goodsmell Apr 13, 2026
1f0e248
refactor: 오타 수정
goodsmell Apr 13, 2026
b45f46c
refactor: 사용자 친화적 에러메시지로 변경
goodsmell Apr 13, 2026
98deb00
refactor: 빈 main 블록 제거
goodsmell Apr 13, 2026
90f3faa
refactor: 상수화
goodsmell Apr 13, 2026
62c5f46
refactor: requireElement 유틸로 DOM 조회 방식 통일
goodsmell Apr 13, 2026
4694bc2
refactor: 이미지 로딩 로직을 loadImageWithFallback 유틸로 추출
goodsmell Apr 13, 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
57 changes: 57 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: Deploy to GitHub Pages

on:
push:
branches: [ "main" ]
workflow_dispatch:

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: "pages"
cancel-in-progress: true

# Node.js 20 관련 경고 해결을 위한 전역 환경변수 설정
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"

- name: Install dependencies
run: npm ci

- name: Build
run: npm run build -- --base=/javascript-movie-review/
env:
VITE_API_BASE_URL: ${{ secrets.VITE_API_BASE_URL }}
VITE_ACCESS_TOKEN: ${{ secrets.VITE_ACCESS_TOKEN }}

- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: ./dist

deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,5 @@ dist-ssr
*.njsproj
*.sln
*.sw?

.env
1 change: 1 addition & 0 deletions .gitmessage.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Co-authored-by: jebiyeon02 <jhje5595@naver.com>
3 changes: 0 additions & 3 deletions README.md

This file was deleted.

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

FE 레벨1 영화 리뷰 미션

# 요구사항 목록

## 1. 검색

- 검색어를 입력받아 검색한다.
- 검색어에 해당하는 영화를 10개 가져온다.
- 더보기를 누르면 10개를 추가로 가져온다.
Comment on lines +10 to +11
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

검색 결과 개수 기준이 문서 내에서 충돌합니다.

요구사항은 10개 단위인데, E2E 시나리오는 20개 단위로 적혀 있어 구현/테스트 기준이 갈릴 수 있습니다. 한 기준으로 통일해 주세요.

Also applies to: 47-48

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

In `@STEP1-README.md` around lines 10 - 11, Documentation and tests disagree on
page size: update the README lines that currently state "검색어에 해당하는 영화를 10개
가져온다." and "더보기를 누르면 10개를 추가로 가져온다." and the E2E scenario lines referenced
(47-48) so they all use the same page size (choose either 10 or 20
consistently); make the change in the STEP1-README.md text and any E2E scenario
descriptions so wording and numbers match the implemented behavior (and ensure
the wording around "더보기" matches the chosen increment).

- 해당하는 모든 영화를 가져왔을 때 더 보기 버튼을 숨긴다.
- 검색어에 해당하는 영화가 없을 경우, "검색 결과가 없습니다"
- 검색 시 스켈레톤 UI를 표시한다.

## 2. 영화 목록

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



## 배너

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

# 테스트 명세

## 단위 테스트

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

## E2E 시나리오

### 최초 진입 시

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

### 검색 시

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



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

FE 레벨1 영화 리뷰 미션 step2

# 요구사항 목록

## 1. 영화 상세정보 조회

- 영화를 클릭하면 모달이 뜬다
- ESC를 누르면 모달이 사라진다
- x를 누르면 모달이 사라진다

## 2. 별점 매기기

- 영화 상세정보 모달에서 별을 눌러 별점을 설정할 수 있다.
- 별점은 5개로 구성되어 있으며 한 개당 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

오타 수정 필요

"이댜" → "이다"로 수정해 주세요.

- - 별점은 5개로 구성되어 있으며 한 개당 2점이댜.
+ - 별점은 5개로 구성되어 있으며 한 개당 2점이다.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- 별점은 5개로 구성되어 있으며 한 개당 2점이댜.
- 별점은 5개로 구성되어 있으며 한 개당 2점이다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@STEP2-README.md` at line 16, 문서 STEP2-README.md에서 문장 "별점은 5개로 구성되어 있으며 한 개당
2점이댜."의 오타를 수정하세요; 텍스트에서 "이댜"를 "이다"로 바꿔 최종 문장이 "별점은 5개로 구성되어 있으며 한 개당 2점이다."가
되도록 변경하십시오.

- 2점: 최악이예요
- 4점: 별로예요
- 6점: 보통이에요
- 8점: 재미있어요
- 10점: 명작이에요
- 새로고침을 하더라도 별점이 유지된다.
- local storage를 사용하되, 추후에 API 요청으로 갈아끼울 수 있는 형태로 만든다.

## 3. 반응형 UI

## 4. 무한스크롤

- 기존 더보기 버튼이 사라지고 무한 스크롤 형태로 변경
- 사용자가 브라우저 화면의 끝에 도달하면 그 다음 20개의 목록을 서버에 요청하여 추가한다.

## 배너

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

# 테스트 명세

## E2E 시나리오
### 최초 진입 시

1. 배너에 첫 번째 인기 영화의 정보가 표시된다.
2. 최초 진입 시 인기 영화 20개가 표시된다.
3. 스크롤을 내리면 다음 20개가 자동으로 추가된다.
4. 연속 스크롤 시 다음 페이지가 순차적으로 추가된다.
5. 영화를 클릭하면 모달이 열린다.
- 클릭 즉시 모달이 열리고, API 응답 전까지 콘텐츠가 비어있다.
- API 응답 후 모달에 제목, 카테고리, 평점, 줄거리가 채워진다.
- 모달이 열리면 body 스크롤이 방지된다.
- X 버튼을 누르면 모달이 닫힌다.
- ESC 키를 누르면 모달이 닫힌다.
- 배경(딤 영역)을 클릭하면 모달이 닫힌다.
6. 반응형 레이아웃
- 모바일(375px)에서 영화 목록이 1열로 표시된다.
- 태블릿(900px)에서 영화 목록이 3열로 표시된다.
- 데스크톱(1200px)에서 영화 목록이 4열로 표시된다.
- 와이드(1600px)에서 영화 목록이 5열로 표시된다.
- 모바일(375px)에서 모달의 포스터 이미지가 숨겨진다.
- 태블릿(900px)에서 모달이 하단 시트 형태로 표시된다.

### 검색 시

1. "인사이드"를 검색하면 관련 영화가 최대 20개 표시된다.
2. 스크롤을 내리면 다음 검색 결과가 자동으로 추가된다.
3. 마지막 페이지까지 불러오면 더 이상 요청이 발생하지 않는다.
4. 검색 결과가 없으면 안내 메시지를 아이콘과 함께 표시한다.
5. 로고를 누르면 메인 화면으로 돌아온다.
6. 검색 결과에서 영화를 클릭하면 모달이 열린다.
- 모달에 영화 정보가 올바르게 표시된다.
- 모달을 닫고 검색 결과 화면으로 돌아올 수 있다.
68 changes: 68 additions & 0 deletions __tests__/movie.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { describe, it, expect, beforeEach, vi } from "vitest";
import {
fetchPopularMovies,
fetchSearchedMovies,
} from "../src/api/fetchMovies.ts";

describe("영화 목록 테스트", () => {
it("인기 있는 영화 목록을 가져온다", async () => {
// given

// when
const movies = await fetchPopularMovies(1);

// then
expect(movies.movies.length).toBe(20);
});

it("검색어에 맞는 영화만 가져온다.", async () => {
// given

// when
const movies = await fetchSearchedMovies(1, "인사이드");

// then
expect(movies.movies.length).toBe(20);
});
});

describe("영화 목록 API 에러 테스트", () => {
beforeEach(() => {
vi.stubGlobal("fetch", vi.fn());
});

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

// when & then
await expect(fetchPopularMovies(1)).rejects.toThrow(
"영화를 불러오는 데 실패했습니다.",
);
});

it("영화 검색 실패 시(404 에러) 상태 코드를 포함한 에러 메시지를 던진다.", async () => {
// given
const status = 404;
vi.mocked(fetch).mockResolvedValue({
ok: false,
status: status,
} as Response);

// when & then
await expect(fetchSearchedMovies(1, "인사이드")).rejects.toThrow(
`영화 검색 중 에러가 발생했습니다.`,
);
});

it("네트워크 장애(Network Error) 발생 시 에러를 던진다.", async () => {
// given
vi.mocked(fetch).mockRejectedValue(new Error("Network Error"));

// when & then
await expect(fetchPopularMovies(1)).rejects.toThrow("Network Error");
});
});
Loading