-
Notifications
You must be signed in to change notification settings - Fork 155
[2단계 - 상세 정보 & UI/UX 개선하기] 찰리 미션 제출합니다. #292
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: yuncic
Are you sure you want to change the base?
Changes from 51 commits
c256ca3
d51c3f2
0292b66
edc0811
1abceb5
32daaab
de28417
ae0d491
a316471
7f8b604
c8156ec
ee830e4
d21e1be
e743ebc
8da7434
5cd3817
74150b9
00b3354
b9d4435
1dcfc6a
9d9c9e3
5d5098f
df6e2ed
f281375
dd13f81
350ace7
011997c
f8e44f8
0ed101c
2f54796
0504185
b20fbd2
fdad549
8b4d0c6
2254261
c564d97
51249f1
856e62a
6127209
a56ffcf
fcbdab6
e03ba10
6d5ac60
4cb37f6
40af500
1d39687
1630e6a
959a082
dca0674
b49c1e1
e796c59
de26508
384a13f
1d6520e
517e398
f1d8509
5ad7506
97d46db
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| VITE_API_KEY=293805c00692398c046c66e6b7b51d4d | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,8 @@ | ||
| { | ||
| "endOfLine": "auto" | ||
| "endOfLine": "auto", | ||
| "printWidth": 120, | ||
| "tabWidth": 4, | ||
| "singleQuote": true, | ||
| "trailingComma": "all", | ||
| "semi": false | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,51 @@ | ||
| # javascript-movie-review | ||
|
|
||
| FE 레벨1 영화 리뷰 미션 | ||
| FE 레벨1 영화 리뷰 미션 step2 | ||
|
|
||
| ## 요구사항 명세서 | ||
|
|
||
| ## 영화 상세정보 조회 모달 | ||
|
|
||
| ### 이벤트 | ||
|
|
||
| - [x] 클릭시 모달 창 띄우기 | ||
| - [x] 우측 상단 x버튼 혹은 모달 밖 화면 클릭 시 모달 창 끄기 | ||
|
|
||
| ### UI | ||
|
|
||
| - [x] 포스터 클릭시 상세정보 모달 띄우기 | ||
| - [x] 포스터 사진 (poster_path) | ||
| - [x] 제목 (title) | ||
| - [x] 평균 별점 (vote_average) | ||
| - [x] 내 별점 | ||
| - [x] 줄거리 (overview) | ||
|
|
||
| ### 내 별점 | ||
|
|
||
| - local storage 사용 | ||
|
|
||
| * [x] 사용자는 영화에 대해 별점을 줄 수 있으며 새로고침하더라도 사용자가 남긴 별점은 유지되어야 한다. | ||
| * [x] 별점은 5개로 구성되어 있으며 한 개당 2점이며 1점 단위는 고려하지 않는다. | ||
| * [x] 2점: 최악이예요 | ||
| * [x] 4점: 별로예요 | ||
| * [x] 6점: 보통이에요 | ||
| * [x] 8점: 재미있어요 | ||
| * [x] 10점: 명작이에요 | ||
|
|
||
| ## 무한 스크롤 | ||
|
|
||
| - [x] 더보기 버튼 대신 무한스크롤 적용 | ||
|
|
||
| ## 반응형 웹 | ||
|
|
||
| - 분기별로 잘라서 3가지 타입으로 진행 | ||
|
|
||
| - [x] 데스크톱 | ||
| - [x] 태블릿 | ||
| - [x] 모바일 | ||
|
|
||
| ## E2E 테스트 | ||
|
|
||
| - [x] 포스터를 클릭하면 모달창이 뜬다. | ||
| - [x] 내 별점을 클릭하면 별점이 적용이 된다. | ||
| - [x] 스크롤을 끝까지 내리면 다음 영화 리스트가 나온다. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| describe('click poster test', () => { | ||
| beforeEach(() => { | ||
| cy.intercept('GET', '**/movie/popular**', { fixture: 'popularMovies.json' }) | ||
| cy.intercept('GET', '**/search/movie**', { fixture: 'searchMovies.json' }) | ||
| }) | ||
|
|
||
| it('인기순 영화 페이지에서 두 번째 포스터를 클릭하면 백그라운드에 해당 영화 정보가 띄워진다.', () => { | ||
| cy.visit('https://javascript-movie-review-dvlk.vercel.app/') | ||
yuncic marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| cy.get('.thumbnail-list li') | ||
| .eq(1) | ||
| .find('#title') | ||
| .invoke('text') | ||
| .then((defaultTitle) => { | ||
| cy.get('.thumbnail-list li').eq(1).click() | ||
| cy.get('.top-rated-movie') | ||
| .find('.title') | ||
| .invoke('text') | ||
| .should((searchTitle) => { | ||
| expect(searchTitle.trim()).to.equal(defaultTitle.trim()) | ||
| }) | ||
| }) | ||
| }) | ||
|
|
||
| it('인기순 영화 페이지에서 다섯 번째 포스터를 클릭하면 백그라운드에 해당 영화 정보가 띄워진다.', () => { | ||
| cy.visit('https://javascript-movie-review-dvlk.vercel.app/') | ||
| cy.get('.thumbnail-list li') | ||
| .eq(6) | ||
| .find('#title') | ||
| .invoke('text') | ||
| .then((defaultTitle) => { | ||
| cy.get('.thumbnail-list li').eq(6).click() | ||
| cy.get('.top-rated-movie') | ||
| .find('.title') | ||
| .invoke('text') | ||
| .should((searchTitle) => { | ||
| expect(searchTitle.trim()).to.equal(defaultTitle.trim()) | ||
| }) | ||
| }) | ||
| }) | ||
|
|
||
| it('인기순 영화 페이지에서 열한 번째 포스터를 클릭하면 백그라운드에 해당 영화 정보가 띄워진다.', () => { | ||
| cy.visit('https://javascript-movie-review-dvlk.vercel.app/') | ||
| cy.get('.thumbnail-list li') | ||
| .eq(12) | ||
| .find('#title') | ||
| .invoke('text') | ||
| .then((defaultTitle) => { | ||
| cy.get('.thumbnail-list li').eq(12).click() | ||
| cy.get('.top-rated-movie') | ||
| .find('.title') | ||
| .invoke('text') | ||
| .should((searchTitle) => { | ||
| expect(searchTitle.trim()).to.equal(defaultTitle.trim()) | ||
| }) | ||
| }) | ||
| }) | ||
| }) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| describe('search test', () => { | ||
| beforeEach(() => { | ||
| cy.intercept('GET', '**/movie/*', { fixture: 'infoModal.json' }) | ||
| cy.intercept('GET', '**/movie/popular**', { fixture: 'popularMovies.json' }) | ||
| cy.intercept('GET', '**/search/movie**', { fixture: 'searchMovies.json' }) | ||
| }) | ||
|
|
||
| it('인기순 영화 페이지에서 두 번째 포스터를 클릭하면 영화 상세정보 모달이 띄워진다.', () => { | ||
| cy.visit('https://javascript-movie-review-dvlk-a6xn5spuo-yun-cics-projects.vercel.app/') | ||
yuncic marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
yuncic marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| cy.get('.thumbnail-list li') | ||
| .eq(1) | ||
| .find('#title') | ||
| .invoke('text') | ||
| .then((listTitle) => { | ||
| cy.get('.thumbnail-list li').eq(1).click() | ||
| cy.get('#modalBackground').should('have.class', 'active') | ||
| cy.get('#modalTitle') | ||
| .invoke('text') | ||
| .should((modalTitle) => { | ||
| expect(modalTitle.trim()).to.equal(listTitle.trim()) | ||
| }) | ||
| }) | ||
| }) | ||
|
|
||
| it('검색 결과에서 포스터를 클릭하면 모달이 열린다.', () => { | ||
| cy.visit('https://javascript-movie-review-dvlk-a6xn5spuo-yun-cics-projects.vercel.app/') | ||
| cy.get('.search-bar').type('스파이더맨') | ||
| cy.get('.search-btn').click() | ||
| cy.get('.thumbnail-list li').first().click() | ||
| cy.get('#modalBackground').should('have.class', 'active') | ||
| cy.get('#modalTitle').should('not.be.empty') | ||
| }) | ||
| }) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| describe('infinite scroll test', () => { | ||
yuncic marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| beforeEach(() => { | ||
| cy.intercept('GET', '**/movie/popular**', { fixture: 'popularMovies.json' }) | ||
| }) | ||
|
|
||
| it('스크롤을 내리면 추가 영화 목록을 불러온다.', () => { | ||
| cy.visit('https://javascript-movie-review-dvlk-a6xn5spuo-yun-cics-projects.vercel.app/') | ||
|
|
||
| cy.get('.thumbnail-list li').should('have.length', 20) | ||
|
|
||
| cy.get('.thumbnail-list li').last().scrollIntoView() | ||
|
|
||
| cy.get('.thumbnail-list li').should('have.length', 40) | ||
yuncic marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| cy.get('.thumbnail-list li').last().scrollIntoView() | ||
|
|
||
| cy.get('.thumbnail-list li').should('have.length', 60) | ||
|
|
||
| cy.get('.thumbnail-list li').last().scrollIntoView() | ||
|
|
||
| cy.get('.thumbnail-list li').should('have.length', 80) | ||
|
|
||
| cy.get('.thumbnail-list li').last().scrollIntoView() | ||
|
|
||
| cy.get('.thumbnail-list li').should('have.length', 100) | ||
|
|
||
| cy.get('.thumbnail-list li').last().scrollIntoView() | ||
|
|
||
| cy.get('.thumbnail-list li').should('have.length', 120) | ||
|
|
||
| cy.get('.thumbnail-list li').last().scrollIntoView() | ||
|
|
||
| cy.get('.thumbnail-list li').should('have.length', 140) | ||
|
|
||
| cy.get('.thumbnail-list li').last().scrollIntoView() | ||
|
|
||
| cy.get('.thumbnail-list li').should('have.length', 160) | ||
|
|
||
| cy.get('.thumbnail-list li').last().scrollIntoView() | ||
|
|
||
| cy.get('.thumbnail-list li').should('have.length', 180) | ||
|
|
||
| cy.get('.thumbnail-list li').last().scrollIntoView() | ||
|
|
||
| cy.get('.thumbnail-list li').should('have.length', 200) | ||
| }) | ||
| }) | ||
|
|
||
| // describe('more btn test', () => { | ||
| // beforeEach(() => { | ||
| // cy.intercept('GET', '**/movie/popular**', { fixture: 'popularMovies.json' }) | ||
| // cy.intercept('GET', '**/search/movie**', { fixture: 'searchMovies.json' }) | ||
| // }) | ||
| // it('페이지 접속 후 더보기 버튼을 1번 누르면 영화 개수가 40개가 된다.', () => { | ||
| // cy.visit('https://javascript-movie-review-dvlk.vercel.app/') | ||
|
|
||
| // cy.get('.item').should('have.length', 20) | ||
|
|
||
| // cy.get('.display-more-btn').click() | ||
|
|
||
| // cy.get('.item').should('have.length', 40) | ||
| // }) | ||
| // it('페이지 접속 후 더보기 버튼을 10번 누르면 영화 개수가 220개가 된다.', () => { | ||
| // cy.visit('https://javascript-movie-review-dvlk.vercel.app/') | ||
| // cy.get('.item').should('have.length', 20) | ||
| // for (let i = 0; i < 10; i++) { | ||
| // cy.get('.display-more-btn').click() | ||
| // } | ||
| // cy.get('.item').should('have.length', 220) | ||
| // }) | ||
| // }) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| describe('search test', () => { | ||
| beforeEach(() => { | ||
| cy.intercept('GET', '**/movie/popular**', { fixture: 'popularMovies.json' }) | ||
| cy.intercept('GET', '**/search/movie**', { fixture: 'searchMovies.json' }) | ||
| }) | ||
| it('검색어를 입력한 뒤 검색 버튼을 누르면 필터링된 영화 목록을 보여준다.', () => { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 우선 테스트 시나리오를 유저 행동 기반으로 작성한 점은 정말 좋다고 생각해요.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 검색을 하지 않았을때 뽑은 타이틀 텍스트와 비교하기 위해 그랬던 것인데, fixture 데이터를 정의했을 시점에 수정했어야하는데 놓친 것 같습니다..! |
||
| cy.visit('https://javascript-movie-review-dvlk-a6xn5spuo-yun-cics-projects.vercel.app/') | ||
|
|
||
| cy.get('.thumbnail-list li') | ||
| .first() | ||
| .find('#title') | ||
| .invoke('text') | ||
| .then((defaultTitle) => { | ||
| cy.get('.search-bar').type('스파이더맨') | ||
| cy.get('.search-btn').click() | ||
|
|
||
| cy.get('.thumbnail-list li') | ||
| .first() | ||
| .find('#title') | ||
| .invoke('text') | ||
| .should((searchTitle) => { | ||
| expect(searchTitle.trim()).not.to.equal(defaultTitle.trim()) | ||
| }) | ||
| }) | ||
| }) | ||
|
|
||
| it('검색어를 입력한 뒤 엔터키를 누르면 필터링된 영화 목록을 보여준다.', () => { | ||
| cy.visit('https://javascript-movie-review-dvlk-a6xn5spuo-yun-cics-projects.vercel.app/') | ||
|
|
||
| cy.get('.thumbnail-list li') | ||
| .first() | ||
| .find('#title') | ||
| .invoke('text') | ||
| .then((defaultTitle) => { | ||
| cy.get('.search-bar').type('스파이더맨{enter}') | ||
|
|
||
| cy.get('.thumbnail-list li') | ||
| .first() | ||
| .find('#title') | ||
| .invoke('text') | ||
| .should((searchTitle) => { | ||
| expect(searchTitle.trim()).not.to.equal(defaultTitle.trim()) | ||
| }) | ||
| }) | ||
| }) | ||
| it("검색란에 검색어를 입력해도 결과가 존재하지 않다면 '검색 결과가 없습니다' 텍스트를 띄운다", () => { | ||
| cy.intercept('GET', '**/search/movie**', { body: { results: [] } }) | ||
| cy.visit('https://javascript-movie-review-dvlk-a6xn5spuo-yun-cics-projects.vercel.app/') | ||
| cy.get('.search-bar').type('ㄴㅇ러ㅏㅗㅁ라ㅗ어ㅏ로머ㅏJklhdskldh') | ||
| cy.get('.search-btn').click() | ||
| cy.get('.search-error-text').should('have.text', '검색 결과가 없습니다.') | ||
| }) | ||
|
|
||
| it('네트워크 오류 시 알림을 띄운다', () => { | ||
| cy.intercept('GET', '**/movie/popular**', { forceNetworkError: true }) | ||
| cy.visit('https://javascript-movie-review-dvlk-a6xn5spuo-yun-cics-projects.vercel.app/') | ||
| cy.on('window:alert', (text) => { | ||
| expect(text).to.equal('네트워크 오류가 발생하였습니다.') | ||
| }) | ||
| }) | ||
|
||
|
|
||
| it('API 오류 시 알림을 띄운다', () => { | ||
| cy.intercept('GET', '**/movie/popular**', { statusCode: 401 }) | ||
| cy.visit('https://javascript-movie-review-dvlk-a6xn5spuo-yun-cics-projects.vercel.app/') | ||
| cy.on('window:alert', (text) => { | ||
| expect(text).to.equal('데이터를 불러오지 못했습니다.') | ||
| }) | ||
| }) | ||
| }) | ||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| describe('별점 매기기 테스트', () => { | ||
| beforeEach(() => { | ||
| cy.intercept('GET', '**/movie/*', { fixture: 'infoModal.json' }) | ||
| cy.intercept('GET', '**/movie/popular**', { fixture: 'popularMovies.json' }) | ||
| cy.clearLocalStorage() | ||
| cy.visit('https://javascript-movie-review-dvlk-a6xn5spuo-yun-cics-projects.vercel.app/') | ||
| cy.get('.thumbnail-list li').first().click() | ||
| cy.get('#modalBackground').should('have.class', 'active') | ||
| }) | ||
|
|
||
| it('별점 클릭 시 해당 별점 텍스트가 표시된다.', () => { | ||
| cy.get('.star-icon[data-value="2"]').click() | ||
| cy.get('.my-rate-text').should('have.text', '최악이에요 (2/10)') | ||
|
|
||
| cy.get('.star-icon[data-value="4"]').click() | ||
| cy.get('.my-rate-text').should('have.text', '별로예요 (4/10)') | ||
|
|
||
| cy.get('.star-icon[data-value="6"]').click() | ||
| cy.get('.my-rate-text').should('have.text', '보통이에요 (6/10)') | ||
|
|
||
| cy.get('.star-icon[data-value="8"]').click() | ||
| cy.get('.my-rate-text').should('have.text', '재미있어요 (8/10)') | ||
|
|
||
| cy.get('.star-icon[data-value="10"]').click() | ||
| cy.get('.my-rate-text').should('have.text', '명작이에요 (10/10)') | ||
| }) | ||
|
|
||
| it('별점 클릭 시 클릭한 별까지 채워진 별로 변경된다.', () => { | ||
| // 클릭 전 빈 별 src 저장 | ||
| cy.get('.star-icon[data-value="10"]') | ||
| .invoke('attr', 'src') | ||
| .then((emptySrc) => { | ||
| cy.get('.star-icon[data-value="6"]').click() | ||
|
|
||
| // 클릭한 별까지는 src가 빈 별과 달라야 함 | ||
| cy.get('.star-icon[data-value="2"]').invoke('attr', 'src').should('not.equal', emptySrc) | ||
| cy.get('.star-icon[data-value="4"]').invoke('attr', 'src').should('not.equal', emptySrc) | ||
| cy.get('.star-icon[data-value="6"]').invoke('attr', 'src').should('not.equal', emptySrc) | ||
|
|
||
| // 클릭하지 않은 별은 여전히 빈 별이어야 함 | ||
| cy.get('.star-icon[data-value="8"]').invoke('attr', 'src').should('equal', emptySrc) | ||
| cy.get('.star-icon[data-value="10"]').invoke('attr', 'src').should('equal', emptySrc) | ||
| }) | ||
| }) | ||
|
|
||
| it('별점 클릭 시 localStorage에 별점이 저장된다.', () => { | ||
|
||
| cy.get('.star-icon[data-value="8"]').click() | ||
|
|
||
| // infoModal.json movie id 83533 | ||
| cy.window().then((win) => { | ||
| expect(win.localStorage.getItem('rating_83533')).to.equal('8') | ||
| }) | ||
| }) | ||
|
|
||
| it('모달을 닫았다가 다시 열면 이전에 저장한 별점이 복원된다.', () => { | ||
| cy.get('.star-icon[data-value="6"]').click() | ||
| cy.get('.my-rate-text').should('have.text', '보통이에요 (6/10)') | ||
|
|
||
| cy.get('#closeModal').click() | ||
| cy.get('#modalBackground').should('not.have.class', 'active') | ||
|
|
||
| cy.get('.thumbnail-list li').first().click() | ||
| cy.get('#modalBackground').should('have.class', 'active') | ||
|
|
||
| cy.get('.my-rate-text').should('have.text', '보통이에요 (6/10)') | ||
| cy.get('.star-icon[data-value="8"]') | ||
| .invoke('attr', 'src') | ||
| .then((emptySrc) => { | ||
| cy.get('.star-icon[data-value="2"]').invoke('attr', 'src').should('not.equal', emptySrc) | ||
| cy.get('.star-icon[data-value="6"]').invoke('attr', 'src').should('not.equal', emptySrc) | ||
| cy.get('.star-icon[data-value="8"]').invoke('attr', 'src').should('equal', emptySrc) | ||
| }) | ||
| }) | ||
| }) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
실제 API 키가 저장소에 노출되어 즉시 조치가 필요합니다.
Line 1에 비밀값이 하드코딩되어 커밋되었습니다. 키 유출 상태이므로 즉시 키 폐기(rotate) 후 재발급하고, 저장소에는 placeholder만 남기는 방식으로 바꿔주세요.
보안 조치 예시 (placeholder로 교체)
원하시면
.env.example기준으로 팀 온보딩 흐름(로컬 설정 문서 포함)까지 같이 정리해드릴게요.📝 Committable suggestion
🧰 Tools
🪛 dotenv-linter (4.0.0)
[warning] 1-1: [EndingBlankLine] No blank line at the end of the file
(EndingBlankLine)
🤖 Prompt for AI Agents