Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
ab98b35
docs: 영화 목록 불러오기 미션 요구사항 정리
boboyoii Mar 31, 2026
eb0458c
chore: 초기 렌더링 화면 세팅
boboyoii Mar 31, 2026
3ea0f8b
feat: 페이지 번호에 따라 영상 목록을 불러오는 기능 추가
boboyoii Mar 31, 2026
00ff192
feat: 불러온 영화 목록을 화면에 렌더링 하는 기능 추가
boboyoii Mar 31, 2026
2bff17a
reafctor: api 함수 분리
boboyoii Mar 31, 2026
07fd962
refactor: 영화 목록 렌더링 기능 함수 분리
boboyoii Mar 31, 2026
534ade5
feat: 페이지 상단에 평점이 가장 높은 영화 출력 기능 추가
boboyoii Mar 31, 2026
32e6536
feat: 더보기 버튼 클릭 시 영화 목록이 추가되는 기능 추가
boboyoii Mar 31, 2026
d0d406c
feat: 영화 목록 끝에 도달한 경우에 더보기 버튼 숨김 기능 추가
boboyoii Mar 31, 2026
2911dbc
feat: 영화 검색 기능 추가
boboyoii Mar 31, 2026
181716b
feat: 검색 결과가 없을 때 안내메시지 출력 기능 추가
boboyoii Mar 31, 2026
1a4964c
feat: 영화 목록이 렌더링 되기 전에 스켈레턴을 렌더링하는 기능 추가
boboyoii Apr 1, 2026
6a16aa5
test: 영화 목록 조회 기능 테스트
boboyoii Apr 1, 2026
895e47a
test: 영화 검색 기능 테스트 추가
boboyoii Apr 1, 2026
99c3f0e
fix: 검색 더보기 버튼에서 인기 api 호출 되는 문제 수정
boboyoii Apr 1, 2026
9877f63
refactor: 렌더링하는 함수 파일로 분리 및 검색 기능의 공통 함수 분리
boboyoii Apr 1, 2026
29f0b85
refactor: 렌더 함수 컴포넌트 별로 파일 분리
boboyoii Apr 2, 2026
1c62e42
refactor: 페이지 상태값 클래스로 관리
boboyoii Apr 2, 2026
fcf82b3
feat: 검색어가 입력되지 않을 경우 검색 기능이 수행되지 않도록 하는 기능
boboyoii Apr 2, 2026
3c5b5a3
feat: api 반환 값이 200이 아닐 경우 에러 메시지를 띄우는 기능 추가
boboyoii Apr 2, 2026
4aa9426
style: 검색창과 검색 입력시 결과 없음 스타일 적용
boboyoii Apr 2, 2026
5815f2c
chore: 에러메시지 수정
boboyoii Apr 2, 2026
f190475
test: api 테스트 목업데이터 구조 수정
boboyoii Apr 2, 2026
982c938
chore: 이미지, style 경로 수정
boboyoii Apr 2, 2026
b3ef101
chore: 오타 수정 및 CSS 중복 속성 제거
boboyoii Apr 6, 2026
c2edebe
reafactor: 인기 영화, 평점 가장 높은 영와 로드 하는 익명 함수 일반 함수로 분리
boboyoii Apr 6, 2026
912ac5e
refactor: 검색 기능, 더보기 기능 익명 함수 제거 및 로드 함수명 통일
boboyoii Apr 6, 2026
07d9f48
refactor: 평점이 가장 높은 영화만 전달하도록 수정
boboyoii Apr 6, 2026
b8cf963
refactor: 에러 처리 함수를 분리하고 load 함수에 try/catch 적용
boboyoii Apr 7, 2026
d109d2a
fix: 평점 높은 영화와 검색 API에 에러 처리 추가
boboyoii Apr 7, 2026
be6f5ce
renderMovieList의 더보기 버튼 로직 분리
boboyoii Apr 7, 2026
fba5450
fix: 로고 버튼이 홈 화면으로 이동하지 않던 문제 수정
boboyoii Apr 7, 2026
e507eb8
fix: 검색 시 URL 경로가 잘못 설정되던 문제 수정
boboyoii Apr 7, 2026
e34f8ac
fix: 검색 시 URL 경로 지정 방법 변경
boboyoii Apr 7, 2026
147a72a
fix: 새로고침 시 검색 목록 화면이 유지되도록 수정
boboyoii Apr 7, 2026
e8f24f7
refactor: 검색과 인기 영화의 페이지 상태 분리
boboyoii Apr 7, 2026
2f566ef
refactor: 페이지 상태를 렌더링 성공 이후에 갱신하도록 수정
boboyoii Apr 7, 2026
bfce691
refactor: 목록 초기화 시 요소를 독립적으로 처리하도록 수정
boboyoii Apr 7, 2026
76d42d8
refactor: 스켈레톤 레이아웃을 블록 방식으로 변경하고 더보기 로딩 반영
boboyoii Apr 8, 2026
7c71154
docs: 2단계 상세 정보 & UI/UX 개선하기 미션 요구사항 정리
boboyoii Apr 9, 2026
27d39bd
refactor: 메인 로직을 controller로 분리
boboyoii Apr 9, 2026
5b607a3
feat: 영화의 상세 정보를 불러오는 기능 추가
boboyoii Apr 9, 2026
b78d3ef
feat: 상세 정보를 모달창으로 렌더링 하는 기능 추가
boboyoii Apr 11, 2026
a7b8008
feat: 모달창 닫기 기능 추가
boboyoii Apr 11, 2026
dd12706
feat: 영화 별점을 매기는 기능 추가
boboyoii Apr 11, 2026
198c1ca
feat: 별점을 로컬스토리지에 저장하고 상세페이지를 볼때 저장한 별점을 다시 확인하는 기능 추가
boboyoii Apr 11, 2026
1cd6b40
test: 더보기 버튼 테스트를 무한 스크롤 테스트로 수정
boboyoii Apr 11, 2026
0d2baf6
feat: 무한스크롤 기능 추가
boboyoii Apr 11, 2026
6097254
refactor: 영화 노드 조회와 값 설정 로직 분리
boboyoii Apr 11, 2026
58a0b2c
refactor: 요소 못 찾으면 종료 대신 찾은 요소 업데이트로 변경
boboyoii Apr 11, 2026
7454174
refactor: 폴더 구조 변경 및 함수 스타일 통일
boboyoii Apr 11, 2026
58a6fed
refactor: 검색 url 생성 로직 분리 및 이벤트 핸들러 함수명 수정
boboyoii Apr 11, 2026
f8905d3
refactor: 모달 관련 이벤트 수행함수 분리
boboyoii Apr 11, 2026
ec3272e
refactor: 별점 UI 업데이트와 저장 로직 분리
boboyoii Apr 11, 2026
d0c0c3e
refactor: 이벤트 바인딩과 핸들러 구조 정리
boboyoii Apr 11, 2026
37c7ac3
feat: 모달 열림 시 배경 스크롤 막는 기능 추가
boboyoii Apr 12, 2026
b7c6219
style: 태블릿 화면에서 반응형 레이아웃 적용
boboyoii Apr 12, 2026
3bc2492
style: 모바일 화면에서 반응형 레이아웃 적용
boboyoii Apr 12, 2026
49be0e8
feat: 모달 바깥 클릭 시 닫기 기능 추가
boboyoii Apr 12, 2026
5c5905e
fix: 화면 크기에 따라 상단 배경 이미지가 비율대로 축소되도록 수정
boboyoii Apr 12, 2026
c5715c6
fix: 검색 시 상단 배경 이미지와 오버레이 제거
boboyoii Apr 12, 2026
047f86a
test: 모달 열림 닫힘 기능 테스트
boboyoii Apr 12, 2026
deb2ec4
test: 별점 선택 및 저장 기능 테스트 추가
boboyoii Apr 12, 2026
661fac1
Merge remote-tracking branch 'upstream/boboyoii' into step2
boboyoii Apr 13, 2026
e37cfa3
fix: 영화 상세 모달 API 호출 예외 처리 추가
boboyoii Apr 13, 2026
5d212b9
refactor: localStorage 조회 로직 분리
boboyoii 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
28 changes: 27 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# 1단계 - 영화 목록 불러오기
# 영화 리뷰 - 웹 앱

