Skip to content
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

[2단계 - 나만의 유튜브 강의실] 하루(김하루) 미션 제출합니다 #46

Merged
merged 27 commits into from
Mar 16, 2021
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
4b009e3
docs: 2단계 구현기능 목록 작성
365kim Mar 7, 2021
fbbb5ab
test: 저장된 영상이 없는 경우, '저장영상 없음' 이미지 표시
365kim Mar 7, 2021
3d2cf2a
test: 저장된 영상이 있는 경우에 페이지 재방문시, 시청중인 영상목록 표시
365kim Mar 7, 2021
f6d1883
test: 시청완료 체크버튼 클릭 시, 시청완료 목록으로 해당 영상 이동
365kim Mar 7, 2021
8223a86
test: 시청완료 체크버튼 해제 시, 시청중인 목록으로 해당 영상 이동
365kim Mar 10, 2021
2d4fe64
test: [시청 중인 영상] 및 [시청 완료 영상] 메뉴버튼으로 영상 필터링
365kim Mar 10, 2021
e2c4d9f
refactor:비디오 '저장' 변수명 통일
365kim Mar 10, 2021
9d96bf8
feat: 강의실에 저장된 영상이 없는 경우, '비어있음' 이미지 표시
365kim Mar 10, 2021
d57ffae
feat: 저장된 영상이 있는 경우 나의 강의실에 영상 목록 표시
365kim Mar 10, 2021
cb58e41
refactor: 복수의 '비어있음' 이미지를 단일 이미지 스프라이트로 변경
365kim Mar 11, 2021
ac37675
feat: [시청 중인 영상] 및 [시청 완료 영상] 메뉴버튼 구현
365kim Mar 11, 2021
d3b2dfa
feat: [시청 중인 영상]의 시청완료 체크버튼 구현
365kim Mar 11, 2021
66f1bfe
fix: 저장영상 갯수 변경될 때 '비어있음' 이미지 안뜨는 문제 해결
365kim Mar 12, 2021
aca0bc5
feat: 영상삭제버튼 기능 구현
365kim Mar 12, 2021
8add17d
test: 단위테스트 시작 시 localStorage 자동 초기화되어 beforeEach 적용
365kim Mar 12, 2021
e1430a7
feat: [시청 중인 영상], [시청 완료 영상] CSS 스타일링
365kim Mar 12, 2021
d954dce
feat: 버튼 및 채널링크 hover효과 수정, 불필요한 !important 삭제
365kim Mar 13, 2021
95c90dd
fix: 최근검색어 사이 여백 클릭 시 모든 검색어가 합쳐져서 검색되는 버그 해결
365kim Mar 13, 2021
8406b11
feat: 저장된 영상 갯수 최대용량(100개) 표시
365kim Mar 13, 2021
bb806fd
refactor: constants 객체로 관리하도록 수정
365kim Mar 13, 2021
0b051d9
feat: 스크롤 이벤트에 쓰로틀링 적용
365kim Mar 13, 2021
5efb449
feat: 최근검색어 삭제 기능 추가
365kim Mar 13, 2021
7dd7c06
fix: 특수문자 치환 문제 해결
365kim Mar 13, 2021
250c62a
feat: 커스텀 Confirm 창 구현
365kim Mar 14, 2021
3767d19
refactor: SearchModel 내 메서드를 기능 별로 세분화
365kim Mar 14, 2021
924a74e
refactor: 메서드 이름 변경
365kim Mar 14, 2021
768a89c
feat: Confirm창 색상코드 수정
365kim Mar 14, 2021
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
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,17 @@

### 🎯🎯 step2 강의실 관리 기능

