Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
104 commits
Select commit Hold shift + click to select a range
9f0c0c8
docs: 요구사항 목록 정리
kojesung Mar 31, 2026
44ef190
chore: 템플릿 적용
kojesung Mar 31, 2026
18bd7d4
feat: 검색 UI 구현
kojesung Mar 31, 2026
0e76463
docs: 요구 사항 목록 업데이트
kojesung Mar 31, 2026
f5b3b80
delete: 정렬 탭 삭제
kojesung Mar 31, 2026
1818cce
feat: 더 보기 버튼 UI 구현
kojesung Mar 31, 2026
cd41299
feat: 영화 리스트 배경 색 변경
kojesung Mar 31, 2026
4e8951f
feat: 검색 결과 없음 UI 구현
kojesung Mar 31, 2026
971e972
feat: 조회 에러 UI 구현
kojesung Mar 31, 2026
8d67a70
feat: 검색 부분 배너에서 분리
kojesung Mar 31, 2026
9cae087
feat: 검색 시 배너 삭제
kojesung Mar 31, 2026
8c410f1
refactor: 배너 영역 숨김 방식 변경
kojesung Mar 31, 2026
6c8bf98
feat: sub-title 폰트 스타일링
kojesung Mar 31, 2026
87b2bb5
feat: 검색 이벤트 헨들러 추가
kojesung Mar 31, 2026
5ea6373
feat: 검색 결과별 result-section 컨텐츠 렌더링
kojesung Mar 31, 2026
6972e6f
feat: 스켈레톤 UI 구현
kojesung Mar 31, 2026
33bcec4
feat: 공통 fetch 처리 함수 구현
kojesung Mar 31, 2026
063162a
refactor: fetcher defaultOption Authorization 제거
kojesung Apr 1, 2026
3d795b9
chore: vitest 환경 설정
kojesung Apr 1, 2026
f730e37
test: fetcher 테스트 코드 작성
kojesung Apr 1, 2026
9fe3c54
refactor: fetcher options 옵셔널로 변경
kojesung Apr 1, 2026
c4c9026
feat: tmdbFetcher 구현
kojesung Apr 1, 2026
1c32529
feat: getPopularMovies 구현
kojesung Apr 1, 2026
0e55c8b
feat: getPopularMovies 결과 메인에 렌더링
kojesung Apr 1, 2026
ac4e310
refactor: searchParams 생성 유틸 분리
kojesung Apr 1, 2026
85f7166
feat: 검색 api 연동
kojesung Apr 1, 2026
3b81fae
refactor: 렌더 함수 폴더 분리
kojesung Apr 1, 2026
2c53fbb
refactor: renderResultSectionContent props 전달 방식 변경
kojesung Apr 1, 2026
68687a9
refactor: renderThumbnailList props 전달 방식 변경
kojesung Apr 1, 2026
065cafe
refactor: 이벤트 헨들러 폴더 분리
kojesung Apr 1, 2026
2d02303
feat: getPopularMovies 에러 헨들링 추가
kojesung Apr 1, 2026
d955370
feat: 배너 렌더링 구현
kojesung Apr 1, 2026
212dad7
feat: isLastPage로 더 보기 버튼 조건부 렌더링
kojesung Apr 1, 2026
ceb199f
refactor: 검색과 메인 태그 및 선언 방식 분리
kojesung Apr 1, 2026
e99eb22
feat: main 더보기 버튼 헨들러 구현 및 연동
kojesung Apr 1, 2026
f19f851
feat: 검색 페이지 더보기 버튼 헨들러 구현 및 연동
kojesung Apr 1, 2026
9af1055
fix: 검색과 메인 더보기 api 호출 함수 수정
kojesung Apr 2, 2026
5a9ad1e
feat: 로고 클릭 시 메인화면으로
kojesung Apr 2, 2026
8e5f36c
docs: 요구 사항 목록 업데이트
kojesung Apr 2, 2026
b0ec7d3
feat: search input value와 parameter 동기화하여 관리
kojesung Apr 2, 2026
90caada
fix: 이전 검색 기록 남아있는 버그 수정
kojesung Apr 2, 2026
0838ea1
feat: 검색 페이지 api 호출 결과 에러 헨들링 추가
kojesung Apr 2, 2026
cfc3fdf
refactor: render 함수 분리
kojesung Apr 2, 2026
f87e882
refactor: logo 클릭 시 파라미터 초기화
kojesung Apr 2, 2026
7ad48fe
feat: input 태그 자동완성 기능 off 및 유효하지 않은 이미지 fallback 처리
kojesung Apr 2, 2026
1cc3d63
feat: 영화 검색 창 빈 문자열일 때 메인 화면으로 이동
kojesung Apr 2, 2026
417dd03
fix: 빈 문자열 입력 시 메인으로 이동 함수 버그 수정(파라미터 검증)
kojesung Apr 2, 2026
9e7edff
docs: E2E 테스트 시나리오 작성
kojesung Apr 2, 2026
ef84766
test: 테스트용 더미 데이터 및 모킹 함수 생성
kojesung Apr 2, 2026
f09a5ba
test: 메인 구성 요소 렌더링 테스트
kojesung Apr 2, 2026
04b0cbd
test: 영화 검색 동작 테스트
kojesung Apr 2, 2026
559dfdf
test: 더 보기 버튼 클릭 동작 테스트
kojesung Apr 2, 2026
4ff24af
test: 검색 작업 후 메인으로 복귀 동작 테스트
kojesung Apr 2, 2026
95ce7d8
docs: E2E 테스트 요구 사항 목록 업데이트
kojesung Apr 2, 2026
58d748f
feat: TMDB 에러 클래스 생성하여 tmdbFetcher에서 에러 반환 분기
kojesung Apr 2, 2026
46a7a51
feat: api 에러 응답 타입별 에러 메세지 분기 처리 추가
kojesung Apr 2, 2026
c2a6a00
refactor: 해결된 TODO 주석 및 불필요한 필드 전달 삭제
kojesung Apr 2, 2026
d788fbc
feat: 푸터 하단 고정 및 레이아웃 수정
kojesung Apr 2, 2026
d21b705
fix: 홈으로 리다이렉션 시 base url 유지하도록 수정
kojesung Apr 2, 2026
bfde8e4
refactor: image 폴더 public 내부로 이동
kojesung Apr 2, 2026
aee600f
fix: 경로 public/ 참조 제거
kojesung Apr 2, 2026
537b255
feat: 에러 렌더 함수 생성
kojesung Apr 4, 2026
d995a59
feat: Error 타입별 메세지 분기 유틸 생성
kojesung Apr 4, 2026
e8e21d1
feat: 더보기 api 에러 헨들링 추가
kojesung Apr 4, 2026
9982917
feat: renderBanner movie가 없을 때 배너 렌더링하지 않도록 분기 처리 추가
kojesung Apr 4, 2026
3f616e8
feat: SearchUI에서 searchInput 요소 유무에 따라 early return 분기 조건 추가
kojesung Apr 6, 2026
73dd1a3
feat: response.json 실패 예외 처리 추가
kojesung Apr 6, 2026
5ae53f1
refactor: 불필요한 try-catch 문 제거
kojesung Apr 6, 2026
73f0014
Merge branch 'step1' into step2
kojesung Apr 9, 2026
8e060ef
refactor: banner 조건부로 렌더링하도록 수정
kojesung Apr 9, 2026
19c8538
feat: MainUI 상태 관리하는 MainUI 클래스 생성
kojesung Apr 9, 2026
2620717
feat: MainUI error type일 때 메세지 전달 추가
kojesung Apr 9, 2026
ad6225a
feat: SearchUI 상태 관리하는 SearchUI 클래스 생성
kojesung Apr 9, 2026
57e0ac6
feat: searchParams에서 keyword 불러오는 유틸 생성
kojesung Apr 9, 2026
2edb92a
feat: searchParams의 page key 관리 유틸 생성
kojesung Apr 9, 2026
c872de9
feat: UI관리 클래스에 seeMore 메서드 구현
kojesung Apr 9, 2026
1a72a45
refactor: main 이벤트 리스너에 UI 상태 관리 클래스의 메서드 호출 방식으로 변경
kojesung Apr 10, 2026
e074a72
fix: SearchUI에 resultSection 패딩 요소 추가
kojesung Apr 10, 2026
1782643
feat: MainUI 배너 요소 렌더링 추가
kojesung Apr 10, 2026
b829bfa
feat: searchParams set 유틸 추가
kojesung Apr 10, 2026
17ee5f0
feat: SearchUI subtitle 추가
kojesung Apr 10, 2026
3ebd2e2
fix: SearchUI에서 load 메서드 호출 시 이전 기록 삭제
kojesung Apr 10, 2026
7b00482
docs: step2 요구사항 목록 작성
kojesung Apr 10, 2026
f631959
feat: api 호출 트리거 역할의 태그 추가
kojesung Apr 11, 2026
f2bab66
feat: seeMore 메서드에서 마지막 페이지 플래그 반환 추가
kojesung Apr 11, 2026
dcde1b5
feat: 누락된 파일 추가(마지막 페이지 플래그 반환 추가)
kojesung Apr 11, 2026
9215199
feat: IntersectionObserver 기반 더보기 api 호출
kojesung Apr 11, 2026
b5c2254
docs: 요구 사항 목록 업데이트
kojesung Apr 11, 2026
f3e04db
feat: Modal 상태 관리 클래스 구현
kojesung Apr 11, 2026
1947027
feat: thumbnail요소에 이벤트 리스너 부여하여 모달 렌더링
kojesung Apr 11, 2026
140a004
feat: 영화 상세 조회 api
kojesung Apr 11, 2026
b003897
feat: ModalUI 스켈레톤 추가
kojesung Apr 11, 2026
3b28260
feat: ESC로 모달 창 닫기 구현
kojesung Apr 11, 2026
c5c6d41
feat: 모달 내 별점 UI 추가 및 sub title 추가
kojesung Apr 11, 2026
f468fa8
feat: 별점 관리 Repository 클래스 구현
kojesung Apr 12, 2026
84e0e0f
feat: 별점 관리 repository 의존성 주입 및 업데이트 적용
kojesung Apr 12, 2026
fcdb398
docs: 기능 목록 업데이트
kojesung Apr 12, 2026
8d5542c
feat: thumbnail-list break point 추가 및 리스트 래퍼 요소에 width 제거
kojesung Apr 12, 2026
024b4e3
refactor: 헤더 레이아웃 수정
kojesung Apr 13, 2026
d992906
feat: 모달 UI 반응형 대응
kojesung Apr 13, 2026
ca9731e
delete: 리팩토링 이후 잔여 파일 삭제
kojesung Apr 13, 2026
d6900f5
feat: handleSearch 로직에 input이 비었을 때 main 렌더링 로직 추가
kojesung Apr 13, 2026
5109e6d
feat: E2E 테스트용 인터셉터 및 mock response 생성
kojesung Apr 13, 2026
a7afd66
test: 모달 관련 E2E 테스트 작성
kojesung 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
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,22 @@
- main-thumbnail-list 렌더링
- banner 렌더링
- keyword parameter 제거

