Skip to content
Open
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
72104b8
fix: 영화 정보에 vote_average 속성이 없는 경우 처리
GamjaIsMine02 Apr 8, 2026
0bf6f7f
fix: 검색 페이지에서 포스터 클릭 시 배경에 표시되는 오류 수정
GamjaIsMine02 Apr 8, 2026
bb5adc5
docs: 요구사항 명세서 변경 및 추가
GamjaIsMine02 Apr 8, 2026
2eb8cf7
feat: 영화 세부정보 모달 구현
GamjaIsMine02 Apr 8, 2026
0438b33
refactor: 더 보기 버튼 기능을 무한 스크롤 방식으로 변경
GamjaIsMine02 Apr 8, 2026
8620a14
ƒeat: 영화 상세 정보를 불러오지 못했을 때 에러 핸들링 추가
GamjaIsMine02 Apr 8, 2026
f9b62aa
ƒeat: 상세 정보가 없는 경우 에러 문자열 추가 & ESC로 모달을 닫을 수 있도록 기능 구현
GamjaIsMine02 Apr 8, 2026
3bf96cb
feat: 모달이 표시된 상태에서 스크롤 방지 기능 구현
GamjaIsMine02 Apr 8, 2026
8d395d3
refactor: getElement 함수 사용 통일 및 불필요한 함수 제거
GamjaIsMine02 Apr 8, 2026
5485d2f
feat: 사용자 별점 기능 UI 구성 및 스타일 추가
GamjaIsMine02 Apr 8, 2026
6f080aa
feat: 사용자 별점 기능 구현 및 UI 업데이트
GamjaIsMine02 Apr 8, 2026
edc1c22
feat: 사용자 별점 기능을 위한 로컬 스토리지 저장 및 불러오기 기능 추가
GamjaIsMine02 Apr 9, 2026
e44c031
feat: 사용자 별점 기능을 위한 사용자 평점 렌더링 로직을 userRatingView로 이동 및 스타일 수정
GamjaIsMine02 Apr 9, 2026
5f77764
docs: 모달 및 별점 기능 요구사항을 완료로 업데이트
GamjaIsMine02 Apr 9, 2026
66812b8
refactor: 모달 관련 로직을 modalView로 이동 및 movieListView에서 제거
GamjaIsMine02 Apr 9, 2026
d08842f
feat: 영화 목록 및 모달 UI 개선, 반응형 디자인 추가
GamjaIsMine02 Apr 9, 2026
e5397fc
docs: 반응형 디자인 요구사항을 완료로 업데이트
GamjaIsMine02 Apr 9, 2026
ae2976d
refactor: 영화 제목 표시를 위한 HTML 수정 및 모바일, 태블릿 스타일 일부 수정
GamjaIsMine02 Apr 9, 2026
935fe32
feat: 모달 테스트 및 영화 상세 정보 관련 API 모킹 추가
GamjaIsMine02 Apr 9, 2026
6bb8e4a
feat: 영화 목록 및 UI 개선을 위한 스타일 수정 및 불필요한 코드 제거
GamjaIsMine02 Apr 9, 2026
480ecdc
feat: 모달 배경 패딩 추가 및 스타일 일관성 개선
GamjaIsMine02 Apr 9, 2026
8bd5001
feat: 영화 상세 정보 요청 실패 시 에러 메시지 표시 기능 추가 및 스크롤로 영화 목록 추가 기능 개선
GamjaIsMine02 Apr 9, 2026
72b7f52
feat: 모달 설명 제목 중앙 정렬 스타일 추가
GamjaIsMine02 Apr 9, 2026
980a235
refactir: 네비게이션 바 위치 수정 및 페이지 제목 스타일 수정
GamjaIsMine02 Apr 10, 2026
9de06c3
fix: 스크롤 이벤트에서 빈 영화 목록 처리 및 요구사항 완료로 업데이트
GamjaIsMine02 Apr 10, 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
35 changes: 34 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,47 @@ FE 레벨1 영화 리뷰 미션

