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기 소인성] 자동차 경주 미션 STEP1 ~ 3 #62

Merged
merged 47 commits into from
Apr 13, 2022
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
62fb0d7
🔬Chore: 패키지 의존성을 설치한다.
InSeong-So Mar 31, 2022
25971ef
🧪Test: TDD 진행
InSeong-So Mar 31, 2022
364a0bb
🧪Test: TDD 진행2, 실패하는 테스트 케이스 작성
InSeong-So Mar 31, 2022
ff6e0ef
✒️Feat: 도메인 모델 정의
InSeong-So Mar 31, 2022
9da9017
✒️Feat: 도메인 서비스 정의
InSeong-So Mar 31, 2022
59de0bd
✒️Feat: 도메인 컨트롤러 정의
InSeong-So Mar 31, 2022
22059ba
✒️Feat: 도메인 컨트롤러를 호출하는 응용 계층 추가
InSeong-So Mar 31, 2022
4b24a29
✒️Feat: View 분리
InSeong-So Mar 31, 2022
99d12a5
✒️Feat: actions 구조 정의
InSeong-So Mar 31, 2022
fb508cf
✒️Feat: view와 actions을 합성하는 컴포넌트 작성
InSeong-So Mar 31, 2022
f4fec6f
✒️Feat: 유틸 함수 추가
InSeong-So Mar 31, 2022
a6d45af
✒️Feat: 응용 계층이 렌더링을 담당하도록 개발
InSeong-So Mar 31, 2022
2b923c8
🧪Test: 고려 결과 공백 문자는 허용하되, 제거하는 것으로 수정한다.
InSeong-So Apr 1, 2022
a644aa6
🗃Refactor: 도메인 로직 수정
InSeong-So Apr 1, 2022
fd10486
✒️Feat: 인터페이스 정의 함수 추가
InSeong-So Apr 1, 2022
34d84d1
✒️Feat: 메인 어플리케이션 수정
InSeong-So Apr 1, 2022
cdae25a
✒️Feat: Setup 컴포넌트 수정
InSeong-So Apr 1, 2022
7ba84b6
🧪Test: 성공하는 테스트 케이스를 확인한다.
InSeong-So Apr 2, 2022
688bd24
🎆Style: 애니메이션 효과를 추가한다.
InSeong-So Apr 2, 2022
6b89baf
✒️Feat: Application 기능 고도화
InSeong-So Apr 2, 2022
fa5ae15
✒️Feat: 유저 이벤트 함수 분리
InSeong-So Apr 2, 2022
6c98a4c
✒️Feat: Helper 함수 고도화
InSeong-So Apr 2, 2022
452e8c0
✒️Feat: View 고도화
InSeong-So Apr 2, 2022
7a6fbe3
✒️Feat: 컴포넌트 파라미터 추가
InSeong-So Apr 2, 2022
fbcf53b
✒️Feat: 도메인 로직 추가
InSeong-So Apr 2, 2022
4bf85e6
✒️Feat: 상수 및 엔트리 파일 수정
InSeong-So Apr 2, 2022
a6988b7
🗃Refactor: 기능 함수 분리
InSeong-So Apr 2, 2022
be3e6ab
✂️Rename: 파일 이름 변경
InSeong-So Apr 2, 2022
363080d
🗃Refactor: 주석 제거
InSeong-So Apr 2, 2022
75fc691
🗃Refactor: 코드 정리
InSeong-So Apr 3, 2022
5676ef1
🔬Chore: 앱 구성 방식 변경
InSeong-So Apr 7, 2022
4a13301
✒️Feat: 작업사항 중간저장
InSeong-So Apr 8, 2022
0911dd0
✒️Feat: 기초 기능 구현
InSeong-So Apr 9, 2022
68cba64
🎆Style: CSS 수정
InSeong-So Apr 10, 2022
f7a618d
🎆Style: CSS 수정
InSeong-So Apr 10, 2022
3bb9968
✒️Feat: 헬퍼, 서비스 로직 수정
InSeong-So Apr 10, 2022
a98b4c7
🗃Refactor: 엔트리 파일 간소화
InSeong-So Apr 10, 2022
4b3fcdd
🗃Refactor: 커스텀 컴포넌트 리팩토링
InSeong-So Apr 10, 2022
f9d77d9
✒️Feat: delay 함수를 추가한다.
InSeong-So Apr 12, 2022
852c8b8
🗃Refactor: setInterval 로직 수정
InSeong-So Apr 12, 2022
5361c84
🎆Style: 가독성 향상을 위해 수정
InSeong-So Apr 12, 2022
39cc7d2
🗃Refactor: 요구사항에 맞게 수정
InSeong-So Apr 12, 2022
8f1b4d9
🗃Refactor: 피드백 반영 1
InSeong-So Apr 13, 2022
1ddee2f
🗃Refactor: 피드백 반영 2
InSeong-So Apr 13, 2022
a72ad28
🗃Refactor: 피드백 반영 3
InSeong-So Apr 13, 2022
db5daa1
🗃Refactor: 피드백 반영 4
InSeong-So Apr 13, 2022
71b2d8f
🎆Style: 놓쳤던 import 구문 제거
InSeong-So Apr 13, 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
4 changes: 4 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# don't ever lint node_modules
node_modules
# don't lint build output (make sure it's set to your correct build folder name)
dist
36 changes: 36 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"env": {
"browser": true,
"es2021": true,
"jest": true,
"node": true
},
"extends": [
"eslint:recommended",
"prettier",
"plugin:cypress/recommended"
],
"plugins": [
"prettier"
],
"parser": "@babel/eslint-parser",
"parserOptions": {
"requireConfigFile": false
},
"rules": {
"prettier/prettier": [
"error",
{
"singleQuote": true,
"semi": true,
"useTabs": false,
"tabWidth": 2,
"trailingComma": "all",
"printWidth": 100,
"bracketSpacing": true,
"arrowParens": "avoid",
"endOfLine": "auto"
}
]
}
}
108 changes: 108 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage
*.lcov