- [ ] 가장 처음에는 저장된 영상이 없음으로, 비어있다는 것을 사용자에게 알려주는 상태를 보여준다.
- [ ] 이후 페이지를 방문했을 때 기본 메인 화면은 내가 **볼 영상**들의 리스트를 보여준다.
- [ ] 가장 처음에는 저장된 영상이 없으므로, 비어있다는 것을 사용자에게 알려주는 상태를 보여준다.
- [ ] 이후 페이지를 방문했을 때 기본 메인 화면은 내가 **시청 중인** 영상 들의 리스트를 보여준다.
- [ ] 영상 카드의 이모지 버튼을 클릭하여 아래와 같은 상태 변경이 가능해야 한다.
- [ ] ✅ 영상으로 체크
- [ ] ✅ 시청 완료 영상으로 체크
- [ ] 🗑️ 버튼으로 저장된 리스트에서 삭제할 수 있습니다. (삭제 시 사용자에게 정말 삭제할 것인지 물어봅니다.)
- [ ] 사용자가 버튼을 클릭했을 때 해당 행위가 정상적으로 동작하거나, 실패하였음을 `snackbar`를 통해 보여준다.
- [ ] 본 영상, 볼 영상 버튼을 눌러 필터링 할 수 있다.
- [ ] **시청 중인** 영상에서 **시청 완료** 영상으로 체크하면 '영상이 **시청 완료** 영상으로 이동되었습니다.'
- [ ] **시청 완료** 영상에서 **시청 중인** 영상으로 체크하면 '영상이 **시청 중인** 영상으로 이동되었습니다.'
- [ ] 삭제버튼을 클릭하면 '영상 삭제가 완료되었습니다.'
- [ ] 요청 작업 실패시 '요청하신 작업을 수행할 수 없습니다. 문제가 지속적으로 발생되면 관리자에게 문의해주세요.'
- [ ] 시청 중인 영상, 시청 완료 영상 메뉴버튼을 눌러 필터링할 수 있다.

### 🎯🎯🎯 step3 유저 경험 증가 기능

Expand Down
91 changes: 41 additions & 50 deletions cypress/integration/searchModal.spec.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,16 @@
import {
MAX_RECENT_KEYWORD_COUNT,
MAX_RESULT_COUNT,
MAX_VIDEO_STORAGE_CAPACITY,
STORAGE_CAPACITY_IS_FULL,
KEY_VIDEOS_WATCHING,
VIDEO_IS_SAVED_SUCCESSFULLY,
API_SEARCH_ENDPOINT,
PART_TYPE,
SEARCH_TYPE_VIDEO,
REGION_CODE,
} from '../../src/js/constants.js';
import { MAX_RECENT_KEYWORD_COUNT, YOUTUBE_API, MESSAGE, DB_KEY } from '../../src/js/constants.js';
import { getListFromLocalStorage } from '../../src/js/utils/localStorage.js';
import { removeNewLine } from '../../src/js/utils/escapeSpecialCharacter.js';