### 더 보기 기능

- [x] '더보기' 버튼을 누르면 영화 20개를 추가로 띄운다.
- [x] '더보기' 버튼을 누르면 영화 20개를 추가로 띄운다. [요구사항 변경]
- [x] 더 보기 기능을 페이징하는 방식에서 무한 스크롤 방식으로 변경한다.
Comment on lines +19 to +20
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

문서 기준이 아직 버튼 방식과 무한 스크롤 방식으로 섞여 있습니다.

여기서는 무한 스크롤로 정리됐는데, 같은 README의 Line 66과 Line 72는 아직 "더 보기 버튼" 기준으로 서술돼 있습니다. 요구사항과 테스트 체크리스트가 서로 다른 기준을 가리키지 않도록 한쪽으로 맞춰두는 편이 좋겠습니다.

Also applies to: 74-77

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

In `@README.md` around lines 19 - 20, Update README.md to make the UI behavior
consistent: replace any remaining "더 보기 버튼" descriptions in the checklist and
requirements (specifically the checklist items around the same section that
reference lines describing the button and items 74-77) with the infinite-scroll
wording used elsewhere, and remove or refactor the checklist entries that assume
a paging/button flow so the document consistently describes the infinite scroll
behavior and its acceptance criteria.


### 검색 기능

- [x] 검색란에 영화 제목을 입력해서 검색 버튼 또는 Enter키를 눌러 필터링된 영화 20개를 띄운다.
- [x] 검색 결과가 존재하지 않으면 "검색 결과가 없습니다" 텍스트를 띄운다.
- [x] 검색 결과 화면에서 검색란에 검색어가 존재하지 않은 상태에서 검색 버튼 또는 Enter키를 눌러 인기순 영화 페이지로 돌아간다.

### 모달

- [x] 영화 포스터나 제목을 클릭하면 영화 세부정보를 모달로 띄운다.
- [x] 모달에는 포스터, 장르, 평점, 내 별점, 줄거리가 포함된다.
- [x] 닫기 버튼 또는 ESC키를 눌러 모달을 닫는다.

### 별점 기능

- [x] 세부정보 모달에서 사용자가 별점을 매길 수 있고,
- [x] 별점은 5개로 구성되어 있으며 한 개당 2점이며 1점 단위는 고려하지 않는다.
- 2점: 최악이예요
- 4점: 별로예요
- 6점: 보통이에요
- 8점: 재미있어요
- 10점: 명작이에요
- [x] 사용자가 페이지를 새로고침하더라도 매긴 별점은 유지한다.

### 에러 처리

- [x] 초기 영화 목록 로딩에 실패하면 에러 메세지를 보여준다.
- [x] 검색 요청이 실패하면 검색 에러 메세지를 보여준다.
- [x] 더 보기 요청이 실패하면 추가 로딩 에러 메세지를 보여준다.

### 반응형 디자인

- [x] 데스크톱 화면의 경우 페이지 가로 사이즈에 따라 한 줄에 표시되는 영화의 수가 5개이다.
- [x] 데스크톱 화면의 경우 모달이 화면 정 가운데 표시된다.
- [x] 태블릿 화면의 경우 페이지 한 줄에 표시되는 영화의 수가 3개이다.
- [x] 태블릿 화면의 경우 모달이 화면 하단에 표시된다.
- [x] 모바일 화면의 경우 페이지 한 줄에 표시되는 영화의 수가 1개이다.
- [x] 모바일 화면의 경우 포스터를 제외한 모달이 화면 하단에 표시된다.

## 테스트 시나리오

### 인기순 영화 페이지 흐름
Expand All @@ -44,8 +71,14 @@ FE 레벨1 영화 리뷰 미션
- [x] 검색 결과가 없으면 "검색 결과가 없습니다" 메시지를 표시한다.
- [x] 검색 결과 화면에서 더 보기 버튼을 누르면 다음 페이지 영화가 기존 목록 뒤에 추가된다.

### 세부정보 모달 & 별점 기능 흐름

