diff --git a/.eslintrc.json b/.eslintrc.json
new file mode 100644
index 000000000..3dbed94f6
--- /dev/null
+++ b/.eslintrc.json
@@ -0,0 +1,19 @@
+{
+ "env": {
+ "browser": true,
+ "es2021": true
+ },
+ "extends": [
+ "airbnb-base"
+ ],
+ "parser": "@typescript-eslint/parser",
+ "parserOptions": {
+ "ecmaVersion": 12,
+ "sourceType": "module"
+ },
+ "plugins": [
+ "@typescript-eslint"
+ ],
+ "rules": {
+ }
+}
diff --git a/README.md b/README.md
index 0886ebbbd..eca496e8e 100644
--- a/README.md
+++ b/README.md
@@ -12,23 +12,26 @@
## π₯ Projects!
+
### π― step1
-- [ ] μ£Όμ΄μ§ νμ λμ nλμ μλμ°¨λ μ μ§ λλ λ©μΆ μ μλ€.
-- [ ] μλμ°¨μ μ΄λ¦μ λΆμ¬ν μ μλ€. μ μ§νλ μλμ°¨λ₯Ό μΆλ ₯ν λ μλμ°¨ μ΄λ¦μ κ°μ΄ μΆλ ₯νλ€.
-- [ ] μλμ°¨ μ΄λ¦μ μΌν(,)λ₯Ό κΈ°μ€μΌλ‘ ꡬλΆνλ©° μ΄λ¦μ 5μ μ΄νλ§ κ°λ₯νλ€.
-- [ ] μ¬μ©μλ λͺ λ²μ μ΄λμ ν κ²μΈμ§λ₯Ό μ
λ ₯ν μ μμ΄μΌ νλ€.
-- [ ] μ μ§νλ 쑰건μ 0μμ 9 μ¬μ΄μμ random κ°μ ꡬν ν random κ°μ΄ 4 μ΄μμΌ κ²½μ° μ μ§νκ³ , 3 μ΄νμ κ°μ΄λ©΄ λ©μΆλ€.
-- [ ] μλμ°¨ κ²½μ£Ό κ²μμ μλ£ν ν λκ° μ°μΉνλμ§λ₯Ό μλ €μ€λ€. μ°μΉμλ ν λͺ
μ΄μμΌ μ μλ€.
-- [ ] μ°μΉμκ° μ¬λ¬λͺ
μΌ κ²½μ° ,λ₯Ό μ΄μ©νμ¬ κ΅¬λΆνλ€.
+
+- [x] μ£Όμ΄μ§ νμ λμ nλμ μλμ°¨λ μ μ§ λλ λ©μΆ μ μλ€.
+- [x] μλμ°¨μ μ΄λ¦μ λΆμ¬ν μ μλ€. μ μ§νλ μλμ°¨λ₯Ό μΆλ ₯ν λ μλμ°¨ μ΄λ¦μ κ°μ΄ μΆλ ₯νλ€.
+- [x] μλμ°¨ μ΄λ¦μ μΌν(,)λ₯Ό κΈ°μ€μΌλ‘ ꡬλΆνλ©° μ΄λ¦μ 5μ μ΄νλ§ κ°λ₯νλ€.
+- [x] μ¬μ©μλ λͺ λ²μ μ΄λμ ν κ²μΈμ§λ₯Ό μ
λ ₯ν μ μμ΄μΌ νλ€.
+- [x] μ μ§νλ 쑰건μ 0μμ 9 μ¬μ΄μμ random κ°μ ꡬν ν random κ°μ΄ 4 μ΄μμΌ κ²½μ° μ μ§νκ³ , 3 μ΄νμ κ°μ΄λ©΄ λ©μΆλ€.
+- [x] μλμ°¨ κ²½μ£Ό κ²μμ μλ£ν ν λκ° μ°μΉνλμ§λ₯Ό μλ €μ€λ€. μ°μΉμλ ν λͺ
μ΄μμΌ μ μλ€.
+- [x] μ°μΉμκ° μ¬λ¬λͺ
μΌ κ²½μ° ,λ₯Ό μ΄μ©νμ¬ κ΅¬λΆνλ€.
### π―π― step2
-- [ ] μλμ°¨ κ²½μ£Ό κ²μμ ν΄μ΄ μ§ν λ λλ§λ€ 1μ΄μ ν
(progressive μ¬μ)μ λκ³ μ§ννλ€.
- - [ ] μ λλ©μ΄μ
ꡬνμ μν΄ setInterval, setTimeout, requestAnimationFrame μ νμ©νλ€.
-- [ ] μ μμ μΌλ‘ κ²μμ ν΄μ΄ λ€ λμλ νμλ κ²°κ³Όλ₯Ό 보μ¬μ£Όκ³ , 2μ΄ νμ μΆνμ alert λ©μΈμ§λ₯Ό λμ΄λ€.
+
+- [x] μλμ°¨ κ²½μ£Ό κ²μμ ν΄μ΄ μ§ν λ λλ§λ€ 1μ΄μ ν
(progressive μ¬μ)μ λκ³ μ§ννλ€.
+ - [x] μ λλ©μ΄μ
ꡬνμ μν΄ setInterval, setTimeout, requestAnimationFrame μ νμ©νλ€.
+- [x] μ μμ μΌλ‘ κ²μμ ν΄μ΄ λ€ λμλ νμλ κ²°κ³Όλ₯Ό 보μ¬μ£Όκ³ , 2μ΄ νμ μΆνμ alert λ©μΈμ§λ₯Ό λμ΄λ€.
- [ ] μ κΈ°λ₯λ€μ΄ μ μμ μΌλ‘ λμνλμ§ Cypressλ₯Ό μ΄μ©ν΄ ν
μ€νΈνλ€.
@@ -53,7 +56,7 @@ live-server ν΄λλͺ
## π Contributing
-λ§μ½ λ―Έμ
μν μ€μ κ°μ μ¬νμ΄ λ³΄μΈλ€λ©΄, μΈμ λ μμ λ‘κ² PRμ 보λ΄μ£ΌμΈμ.
+λ§μ½ λ―Έμ
μν μ€μ κ°μ μ¬νμ΄ λ³΄μΈλ€λ©΄, μΈμ λ μμ λ‘κ² PRμ 보λ΄μ£ΌμΈμ.
@@ -66,3 +69,83 @@ live-server ν΄λλͺ
## π License
This project is [MIT](https://github.com/woowacourse/javascript-racingcar/blob/main/LICENSE) licensed.
+
+## μ§νκ³Όμ
+
+### νκ²½μ€μ
+
+- [x] prettier
+- [x] eslint
+- [x] typescript
+- [x] cypress
+- [x] tsconfig.json
+- [x] cypress.json
+- [x] package.json
+
+| https://reactjs-kr.firebaseapp.com/docs/thinking-in-react.html μ κΈ°μ λ λλ‘ μ§ν
+
+### Componentλ‘ λΆλ¦¬
+
+- app: νμ΄μ§ μ 체λ₯Ό νν
+ - section: κ²μ μ μ€μ λΆλΆ
+ - form: κ²μ κ΄λ ¨ μ€μ λΆλΆ
+ - fieldset: μ°¨ μ΄λ¦ μ€μ λΆλΆ
+ - h1: νμ΄ν
+ - p: μ°¨ μ΄λ¦ μ€λͺ
+ - div: μ°¨ μ΄λ¦ μ
λ ₯
+ - fieldset: κ²μ νμ μ€μ λΆλΆ
+ - p: μλ νμ μ€λͺ
+ - div: μλ νμ μ
λ ₯
+ - input: μλ νμ μ
λ ₯
+ - button: μλ νμ μ μΆ
+ - section: μλμ°¨ κ²½μ£Ό λΆλΆ
+ - div: μλμ°¨ κ²½μ£Ό λΆλΆ
+ - div: μ°¨ 1 κ²½μ£Ό λΆλΆ
+ - div: μ°¨ 1 μ΄λ¦
+ - div: go νμ β¬οΈ (μ¬λ¬κ° μμ μ μμ)
+ - div: wait νμ
+ - div
+ - span
+ - section: μ°μΉμ μΆλ ₯ λΆλΆ
+ - div
+ - h2: μ°μΉμ μΆλ ₯
+ - div: λ€μ μμνκΈ°
+ - button: λ€μ μμνκΈ° λ²νΌ
+
+### μ μ λ²μ λ§λ€κΈ°
+
+- [x] app
+- [x] settingSection
+- [x] racingSection
+ - [x] racingCarDiv
+- [x] resultSection
+
+## ν
μ€νΈ
+
+- [x] μλμ°¨ μ
λ ₯
+ - [x] 5μ μ΄μμ μλμ°¨ μ
λ ₯μ κ²½κ³ μ°½ μΆλ ₯ // 123456, EAST, WEST, SOUTH
+ - [x] μ½€λ§λ₯Ό μ°μν΄μ μ
λ ₯μ 무μ // EAST,,,WEST,SOUTH,NORTH
+ - [x] μ°¨ μ΄λ¦μ΄ μλ κ²½μ° κ²½κ³ μ°½ μΆλ ₯(ex: κ³΅λ°±λ§ μ
λ ₯) // " " μ
λ ₯
+ - [x] 곡백 무μ μ¬λΆ // EAST , WEST ,SOUTH,NORTH
+ - [x] μλμ°¨ μ€λ³΅ μ¬λΆ // EAST,WEST,SOUTH,NORTH,EAST
+- [x] μλ νμ μ
λ ₯
+ - [x] input min μ€μ μ¬λΆ
+ - [x] μμ μ
λ ₯ μ κ²½κ³ μ°½ μΆλ ₯
+- [x] μ°μΉμ μΆλ ₯
+ - [x] μ°μΉμ λ¬Έμ μμ μΆλ ₯ μ¬λΆ // should not ""
+ - [x] μ°μΉμ alertμΌλ‘ 2μ΄ λ€μ μΆλ ₯ μ¬λΆ
+
+### κΈ°λ₯
+
+- [x] μλμ°¨ μ΄λ¦ μ
λ ₯
+- [x] μλμ°¨ μ΄λ¦ μ μΆ
+ - [x] racingSectionμ μλμ°¨ μ΄λ¦ μΆλ ₯
+- [x] μλ νμ μ
λ ₯
+- [x] μλ νμ μ μΆ
+ - [x] κ²μ μμ
+ - [x] μλμ°¨ μ μ§
+ - [x] μλμ°¨ λκΈ°
+- [x] μ°μΉμ μΆλ ₯
+- [x] μ°μΉμ alert μΆλ ₯
+- [x] λ€μ μμνκΈ°
+ - [x] μμ μμ
diff --git a/cypress.json b/cypress.json
new file mode 100644
index 000000000..0967ef424
--- /dev/null
+++ b/cypress.json
@@ -0,0 +1 @@
+{}
diff --git a/cypress/fixtures/example.json b/cypress/fixtures/example.json
new file mode 100644
index 000000000..02e425437
--- /dev/null
+++ b/cypress/fixtures/example.json
@@ -0,0 +1,5 @@
+{
+ "name": "Using fixtures to represent data",
+ "email": "hello@cypress.io",
+ "body": "Fixtures are a great way to mock data for responses to routes"
+}
diff --git a/cypress/integration/basic.ts b/cypress/integration/basic.ts
new file mode 100644
index 000000000..dfd24c01b
--- /dev/null
+++ b/cypress/integration/basic.ts
@@ -0,0 +1,110 @@
+import { ERROR_MSG } from "../../src/js/controller/SettingSectionControllerError.js";
+
+context("racing cars", () => {
+ beforeEach(() => {
+ cy.visit("http://localhost:5500/");
+ });
+
+ it("car's name limit test(within 5 characters)", () => {
+ const stub = cy.stub();
+ cy.on("window:alert", stub);
+ cy.get("input[type='text']").type("123456, a, bcd,wd");
+ cy.get("button")
+ .eq(0)
+ .click()
+ .then(() => {
+ expect(stub.getCall(0)).to.be.calledWith(ERROR_MSG.OVER_CHARACTERS);
+ });
+ });
+
+ it("double comma test", () => {
+ cy.get("input[type='text']").type("EAST,,WEST, SOUTH,NORTH");
+ cy.get("button").eq(0).click();
+ cy.get("div.car-player").eq(0).should("have.text", "EAST");
+ cy.get("div.car-player").eq(1).should("have.text", "WEST");
+ cy.get("div.car-player").eq(2).should("have.text", "SOUTH");
+ cy.get("div.car-player").eq(3).should("have.text", "NORTH");
+ });
+
+ it("no car's name test", () => {
+ const stub = cy.stub();
+ cy.on("window:alert", stub);
+ cy.get("input[type='text']").type(" ");
+ cy.get("button")
+ .eq(0)
+ .click()
+ .then(() => {
+ expect(stub.getCall(0)).to.be.calledWith(ERROR_MSG.NO_CAR);
+ });
+ });
+
+ it("ignore whitespace test", () => {
+ cy.get("input[type='text']").type(
+ "EAST , WEST ,SOUTH,NORTH "
+ );
+ cy.get("button").eq(0).click();
+ cy.get("div.car-player").eq(0).should("have.text", "EAST");
+ cy.get("div.car-player").eq(1).should("have.text", "WEST");
+ cy.get("div.car-player").eq(2).should("have.text", "SOUTH");
+ cy.get("div.car-player").eq(3).should("have.text", "NORTH");
+ });
+
+ it("duplicate car name", () => {
+ const stub = cy.stub();
+ cy.on("window:alert", stub);
+ cy.get("input[type='text']").type("EAST,WEST,SOUTH,NORTH,EAST");
+ cy.get("button")
+ .eq(0)
+ .click()
+ .then(() => {
+ expect(stub.getCall(0)).to.be.calledWith(ERROR_MSG.DUPLICATE_CAR_NAME);
+ });
+ });
+
+ it("input number min test", () => {
+ cy.get("input[type='text']").type("EAST,WEST,SOUTH,NORTH");
+ cy.get("button").eq(0).click();
+ cy.get("input[type='number']")
+ .type("{downarrow}")
+ .type("{downarrow}")
+ .type("{downarrow}")
+ .type("{downarrow}")
+ .type("{downarrow}");
+ cy.get("input[type='number']").should("have.value", 0);
+ });
+
+ it("input minus number test", () => {
+ const stub = cy.stub();
+ cy.on("window:alert", stub);
+ cy.get("input[type='text']").type("EAST,WEST,SOUTH,NORTH");
+ cy.get("button").eq(0).click();
+ cy.get("input[type='number']").type("-1");
+ cy.get("button")
+ .eq(1)
+ .click()
+ .then(() => {
+ expect(stub.getCall(0)).to.be.calledWith(ERROR_MSG.WRONG_RACING_ROUND);
+ });
+ });
+
+ it("winner must print on html test", () => {
+ cy.get("input[type='text']").type("EAST,WEST,SOUTH,NORTH");
+ cy.get("button").eq(0).click();
+ cy.get("input[type='number']").type("2");
+ cy.get("button").eq(1).click();
+ cy.get("h2").should("not.have.text", "π μ΅μ’
μ°μΉμ: π");
+ });
+
+ it("winner on alert test", () => {
+ const stub = cy.stub();
+ cy.on("window:alert", stub);
+ cy.get("input[type='text']").type("EAST");
+ cy.get("button").eq(0).click();
+ cy.get("input[type='number']").type("2");
+ cy.get("button").eq(1).click();
+ cy.wait(4000);
+ cy.then(() => {
+ expect(stub.getCall(0)).to.be.calledWith("π WINNER is EAST π");
+ });
+ });
+});
diff --git a/cypress/plugins/index.js b/cypress/plugins/index.js
new file mode 100644
index 000000000..59b2bab6e
--- /dev/null
+++ b/cypress/plugins/index.js
@@ -0,0 +1,22 @@
+///
+// ***********************************************************
+// 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
+}
diff --git a/cypress/support/commands.js b/cypress/support/commands.js
new file mode 100644
index 000000000..119ab03f7
--- /dev/null
+++ b/cypress/support/commands.js
@@ -0,0 +1,25 @@
+// ***********************************************
+// This example commands.js shows you how to
+// create various custom commands and overwrite
+// existing commands.
+//
+// For more comprehensive examples of custom
+// commands please read more here:
+// https://on.cypress.io/custom-commands
+// ***********************************************
+//
+//
+// -- This is a parent command --
+// Cypress.Commands.add('login', (email, password) => { ... })
+//
+//
+// -- This is a child command --
+// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
+//
+//
+// -- This is a dual command --
+// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
+//
+//
+// -- This will overwrite an existing command --
+// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
diff --git a/cypress/support/index.js b/cypress/support/index.js
new file mode 100644
index 000000000..d68db96df
--- /dev/null
+++ b/cypress/support/index.js
@@ -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')
diff --git a/index.html b/index.html
index ea5891942..071163061 100644
--- a/index.html
+++ b/index.html
@@ -6,66 +6,7 @@
-
-
-
-
-
-
EAST
-
β¬οΈοΈ
-
β¬οΈοΈ
-
-
-
-
-
-
-
-
-
π μ΅μ’
μ°μΉμ: EAST, WEST π
-
-
-
-
-
-
+
+