const interceptYoutubeApiRequest = (keyword, alias) => {
cy.intercept({
url: `${API_SEARCH_ENDPOINT}?`,
url: `${YOUTUBE_API.SEARCH_ENDPOINT}?`,
query: {
part: PART_TYPE,
part: YOUTUBE_API.PART_TYPE,
q: keyword,
type: SEARCH_TYPE_VIDEO,
maxResults: MAX_RESULT_COUNT.toString(),
regionCode: REGION_CODE,
type: YOUTUBE_API.SEARCH_TYPE_VIDEO,
maxResults: YOUTUBE_API.MAX_RESULT_COUNT.toString(),
regionCode: YOUTUBE_API.REGION_CODE,
},
}).as(alias);
};
Expand All @@ -35,27 +25,27 @@ describe('검색 모달 테스트', () => {

it('검색 모달에서 "엔터키"를 누르면, 최초 검색 결과 10개가 화면에 표시된다.', () => {
interceptYoutubeApiRequest(KEYWORD, INTERCEPT_ALIAS);
cy.get('.js-search-button').click();
cy.get('.js-search-menu-button').click();
cy.get('.js-search-keyword-input').type(KEYWORD).type('{enter}');
cy.wait(`@${INTERCEPT_ALIAS}`);

cy.get('.js-search-result-group').children().should('have.length', MAX_RESULT_COUNT);
cy.get('.js-search-result-group').children().should('have.length', YOUTUBE_API.MAX_RESULT_COUNT);
});

it('검색 모달에서 "검색 버튼"을 클릭하면, 최초 검색 결과 10개가 화면에 표시된다.', () => {
interceptYoutubeApiRequest(KEYWORD, INTERCEPT_ALIAS);
cy.get('.js-search-button').click();
cy.get('.js-search-menu-button').click();
cy.get('.js-search-keyword-input').type(KEYWORD);
cy.get('.js-search-keyword-button').click();
cy.wait(`@${INTERCEPT_ALIAS}`);

cy.get('.js-search-result-group').children().should('have.length', MAX_RESULT_COUNT);
cy.get('.js-search-result-group').children().should('have.length', YOUTUBE_API.MAX_RESULT_COUNT);
});

it('검색 키워드 제출 후, 데이터를 불러오기 전이면 skeleton UI가 화면에 표시된다.', () => {
const ANOTHER_KEYWORD = '우아한 형제들';

cy.get('.js-search-button').click();
cy.get('.js-search-menu-button').click();
cy.get('.js-search-keyword-input').type(ANOTHER_KEYWORD);
cy.get('.js-search-keyword-form').submit();

Expand All @@ -70,7 +60,7 @@ describe('검색 모달 테스트', () => {
const KEYWORD_FOR_NO_RESULT = 'dsflmkfsdlkjweljksf';

interceptYoutubeApiRequest(KEYWORD_FOR_NO_RESULT, INTERCEPT_ALIAS);
cy.get('.js-search-button').click();
cy.get('.js-search-menu-button').click();
cy.get('.js-search-keyword-input').type(KEYWORD_FOR_NO_RESULT);
cy.get('.js-search-keyword-form').submit();
cy.wait(`@${INTERCEPT_ALIAS}`);
Expand All @@ -80,61 +70,61 @@ describe('검색 모달 테스트', () => {

it('스크롤바를 최하단으로 이동시킬 경우, 다음 10개 아이템을 추가로 화면에 표시한다.', () => {
interceptYoutubeApiRequest(KEYWORD, INTERCEPT_ALIAS);
cy.get('.js-search-button').click();
cy.get('.js-search-menu-button').click();
cy.get('.js-search-keyword-input').type(KEYWORD);
cy.get('.js-search-keyword-form').submit();
cy.wait(`@${INTERCEPT_ALIAS}`);

cy.get('.js-search-result-wrapper').scrollTo('bottom');
cy.get('.js-modal article').should('have.length', MAX_RESULT_COUNT * 2);
cy.get('.js-modal article').should('have.length', YOUTUBE_API.MAX_RESULT_COUNT * 2);
});

it('저장버튼을 누르면 localStorage에 해당 영상이 저장된다.', () => {
const FIRST_INDEX = 0;

interceptYoutubeApiRequest(KEYWORD, INTERCEPT_ALIAS);
cy.get('.js-search-button').click();
cy.get('.js-search-menu-button').click();
cy.get('.js-search-keyword-input').type(KEYWORD);
cy.get('.js-search-keyword-form').submit();
cy.wait(`@${INTERCEPT_ALIAS}`);

localStorage.setItem('test', ['test']);
cy.get('.js-save-button')
.eq(FIRST_INDEX)
.click()
.then(($el) => {
const list = JSON.parse(getListFromLocalStorage(KEY_VIDEOS_WATCHING));
const list = JSON.parse(getListFromLocalStorage(DB_KEY.VIDEOS));

cy.wrap($el).should('have.id', list[FIRST_INDEX].videoId);
cy.get('.js-snackbar').contains(removeNewLine(MESSAGE.VIDEO_IS_SAVED_SUCCESSFULLY));
});
});

it('검색 모달에 다시 접근했을 때, 가장 마지막에 검색한 결과를 화면에 표시한다.', () => {
interceptYoutubeApiRequest(KEYWORD, INTERCEPT_ALIAS);
cy.get('.js-search-button').click();
cy.get('.js-search-menu-button').click();
cy.get('.js-search-keyword-input').type(KEYWORD);
cy.get('.js-search-keyword-form').submit();
cy.wait(`@${INTERCEPT_ALIAS}`);
cy.reload();

cy.get('.js-search-button').click();
cy.get('.js-recent-keyword').children(0).should('have.text', KEYWORD);
cy.get('.js-modal article').should('have.length', MAX_RESULT_COUNT);
cy.get('.js-search-menu-button').click();
cy.get('.js-keyword-link').eq(0).should('have.text', KEYWORD);
cy.get('.js-modal article').should('have.length', YOUTUBE_API.MAX_RESULT_COUNT);
});

it('키워드 4개를 연속해서 검색했을 때, 최근 검색키워드 3개를 검색창 하단에 보여준다.', () => {
const KEYWORDS = ['쿠팡', '네이버', '토스', '우아한형제들'];
const TRY_COUNT = KEYWORDS.length;

cy.get('.js-search-button').click();
cy.get('.js-search-menu-button').click();
KEYWORDS.forEach((keyword) => {
interceptYoutubeApiRequest(keyword, `${INTERCEPT_ALIAS}-${keyword}`);
cy.get('.js-search-keyword-input').clear().type(keyword);
cy.get('.js-search-keyword-form').submit();
cy.wait(`@${INTERCEPT_ALIAS}-${keyword}`);
});

cy.get('.js-recent-keyword')
.children()
cy.get('.js-keyword-link')
.should('have.length', MAX_RECENT_KEYWORD_COUNT)
.each(($el, i) => {
cy.wrap($el).should('have.text', KEYWORDS[TRY_COUNT - 1 - i]);
Expand All @@ -144,30 +134,31 @@ describe('검색 모달 테스트', () => {

describe('예외 처리 테스트', () => {
const KEYWORD = 'javascript';
const INTERCEPT_ALIAS = 'search';

beforeEach(() => {
cy.visit('http://localhost:5500/');
});

it('100개의 영상을 저장했을 때, 더 저장하려고 시도할 경우 저장용량 초과 메세지를 표시한다.', () => {
cy.get('.js-search-button').click();
cy.get('.js-search-menu-button').click();
cy.get('.js-search-keyword-input').type(KEYWORD);
cy.get('.js-search-keyword-form').submit();

interceptYoutubeApiRequest(KEYWORD);
for (let i = 0; i < 10; i++) {
cy.get('.modal').scrollTo('bottom');
interceptYoutubeApiRequest(KEYWORD, INTERCEPT_ALIAS);
for (let i = 0; i < 9; i++) {
cy.get('.js-search-result-wrapper').scrollTo('bottom');
cy.wait('@search');
}
cy.get('.js-save-button').each(($el, i) => {
if (i >= MAX_VIDEO_STORAGE_CAPACITY) {
$el.click();
cy.wrap($el).should(be.visible);
cy.get('.js-snackbar').contains(STORAGE_CAPACITY_IS_FULL);
return;
}
$el.click();
cy.get('.js-snackbar').contains(VIDEO_IS_SAVED_SUCCESSFULLY);
});
cy.get('.js-save-button').each(($el) => $el.click());
cy.get('.js-search-result-wrapper').scrollTo('bottom');
cy.wait('@search');

cy.get('.js-save-button')
.eq(100)
.click()
.then(($el) => {
cy.get('.js-snackbar').contains(removeNewLine(MESSAGE.STORAGE_CAPACITY_IS_FULL));
cy.wrap($el).should('not.have.class', 'saved');
});
});
});
123 changes: 123 additions & 0 deletions cypress/integration/storedVideoManagement.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { YOUTUBE_API, MESSAGE } from '../../src/js/constants';
import { escape, removeNewLine } from '../../src/js/utils/escapeSpecialCharacter.js';

describe('저장된 비디오 관리 기능 테스트', () => {
beforeEach(() => {
cy.visit('http://localhost:5500/');
});

const KEYWORD = '테코톡';

it('저장된 영상이 없는 경우, 비어있다는 것을 사용자에게 알려주는 상태를 표시한다.', () => {
cy.get('.js-no-video-found').should('have.class', 'no-watching');
});

it('저장된 영상이 있는 경우에 페이지를 다시 방문하면, 시청중인 영상 목록을 표시한다.', () => {
const savedVideoTitles = [];

cy.get('.js-search-menu-button').click();
cy.get('.js-search-keyword-input').type(KEYWORD);
cy.get('.js-search-keyword-form').submit();
cy.wait(2000);

cy.get('.js-save-button').each(($el) => {
$el.click();
cy.wrap($el)
.parent()
.siblings('.js-video-title')
.invoke('text')
.then((videoTitle) => savedVideoTitles.push(`${videoTitle}`));
});

cy.reload();
cy.get('.js-saved-videos-wrapper .js-video-title').each(($el, index) => {
cy.wrap($el)
.invoke('text')
.then((title) => expect(escape(title)).to.be.eq(savedVideoTitles[index]));
});
});

it('[시청 중인 영상] 메뉴에서 영상 카드의 시청완료 체크버튼을 클릭하면, 해당 영상이 시청중인 영상에서 시청완료 영상으로 옮겨지고 알림이 표시된다.', () => {
cy.get('.js-search-menu-button').click();
cy.get('.js-search-keyword-input').type(KEYWORD);
cy.get('.js-search-keyword-form').submit();
cy.wait(2000);
cy.get('.js-save-button').each(($el) => $el.click());
cy.get('.js-modal-close-button').click();

cy.get('.js-check-button').each(($el, index) => {
if (index > YOUTUBE_API.MAX_RESULT_COUNT / 2) {
return;
}
cy.wrap($el).click();

cy.get('.js-snackbar').contains(removeNewLine(MESSAGE.VIDEO_IS_MOVED_TO_WATCHED_MENU));
cy.wrap($el).closest('article').should('not.be.visible');
cy.get('.js-watched-menu-button').click();
cy.wrap($el).closest('article').should('be.visible');
cy.get('.js-watching-menu-button').click();
});
});

it('[시청 완료 영상] 메뉴에서 영상 카드의 시청완료 체크버튼을 해제하면, 해당 영상이 시청완료 영상에서 시청중인 영상으로 옮겨지고 알림이 표시된다.', () => {
cy.get('.js-search-menu-button').click();
cy.get('.js-search-keyword-input').type(KEYWORD);
cy.get('.js-search-keyword-form').submit();
cy.wait(2000);
cy.get('.js-save-button').each(($el) => $el.click());
cy.get('.js-modal-close-button').click();
cy.get('.js-check-button').each(($el, index) => {
if (index > YOUTUBE_API.MAX_RESULT_COUNT / 2) {
return;
}
cy.wrap($el).click();
});
cy.get('.js-watched-menu-button').click();

cy.get('.js-check-button.checked').each(($el) => {
cy.wrap($el).click();

cy.get('.js-snackbar').contains(removeNewLine(MESSAGE.VIDEO_IS_MOVED_TO_WATCHING_MENU));
cy.wrap($el).closest('article').should('not.be.visible');
cy.get('.js-watching-menu-button').click();
cy.wrap($el).closest('article').should('be.visible');
cy.get('.js-watched-menu-button').click();
});
});

it('영상 카드의 삭제버튼을 클릭하면, 나의 강의실에서 해당 영상이 삭제되고 알림이 표시된다.', () => {
cy.get('.js-search-menu-button').click();
cy.get('.js-search-keyword-input').type(KEYWORD);
cy.get('.js-search-keyword-form').submit();
cy.wait(2000);
cy.get('.js-save-button').eq(0).click();
cy.get('.js-modal-close-button').click();

cy.get('.js-remove-button').click();
cy.get('.js-confirm-approve').click();
cy.get('.js-snackbar').contains(removeNewLine(MESSAGE.VIDEO_IS_REMOVED_SUCCESSFULLY));
cy.get('.js-saved-videos-wrapper article').should('not.exist');
});

it('[시청 중인 영상]과 [시청 완료 영상] 메뉴버튼을 클릭하여 저장한 영상을 필터링할 수 있다.', () => {
cy.get('.js-search-menu-button').click();
cy.get('.js-search-keyword-input').type(KEYWORD);
cy.get('.js-search-keyword-form').submit();
cy.wait(2000);
cy.get('.js-save-button').each(($el) => $el.click());
cy.get('.js-modal-close-button').click();
cy.get('.js-check-button').each(($el, index) => {
if (index > YOUTUBE_API.MAX_RESULT_COUNT / 2) {
return;
}
cy.wrap($el).click();
});

cy.get('.js-saved-videos-wrapper .watching').each(($el) => cy.wrap($el).should('be.visible'));
cy.get('.js-saved-videos-wrapper .watched').each(($el) => cy.wrap($el).should('not.be.visible'));

cy.get('.js-watched-menu-button').click();
cy.get('.js-saved-videos-wrapper .watched').each(($el) => cy.wrap($el).should('be.visible'));
cy.get('.js-saved-videos-wrapper .watching').each(($el) => cy.wrap($el).should('not.be.visible'));
});
});
Loading