---

# 상세 정보 & UI/UX 개선하기

- [x] 더 보기 -> 무한스크롤
- 메인 페이지
- 검색 페이지
- [x] 영화 상세정보 조회
- 상세정보 UI
- ESC키를 누르면 닫히는 모달 창
- 상세정보 요청 API
- [x] 별점 매기기
- 2점 단위 별 5개로 구성
- API 연동 고려한 localsStorage 활용
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

용어 오탈자 수정이 필요합니다

localsStoragelocalStorage로 수정하는 편이 문서 신뢰성과 검색성을 높입니다.

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

In `@README.md` at line 145, Fix the typo in the README by changing the term
"localsStorage" to "localStorage" wherever it appears (notably the line that
currently reads "API 연동 고려한 localsStorage 활용") so the document consistently and
correctly uses the web-standard identifier localStorage for clarity and
searchability.

- [ ] 반응형 웹
- 데스크톱
- 태블릿
- 모바일
125 changes: 125 additions & 0 deletions cypress/e2e/Modal.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import {
interceptPopularPage1,
interceptMovieDetail,
interceptMovieDetailWithDelay,
} from "./spec";

describe("모달", () => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

스켈레톤 표시, 데이터 렌더링, 닫기(X/배경/ESC), 별점 유지까지. 모달 E2E 테스트가 매우 꼼꼼하네요👍

beforeEach(() => {
interceptPopularPage1();
cy.visit("/");
cy.wait("@getPopularPage1");
});

it("썸네일 클릭 시 모달이 열린다", () => {
interceptMovieDetail();
cy.get("li[id^='movie-']").first().click();
cy.get("#modalBackground").should("not.have.class", "hidden");
});

it("모달이 열리면 API 응답 전 스켈레톤이 표시된다", () => {
interceptMovieDetailWithDelay(500);
cy.get("li[id^='movie-']").first().click();
cy.get("#modal-title").should("have.class", "modal-skeleton");
cy.get("#modal-category").should("have.class", "modal-skeleton");
cy.get("#modal-rate").should("have.class", "modal-skeleton");
cy.get("#modal-detail").should("have.class", "modal-skeleton");
});

it("API 응답 후 영화 정보가 렌더링된다", () => {
interceptMovieDetail();
cy.get("li[id^='movie-']").first().click();
cy.wait("@getMovieDetail");
cy.get("#modal-title").should("contain.text", "영화1");
cy.get("#modal-category")
.should("contain.text", "2024")
.and("contain.text", "액션");
cy.get("#modal-rate").should("contain.text", "8.0");
cy.get("#modal-detail").should("contain.text", "줄거리1");
});

it("API 응답 후 스켈레톤이 제거된다", () => {
interceptMovieDetail();
cy.get("li[id^='movie-']").first().click();
cy.wait("@getMovieDetail");
cy.get("#modal-title").should("not.have.class", "modal-skeleton");
cy.get("#modal-category").should("not.have.class", "modal-skeleton");
cy.get("#modal-rate").should("not.have.class", "modal-skeleton");
cy.get("#modal-detail").should("not.have.class", "modal-skeleton");
});

describe("모달 닫기", () => {
beforeEach(() => {
interceptMovieDetail();
cy.get("li[id^='movie-']").first().click();
cy.wait("@getMovieDetail");
});

it("X 버튼 클릭 시 모달이 닫힌다", () => {
cy.get("#closeModal").click();
cy.get("#modalBackground").should("have.class", "hidden");
});

it("배경 클릭 시 모달이 닫힌다", () => {
cy.get("#modalBackground").click({ force: true });
cy.get("#modalBackground").should("have.class", "hidden");
});

it("ESC 키 입력 시 모달이 닫힌다", () => {
cy.get("body").type("{esc}");
cy.get("#modalBackground").should("have.class", "hidden");
});
});

describe("별점", () => {
beforeEach(() => {
interceptMovieDetail();
cy.get("li[id^='movie-']").first().click();
cy.wait("@getMovieDetail");
});

it("별을 클릭하면 해당 별까지 채워진다", () => {
cy.get("#modal-my-rate-star-3").click();
cy.get("#modal-my-rate-star-1")
.should("have.attr", "src")
.and("include", "star_filled");
cy.get("#modal-my-rate-star-2")
.should("have.attr", "src")
.and("include", "star_filled");
cy.get("#modal-my-rate-star-3")
.should("have.attr", "src")
.and("include", "star_filled");
cy.get("#modal-my-rate-star-4")
.should("have.attr", "src")
.and("include", "star_empty");
cy.get("#modal-my-rate-star-5")
.should("have.attr", "src")
.and("include", "star_empty");
});

it("별을 클릭하면 별점 레이블이 변경된다", () => {
cy.get("#modal-my-rate-star-3").click();
cy.get("#modal-rate-review").should("contain.text", "보통이에요");
cy.get("#modal-rate-points").should("contain.text", "6/10");
});

it("모달을 닫고 다시 열어도 별점이 유지된다", () => {
cy.get("#modal-my-rate-star-4").click();
cy.get("#closeModal").click();

interceptMovieDetail();
cy.get("li[id^='movie-']").first().click();
cy.wait("@getMovieDetail");

cy.get("#modal-my-rate-star-1")
.should("have.attr", "src")
.and("include", "star_filled");
cy.get("#modal-my-rate-star-4")
.should("have.attr", "src")
.and("include", "star_filled");
cy.get("#modal-my-rate-star-5")
.should("have.attr", "src")
.and("include", "star_empty");
});
});
});
13 changes: 13 additions & 0 deletions cypress/e2e/spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const POPULAR_API = "**/movie/popular**";
const SEARCH_API = "**/search/movie**";
const MOVIE_DETAIL_API = /\/movie\/\d+/;

