Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
155fee3
chore: eslint 설정, CRLF -> LF 변경
usageness Mar 16, 2022
2b83fef
docs: todo list 작성
usageness Mar 16, 2022
0714f78
refactor: 상수를 이용하여 mockdata 사용 여부 결정
usageness Mar 16, 2022
d174a69
test: 처음 실행하면 저장된 영상이 없다는 안내 문구가 나타난다
usageness Mar 16, 2022
ee81b91
feat: 처음 실행하면 저장된 영상이 없다는 안내 문구가 나타나는 기능 구현
usageness Mar 16, 2022
17c33a5
test: 초기 화면에서 저장된 영상을 확인할 수 있어야 한다.
usageness Mar 17, 2022
4d6f70e
feat: 초기 화면에서 저장된 영상을 확인하는 기능 구현
usageness Mar 17, 2022
ecf3d13
test: 초기 화면에서 저장된 영상의 ✅ 버튼을 눌러 본 영상 섹션으로 옮길 수 있어야 한다.
usageness Mar 18, 2022
09e9a8e
feat: 초기 화면에서 저장된 영상의 ✅ 버튼을 눌러 본 영상 섹션으로 옮기는 기능 구현
usageness Mar 18, 2022
f88f820
test: 초기 화면에서 본 영상 버튼을 눌러 본 영상 목록을 확인할 수 있어야 한다.
usageness Mar 18, 2022
05f7d8e
feat: 초기 화면에서 본 영상 / 볼 영상 버튼으로 각각의 목록을 불러오는 기능 구현
usageness Mar 18, 2022
60d3542
chore: es-lint 설정 변경
usageness Mar 18, 2022
f1a1189
test: 본 영상 섹션에서 저장된 영상의 ✅ 버튼을 눌러 볼 영상 섹션으로 옮길 수 있어야 한다.
usageness Mar 18, 2022
104123f
feat: ✅ 버튼으로 영상의 시청 상태 변경 기능 구현
usageness Mar 18, 2022
eb5ac95
test: 초기 화면에서 저장된 영상의 🗑️ 버튼을 눌러 영상을 삭제할 수 있어야 한다.
usageness Mar 19, 2022
370faa6
feat: 초기 화면에서 저장된 영상의 🗑️ 버튼을 눌러 영상을 삭제하는 기능 구현
usageness Mar 19, 2022
99a2713
fix: no result 페이지가 중복으로 표시되던 오류 수정
usageness Mar 19, 2022
5df0f9a
fix: 비디오 상태에 따라 결과 없음 화면이 출력되지 않던 오류 수정
usageness Mar 19, 2022
48105a2
test: 초기 화면에서 삭제시 사용자에게 물어보는지 확인한다.
usageness Mar 19, 2022
82cfd77
feat: 영상을 삭제하기 전 사용자에게 물어보는 기능 구현
usageness Mar 19, 2022
9955e34
chore: es-lint no-alert 확인 제거
usageness Mar 19, 2022
ee21df7
feat: 반응형 웹 구현
usageness Mar 19, 2022
6bf3540
chore: 주석, 콘솔로그 제거
usageness Mar 19, 2022
b9fc002
feat: 커스텀 에러 핸들러 추가
usageness Mar 19, 2022
7a47dd6
feat: getSearchResult 함수가 실행중일때는 중복 실행되지 않도록 설정
usageness Mar 19, 2022
452cac0
feat: 검색된 영상 클릭 시 유튜브로 바로가기 기능 추가
usageness Mar 19, 2022
554b1ac
test: 테스트 함수 추상화
usageness Mar 19, 2022
260ac79
chore: 콘솔로그 제거
usageness Mar 19, 2022
4d93220
fix: searchWithNoKeyword test에서 assert가 나오지 않는 코드 수정
usageness Mar 20, 2022
de6a743
refactor: templates.js 코드 각 view 파일로 일원화
usageness Mar 20, 2022
f238a3e
refactor: searchModal의 no-result 페이지 표시 방식을 hide 클래스 추가/제거로 개선
usageness Mar 20, 2022
98c5a19
refactor: handleError 메서드를 api 내부로 이동
usageness Mar 20, 2022
4cde156
refactor: VideoStorage의 getStorage()를 getVideos() 으로 변경
usageness Mar 20, 2022
592d52e
fix: 사용되지 않던 메서드 isSavedVideoId 삭제
usageness Mar 20, 2022
45c75c2
chore: 미사용 테스트 삭제
usageness Mar 20, 2022
bb3cfcb
fix: 불필요한 재검색 로직 제거
usageness Mar 20, 2022
84faf8b
refactor: innerHTML 메서드의 대체로 깜빡임 현상 개선
usageness Mar 20, 2022
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
8 changes: 7 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,20 @@
"sourceType": "module"
},
"rules": {
"no-new": 0,
"no-var": "error",
"max-depth": ["error", 2],
"no-alert": 0,
"default-case": 0,
"no-console": "warn",
"no-param-reassign": "error",
"no-undefined": 0,
"no-constant-condition": 0,
"no-unused-private-class-members": 0,
"class-methods-use-this": 0,
"lines-between-class-members": 0,
"no-useless-escape": 0
"no-useless-escape": 0,
"import/no-unresolved": 0,
"import/extensions": 0
}
}
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
</p>

