Skip to content

Commit

Permalink
[1단계 - 음식점 목록] - 초코(강다빈) 미션 제출합니다. (#127)
Browse files Browse the repository at this point in the history
* 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
00kang and llqqssttyy authored Mar 12, 2024
1 parent 038dae2 commit 40da872
Show file tree
Hide file tree
Showing 37 changed files with 10,556 additions and 3,615 deletions.
19 changes: 15 additions & 4 deletions .eslintrc.json
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"]
}
11 changes: 10 additions & 1 deletion .prettierrc
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
}
44 changes: 44 additions & 0 deletions __test__/RestaurantManager.test.js
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);
});
});
});
25 changes: 25 additions & 0 deletions docs/REQUIREMENTS.md
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🐞
- [ ] 유효하지 않은 링크를 입력한 뒤 유효한 링크를 입력했을 때 정상제출 되지 않는 버그 해결
Binary file added docs/imgs/result.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
161 changes: 153 additions & 8 deletions index.html
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>
40 changes: 40 additions & 0 deletions jest.config.js
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/'],
};
Loading

0 comments on commit 40da872

Please sign in to comment.