export const interceptPopularPage1 = () => {
cy.intercept("GET", POPULAR_API, { fixture: "popularMoviesPage1.json" }).as(
Expand All @@ -24,3 +25,15 @@ export const interceptSearchPage2 = () => {
"getSearchPage2",
);
};

export const interceptMovieDetail = () => {
cy.intercept("GET", MOVIE_DETAIL_API, { fixture: "movieDetail.json" }).as(
"getMovieDetail",
);
};

export const interceptMovieDetailWithDelay = (delay: number) => {
cy.intercept("GET", MOVIE_DETAIL_API, (req) => {
req.reply({ delay, fixture: "movieDetail.json" });
}).as("getMovieDetail");
};
31 changes: 31 additions & 0 deletions cypress/fixtures/movieDetail.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"id": 1,
"title": "영화1",
"poster_path": "/poster1.jpg",
"backdrop_path": "/back1.jpg",
"vote_average": 8.0,
"overview": "줄거리1 상세 설명입니다.",
"release_date": "2024-01-01",
"genres": [
{ "id": 28, "name": "액션" },
{ "id": 12, "name": "어드벤처" }
],
"adult": false,
"belongs_to_collection": null,
"budget": 0,
"homepage": "",
"imdb_id": "tt0000001",
"origin_country": ["KR"],
"original_language": "ko",
"original_title": "Movie1",
"popularity": 100,
"production_companies": [],
"production_countries": [],
"revenue": 0,
"runtime": 120,
"spoken_languages": [],
"status": "Released",
"tagline": "",
"video": false,
"vote_count": 1000
}
91 changes: 83 additions & 8 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@
<link rel="stylesheet" href="./styles/reset.css" />
<link rel="stylesheet" href="./styles/main.css" />
<link rel="stylesheet" href="./styles/thumbnail.css" />
<link rel="stylesheet" href="./styles/modal.css" />
<title>영화 리뷰</title>
</head>
<body>
<div id="app">
<header>
<div class="logo-input-wrapper">
<button class="logo" id="logo">
<img src="./public/images/logo.png" alt="MovieList" />
<img src="./images/logo.png" alt="MovieList" />
</button>
<div class="movie-search">
<input
Expand All @@ -23,7 +24,7 @@
autocomplete="off"
/>
<button id="search-button">
<img src="./public/images/search.png" alt="검색" />
<img src="./images/search.png" alt="검색" />
</button>
</div>
</div>
Expand All @@ -32,7 +33,7 @@
<div class="top-rated-container">
<div class="top-rated-movie">
<div class="rate">
<img src="./public/images/star_empty.png" class="star" />
<img src="./images/star_empty.png" class="star" />
<span class="rate-value"></span>
</div>
<h3 class="title"></h3>
Expand All @@ -48,7 +49,7 @@ <h2 class="sub-title" id="sub-title">지금 인기 있는 영화</h2>