FE 레벨1 영화 리뷰 미션입니다.

## 1단계 - 영화 목록 불러오기

## 영화 목록 조회 기능

- [x] 페이지 상단에 평점이 가장 높은 영화를 출력한다.
Expand All @@ -24,3 +26,27 @@ FE 레벨1 영화 리뷰 미션입니다.
- [x] 검색어가 입력되지 않았을 때 검색 기능이 수행되지 않도록 한다.
- [x] api 반환 값이 200이 아닐 경우 에러 메시지를 모달로 보여준다.
- [ ] api가 정상적으로 동작하지 않을 경우 에러 메시지를 모달로 보여준다.

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

### 영화 상세 정보 조회

- [x] 해당 id를 가진 영화의 상세 정보를 불러온다.
- [x] 상세 정보를 모달창으로 렌더링 한다.
- [x] 키보드의 ESC 키를 누르면 모달 창을 닫는다.

### 별점 매기기

- [x] 영화 별점을 매긴다. (별점은 5개이고 한개당 2점)
- 2점: 최악이예요
- 4점: 별로예요
- 6점: 보통이에요
- 8점: 재미있어요
- 10점: 명작이에요
- [x] 사용자가 매긴 별점은 로컬스토리지에 저장한다.

### UI⁄UX 개선하기