- [x] 영화 포스터 또는 제목을 클릭하면 세부 정보 모달을 띄울 수 있고, 별점을 매길 수 있다.
- [x] 닫기 버튼 또는 ESC키를 눌러 모달을 닫을 수 있고, 새로고침해도 사용자가 매긴 별점이 유지된다.

### 에러 메시지 흐름

- [x] 초기 영화 목록 로딩에 실패하면 에러 메시지를 보여준다.
- [x] 검색 요청이 실패하면 검색 에러 메시지를 보여준다.
- [x] 더 보기 요청이 실패하면 추가 로딩 에러 메시지를 보여준다.
- [ ] 모달을 띄울 시 영화 상세 정보 요청이 실패하면 상세 정보 에러 메세지를 보여준다.
25 changes: 24 additions & 1 deletion cypress/e2e/errorTest.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,35 @@ describe("에러 메시지 흐름", () => {
cy.visit("http://localhost:5173");
cy.wait("@getPopularMovies");

cy.get(".display-more-btn").click();
cy.scrollTo("bottom");
cy.wait("@getPopularMovies");

cy.get(".error-text").should(
"contain.text",
"영화를 추가로 불러오지 못했습니다.",
);
});
it("모달을 띄울 시 영화 상세 정보 요청이 실패하면 상세 정보 에러 메시지를 보여준다.", () => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3.

사람마다 상이할 수 있으나 에러 케이스만 모아두는 것 보다, 각 페이지별 시나리오로 보아두는게 더 좋을 것 같아요.

현재는 에러 시나리오가 1~2개이지만 이후 에러 케이스가 늘어나면 각 페이지에서 어떤 에러가 발생 할 수 있는지 확인이 어려울 가능성이 높아요.
테스트 코드는 에러 케이스를 사전에 발견하는 목적도 있지만 해당 스펙이 어떻게 동작하는지 흐름을 파악하기 위한 용도로도 사용 될 수 있다는 것을 알고계시면 좋을 것 같아요.

  • 별도 에러 파일로 나누면 좋을 내용

    • 공통된 에러 케이스 (토큰 만료, not fount 등등)
  • 페이지 별로 묶어두면 좋을 내용

    • 각 페이지 행동 중 발생 할 수 있는 에러 케이스

mockPopularMovies({
1: createMoviePage("인기 영화", 1),
});

cy.intercept("GET", /\/movie\/\d+\?/, {
statusCode: 500,
body: {},
}).as("getMovieDetail");

cy.visit("http://localhost:5173");
cy.wait("@getPopularMovies");

cy.get(".thumbnail-list li").first().find(".thumbnail").click();
cy.wait("@getMovieDetail");

cy.get(".error-text").should(
"contain.text",
"영화 상세 정보를 불러오지 못했습니다.",
);

cy.get(".modal-background").should("not.have.class", "active");
});
});
52 changes: 52 additions & 0 deletions cypress/e2e/modalTest.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { createMovieDetail, mockMovieDetails } from "../support/modalApi";
import { createMoviePage, mockPopularMovies } from "../support/movieApi";