<ul class="thumbnail-list hidden" id="main-thumbnail-list"></ul>
<ul class="thumbnail-list hidden" id="search-thumbnail-list"></ul>

<div id="main-error-container"></div>
<ul class="thumbnail-list hidden" id="skeleton-list">
<li class="skeleton">
<div class="item">
Expand Down Expand Up @@ -188,12 +189,12 @@ <h2 class="sub-title" id="sub-title">지금 인기 있는 영화</h2>
</ul>

<div class="unexpected-container hidden" id="empty-container">
<img src="./public/images/으아아행성이.png" alt="" />
<img src="./images/으아아행성이.png" alt="" />
<p>검색 결과가 없습니다.</p>
</div>

<div class="unexpected-container hidden" id="error-container">
<img src="./public/images/으아아행성이.png" alt="" />
<img src="./images/으아아행성이.png" alt="" />
<p>🚨 에러 발생 🚨</p>
<button>재시도</button>
</div>
Expand All @@ -207,14 +208,88 @@ <h2 class="sub-title" id="sub-title">지금 인기 있는 영화</h2>
</section>
</main>
</div>

<div id="sentinel"></div>
<footer class="footer">
<p>&copy; 우아한테크코스 All Rights Reserved.</p>
<p>
<img src="./public/images/woowacourse_logo.png" width="180" />
<img src="./images/woowacourse_logo.png" width="180" />
</p>
</footer>
</div>
<div class="modal-background active" id="modalBackground">
<div class="modal" id="modal">
<button class="close-modal" id="closeModal">
<img src="./images/modal_button_close.png" />
</button>
Comment on lines +221 to +223
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