# nyc test coverage
.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# TypeScript v1 declaration files
typings/

# TypeScript cache
*.tsbuildinfo

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variables file
.env
.env.test

# parcel-bundler cache (https://parceljs.org/)
.cache

# Next.js build output
.next

# Nuxt.js build / generate output
.nuxt
dist

# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and *not* Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public

# vuepress build output
.vuepress/dist

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

# TernJS port file
.tern-port

# dependencies
package-lock.json
yarn-lock.json
5 changes: 5 additions & 0 deletions cypress.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"testFiles": "*.test.js",
"screenshotOnRunFailure": false,
"video": false
}
135 changes: 135 additions & 0 deletions cypress/integration/app.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { ERROR_MESSAGE, MAX_GAME_TRY_COUNT } from '../../src/js/constants.js';
import { inputCarNamesParsing } from '../../src/js/infrastructure/actions/inputSection.action.js';

const BASE_URL = '../../index.html';

describe('Racing Car Game', () => {
beforeEach(() => {
cy.visit(BASE_URL);
});

describe('최초 렌더링 시', () => {
it('자동차 이름 입력창만 보여야 한다.', () => {
cy.$('[data-props="car-names-field"]').should('be.visible');
cy.$('[data-props="game-try-count-field"]').should('not.be.visible');
cy.$('[data-props="game-section"]').should('not.be.visible');
cy.$('[data-props="result-section"]').should('not.be.visible');
});
it('자동차 이름 입력창의 값은 비어 있어야 한다.', () => {
cy.$('[data-props="car-names-input"]').should('have.text', '');
});
});

describe('자동차 이름을 입력한 뒤 확인 버튼을 누른다.', () => {
it('자동차 이름 입력창이 비어 있다면 "자동차 이름을 입력해주세요!" 경고창을 출력한다.', () => {
const alertStub = cy.stub();
cy.on('window:alert', alertStub);
cy.get('[data-props="car-names-confirm-button"]')
.click()
.then(() => {
expect(alertStub).to.be.calledWith(ERROR_MESSAGE.REQUIRED_NAME);
});
});
it('자동차 이름은 쉼표로 구분된다.', () => {
const carNames = 'EAST, WEST, SOUTH, NORTH';
cy.inputCarNames(carNames).then(() => {
expect(inputCarNamesParsing(carNames)).to.have.length(4);
});
});
it('자동차 이름의 처음과 마지막에 쉼표가 존재하면 제거한다.', () => {
const carNames = ',EAST, WEST, SOUTH, NORTH,';
cy.inputCarNames(carNames).then(() => {
expect(inputCarNamesParsing(carNames)).to.have.length(4);
});
});

describe('입력된 자동차 이름들이 유효하지 않으면 에러를 출력한다.', () => {
it('자동차 이름이 하나라도 비어 있다면 "자동차 이름을 입력해주세요!" 경고창을 출력한다.', () => {
const carNames = 'EAST,, SOUTH, NORTH';
const alertStub = cy.stub();
cy.on('window:alert', alertStub);

cy.inputCarNames(carNames).then(() => {
expect(alertStub).to.be.calledWith(ERROR_MESSAGE.REQUIRED_NAME);
});
});
it('자동차 이름이 5자를 초과하면 "자동차 이름은 5자 이하여야만 해요!" 경고창을 출력한다.', () => {
const carNames = 'EAST, WEST, SOUTH, NORTH2222';
const alertStub = cy.stub();
cy.on('window:alert', alertStub);

cy.inputCarNames(carNames).then(() => {
expect(alertStub).to.be.calledWith(ERROR_MESSAGE.MUST_LESS_THAN);
});
});
it('중복된 자동차 이름이 존재하면 "자동차 이름은 중복될 수 없어요!" 경고창을 출력한다.', () => {
const carNames = 'EAST, EAST, SOUTH, NORTH';
const alertStub = cy.stub();
cy.on('window:alert', alertStub);

cy.inputCarNames(carNames).then(() => {
expect(alertStub).to.be.calledWith(ERROR_MESSAGE.NOT_ACCEPT_DUPLICATED);
});
});
});

describe('입력된 자동차 이름들이 유효한 경우 시도 횟수 입력창을 표시한다.', () => {
it('시도 횟수 입력창이 보여야 한다.', () => {
cy.inputCarNames('EAST, WEST, SOUTH, NORTH');
cy.$('[data-props="game-try-count-field"]').should('be.visible');
});
});
});

describe('시도 횟수를 입력한 뒤 확인 버튼을 누른다.', () => {
beforeEach(() => {
cy.inputCarNames('EAST, WEST, SOUTH, NORTH');
});

describe('입력된 시도 횟수가 유효하지 않으면 에러를 출력한다.', () => {
it('시도 횟수가 공백일 경우 "숫자를 입력해주세요!" 경고창을 출력한다.', () => {
const alertStub = cy.stub();
cy.on('window:alert', alertStub);

cy.get('[data-props="game-try-count-confirm-button"]')
.click()
.then(() => {
expect(alertStub).to.be.calledWith(ERROR_MESSAGE.REQUIRED_DIGIT);
});
});
it('시도 횟수가 음수일 경우 "시도 횟수는 0보다 커야 해요!" 경고창을 출력한다.', () => {
const alertStub = cy.stub();
cy.on('window:alert', alertStub);

cy.inputGameTryCount(0).then(() => {
expect(alertStub).to.be.calledWith(ERROR_MESSAGE.MUST_MORE_THAN_ONE);
});
});
it('시도 횟수가 문자일 경우 "숫자를 입력해주세요!" 경고창을 출력한다.', () => {
const alertStub = cy.stub();
cy.on('window:alert', alertStub);

cy.inputGameTryCount('오잉?!😳').then(() => {
expect(alertStub).to.be.calledWith(ERROR_MESSAGE.REQUIRED_DIGIT);
});
});
it(`시도 횟수가 ${MAX_GAME_TRY_COUNT}를 초과하면 "시도 횟수는 ${MAX_GAME_TRY_COUNT}보다 낮아야 해요!" 경고창을 출력한다.`, () => {
const alertStub = cy.stub();
cy.on('window:alert', alertStub);

cy.inputGameTryCount(MAX_GAME_TRY_COUNT + 1).then(() => {
expect(alertStub).to.be.calledWith(ERROR_MESSAGE.MUST_LESS_THAN_MAX_GAME_TRY_COUNT);
});
});
});
});

describe('주어진 시도 횟수 동안 n대의 자동차는 난수 값에 따라 전진/또는 멈출 수 있다.', () => {
it('난수 값이 4 이상인 경우 전진하고 3 이하의 값이라면 움직이지 않는다.', () => {});
});

describe('주어진 시도 횟수가 소진된 경우 가장 많이 전진한 자동차가 우승한다.', () => {
it('자동차가 중복인 경우 공동 우승한다.', () => {});
it('자동차가 하나인 경우 단독 우승한다.', () => {});
});
});
22 changes: 22 additions & 0 deletions cypress/plugins/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/// <reference types="cypress" />
// ***********************************************************
// This example plugins/index.js can be used to load plugins
//
// You can change the location of this file or turn off loading
// the plugins file with the 'pluginsFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/plugins-guide
// ***********************************************************

// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)

/**
* @type {Cypress.PluginConfig}
*/
// eslint-disable-next-line no-unused-vars
module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
};
13 changes: 13 additions & 0 deletions cypress/support/commands.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Cypress.Commands.add('$', selector => {
return cy.get(selector);
});

Cypress.Commands.add('inputCarNames', names => {
cy.get('[data-props="car-names-input"]').type(names);
cy.get('[data-props="car-names-confirm-button"]').click();
});

Cypress.Commands.add('inputGameTryCount', count => {
cy.get('[data-props="game-try-count-input"]').type(count);
cy.get('[data-props="game-try-count-confirm-button"]').click();
});
20 changes: 20 additions & 0 deletions cypress/support/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// ***********************************************************
// This example support/index.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************

// Import commands.js using ES2015 syntax:
import './commands';

// Alternatively you can use CommonJS syntax:
// require('./commands')
Loading