describe("세부정보 모달 & 별점 기능 흐름", () => {
beforeEach(() => {
mockPopularMovies({
1: createMoviePage("인기 영화", 1),
});

mockMovieDetails({
5: createMovieDetail(5, "인기 영화 5"),
});

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

it("영화 포스터 또는 제목을 클릭하면 세부 정보 모달을 띄울 수 있고, 별점을 매길 수 있다.", () => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2;

우선 테스트 시나리오를 유저 행동 기반으로 작성한 점은 정말 좋다고 생각해요.
다만 현재 해당 테스트 시나리오는 한번에 2개를 검증하고 있는데 이렇게 하신 의도가 있을까요?
따로따로 나눠서 해도 되지 않을까 해서요!

만약 별점을 매기기 위해 모달을 반드시 띄워야 했다면 beforEach 등으로 모달을 띄우는 과정을 기본으로 설정해둬도 좋을 것 같아요.

cy.get(".thumbnail-list li").eq(4).find(".thumbnail").click();
cy.wait("@getMovieDetail");

cy.get(".modal-background").should("have.class", "active");
cy.get(".movie-detail h2").should("have.text", "인기 영화 5");
cy.get(".detail").should("contain.text", "인기 영화 5 줄거리");

cy.get(".rate-description").should("have.text", "별점 평가 전");

cy.get('.star-container .star[value="4"]').click();

cy.get(".rate-description").should("have.text", "재미있어요");
cy.get(".rate-percentage").should("have.text", "(8/10)");
});

it("닫기 버튼 또는 ESC키를 눌러 모달을 닫을 수 있고, 새로고침해도 사용자가 매긴 별점이 유지된다.", () => {
cy.get(".thumbnail-list li").eq(4).find(".thumbnail").click();
cy.wait("@getMovieDetail");

cy.get('.star-container .star[value="4"]').click();

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

cy.get(".thumbnail-list li").eq(4).find(".thumbnail").click();
cy.wait("@getMovieDetail");

cy.get(".rate-description").should("have.text", "재미있어요");
cy.get(".rate-percentage").should("have.text", "(8/10)");

Comment on lines +34 to +48
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

테스트 시나리오 이름과 검증 내용이 불일치합니다.

케이스명은 “새로고침해도 별점 유지”인데, 본문에는 실제 reload 단계가 없어 “재오픈 시 유지”만 검증하고 있습니다. 새로고침 후에도 로컬 스토리지 복원이 되는지를 명시적으로 검증해 주세요.

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

In `@cypress/e2e/modalTest.cy.ts` around lines 34 - 48, The test name claims to
verify persistence across a browser reload but the body only reopens the modal;
update the "it" test (the spec string) or better yet add an explicit reload
step: after clicking ".close-modal" and asserting the modal is closed, call
cy.reload() to simulate a page refresh, then reopen the thumbnail
(cy.get(".thumbnail-list li").eq(4).find(".thumbnail").click()) and wait
"@getMovieDetail" and finally assert ".rate-description" and ".rate-percentage"
still show the saved rating; you can also optionally assert localStorage
contains the expected rating key/value before or after reload to prove
restoration.

cy.get("body").type("{esc}");
cy.get(".modal-background").should("not.have.class", "active");
});
});
16 changes: 14 additions & 2 deletions cypress/e2e/popularPageTest.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,22 @@ describe("인기순 영화 페이지 흐름", () => {
cy.get(".top-rated-movie .title").should("have.text", "인기 영화 5");
});

it("더 보기 버튼을 누르면 다음 페이지 영화가 기존 목록 뒤에 추가된다.", () => {
// it("더 보기 버튼을 누르면 다음 페이지 영화가 기존 목록 뒤에 추가된다.", () => {
// cy.get(".thumbnail-list li").should("have.length", 20);

// cy.get(".display-more-btn").click();
// cy.wait("@getPopularMovies");

// cy.get(".thumbnail-list li").should("have.length", 40);
// cy.get(".thumbnail-list li")
// .eq(20)
// .find(".title")
// .should("have.text", "인기 영화 21");
// });
it("페이지 하단까지 스크롤하면 다음 페이지 영화가 기존 목록 뒤에 추가된다.", () => {
cy.get(".thumbnail-list li").should("have.length", 20);

cy.get(".display-more-btn").click();
cy.scrollTo("bottom");
cy.wait("@getPopularMovies");

cy.get(".thumbnail-list li").should("have.length", 40);
Expand Down
4 changes: 2 additions & 2 deletions cypress/e2e/searchPageTest.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ describe("영화 검색 결과 페이지 흐름", () => {
cy.get(".error-text").should("have.text", "검색 결과가 없습니다.");
});

it("검색 결과 화면에서 더 보기 버튼을 누르면 다음 페이지 영화가 기존 목록 뒤에 추가된다.", () => {
it("검색 결과 화면에서 페이지 하단까지 스크롤하면 다음 페이지 영화가 기존 목록 뒤에 추가된다.", () => {
mockSearchMoviePages({
1: createMoviePage("스파이더맨", 1),
2: createMoviePage("스파이더맨", 2),
Expand All @@ -54,7 +54,7 @@ describe("영화 검색 결과 페이지 흐름", () => {

cy.get(".thumbnail-list li").should("have.length", 20);

cy.get(".display-more-btn").click();
cy.scrollTo("bottom");
cy.wait("@searchMovies");

cy.get(".thumbnail-list li").should("have.length", 40);
Expand Down
28 changes: 28 additions & 0 deletions cypress/support/modalApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { MockMovieDetail } from "./types";

export const createMovieDetail = (
id: number,
title: string,
): MockMovieDetail => ({
id,
title,
poster_path: "/test-poster.jpg",
backdrop_path: "/test-backdrop.jpg",
vote_average: 8.5,
overview: `${title} 줄거리`,
release_date: "2024-01-01",
genres: [{ name: "드라마" }],
});

export const mockMovieDetails = (
movieDetails: Record<number, MockMovieDetail>,
) => {
cy.intercept("GET", /\/movie\/\d+\?/, (req) => {
const id = Number(req.url.split("/movie/")[1].split("?")[0]);

req.reply({
statusCode: 200,
body: movieDetails[id],
});
Comment on lines +20 to +26
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

누락된 상세 mock을 200 응답으로 돌려주지 않는 편이 좋습니다.

지금은 movieDetails[id]가 없더라도 200 + undefined body가 내려가서, 실제로는 mock 누락인데 앱 버그처럼 보이는 실패가 납니다. 이 헬퍼에서 없는 ID를 바로 드러내야 테스트 원인을 훨씬 빨리 찾을 수 있습니다.

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

In `@cypress/support/modalApi.ts` around lines 20 - 26, 현재 cy.intercept handler가
movieDetails[id]가 없을 때도 200 + undefined body를 반환하므로 mock 누락을 숨깁니다; in the
intercept callback (the cy.intercept handler) check whether movieDetails[id]
exists and if not return a non-200 error response (e.g., 404 with an explanatory
body) or explicitly fail the request via req.reply with an error, otherwise
return the normal 200 body; reference the intercept callback, movieDetails and
req.reply to locate and update the logic.

}).as("getMovieDetail");
};
8 changes: 1 addition & 7 deletions cypress/support/movieApi.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
type MockMovie = {
id: number;
title: string;
poster_path: string;
backdrop_path: string;
vote_average: number;
};
import { MockMovie } from "./types";

export const createMovie = (id: number, title: string): MockMovie => ({
id,
Expand Down
13 changes: 13 additions & 0 deletions cypress/support/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export type MockMovie = {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2;

실제 API의 응답을 MOCK 데이터로 활용할 것 같은데요. 별도의 MockMovie 타입을 만드는 이유가 있을까요?

id: number;
title: string;
poster_path: string;
backdrop_path: string;
vote_average: number;
};

export type MockMovieDetail = MockMovie & {
overview: string;
release_date: string;
genres: { name: string }[];
};
94 changes: 87 additions & 7 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,29 @@
<body>
<div id="app">
<header>
<div class="search-container">
<input class="search-bar" placeholder="검색어를 입력하세요" />
<button class="search-btn">🔍</button>
<div class="background-container">
<div class="logo-container">
<img src="./templates/images/logo.png" alt="MovieList" />
<div class="search-container">
<input class="search-bar" placeholder="검색어를 입력하세요" />
<button class="search-btn">🔍</button>
</div>
</div>
<div class="overlay" aria-hidden="true">
<img alt="영화 이미지" />
</div>
<div class="top-rated-container">
<div class="top-rated-movie">
<div class="rate">
<img src="./templates/images/star_empty.png" class="star" />
<span class="rate-value">0</span>
</div>
<div class="title"></div>
</div>
</div>
</div>
<div class="background-container"></div>
</header>

<div class="container">
<main>
<section>
Expand All @@ -28,12 +45,11 @@
/>
<p class="error-text"></p>
</div>

<ul class="thumbnail-list"></ul>
</section>
<div class="btn-container">
<!-- <div class="btn-container">
<button type="button" class="display-more-btn">더 보기</button>
</div>
</div> -->
</main>
</div>

Expand All @@ -42,6 +58,70 @@
<p><img src="./templates/images/woowacourse_logo.png" width="180" /></p>
</footer>
</div>

<div class="modal-background" id="modalBackground">
<div class="modal">
<button class="close-modal" id="closeModal">
<img src="./templates/images/modal_button_close.png" />
</button>
Comment on lines +64 to +68
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

모달에 dialog semantics와 닫기 버튼 이름이 빠져 있습니다.

현재 마크업만 보면 보조기기는 이 레이어를 모달 대화상자로 인지하기 어렵고, 닫기 버튼도 이미지뿐이라 용도를 제대로 읽어주지 못할 가능성이 큽니다. 새 핵심 UI인 만큼 dialog 역할과 명확한 접근성 이름은 같이 들어가는 편이 안전합니다.

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

In `@index.html` around lines 62 - 66, Wrap the modal markup with proper
ARIA/dialog semantics: add role="dialog" and aria-modal="true" to the element
with id="modal" (or the same container referenced by id="modalBackground"),
ensure the dialog has an accessible name via aria-labelledby pointing to a
visible heading inside the dialog or aria-label on the dialog, and make the
close control with id="closeModal" keyboard-and-screenreader friendly by giving
the button an accessible name (e.g., aria-label="Close" or visible text) and
ensure the <img> inside has an appropriate alt attribute or is aria-hidden if
decorative; also consider adding tabindex="-1" on the dialog container so focus
can be programmatically moved into the dialog.

<div class="modal-container">
<div class="modal-image">
<img
src="https://image.tmdb.org/t/p/original//pmemGuhr450DK8GiTT44mgwWCP7.jpg"
/>
</div>
<div class="modal-description">
<div class="movie-detail">
<h2></h2>
<p class="category"></p>
<div class="rate">
<span>평균 &nbsp;</span>
<img src="./templates/images/star_filled.png" class="star" />
<p class="rate_average"></p>
</div>
</div>
<hr />
<p class="rate-title">내 별점</p>
<div class="user-rate">
<div class="star-container">
<img
src="./templates/images/star_empty.png"
class="star"
value="1"
/>
<img
src="./templates/images/star_empty.png"
class="star"
value="2"
/>
<img
src="./templates/images/star_empty.png"
class="star"
value="3"
/>
<img
src="./templates/images/star_empty.png"
class="star"
value="4"
/>
<img
src="./templates/images/star_empty.png"
class="star"
value="5"
/>
</div>
<div class="rate-detail">
<span class="rate-description"></span>
<span class="rate-percentage"></span>
</div>
Comment on lines +87 to +118
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

별점 컨트롤이 키보드로는 조작되지 않습니다.

클릭 가능한 <img>만으로 별점을 만들면 Tab/Enter/Space 흐름에서 평점을 줄 수 없습니다. 이 기능은 핵심 사용자 시나리오라서, 최소한 포커스 가능한 버튼이나 라디오 그룹 semantics가 필요합니다.

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

In `@index.html` around lines 85 - 116, The star rating is currently built from
non-focusable <img> elements (class "star" inside "star-container") so keyboard
users cannot Tab/Enter/Space to set a rating; replace those images with
accessible, focusable controls (preferably an input[type="radio"] group with the
same name or button elements with role="radio") inside the .star-container, give
each control a value and aria-label, mark the container as a radiogroup
(role="radiogroup") and update each option's aria-checked when selected, ensure
Enter/Space keyboard handlers are present and visual focus styles remain, and
update .rate-description and .rate-percentage from the new control change
events.

</div>
<hr />
<p class="detail-title">줄거리</p>
<p class="detail"></p>
</div>
</div>
</div>
</div>
</body>
</html>

Expand Down
Loading