닫기 버튼에 접근 가능한 이름을 넣어 주세요.

아이콘만 들어 있는 버튼인데 텍스트나 aria-label이 없어 스크린리더에서는 그냥 “button”으로 읽힙니다. 모달을 닫는 핵심 컨트롤이라 이름이 꼭 필요합니다.

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

In `@index.html` around lines 221 - 223, The close button (<button
class="close-modal" id="closeModal">) lacks an accessible name; update the
element to provide one by adding an appropriate aria-label (e.g.,
aria-label="Close modal") or visible text inside the button, ensuring the label
describes its action (closing the modal) while keeping the existing <img
src="./images/modal_button_close.png"> for visuals.

<div class="modal-container">
<div class="modal-image modal-skeleton-image" id="modal-image">
<img src="" alt="" />
Comment on lines +225 to +226
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:

# First, let's check the index.html file around lines 225-226
head -n 230 index.html | tail -n 20

Repository: woowacourse/javascript-movie-review

Length of output: 821


🏁 Script executed:

# Check if there's a linting configuration in the repository
find . -maxdepth 2 -type f \( -name ".eslintrc*" -o -name "eslintignore" -o -name ".htmlhintrc" -o -name "w3crc" -o -name "prettier*" \)

Repository: woowacourse/javascript-movie-review