- [x] 브라우저 화면의 끝에 도달하면 영화 20개를 자동으로 로드한다. (무한 스크롤)
- [x] 태블릿 화면에서 영화 목록과 모달 레이아웃이 반응형으로 동작하도록 한다.
- [x] 모바일 화면에서 영화 목록과 모달 레이아웃이 반응형으로 동작하도록 한다.
7 changes: 4 additions & 3 deletions cypress/e2e/error-handling.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { moviesFixture } from "../../test/fixtures";

describe("오류 대응 테스트", () => {
beforeEach(() => {
cy.intercept("GET", "**/movie/popular?page=1", {
cy.intercept("GET", "**/movie/popular?page=1&language=ko-KR", {
statusCode: 200,
body: {
page: 1,
Expand All @@ -12,7 +12,7 @@ describe("오류 대응 테스트", () => {
},
}).as("getPopularPage1");

cy.intercept("GET", "**/movie/popular?page=2", {
cy.intercept("GET", "**/movie/popular?page=2&language=ko-KR", {
statusCode: 400,
body: {
success: false,
Expand All @@ -36,7 +36,8 @@ describe("오류 대응 테스트", () => {
const alertSpy = cy.stub();
cy.on("window:alert", alertSpy);

cy.get("#more-button").click();
cy.get(".scroll-sentinel").scrollIntoView();
cy.wait("@getInvalidPopularPage");
cy.wrap(alertSpy).should("have.been.called");
});
});
17 changes: 9 additions & 8 deletions cypress/e2e/movie-list-rendering.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { moviesFixture } from "../../test/fixtures";

describe("영화 목록 조회 기능 테스트", () => {
beforeEach(() => {
cy.intercept("GET", "**/movie/popular?page=1", {
cy.intercept("GET", "**/movie/popular?page=1&language=ko-KR", {
statusCode: 200,
body: {
page: 1,
Expand All @@ -12,7 +12,7 @@ describe("영화 목록 조회 기능 테스트", () => {
},
}).as("getPopularPage1");

cy.intercept("GET", "**/movie/popular?page=2", {
cy.intercept("GET", "**/movie/popular?page=2&language=ko-KR", {
statusCode: 200,
body: {
page: 2,
Expand All @@ -30,17 +30,18 @@ describe("영화 목록 조회 기능 테스트", () => {
cy.get("#movie-list li").should("have.length", 20);
});

it("더보기 버튼을 누르면 영화 목록이 추가로 생성되어 렌더링 된다.", () => {
cy.get("#more-button").click();
it("리스트 아래 sentinel이 보이면 영화 목록이 추가로 생성되어 렌더링 된다.", () => {
cy.get(".scroll-sentinel").scrollIntoView();
cy.wait("@getPopularPage2");

cy.get("#movie-list li").should("have.length.greaterThan", 20);
cy.get("#movie-list li").should("have.length", 40);
});

it("마지막 페이지까지 렌더링 됬을 때 더보기 버튼을 출력하지 않는다.", () => {
cy.get("#more-button").click();
it("마지막 페이지까지 렌더링되면 추가 요청이 발생하지 않는다.", () => {
cy.get(".scroll-sentinel").scrollIntoView();
cy.wait("@getPopularPage2");

cy.get("#more-button").should("not.be.visible");
cy.get(".scroll-sentinel").scrollIntoView();
cy.get("#movie-list li").should("have.length", 40);
});
});
56 changes: 56 additions & 0 deletions cypress/e2e/movie-modal.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { movieDetailFixture, moviesFixture } from "../../test/fixtures";

describe("영화 모달 기능 테스트", () => {
const openMovieModal = () => {
cy.get("#movie-list li").first().click();
cy.wait("@getMovieDetail");
cy.get("#modal-background").should("have.class", "active");
};
Comment on lines +4 to +8
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

movie-rating.cy.ts 도 동일한 openMovieModal() 헬퍼를 각각 정의하고 있는데요. 공통유틸로 분리해보면 어떨까요?


beforeEach(() => {
cy.intercept("GET", "**/movie/popular?page=1&language=ko-KR", {
statusCode: 200,
body: {
page: 1,
results: [...moviesFixture],
total_pages: 2,
total_results: 40,
},
}).as("getPopularPage1");

cy.intercept("GET", "**/movie/640146?language=ko-KR", {
statusCode: 200,
body: movieDetailFixture,
}).as("getMovieDetail");
Comment on lines +21 to +24
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check if movie ID 640146 matches the fixture
# Expected: moviesFixture should have id: 640146 as first element

echo "=== Checking moviesFixture for movie ID ==="
rg -n "640146|id:" test/fixtures

Repository: woowacourse/javascript-movie-review

Length of output: 193


🏁 Script executed:

#!/bin/bash
# Find fixture files in the repository
echo "=== Finding fixture files ==="
find . -name "*fixture*" -type f 2>/dev/null | head -20

Repository: woowacourse/javascript-movie-review

Length of output: 127


🏁 Script executed:

#!/bin/bash
# Check the actual test file structure
echo "=== Checking movie-modal.cy.ts file ==="
cat -n cypress/e2e/movie-modal.cy.ts

Repository: woowacourse/javascript-movie-review

Length of output: 1957


🏁 Script executed:

#!/bin/bash
# Search for moviesFixture and movie IDs throughout the codebase
echo "=== Searching for moviesFixture ==="
rg -n "moviesFixture" --type ts --type js | head -20

Repository: woowacourse/javascript-movie-review

Length of output: 1045


🏁 Script executed:

#!/bin/bash
# Read the fixtures file to check the first movie's ID
echo "=== Reading test/fixtures.ts ==="
cat -n test/fixtures.ts | head -50

Repository: woowacourse/javascript-movie-review

Length of output: 2629


하드코딩된 영화 ID와 fixture의 연계성을 개선하세요.

현재 640146moviesFixture[0].id와 일치하지만, 이 값이 하드코딩되어 있으면 나중에 fixture 데이터를 변경할 때 동기화를 유지하기 어렵습니다.

다음을 고려해 보세요:

  • fixture에서 동적으로 영화 ID를 가져오는 방법이 있을까요?
  • beforeEach에서 moviesFixture[0].id를 변수로 저장한 후 재사용할 수 있을까요?
  • 현재 구조에서 테스트의 의도는 무엇이며, 그 의도를 유지하면서 더 유지보수하기 좋은 방식은 무엇일까요?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cypress/e2e/movie-modal.cy.ts` around lines 21 - 24, The test hardcodes movie
ID 640146 in the cy.intercept call which can drift from fixtures; instead, in
beforeEach grab the id from moviesFixture[0].id (assign to a local variable like
movieId), use that variable in the intercept pattern (e.g., build the URL with
movieId) and ensure movieDetailFixture is either created from or updated to
reflect that same movieId (e.g., set movieDetailFixture.id = movieId or derive
movieDetailFixture from moviesFixture[0]) so the intercepted request URL and
fixture data stay in sync; update references to cy.intercept, moviesFixture, and
movieDetailFixture accordingly.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

요것도 좋은 리뷰인 것 같습니다!


cy.visit("localhost:5173");
cy.wait("@getPopularPage1");
});

it("영화 목록을 클릭하면 모달이 뜨고 닫기 버튼으로 닫을 수 있다", () => {
openMovieModal();

cy.get("#close-modal").click();
cy.get("#modal-background").should("not.have.class", "active");
});

it("ESC 버튼을 누르면 모달이 닫힌다", () => {
openMovieModal();

cy.get("body").type("{esc}");
cy.get("#modal-background").should("not.have.class", "active");
});

it("모달창이 아닌 부분을 클릭하면 모달이 닫힌다", () => {
openMovieModal();

cy.get("#modal-background").click("topLeft");
cy.get("#modal-background").should("not.have.class", "active");
});

it("모달창이 뜨면 배경 스크롤이 적용되지 않는다", () => {
openMovieModal();

cy.get("body").should("have.class", "modal-open");
});
});
77 changes: 77 additions & 0 deletions cypress/e2e/movie-rating.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { movieDetailFixture, moviesFixture } from "../../test/fixtures";
import { RATING_SCORES, RATING_TEXTS } from "../../src/constants/rating";

describe("영화 별점 기능 테스트", () => {
const openMovieModal = () => {
cy.get("#movie-list li").first().click();
cy.wait("@getMovieDetail");
cy.get("#modal-background").should("have.class", "active");
};

beforeEach(() => {
cy.intercept("GET", "**/movie/popular?page=1&language=ko-KR", {
statusCode: 200,
body: {
page: 1,
results: [...moviesFixture],
total_pages: 2,
total_results: 40,
},
}).as("getPopularPage1");

cy.intercept("GET", "**/movie/640146?language=ko-KR", {
statusCode: 200,
body: movieDetailFixture,
}).as("getMovieDetail");

cy.visit("localhost:5173");
cy.wait("@getPopularPage1");
cy.clearLocalStorage();
});

it("모달창에서 별점을 클릭하면 해당 별만큼 채워지고 점수와 평가 문구가 바뀐다", () => {
openMovieModal();

cy.get(".movie-rating .stars img").eq(3).click();

cy.get(".movie-rating .stars img")
.eq(0)
.should("have.attr", "src")
.and("include", "star_filled.png");

cy.get(".movie-rating .stars img")
.eq(3)
.should("have.attr", "src")
.and("include", "star_filled.png");

cy.get(".movie-rating .stars img")
.eq(4)
.should("have.attr", "src")
.and("include", "star_empty.png");

cy.get(".rating-text").should("have.text", RATING_TEXTS[3]);
cy.get("#rating-value").should("have.text", RATING_SCORES[3]);
});

it("별점을 매기고 다시 모달을 열면 이전 별점이 유지된다", () => {
openMovieModal();

cy.get(".movie-rating .stars img").eq(3).click();
cy.get("#close-modal").click();

openMovieModal();

cy.get(".rating-text").should("have.text", RATING_TEXTS[3]);
cy.get("#rating-value").should("have.text", RATING_SCORES[3]);

cy.get(".movie-rating .stars img")
.eq(3)
.should("have.attr", "src")
.and("include", "star_filled.png");

cy.get(".movie-rating .stars img")
.eq(4)
.should("have.attr", "src")
.and("include", "star_empty.png");
});
});
37 changes: 21 additions & 16 deletions cypress/e2e/movie-search.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ describe("영화 검색 기능 테스트", () => {
beforeEach(() => {
cy.intercept(
"GET",
"**/search/movie?page=1&query=%EC%8A%A4%ED%8C%8C%EC%9D%B4",
"**/search/movie?page=1&query=%EC%8A%A4%ED%8C%8C%EC%9D%B4&language=ko-KR",
{
statusCode: 200,
body: {
Expand All @@ -18,7 +18,7 @@ describe("영화 검색 기능 테스트", () => {

cy.intercept(
"GET",
"**/search/movie?page=2&query=%EC%8A%A4%ED%8C%8C%EC%9D%B4",
"**/search/movie?page=2&query=%EC%8A%A4%ED%8C%8C%EC%9D%B4&language=ko-KR",
{
statusCode: 200,
body: {
Expand All @@ -30,15 +30,19 @@ describe("영화 검색 기능 테스트", () => {
},
).as("getSearchPage2");

cy.intercept("GET", "**/search/movie?page=1&query=%EB%B7%80", {
statusCode: 200,
body: {
page: 1,
results: [],
total_pages: 1,
total_results: 0,
cy.intercept(
"GET",
"**/search/movie?page=1&query=%EB%B7%80&language=ko-KR",
{
statusCode: 200,
body: {
page: 1,
results: [],
total_pages: 1,
total_results: 0,
},
},
}).as("getSearchNoResult");
).as("getSearchNoResult");

cy.visit("localhost:5173");
});
Expand All @@ -59,26 +63,27 @@ describe("영화 검색 기능 테스트", () => {
cy.get("#movie-list li").should("have.length.greaterThan", 0);
});

it("검색 후 더보기 버튼을 클릭하면 필터링 된 영화 목록이 추가로 출력된다.", () => {
it("검색 후 스크롤을 내려 sentinel 요소가 보이면 필터링 된 영화 목록이 추가로 출력된다.", () => {
cy.get("#search-input").type("스파이");
cy.get("#search-button").click();
cy.wait("@getSearchPage1");

cy.get("#more-button").click();
cy.get(".scroll-sentinel").scrollIntoView();
cy.wait("@getSearchPage2");

cy.get("#movie-list li").should("have.length.greaterThan", 20);
cy.get("#movie-list li").should("have.length", 40);
});

it("필터링 된 영화 목록이 마지막 페이지면 더보기 버튼을 출력하지 않는다.", () => {
it("필터링 된 영화 목록이 마지막 페이지면 sentinel 요소가 보여도 추가로 요청하지 않는다.", () => {
cy.get("#search-input").type("스파이");
cy.get("#search-button").click();
cy.wait("@getSearchPage1");

cy.get("#more-button").click();
cy.get(".scroll-sentinel").scrollIntoView();
cy.wait("@getSearchPage2");

cy.get("#more-button").should("not.be.visible");
cy.get(".scroll-sentinel").scrollIntoView();
cy.get("#movie-list li").should("have.length", 40);
});

it("검색 결과가 없을 때는 안내메시지를 출력한다.", () => {
Expand Down
47 changes: 45 additions & 2 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<link rel="stylesheet" href="./styles/skeleton.css" />
<link rel="stylesheet" href="./styles/search.css" />
<link rel="stylesheet" href="./styles/message-box.css" />
<link rel="stylesheet" href="./styles/modal.css" />
<title>영화 리뷰</title>
</head>
<body>
Expand Down Expand Up @@ -243,8 +244,7 @@ <h2 id="movie-list-title">지금 인기 있는 영화</h2>
</div>
</li>
</ul>

<button class="primary" id="more-button">더보기</button>
<div class="scroll-sentinel"></div>
</section>
</main>
</div>
Expand All @@ -254,6 +254,49 @@ <h2 id="movie-list-title">지금 인기 있는 영화</h2>
<p><img src="./images/woowacourse_logo.png" width="180" /></p>
</footer>
</div>

<div class="modal-background" id="modal-background">
<div class="modal">
<button class="close-modal" id="close-modal">
<img src="./images/modal_button_close.png" />
</button>
<div class="modal-container">
Comment on lines +258 to +263
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 | 🟠 Major

모달 접근성 시맨틱이 부족해 스크린리더 사용성이 떨어집니다.

Line 258-263, 268 구간에서 대화상자 역할(role="dialog", aria-modal)과 제목 연결, 닫기 버튼의 접근 가능한 이름을 보강해 주세요. 현재 구조는 보조기기에서 맥락 파악이 어렵습니다.

개선 예시
-<div class="modal-background" id="modal-background">
-  <div class="modal">
-    <button class="close-modal" id="close-modal">
-      <img src="./images/modal_button_close.png" />
+<div class="modal-background" id="modal-background">
+  <div class="modal" role="dialog" aria-modal="true" aria-labelledby="modal-title">
+    <button class="close-modal" id="close-modal" aria-label="모달 닫기">
+      <img src="./images/modal_button_close.png" alt="" />
     </button>
@@
-      <h2></h2>
+      <h2 id="modal-title"></h2>

Also applies to: 268-268

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

In `@index.html` around lines 258 - 263, The modal markup lacks ARIA dialog
semantics and accessible naming; update the element with class "modal" (id
"modal-background"/container structure) to include role="dialog" and
aria-modal="true", add a visible heading inside the "modal-container" and give
it an id (e.g., modal-title) then set aria-labelledby on the "modal" to that id
so screen readers announce the title, and make the close control (class
"close-modal", id "close-modal") an accessible button by ensuring it is a proper
<button> element with an explicit accessible name (e.g., aria-label="Close" or a
visually hidden label) and type="button"; also ensure focus is trapped into the
dialog and returned on close by wiring focus management in the modal open/close
logic referencing these IDs.

<div class="modal-image">
<img src="" />
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

img src 값은 유효하지 않습니다.

Line 265의 src=""는 마크업 오류이며 불필요한 요청/깨진 이미지 상태를 만들 수 있습니다. 초기 placeholder src를 넣거나 렌더 시점에만 img를 주입하는 방식으로 정리해 주세요.

수정 예시
-<img src="" />
+<img
+  src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=="
+  alt="영화 포스터"
+/>
📝 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
<img src="" />
<img
src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=="
alt="영화 포스터"
/>
🧰 Tools
🪛 HTMLHint (1.9.2)

[error] 265-265: The attribute [ src ] of the tag [ img ] must have a value.

(src-not-empty)

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

In `@index.html` at line 265, 빈 src 속성을 가진 <img> 태그(img 요소 with src="")가 유효하지 않으니
해당 img 요소를 제거하거나 유효한 placeholder URL을 src에 넣거나 조건부 렌더링으로 렌더 시점에만 삽입하도록 수정하세요;
구체적으로 index.html에 존재하는 문제의 img 태그를 찾아(src="") 삭제하거나 src에 투명 SVG/data URL 또는 적절한
placeholder 경로를 지정하고, 만약 동적으로 로드되어야 하면 해당 템플릿/스크립트에서 조건부로 <img> 요소를 생성하도록 변경하세요.

</div>
<div class="modal-description">
<h2></h2>
<p class="category"></p>
<p class="rate">
<span class="rate-label">평균</span>
<img src="./images/star_filled.png" class="star" /><span
class="rate-value"
></span>
</p>
<hr />
<h3>내 별점</h3>
<div class="movie-rating">
<span class="stars"
><img src="./images/star_empty.png" alt="star" />
<img src="./images/star_empty.png" alt="star" />
<img src="./images/star_empty.png" alt="star" />
<img src="./images/star_empty.png" alt="star" />
<img src="./images/star_empty.png" alt="star" />
</span>
<span class="rating-text">평가해주세요</span>
<span class="rating-score"
>(<span id="rating-value">0</span>/10)</span
>
</div>

<hr />
<h3>줄거리</h3>
<p class="detail"></p>
</div>
</div>
</div>
</div>

<script type="module" src="./src/main.ts"></script>
</body>
</html>
Expand Down
Loading