-
Notifications
You must be signed in to change notification settings - Fork 89
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[1단계 - 음식점 목록] - 초코(강다빈) 미션 제출합니다. (#127)
* docs: 기능 요구 사항 정리 Co-Authored-By: 00kang <[email protected]> * chore: 프로젝트 초기 설정 - eslint - prettier - tsconfig(경로 별칭) - package.json(typescript-eslint 설치) Co-Authored-By: 00kang <[email protected]> * feat: 카테고리, 이름, 거리(도보 이동 시간), 설명, 참고 링크를 저장한다. Co-Authored-By: 00kang <[email protected]> * feat: 음식점 목록에 새로운 음식점을 추가 Co-Authored-By: 00kang <[email protected]> * feat: 음식점 목록을 확인 Co-Authored-By: 00kang <[email protected]> * chore: typescript 모듈 테스트를 위한 jest 설정 Co-Authored-By: 00kang <[email protected]> * test: RestaurantManager 기능 테스트 Co-Authored-By: 00kang <[email protected]> * chore: template 이미지 리소스 view로 복사 Co-Authored-By: 00kang <[email protected]> * feat: 모달 열고 닫는 기능 Co-Authored-By: 00kang <[email protected]> * feat: 식당 등록 폼 커스텀 이벤트 Co-Authored-By: 00kang <[email protected]> * feat: Form 제출 시 localStorage에 저장 Co-Authored-By: 00kang <[email protected]> * chore: 정적 파일 import Co-Authored-By: 00kang <[email protected]> * feat: Select 컴포넌트 제작 Co-Authored-By: 00kang <[email protected]> * feat: GNB를 웹 컴포넌트로 전환 Co-Authored-By: 00kang <[email protected]> * chore: Select 컴포넌트 위치 변경 Co-Authored-By: 00kang <[email protected]> * feat: RestaurantForm를 웹 컴포넌트로 전환 Co-Authored-By: 00kang <[email protected]> * feat: Modal을 웹 컴포넌트 AppModal로 전환 Co-Authored-By: 00kang <[email protected]> * chore: AppModal의 이름을 Modal로 변경 Co-Authored-By: 00kang <[email protected]> * chore: 레스토랑 필터 select 박스 배치 Co-Authored-By: 00kang <[email protected]> * feat: localStorage와 도메인 연동 Co-Authored-By: 00kang <[email protected]> * feat: localStorage와 도메인 연동 - 페이지가 로드됐을 때 - 필터를 변경할 때 Co-Authored-By: 00kang <[email protected]> * feat: 음식점 렌더링 컴포넌트 Co-Authored-By: 00kang <[email protected]> * feat: 레스토랑 목록을 렌더링하는 컴포넌트 Co-Authored-By: 00kang <[email protected]> * feat: 식당 데이터를 웹 뷰에 렌더링 Co-Authored-By: 00kang <[email protected]> * test: RestaurantManger filteredAndSortedByOptions 테스트 케이스 추가 Co-Authored-By: 00kang <[email protected]> * chore: eslint 적용에 따른 코드 수정 Co-Authored-By: 00kang <[email protected]> * refactor: WebController 함수 분리 Co-Authored-By: 00kang <[email protected]> * docs: 기능 요구사항 문서 업데이트 Co-Authored-By: 00kang <[email protected]> * fix: 폼이 제출되었을 때 input이 초기화되지 않는 버그 수정 Co-Authored-By: 00kang <[email protected]> * feat: 링크 유효성 검사 Co-Authored-By: 00kang <[email protected]> * chore: console.log 제거 Co-Authored-By: 00kang <[email protected]> * docs: 실행 결과 gif Co-Authored-By: 00kang <[email protected]> * refacotr: 오타 수정 * chore: webpack 재설치 업데이트 * refactor: filtering과 sorting 관련 수정 - 원본 배열이 아닌 복사본으로 sorting - filteredAndSortedByOptions 메서드의 반환타입 명시 * refactor: CSS 파일 수정 - 임포트 파일명 수정 - global.css 에서 분리 - 필요없는 주석 삭제 * refactor: 불필요한 async 삭제, 오타 수정 * refactor: innerHTML 대신 textContent 사용 * refactor: attributeChangedCallback 수정 --------- Co-authored-by: Kim Da Eun <[email protected]>
- Loading branch information
1 parent
038dae2
commit 40da872
Showing
37 changed files
with
10,556 additions
and
3,615 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,22 @@ | ||
{ | ||
"rules": {}, | ||
"rules": { | ||
"max-depth": ["error", 1], | ||
"max-params": ["error", 2], | ||
"max-lines-per-function": ["error", 10], | ||
"no-return-assign": "off", | ||
"no-return-await": "off", | ||
"class-methods-use-this": "off" | ||
}, | ||
"env": { | ||
"browser": true, | ||
"es6": true, | ||
"node": true | ||
"node": true, | ||
"jest": true | ||
}, | ||
"parserOptions": { | ||
"ecmaVersion": "latest" | ||
"ecmaVersion": "latest", | ||
"sourceType": "module" | ||
}, | ||
"extends": ["eslint:recommended", "plugin:prettier/recommended"] | ||
"extends": ["airbnb-base", "prettier"], | ||
"ignorePatterns": ["dist"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,12 @@ | ||
{ | ||
"endOfLine": "auto" | ||
"singleQuote": true, | ||
"semi": true, | ||
"useTabs": false, | ||
"tabWidth": 2, | ||
"trailingComma": "all", | ||
"printWidth": 120, | ||
"bracketSpacing": true, | ||
"arrowParens": "always", | ||
"endOfLine": "auto", | ||
"prefer-const": true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import RestaurantManager from '../src/domain/RestaurantManager'; | ||
|
||
describe('RestaurantManager 기능 테스트', () => { | ||
const RESTAURANT_A = { | ||
category: '분식', | ||
name: '맛있는 집', | ||
distance: 15, | ||
}; | ||
const RESTAURANT_B = { | ||
category: '기타', | ||
name: '맛없는 집', | ||
distance: 10, | ||
}; | ||
const RESTAURANT_C = { | ||
category: '분식', | ||
name: '우리할매 떡볶이', | ||
distance: 5, | ||
}; | ||
const RESTAURANT_INFOS = [RESTAURANT_A, RESTAURANT_B, RESTAURANT_C]; | ||
|
||
const manager = new RestaurantManager(); | ||
beforeAll(() => { | ||
RESTAURANT_INFOS.forEach((info) => manager.add(info)); | ||
}); | ||
|
||
describe('add 기능 테스트', () => { | ||
test('카테고리, 이름, 거리 정보(설명, 링크)를 전달 받아 레스토랑 리스트에 저장한다.', () => { | ||
const RESULT = RESTAURANT_INFOS; | ||
|
||
expect(manager.restaurants).toEqual(RESULT); | ||
}); | ||
}); | ||
|
||
describe('filteredAndSortedByOptions 기능 테스트', () => { | ||
test.each([ | ||
{ category: '한식', option: 'name', result: [] }, | ||
{ category: '분식', option: 'distance', result: [RESTAURANT_C, RESTAURANT_A] }, | ||
{ category: '분식', option: 'name', result: [RESTAURANT_A, RESTAURANT_C] }, | ||
{ category: '전체', option: 'name', result: [RESTAURANT_B, RESTAURANT_A, RESTAURANT_C] }, | ||
])('카테고리와 옵션을 전달받아 정렬한 결과를 반환한다.', ({ category, option, result }) => { | ||
expect(manager.filteredAndSortedByOptions(category, option)).toEqual(result); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# 🎯 요구 사항 | ||
|
||
캠퍼스 주변의 점심 식사 스팟 목록을 관리하는 앱을 만든다. | ||
|
||
## 음식점 | ||
|
||
- [x] 음식점을 추가할 수 있다. | ||
- 카테고리, 이름, 거리는 필수 정보다. | ||
- 설명, 참고 링크는 옵션이다. 정보가 없어도 음식점 추가가 가능하다. | ||
- 카테고리는 "한식", "중식", "일식", "아시안", "양식", "기타" 중 하나를 선택한다. | ||
- 거리는 캠퍼스로부터 도보로 걸리는 시간(분). 5, 10, 15, 20, 30 중 하나를 선택한다. | ||
|
||
## UI | ||
|
||
- [x] 음식점 목록을 확인할 수 있다. | ||
- 카테고리별로 필터링해서 확인할 수 있다. | ||
- 이름순/거리순으로 정렬해서 확인할 수 있다. | ||
- 추가된 음식점은 localStorage에 저장해 새로고침해도 추가한 음식점 정보들이 유지된다. | ||
|
||
- [x] 음식점 추가 form | ||
- 카테고리, 거리는 셀렉트 박스, 이름/설명/참고 링크는 텍스트 인풋을 사용한다. | ||
- 유효하지 않은 링크가 입력되면 '유효하지 않은 링크입니다.'를 노출한다. | ||
|
||
## Bug🐞 | ||
- [ ] 유효하지 않은 링크를 입력한 뒤 유효한 링크를 입력했을 때 정상제출 되지 않는 버그 해결 |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,160 @@ | ||
<!DOCTYPE html> | ||
<html lang="ko"> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
<title>점심 뭐 먹지</title> | ||
</head> | ||
|
||
<head> | ||
<meta charset="UTF-8"> | ||
<meta http-equiv="X-UA-Compatible" content="IE=edge"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
<title>점심 뭐 먹지</title> | ||
</head> | ||
<body> | ||
<template id="template-header"> | ||
<header class="gnb"> | ||
<h1 class="gnb__title text-title">점심 뭐 먹지</h1> | ||
<button type="button" class="gnb__button" aria-label="음식점 추가"> | ||
<img src="./add-button.png" alt="음식점 추가" /> | ||
</button> | ||
</header> | ||
</template> | ||
|
||
<body> | ||
<template id="template-restaurant-form"> | ||
<div class="input-container"> | ||
<!-- 카테고리 --> | ||
<div class="form-item form-item--required"> | ||
<label for="category" class="text-caption">카테고리</label> | ||
<select | ||
is="app-select" | ||
name="category" | ||
id="category" | ||
required | ||
data-options='[{"value":"","option":"선택해 주세요"},{"value":"한식","option":"한식"},{"value":"중식","option":"중식"},{"value":"일식","option":"일식"},{"value":"양식","option":"양식"},{"value":"아시안","option":"아시안"},{"value":"기타","option":"기타"}]' | ||
></select> | ||
</div> | ||
|
||
</body> | ||
<!-- 음식점 이름 --> | ||
<div class="form-item form-item--required"> | ||
<label for="name" class="text-caption">이름</label> | ||
<input type="text" name="name" id="name" required /> | ||
</div> | ||
|
||
<!-- 거리 --> | ||
<div class="form-item form-item--required"> | ||
<label for="distance" class="text-caption">거리(도보 이동 시간) </label> | ||
<select | ||
is="app-select" | ||
name="distance" | ||
id="distance" | ||
required | ||
data-options='[{"value":5,"option":"5분 내"},{"value":10,"option":"10분 내"},{"value":15,"option":"15분 내"},{"value":20,"option":"20분 내"},{"value":30,"option":"30분 내"}]' | ||
></select> | ||
</div> | ||
|
||
<!-- 설명 --> | ||
<div class="form-item"> | ||
<label for="description" class="text-caption">설명</label> | ||
<textarea name="description" id="description" cols="30" rows="5"></textarea> | ||
<span class="help-text text-caption">메뉴 등 추가 정보를 입력해 주세요.</span> | ||
</div> | ||
|
||
<!-- 링크 --> | ||
<div class="form-item"> | ||
<label for="link" class="text-caption">참고 링크</label> | ||
<input type="text" name="link" id="link" /> | ||
<span class="help-text text-caption">매장 정보를 확인할 수 있는 링크를 입력해 주세요.</span> | ||
</div> | ||
</div> | ||
|
||
<!-- 취소/추가 버튼 --> | ||
<div class="button-container"> | ||
<button type="reset" class="button button--secondary text-caption">취소하기</button> | ||
<button type="submit" class="button button--primary text-caption">추가하기</button> | ||
</div> | ||
</template> | ||
|
||
<template id="template-modal"> | ||
<style> | ||
.modal { | ||
display: none; | ||
} | ||
|
||
.modal--open { | ||
display: block; | ||
} | ||
|
||
.modal-backdrop { | ||
position: fixed; | ||
top: 0; | ||
right: 0; | ||
bottom: 0; | ||
left: 0; | ||
|
||
background: rgba(0, 0, 0, 0.35); | ||
} | ||
|
||
.modal-container { | ||
position: fixed; | ||
bottom: 0; | ||
width: 100%; | ||
height: 90%; | ||
|
||
padding: 32px 16px; | ||
box-sizing: border-box; | ||
|
||
border-radius: 8px 8px 0px 0px; | ||
background: var(--grey-100); | ||
} | ||
</style> | ||
<div class="modal"> | ||
<div class="modal-backdrop"></div> | ||
<div class="modal-container"> | ||
<slot name="title"></slot> | ||
<slot name="body"></slot> | ||
</div> | ||
</div> | ||
</template> | ||
|
||
<template id="template-restaurant-item"> | ||
<div class="restaurant__category"> | ||
<img src="" alt="" class="category-icon" /> | ||
</div> | ||
<div class="restaurant__info"> | ||
<h3 class="restaurant__name text-subtitle"></h3> | ||
<span class="restaurant__distance text-body"></span> | ||
<p class="restaurant__description text-body"></p> | ||
</div> | ||
</template> | ||
|
||
<!-- GNB --> | ||
<app-gnb></app-gnb> | ||
|
||
<section class="restaurant-filter-container"> | ||
<select | ||
is="app-select" | ||
name="category" | ||
id="category-filter" | ||
class="restaurant-filter" | ||
data-options='[{"value":"전체","option":"전체"},{"value":"한식","option":"한식"},{"value":"중식","option":"중식"},{"value":"일식","option":"일식"},{"value":"양식","option":"양식"},{"value":"아시안","option":"아시안"},{"value":"기타","option":"기타"}]' | ||
></select> | ||
|
||
<!-- 정렬 셀렉트 박스 --> | ||
<select | ||
is="app-select" | ||
name="sorting" | ||
id="sorting-filter" | ||
class="restaurant-filter" | ||
data-options='[{"value":"name","option":"이름순"},{"value":"distance","option":"거리순"}]' | ||
></select> | ||
</section> | ||
|
||
<!-- 음식점 목록 --> | ||
<section class="restaurant-list-container"> | ||
<ul is="app-restaurant-list" data-restaurants=""></ul> | ||
</section> | ||
|
||
<!-- 음식점 추가 모달 --> | ||
<app-modal> | ||
<h2 slot="title" class="modal-title text-title">새로운 음식점</h2> | ||
<form is="app-restaurant-form" slot="body" id="restaurant-form"></form> | ||
</app-modal> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
// jest.config.js | ||
|
||
/* | ||
* For a detailed explanation regarding each configuration property, visit: | ||
* https://jestjs.io/docs/configuration | ||
*/ | ||
|
||
module.exports = { | ||
// Automatically clear mock calls, instances, contexts and results before every test | ||
clearMocks: true, | ||
|
||
// Indicates whether the coverage information should be collected while executing the test | ||
collectCoverage: false, | ||
|
||
// The directory where Jest should output its coverage files | ||
coverageDirectory: 'coverage', | ||
|
||
// Indicates which provider should be used to instrument code for coverage | ||
coverageProvider: 'v8', | ||
|
||
// An array of file extensions your modules use | ||
moduleFileExtensions: ['js', 'mjs', 'cjs', 'jsx', 'ts', 'tsx', 'json', 'node'], | ||
|
||
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module | ||
moduleNameMapper: { | ||
'^@/(.*)$': '<rootDir>/$1', | ||
}, | ||
|
||
// The test environment that will be used for testing | ||
testEnvironment: 'jest-environment-node', | ||
|
||
// The glob patterns Jest uses to detect test files | ||
testMatch: [ | ||
'<rootDir>/**/*.test.(js|jsx|ts|tsx)', | ||
'<rootDir>/(tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx))', | ||
], | ||
|
||
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation | ||
transformIgnorePatterns: ['<rootDir>/node_modules/'], | ||
}; |
Oops, something went wrong.