Length of output: 109


🏁 Script executed:

# Search for JavaScript that sets the modal image src
rg "modal-image|modal.*src|querySelector.*modal" -A 3 -B 1

Repository: woowacourse/javascript-movie-review

Length of output: 2356


🏁 Script executed:

# Check .htmlhintrc configuration
cat .htmlhintrc

Repository: woowacourse/javascript-movie-review

Length of output: 232


🏁 Script executed:

# Check .eslintrc.json configuration
cat .eslintrc.json

Repository: woowacourse/javascript-movie-review

Length of output: 268


🏁 Script executed:

# Check if there are any other relevant files related to linting
rg "src=\"\"" index.html

Repository: woowacourse/javascript-movie-review

Length of output: 112


🌐 Web query:

empty src attribute browser side effects re-request document

💡 Result:

An empty src attribute on an <img> tag (src="") triggers a network request to the current document's URL in most browsers, potentially causing performance issues, traffic spikes, or state corruption via cookies/sessions. This is due to URI resolution treating "" as a relative path resolving to the base URL per RFC 3986. Key browser behaviors (historical and recent sources up to 2026): - Chrome/Safari/WebKit: Requests the page itself. - Firefox: Fixed in 3.5+ (no request). - IE: Requests the page directory. - Opera: Ignores. HTML spec requires non-empty valid URL for src; empty src shows broken image icon but still fetches in many engines. Recent WHATWG issues (2026) discuss extending no-fetch to whitespace-only src to match Chromium/WebKit interop. No full document reload occurs; it's an additional subresource request visible in Network tab (test: open DevTools Network, load <img src="">). Use data URI, about:blank, or omit src/JS-set later to avoid. Workarounds: - src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" - Omit src, set via JS: img.src = "image.jpg" - Transparent 1x1 placeholder. To verify: Chrome DevTools Network tab shows the request when loading <img src="">.

Citations:


src="" 속성을 제거하고 JavaScript에서 설정하도록 변경하세요.