### 데모 페이지

[데모 페이지](https://usageness.github.io/javascript-youtube-classroom/)

---
Expand All @@ -20,6 +21,8 @@

---

### Step 1

- [x] 메인 화면에서 검색 버튼을 누르면 검색 모달창이 나타난다.
- [x] 유튜브 검색 API를 사용해 내가 보고 싶은 영상들을 검색할 수 있다.
- 엔터키를 눌러 검색할 수 있다.
Expand All @@ -34,19 +37,33 @@
- 저장 가능한 최대 동영상의 갯수는 100개이다.
- [x] 이미 저장된 영상이라면 저장 버튼이 보이지 않도록 한다.

### Step 2

- [x] 가장 처음에는 저장된 영상이 없으므로, 비어있다는 것을 사용자에게 알려주는 상태를 보여준다.
- [x] 이후 페이지를 방문했을 때 기본 메인 화면은 내가 볼 영상들의 리스트를 보여준다.
- [x] 영상 카드의 이모지 버튼을 클릭하여 아래와 같은 상태 변경이 가능해야 한다.
- ✅ 본 영상으로 체크
- 🗑️ 버튼으로 저장된 리스트에서 삭제할 수 있다. (삭제 시 사용자에게 정말 삭제할 것인지 물어봅니다.)
- [x] 본 영상, 볼 영상 버튼을 눌러 필터링 할 수 있다.
- [x] 반응형 웹: 유저가 사용하는 디바이스의 가로 길이에 따라 검색결과의 row 당 column 갯수를 변경한다.
- 1280px 이상: 4개
- 960px이상~1280px 미만: 3개
- 600px이상~960px 미만: 2개
- 600px 미만: 1개

## 테스트 요구사항

---

- [x] 단위 테스트를 Jest로 작성한다.
- [x] E2E 테스트를 Cypress로 작성한다.
- 검증이 필요하다고 생각되는 플로우를 1가지 설정하고 이에 대한 검증을 E2E 테스트로 진행한다.

## 배포

---

- [x] 실행 가능한 페이지에 접근할 수 있도록 github page 기능을 이용하고, 해당 링크를 PR과 README에 작성한다.
- API key를 public repo에 올리지 않은 채로 데모 페이지를 배포하려면, 별도의 설정이 추가로 필요합니다.

## 📝 License

Expand Down
60 changes: 40 additions & 20 deletions cypress/integration/app.test.js
Original file line number Diff line number Diff line change
@@ -1,35 +1,26 @@
import { ITEMS_PER_REQUEST } from "../../src/js/constants/constants";

describe("보고싶은 영상 찾기 모달창 전체 로직 테스트", () => {
describe("나만의 유튜브 강의실 전체 플로우 테스트", () => {
before(() => {
cy.visit("./index.html");
});

const searchKeyword = "xooos";
const errorSearchKeyword = `\!\@\!\@\$\!\%\@\$\^\%\&\$\^\*\%\!\@\!\$\!\%\&\(\^\*\%\$\!\@!@$$!#@!#)_)&_%^_)&%_^)&_@!@#!#$@#$%$@#^%&$%^&#$@$^#%&$%^$^%*$^&^@#$@#$@#%@#$^#%&^**#^#$%@#$@#$^@#$!$@#%@#$%#$^#$%^$%@#$!@#!@#%)^_&)%_^$%#$%#$^#%^#%^#^&_%^_)&_#$)%_)#_$)%#_$%!@#!@$#$!#@!#)_)&_%^_)&%_^)&_%)^_&)%_^&_%^_)&_#$)%_)#_$)%#_$\%`;

it("처음 실행하면 저장된 영상이 없다는 안내 문구가 나타난다.", () => {
cy.get(".no-result__description").should("be.visible");
});

it("초기 화면에서 검색 버튼을 누르면 보고싶은 영상 찾기 모달창이 나타난다.", () => {
cy.openSearchModal();
cy.get(".modal-container").should("be.visible");
});

/**
* youtube에 검색한 결과가 없는 경우를 찾기 어려움... (테스트 통과가 되지 않을 확률이 높다.)
*/
// it("보고싶은 영상 찾기 모달창 안에서 원하는 영상을 검색한 결과가 없는 경우 검색 결과 없음 이미지를 보여준다.", () => {
// cy.get("#search-input-keyword").type(errorSearchKeyword);
// cy.get("#search-button").click();
// cy.get(".search-result--no-result").should("be.visible");
// });

/**
* 실제 API 호출 했을 경우 주석을 제거 후 테스트를 돌려주세요.
*/
// it("보고싶은 영상 찾기 모달창 안에서 검색된 영상을 불러오는 동안 로딩 이미지를 보여준다.", () => {
// cy.get("#search-input-keyword").clear().type(searchKeyword);
// cy.get("#search-button").click();
// cy.get(".skeleton").should("be.visible");
// });
it("보고싶은 영상 찾기 모달창 안에서 검색된 영상을 불러오는 동안 로딩 이미지를 보여준다.", () => {
cy.get("#search-input-keyword").clear().type(searchKeyword);
cy.get("#search-button").click();
cy.get(".skeleton").should("be.visible");
});

it("보고싶은 영상 찾기 모달창 안에서 검색창에 검색어를 입력하지 않으면 에러 메시지를 보여준다.", () => {
cy.searchWithNoKeyword();
Expand All @@ -53,12 +44,41 @@ describe("보고싶은 영상 찾기 모달창 전체 로직 테스트", () => {
});

it("보고싶은 영상 찾기 모달창 안에서 검색된 영상이 이미 저장된 영상이라면 저장 버튼이 보이지 않아야 한다.", () => {
cy.searchWithKeyword(searchKeyword);
cy.get(".video-item__save-button").eq(0).should("be.not.visible");
});

it("보고싶은 영상 찾기 모달창이 열린 상태에서 모달창 뒤의 음영 된 부분을 누르면 모달창이 사라진다.", () => {
cy.closeSearchModal();
cy.get(".modal-container").should("be.not.visible");
});

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.searchWithKeyword(searchKeyword)를 제거하면 이전에 검색한 상태에서 테스트를 진행해서 에러를 없앨 수 있어요 👀

Copy link
Copy Markdown
Author

@usageness usageness Mar 20, 2022

Choose a reason for hiding this comment

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

같은 검색어로도 실제 검색 결과가 다를 수 있다는 점을 간과했네요 😭
불필요한 재검색이기도 해서 삭제하였습니다!

bb3cfcb

it("초기 화면에서 저장된 영상을 확인할 수 있어야 한다.", () => {
cy.get(".video-item").should("be.visible");
});

it("볼 영상 섹션에서 저장된 영상의 ✅ 버튼을 눌러 본 영상 섹션으로 옮길 수 있어야 한다.", () => {
cy.clickVideoWatchButton();
cy.get(".video-item").should("be.not.visible");
});

it("초기 화면에서 본 영상 버튼을 눌러 본 영상 목록을 확인할 수 있어야 한다.", () => {
cy.clickWatchedVideoListTab();
cy.get(".video-item").should("be.visible");
});

it("본 영상 섹션에서 저장된 영상의 ✅ 버튼을 눌러 볼 영상 섹션으로 옮길 수 있어야 한다.", () => {
cy.clickVideoWatchButton();
cy.clickWatchLaterVideoListTab();
cy.get(".video-item").should("be.visible");
});

it("초기 화면에서 저장된 영상의 🗑️ 버튼을 눌러 영상을 삭제할지 확인할 수 있어야 한다.", () => {
cy.clickVideoDeleteButton(false);
cy.get(".video-item").should("be.visible");
});

it("영상을 삭제할지 확인하는 화면에서 확인을 눌러 삭제할 수 있어야 한다.", () => {
cy.clickVideoDeleteButton(true);
cy.get(".video-item").should("be.not.visible");
});
});
30 changes: 27 additions & 3 deletions cypress/support/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ Cypress.Commands.add("searchWithNoKeyword", () => {

cy.on("window:alert", alertStub);
cy.get("#search-input-keyword").clear().type(" ");
cy.get("#search-button").click(() => {
expect(alertStub).to.be.calledWith(ERROR_MESSAGE.SEARCH_INPUT_IS_EMPTY);
});
cy.get("#search-button")
.click()
.then(() => {
expect(alertStub).to.be.calledWith(ERROR_MESSAGE.SEARCH_INPUT_IS_EMPTY);
});
});

Cypress.Commands.add("searchWithKeyword", (keyword) => {
Expand All @@ -30,3 +32,25 @@ Cypress.Commands.add("loadMoreVideos", () => {
Cypress.Commands.add("closeSearchModal", () => {
cy.get(".dimmer").click({ force: true });
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

이전 PR에 테스트중에 searchWithNoKeyword 커맨드 추가에서 고칠 부분이 있어요
assert가 안나오는 문제가 있어서 이렇게 고쳐봅시다 👀

cy.get("#search-button").click().then(() => {
    expect(alertStub).to.be.calledWith(ERROR_MESSAGE.SEARCH_INPUT_IS_EMPTY);
  });

Copy link
Copy Markdown
Author

@usageness usageness Mar 20, 2022

Choose a reason for hiding this comment

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

앗... 다음 동작을 click() 안에 넣어버렸네요 😭😭 수정했습니다!

4d93220

Cypress.Commands.add("clickWatchedVideoListTab", () => {
cy.get("#watched-video-button").click();
});

Cypress.Commands.add("clickWatchLaterVideoListTab", () => {
cy.get("#watch-later-video-button").click();
});

Cypress.Commands.add("clickVideoWatchButton", () => {
cy.get(".video-item__watched-button").eq(0).click();
});

Cypress.Commands.add("clickVideoDeleteButton", (confirmButtonClick) => {
cy.get(".video-item__delete-button").eq(0).click();

cy.on("window:confirm", (text) => {
console.log(text);
expect(text).to.contains("정말로 삭제하시겠습니까?");
return confirmButtonClick;
Comment on lines +52 to +54
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

return은 안해주셔도 괜찮아보여요!

Suggested change
console.log(text);
expect(text).to.contains("정말로 삭제하시겠습니까?");
return confirmButtonClick;
expect(text).to.contains("정말로 삭제하시겠습니까?");

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

return 문에 boolean 값을 넣어주는걸로 comfirm창의 확인 버튼과 취소 버튼을
누를 수 있다고 해서 인자로 받아 넣어주었었는데 혹시 제가 잘못 알고 있었던걸까요?
리턴을 지워주니 오류가 나기에 다시 여쭈어봅니다 😭

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

return 문에 boolean 값을 넣어주는걸로 comfirm창의 확인 버튼과 취소 버튼을 누를 수 있다고 해서 인자로 받아 넣어주었었는데 혹시 제가 잘못 알고 있었던걸까요? 리턴을 지워주니 오류가 나기에 다시 여쭈어봅니다 😭

cypress window:confirm에 return이 필요한걸 깜빡했네요 🙇
말씀하신대로 return을 넣어줘야 confirm창을 확인이나 취소할 수 있네요. 죄송합니다!

});
});
Comment on lines +36 to +56
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Custom Command 잘 사용해주셨어요 👏

35 changes: 34 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,34 @@
<main id="app" class="classroom-container">
<h1 class="classroom-container__title">👩🏻‍💻 나만의 유튜브 강의실 👨🏻‍💻</h1>
<nav class="nav">
<button
id="watch-later-video-button"
class="button nav__button saved-video__button selected"
>
👁️ 볼 영상
</button>
<button
id="watched-video-button"
class="button nav__button saved-video__button"
>
✅ 본 영상
</button>
<button id="search-modal-button" class="button nav__button">
🔍 검색
</button>
</nav>
<section class="saved-video__section">
<h3 hidden>저장된 영상</h3>
<ul class="saved-video-list"></ul>
<div class="no-result hide">
<img src="" alt="no result image" class="no-result__image" />
<p class="no-result__description">
저장된 영상이 없습니다<br />나만의 영상을 검색하여 저장해보세요
</p>
</div>
</section>
</main>
<!-- 1 검색 버튼을 누르면 아래와 같은 검색 모달을 보여줍니다.-->

<div class="modal-container hide">
<div class="dimmer"></div>
<div
Expand Down Expand Up @@ -45,6 +67,17 @@ <h3 hidden>검색어 입력</h3>
<section class="search-result">
<h3 hidden>검색 결과</h3>
<ul class="video-list"></ul>
<div class="no-result hide">
<img
src=""
id="search-modal-no-result__image"
alt="no result image"
class="no-result__image"
/>
<p class="no-result__description">
검색 결과가 없습니다<br />다른 키워드로 검색해보세요
</p>
</div>
</section>
</div>
</div>
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@
},
"repository": {
"type": "git",
"url": "git+https://github.com/kkojae91/javascript-youtube-classroom.git"
"url": "git+https://github.com/usageness/javascript-youtube-classroom.git"
},
"keywords": [],
"author": "",
"license": "ISC",
"bugs": {
"url": "https://github.com/kkojae91/javascript-youtube-classroom/issues"
"url": "https://github.com/usageness/javascript-youtube-classroom/issues"
},
"homepage": "https://github.com/kkojae91/javascript-youtube-classroom#readme",
"homepage": "https://usageness.github.io/javascript-youtube-classroom/",
"devDependencies": {
"@babel/core": "^7.17.5",
"@babel/plugin-transform-runtime": "^7.17.0",
Expand Down
52 changes: 37 additions & 15 deletions src/css/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,34 +11,56 @@ body {
.classroom-container__title {
text-align: center;
font-weight: bold;
font-size: 34px;
line-height: 36px;
margin-bottom: 64px;
}

.nav {
display: flex;
justify-content: center;
margin-bottom: 40px;
}

.button {
cursor: pointer;
border-radius: 4px;
border: none;
font-style: normal;
font-weight: bold;
font-size: 14px;
letter-spacing: 1.25px;
.nav__button:hover {
background: #ebebeb;
}

.nav__button {
width: 80px;
height: 36px;
background: #f5f5f5;
#search-modal-button {
margin-left: auto;
}

.nav__button:hover {
background: #ebebeb;
.saved-video__button {
border: 1px solid #cccccc;
}

.saved-video__button:hover {
background: rgba(0, 188, 212, 0.12);
}

.selected {
background: rgba(0, 188, 212, 0.12);
}

.saved-video-list {
width: 105%;
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 32px 20px;
overflow-y: hidden;
}

.video-button__wrapper {
text-align: right;
}

.no-result {
flex-grow: 1;

display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}

/* ******** */
Expand Down
Loading