.htmlhintrc"src-not-empty": true 규칙에 위배되며, 빈 src=""는 Chrome, Safari 등 주요 브라우저에서 현재 문서를 재요청하는 부수 효과를 유발합니다. ModalUI.ts의 동적 설정 로직은 올바르게 구현되어 있으므로, HTML 마크업에서 src="" 속성을 제거하고 이미지가 준비된 시점에만 JavaScript에서 src를 설정하면 됩니다.

🧰 Tools
🪛 HTMLHint (1.9.2)

[error] 226-226: 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` around lines 225 - 226, HTML에 빈 src="" 속성이 있어 브라우저의 불필요한 문서 재요청을
유발하므로 <img> 엘리먼트의 src 속성(아이디 "modal-image" 또는 클래스 "modal-image
modal-skeleton-image")을 마크업에서 제거하고, ModalUI.ts의 기존 동적 로직을 사용해 이미지가 준비된 시점에만
JavaScript에서 src를 설정하도록 변경하세요; 마크업에서는 alt는 유지하고 src는 삭제한 뒤 ModalUI.ts의 이미지 로딩/설정
코드에서 document.getElementById("modal-image") 또는 해당 셀렉터로 엘리먼트를 찾아 src를 할당하도록 수정하면
됩니다.

</div>
<div class="modal-description" id="modal-description">
<div class="modal-header">
<h2
id="modal-title"
class="modal-skeleton modal-skeleton-title"
></h2>
<p
id="modal-category"
class="modal-skeleton modal-skeleton-category"
></p>
<p class="rate">
<img src="./images/star_filled.png" class="star" /><span
id="modal-rate"
class="modal-skeleton modal-skeleton-rate"
></span>
</p>
</div>
<hr />
<h3 id="modal-my-rate" class="modal-sub-title">내 별점</h3>
<p id="modal-rate-stars" class="modal-rate-stars">
<img
src="./images/star_empty.png"
class="modal-my-rate-star"
id="modal-my-rate-star-1"
/>
<img
src="./images/star_empty.png"
class="modal-my-rate-star"
id="modal-my-rate-star-2"
/>
<img
src="./images/star_empty.png"
class="modal-my-rate-star"
id="modal-my-rate-star-3"
/>
<img
src="./images/star_empty.png"
class="modal-my-rate-star"
id="modal-my-rate-star-4"
/>
<img
src="./images/star_empty.png"
class="modal-my-rate-star"
id="modal-my-rate-star-5"
/>
<span>
<span class="modal-rate-review" id="modal-rate-review"
>명작이에요</span
>
<span class="modal-rate-points" id="modal-rate-points"
>(8/10)</span
></span
>
</p>
<hr />
<h3 id="modal-detail-title" class="modal-sub-title">줄거리</h3>
<p
class="detail"
id="modal-detail"
class="modal-skeleton modal-skeleton-detail"
></p>
Comment on lines +284 to +288
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

🧩 Analysis chain

🏁 Script executed:

find . -name "index.html" -type f

Repository: woowacourse/javascript-movie-review

Length of output: 91


🏁 Script executed:

sed -n '280,290p' ./index.html

Repository: woowacourse/javascript-movie-review

Length of output: 388


class 속성이 중복되어 첫 번째 클래스가 적용되지 않습니다.

동일 요소에 class 속성이 두 번 선언되면 마지막 선언이 이전 선언을 덮어씁니다. 현재 "detail" 클래스가 "modal-skeleton modal-skeleton-detail"로 인해 무시되므로, 줄거리 영역의 스켈레톤 로딩 상태가 의도대로 렌더링되지 않을 수 있습니다. 두 클래스를 하나의 class 속성으로 통합하세요.

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

In `@index.html` around lines 284 - 288, 요소 id="modal-detail"에 class 속성이 중복되어 첫번째
클래스 값("detail")가 덮어씌워지는 문제입니다; id="modal-detail"를 가진 <p> 엘리먼트를 찾아 class 속성들을 하나로
합쳐서 예: "detail modal-skeleton modal-skeleton-detail"처럼 단일 class 속성으로 통합하고 중복된
class 속성 선언을 제거하세요.

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