From a8735a68a024422e085cd91ffbce2f29ff2c62d1 Mon Sep 17 00:00:00 2001 From: intae92 Date: Tue, 22 Mar 2022 14:26:52 +0900 Subject: [PATCH 01/44] =?UTF-8?q?chore:=20=ED=99=98=EA=B2=BD=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: YongRae_Kim (Usage) --- .eslintrc.json | 19 + .prettierrc.json | 13 + package-lock.json | 1503 +++++++++++++++++++++++++++++++++++++++++++-- package.json | 3 + 4 files changed, 1477 insertions(+), 61 deletions(-) create mode 100644 .eslintrc.json create mode 100644 .prettierrc.json diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 000000000..7e784f82d --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,19 @@ +{ + "env": { + "browser": true, + "es2021": true + }, + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "plugin:@typescript-eslint/recommended", + "plugin:@typescript-eslint/recommended-requiring-type-checking" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "module" + }, + "plugins": ["@typescript-eslint"], + "rules": {} +} diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 000000000..2a6798374 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,13 @@ +{ + "printWidth": 80, + "tabWidth": 2, + "useTabs": false, + "semi": true, + "singleQuote": true, + "trailingComma": "all", + "bracketSpacing": true, + "jsxBracketSameLine": true, + "arrowParens": "avoid", + "vueIndentScriptAndStyle": false, + "endOfLine": "auto" +} diff --git a/package-lock.json b/package-lock.json index cdb7ded9e..dc33ccf7c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,10 +13,13 @@ "@babel/preset-env": "^7.16.11", "@babel/preset-typescript": "^7.16.7", "@types/jest": "^27.4.1", + "@typescript-eslint/eslint-plugin": "^5.16.0", + "@typescript-eslint/parser": "^5.16.0", "babel-jest": "^27.4.6", "babel-loader": "^8.2.3", "clean-webpack-plugin": "^4.0.0", "css-loader": "^6.6.0", + "eslint": "^8.11.0", "html-webpack-plugin": "^5.5.0", "jest": "^27.4.7", "style-loader": "^3.3.1", @@ -1684,6 +1687,91 @@ "node": ">=10.0.0" } }, + "node_modules/@eslint/eslintrc": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.1.tgz", + "integrity": "sha512-bxvbYnBPN1Gibwyp6NrpnFzA3YtRL3BBAyEAFVIpNTm2Rn4Vy87GA5M4aSn3InRrlsbX5N0GW7XIx+U4SAEKdQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.3.1", + "globals": "^13.9.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.13.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.13.0.tgz", + "integrity": "sha512-EQ7Q18AJlPwp3vUDL4mKA0KXrXyNIQyWon6T6XQiBQF0XHvRsiCSrWmmeATpUzdJN2HhWZU6Pdl0a9zdep5p6A==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.9.5", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz", + "integrity": "sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -2730,6 +2818,249 @@ "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", "dev": true }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.16.0.tgz", + "integrity": "sha512-SJoba1edXvQRMmNI505Uo4XmGbxCK9ARQpkvOd00anxzri9RNQk0DDCxD+LIl+jYhkzOJiOMMKYEHnHEODjdCw==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.16.0", + "@typescript-eslint/type-utils": "5.16.0", + "@typescript-eslint/utils": "5.16.0", + "debug": "^4.3.2", + "functional-red-black-tree": "^1.0.1", + "ignore": "^5.1.8", + "regexpp": "^3.2.0", + "semver": "^7.3.5", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.16.0.tgz", + "integrity": "sha512-fkDq86F0zl8FicnJtdXakFs4lnuebH6ZADDw6CYQv0UZeIjHvmEw87m9/29nk2Dv5Lmdp0zQ3zDQhiMWQf/GbA==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.16.0", + "@typescript-eslint/types": "5.16.0", + "@typescript-eslint/typescript-estree": "5.16.0", + "debug": "^4.3.2" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.16.0.tgz", + "integrity": "sha512-P+Yab2Hovg8NekLIR/mOElCDPyGgFZKhGoZA901Yax6WR6HVeGLbsqJkZ+Cvk5nts/dAlFKm8PfL43UZnWdpIQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.16.0", + "@typescript-eslint/visitor-keys": "5.16.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.16.0.tgz", + "integrity": "sha512-SKygICv54CCRl1Vq5ewwQUJV/8padIWvPgCxlWPGO/OgQLCijY9G7lDu6H+mqfQtbzDNlVjzVWQmeqbLMBLEwQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/utils": "5.16.0", + "debug": "^4.3.2", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.16.0.tgz", + "integrity": "sha512-oUorOwLj/3/3p/HFwrp6m/J2VfbLC8gjW5X3awpQJ/bSG+YRGFS4dpsvtQ8T2VNveV+LflQHjlLvB6v0R87z4g==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.16.0.tgz", + "integrity": "sha512-SE4VfbLWUZl9MR+ngLSARptUv2E8brY0luCdgmUevU6arZRY/KxYoLI/3V/yxaURR8tLRN7bmZtJdgmzLHI6pQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.16.0", + "@typescript-eslint/visitor-keys": "5.16.0", + "debug": "^4.3.2", + "globby": "^11.0.4", + "is-glob": "^4.0.3", + "semver": "^7.3.5", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.16.0.tgz", + "integrity": "sha512-iYej2ER6AwmejLWMWzJIHy3nPJeGDuCqf8Jnb+jAQVoPpmWzwQOfa9hWVB8GIQE5gsCv/rfN4T+AYb/V06WseQ==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "@typescript-eslint/scope-manager": "5.16.0", + "@typescript-eslint/types": "5.16.0", + "@typescript-eslint/typescript-estree": "5.16.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.16.0.tgz", + "integrity": "sha512-jqxO8msp5vZDhikTwq9ubyMHqZ67UIvawohr4qF3KhlpL7gzSjOd+8471H3nh5LyABkaI85laEKKU8SnGUK5/g==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.16.0", + "eslint-visitor-keys": "^3.0.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@webassemblyjs/ast": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", @@ -2986,6 +3317,15 @@ "acorn": "^8" } }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, "node_modules/acorn-walk": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", @@ -4388,6 +4728,18 @@ "buffer-indexof": "^1.0.0" } }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/dom-converter": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", @@ -4601,61 +4953,366 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true, "engines": { - "node": ">=0.8.0" + "node": ">=0.8.0" + } + }, + "node_modules/escodegen": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", + "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", + "dev": true, + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.11.0.tgz", + "integrity": "sha512-/KRpd9mIRg2raGxHRGwW9ZywYNAClZrHjdueHcrVDuO3a6bj83eoTirCCk0M0yPwOjWYKHwRVRid+xK4F/GHgA==", + "dev": true, + "dependencies": { + "@eslint/eslintrc": "^1.2.1", + "@humanwhocodes/config-array": "^0.9.2", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.1", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^6.0.1", + "globals": "^13.6.0", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-scope/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.13.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.13.0.tgz", + "integrity": "sha512-EQ7Q18AJlPwp3vUDL4mKA0KXrXyNIQyWon6T6XQiBQF0XHvRsiCSrWmmeATpUzdJN2HhWZU6Pdl0a9zdep5p6A==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/eslint/node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" } }, - "node_modules/escodegen": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", - "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", + "node_modules/eslint/node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", "dev": true, "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" }, "engines": { - "node": ">=6.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" + "node": ">= 0.8.0" } }, - "node_modules/escodegen/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "node_modules/eslint/node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, - "optional": true, "engines": { - "node": ">=0.10.0" + "node": ">= 0.8.0" } }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=8.0.0" + "node": ">=8" } }, - "node_modules/eslint-scope/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "node_modules/eslint/node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, "engines": { - "node": ">=4.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.1.tgz", + "integrity": "sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ==", + "dev": true, + "dependencies": { + "acorn": "^8.7.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/esprima": { @@ -4671,6 +5328,18 @@ "node": ">=4" } }, + "node_modules/esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/esrecurse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", @@ -4924,6 +5593,18 @@ "bser": "2.1.1" } }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -4982,6 +5663,40 @@ "node": ">=8" } }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flat-cache/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/flatted": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", + "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", + "dev": true + }, "node_modules/follow-redirects": { "version": "1.14.9", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", @@ -5066,6 +5781,12 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -5527,6 +6248,31 @@ "node": ">= 4" } }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/import-local": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", @@ -7763,6 +8509,12 @@ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, "node_modules/json5": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", @@ -7889,6 +8641,12 @@ "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", "dev": true }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, "node_modules/lower-case": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", @@ -8400,6 +9158,18 @@ "tslib": "^2.0.3" } }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -8941,6 +9711,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, "node_modules/regexpu-core": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.0.1.tgz", @@ -9815,6 +10597,12 @@ "node": ">=8" } }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, "node_modules/throat": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz", @@ -10058,6 +10846,27 @@ "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", "dev": true }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, "node_modules/type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", @@ -10223,6 +11032,12 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, "node_modules/v8-to-istanbul": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz", @@ -12152,6 +12967,72 @@ "integrity": "sha512-ws57AidsDvREKrZKYffXddNkyaF14iHNHm8VQnZH6t99E8gczjNN0GpvcGny0imC80yQ0tHz1xVUKk/KFQSUyA==", "dev": true }, + "@eslint/eslintrc": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.1.tgz", + "integrity": "sha512-bxvbYnBPN1Gibwyp6NrpnFzA3YtRL3BBAyEAFVIpNTm2Rn4Vy87GA5M4aSn3InRrlsbX5N0GW7XIx+U4SAEKdQ==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.3.1", + "globals": "^13.9.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "globals": { + "version": "13.13.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.13.0.tgz", + "integrity": "sha512-EQ7Q18AJlPwp3vUDL4mKA0KXrXyNIQyWon6T6XQiBQF0XHvRsiCSrWmmeATpUzdJN2HhWZU6Pdl0a9zdep5p6A==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + } + } + }, + "@humanwhocodes/config-array": { + "version": "0.9.5", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz", + "integrity": "sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + } + }, + "@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, "@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -12977,48 +13858,185 @@ "integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==", "dev": true, "requires": { - "@types/mime": "^1", - "@types/node": "*" + "@types/mime": "^1", + "@types/node": "*" + } + }, + "@types/sockjs": { + "version": "0.3.33", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz", + "integrity": "sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "dev": true + }, + "@types/ws": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.1.tgz", + "integrity": "sha512-UxlLOfkuQnT2YSBCNq0x86SGOUxas6gAySFeDe2DcnEnA8655UIPoCDorWZCugcvKIL8IUI4oueUfJ1hhZSE2A==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "@types/yargs-parser": { + "version": "20.2.1", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz", + "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", + "dev": true + }, + "@typescript-eslint/eslint-plugin": { + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.16.0.tgz", + "integrity": "sha512-SJoba1edXvQRMmNI505Uo4XmGbxCK9ARQpkvOd00anxzri9RNQk0DDCxD+LIl+jYhkzOJiOMMKYEHnHEODjdCw==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "5.16.0", + "@typescript-eslint/type-utils": "5.16.0", + "@typescript-eslint/utils": "5.16.0", + "debug": "^4.3.2", + "functional-red-black-tree": "^1.0.1", + "ignore": "^5.1.8", + "regexpp": "^3.2.0", + "semver": "^7.3.5", + "tsutils": "^3.21.0" + }, + "dependencies": { + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "@typescript-eslint/parser": { + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.16.0.tgz", + "integrity": "sha512-fkDq86F0zl8FicnJtdXakFs4lnuebH6ZADDw6CYQv0UZeIjHvmEw87m9/29nk2Dv5Lmdp0zQ3zDQhiMWQf/GbA==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "5.16.0", + "@typescript-eslint/types": "5.16.0", + "@typescript-eslint/typescript-estree": "5.16.0", + "debug": "^4.3.2" + } + }, + "@typescript-eslint/scope-manager": { + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.16.0.tgz", + "integrity": "sha512-P+Yab2Hovg8NekLIR/mOElCDPyGgFZKhGoZA901Yax6WR6HVeGLbsqJkZ+Cvk5nts/dAlFKm8PfL43UZnWdpIQ==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.16.0", + "@typescript-eslint/visitor-keys": "5.16.0" } }, - "@types/sockjs": { - "version": "0.3.33", - "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz", - "integrity": "sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==", + "@typescript-eslint/type-utils": { + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.16.0.tgz", + "integrity": "sha512-SKygICv54CCRl1Vq5ewwQUJV/8padIWvPgCxlWPGO/OgQLCijY9G7lDu6H+mqfQtbzDNlVjzVWQmeqbLMBLEwQ==", "dev": true, "requires": { - "@types/node": "*" + "@typescript-eslint/utils": "5.16.0", + "debug": "^4.3.2", + "tsutils": "^3.21.0" } }, - "@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "@typescript-eslint/types": { + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.16.0.tgz", + "integrity": "sha512-oUorOwLj/3/3p/HFwrp6m/J2VfbLC8gjW5X3awpQJ/bSG+YRGFS4dpsvtQ8T2VNveV+LflQHjlLvB6v0R87z4g==", "dev": true }, - "@types/ws": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.1.tgz", - "integrity": "sha512-UxlLOfkuQnT2YSBCNq0x86SGOUxas6gAySFeDe2DcnEnA8655UIPoCDorWZCugcvKIL8IUI4oueUfJ1hhZSE2A==", + "@typescript-eslint/typescript-estree": { + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.16.0.tgz", + "integrity": "sha512-SE4VfbLWUZl9MR+ngLSARptUv2E8brY0luCdgmUevU6arZRY/KxYoLI/3V/yxaURR8tLRN7bmZtJdgmzLHI6pQ==", "dev": true, "requires": { - "@types/node": "*" + "@typescript-eslint/types": "5.16.0", + "@typescript-eslint/visitor-keys": "5.16.0", + "debug": "^4.3.2", + "globby": "^11.0.4", + "is-glob": "^4.0.3", + "semver": "^7.3.5", + "tsutils": "^3.21.0" + }, + "dependencies": { + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + } + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } } }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "@typescript-eslint/utils": { + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.16.0.tgz", + "integrity": "sha512-iYej2ER6AwmejLWMWzJIHy3nPJeGDuCqf8Jnb+jAQVoPpmWzwQOfa9hWVB8GIQE5gsCv/rfN4T+AYb/V06WseQ==", "dev": true, "requires": { - "@types/yargs-parser": "*" + "@types/json-schema": "^7.0.9", + "@typescript-eslint/scope-manager": "5.16.0", + "@typescript-eslint/types": "5.16.0", + "@typescript-eslint/typescript-estree": "5.16.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0" } }, - "@types/yargs-parser": { - "version": "20.2.1", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz", - "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", - "dev": true + "@typescript-eslint/visitor-keys": { + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.16.0.tgz", + "integrity": "sha512-jqxO8msp5vZDhikTwq9ubyMHqZ67UIvawohr4qF3KhlpL7gzSjOd+8471H3nh5LyABkaI85laEKKU8SnGUK5/g==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.16.0", + "eslint-visitor-keys": "^3.0.0" + } }, "@webassemblyjs/ast": { "version": "1.11.1", @@ -13248,6 +14266,13 @@ "dev": true, "requires": {} }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "requires": {} + }, "acorn-walk": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", @@ -14334,6 +15359,15 @@ "buffer-indexof": "^1.0.0" } }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, "dom-converter": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", @@ -14520,6 +15554,194 @@ } } }, + "eslint": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.11.0.tgz", + "integrity": "sha512-/KRpd9mIRg2raGxHRGwW9ZywYNAClZrHjdueHcrVDuO3a6bj83eoTirCCk0M0yPwOjWYKHwRVRid+xK4F/GHgA==", + "dev": true, + "requires": { + "@eslint/eslintrc": "^1.2.1", + "@humanwhocodes/config-array": "^0.9.2", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.1", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^6.0.1", + "globals": "^13.6.0", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + }, + "globals": { + "version": "13.13.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.13.0.tgz", + "integrity": "sha512-EQ7Q18AJlPwp3vUDL4mKA0KXrXyNIQyWon6T6XQiBQF0XHvRsiCSrWmmeATpUzdJN2HhWZU6Pdl0a9zdep5p6A==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + } + } + }, "eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -14538,12 +15760,55 @@ } } }, + "eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^2.0.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + } + } + }, + "eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true + }, + "espree": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.1.tgz", + "integrity": "sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ==", + "dev": true, + "requires": { + "acorn": "^8.7.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^3.3.0" + } + }, "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, + "esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + } + }, "esrecurse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", @@ -14749,6 +16014,15 @@ "bser": "2.1.1" } }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -14800,6 +16074,33 @@ "path-exists": "^4.0.0" } }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "dependencies": { + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, + "flatted": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", + "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", + "dev": true + }, "follow-redirects": { "version": "1.14.9", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", @@ -14854,6 +16155,12 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, "gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -15198,6 +16505,24 @@ "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", "dev": true }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + } + } + }, "import-local": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", @@ -16858,6 +18183,12 @@ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, "json5": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", @@ -16956,6 +18287,12 @@ "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", "dev": true }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, "lower-case": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", @@ -17347,6 +18684,15 @@ "tslib": "^2.0.3" } }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, "parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -17750,6 +19096,12 @@ "define-properties": "^1.1.3" } }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true + }, "regexpu-core": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.0.1.tgz", @@ -18412,6 +19764,12 @@ "minimatch": "^3.0.4" } }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, "throat": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz", @@ -18576,6 +19934,23 @@ "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", "dev": true }, + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, "type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", @@ -18695,6 +20070,12 @@ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "dev": true }, + "v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, "v8-to-istanbul": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz", diff --git a/package.json b/package.json index c9465bd78..58e076533 100644 --- a/package.json +++ b/package.json @@ -20,10 +20,13 @@ "@babel/preset-env": "^7.16.11", "@babel/preset-typescript": "^7.16.7", "@types/jest": "^27.4.1", + "@typescript-eslint/eslint-plugin": "^5.16.0", + "@typescript-eslint/parser": "^5.16.0", "babel-jest": "^27.4.6", "babel-loader": "^8.2.3", "clean-webpack-plugin": "^4.0.0", "css-loader": "^6.6.0", + "eslint": "^8.11.0", "html-webpack-plugin": "^5.5.0", "jest": "^27.4.7", "style-loader": "^3.3.1", From 9bfd57cbc50a08875c19fec471ee76a4d6b520e7 Mon Sep 17 00:00:00 2001 From: intae92 Date: Tue, 22 Mar 2022 14:51:44 +0900 Subject: [PATCH 02/44] =?UTF-8?q?docs:=20=EC=9A=94=EA=B5=AC=EC=82=AC?= =?UTF-8?q?=ED=95=AD=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: YongRae_Kim (Usage) --- src/docs/README.md | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 src/docs/README.md diff --git a/src/docs/README.md b/src/docs/README.md new file mode 100644 index 000000000..30835b098 --- /dev/null +++ b/src/docs/README.md @@ -0,0 +1,39 @@ +# 🎯 요구사항 - 관리자 기능 + +### 공통 + +- [ ] 도메인 영역을 타입스크립트를 이용하여 구현한다. + - Interface 또는 type을 이용하여, 주요 도메인 객체의 타입을 정의하고 설계한다. + +### 라우팅 기능 + +- [ ] Browser History Api를 이용하여 SPA처럼 라우팅을 적용한다. + - 매번 페이지를 로드 하는 것이 아닌, 히스토리를 관리하고, 페이지를 url에 따라 동적으로 렌더링한다 + - 상품 관리, 잔돈 충전, 상품 구매 페이지는 모두 동적으로 렌더링해야 한다. + +### 상품 관리 탭 + +**상품 관리탭은 자판기가 보유하고 있는 상품을 추가하는 기능을 수행한다.** + +- [ ] 최초 상품 목록은 비워진 상태이다. +- [ ] 상품명, 가격, 수량을 입력해 상품을 추가할 수 있다. + - 상품명은 최대 10글자까지 가능하다. + - 상품 가격은 100원부터 시작하며, 최대 10,000원까지 가능하다. 그리고 10원으로 나누어 떨어져야 한다. + - 한 제품당 수량은 최대 20개까지 넣을 수있다. +- [ ] 관리자는 추가한 상품을 확인할 수 있다. +- [ ] 관리자는 추가한 상품을 수정, 삭제할 수 있다. +- [ ] 수정 시 상품명, 가격, 수량 정보 영역 자체가 인풋 영역으로 변경된다. +- [ ] 삭제 시 confirm을 활용하여 사용자에게 다시 한번 확인한다. + +### 잔돈 충전 탭 + +- [ ] 잔돈 충전탭은 자판기가 보유할 금액을 충전하는 기능을 수행한다. +- [ ] 잔돈 충전 탭에서 최초 자판기가 보유한 금액은 0원이며, 각 동전의 개수는 0개이다. +- [ ] 잔돈 충전 입력 요소에 충전할 금액을 입력한 후, 충전하기 버튼을 눌러 자판기 보유 금액을 충전할 수 있다. +- [ ] 잔돈은 10원으로 나누어 떨어지는 금액만 투입할 수 있다. 보유할 수 있는 최대 금액은 100,000원이다. +- [ ] 자판기 보유 금액만큼의 동전이 무작위로 생성된다. +- [ ] 자판기 보유 금액을 누적하여 충전할 수 있다. 추가 충전 금액만큼의 동전이 무작위로 생성되어 기존 동전들에 더해진다. + +### 테스트 요구사항 + +- [ ] 비즈니스 로직에 대한 단위 테스트를 Jest로 작성한다. From ab85237f560f5d62eb658042aaa889118e78c4ce Mon Sep 17 00:00:00 2001 From: intae92 Date: Tue, 22 Mar 2022 15:48:53 +0900 Subject: [PATCH 03/44] =?UTF-8?q?feat:=20VendingMachine=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 자판기 도메인 설계(interface Product, Coin 정의) - 상품 추가, 삭제, 수정 메서드 구현 Co-authored-by: YongRae_Kim (Usage) --- src/js/model/VendingMachine.ts | 52 ++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 src/js/model/VendingMachine.ts diff --git a/src/js/model/VendingMachine.ts b/src/js/model/VendingMachine.ts new file mode 100644 index 000000000..f911e43bc --- /dev/null +++ b/src/js/model/VendingMachine.ts @@ -0,0 +1,52 @@ +interface Coin { + coin10: number; + coin50: number; + coin100: number; + coin500: number; +} + +interface Product { + name: string; + amount: number; + price: number; +} + +class VendingMachine { + products: Array; // name amount price + changes: Coin; // 자판기가 보유하고 있는 돈 = 잔돈 + + constructor() { + this.products = []; + this.changes = { coin10: 0, coin50: 0, coin100: 0, coin500: 0 }; + } + + addProduct(product: Product) { + const productIndex = this.findProductIndex(product.name); + const isExist = productIndex > 0; + + if (isExist) { + alert('이미 존재하는 이름의 상품입니다.'); + return; + } + + this.products.push(product); + } + + findProductIndex(name: string) { + return this.products.findIndex(product => product.name === name); + } + + removeProduct(product: Product) { + const productIndex = this.findProductIndex(product.name); + const isExist = productIndex > 0; + + if (isExist) { + this.products.splice(productIndex, 1); + } + } + + modifyProduct(oldProduct: Product, newProduct: Product) { + const oldProductIndex = this.findProductIndex(oldProduct.name); + this.products[oldProductIndex] = newProduct; + } +} From 29d2ec10eaa5866cac8917fe43af71c7b25c9557 Mon Sep 17 00:00:00 2001 From: intae92 Date: Tue, 22 Mar 2022 16:35:11 +0900 Subject: [PATCH 04/44] =?UTF-8?q?feat:=20=EC=9E=94=EB=8F=99=20=EC=B6=A9?= =?UTF-8?q?=EC=A0=84=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/js/model/VendingMachine.ts | 40 ++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/js/model/VendingMachine.ts b/src/js/model/VendingMachine.ts index f911e43bc..170333be5 100644 --- a/src/js/model/VendingMachine.ts +++ b/src/js/model/VendingMachine.ts @@ -14,10 +14,12 @@ interface Product { class VendingMachine { products: Array; // name amount price changes: Coin; // 자판기가 보유하고 있는 돈 = 잔돈 + totalMoney: number; constructor() { this.products = []; this.changes = { coin10: 0, coin50: 0, coin100: 0, coin500: 0 }; + this.totalMoney = 0; } addProduct(product: Product) { @@ -49,4 +51,42 @@ class VendingMachine { const oldProductIndex = this.findProductIndex(oldProduct.name); this.products[oldProductIndex] = newProduct; } + + inputChanges(money: number) { + // 1. 돈이 10원으로 나누어지는지 -> 어디에 로직을 둘것인지 킵 + + if (money > 100000) { + alert('자판기가 보유할 수 있는 최대 금액은 100,000원 입니다.'); + return; + } + + this.totalMoney += money; + + const coin = this.getChangeCoin(money); + + switch (coin) { + case 500: + this.changes.coin500 += 1; + break; + case 100: + this.changes.coin100 += 1; + break; + case 50: + this.changes.coin50 += 1; + break; + case 10: + this.changes.coin10 += 1; + break; + } + } + + getRandomInt(max: number) { + return Math.floor(Math.random() * max + 1); // 0 ~ max + } + + getChangeCoin(money: number) { + const coins = [500, 100, 50, 10].filter(coin => coin <= money); + const index = this.getRandomInt(coins.length - 1); + return coins[index]; + } } From bb8519f368e6c88ebfc2537425e7a9a6e5725f55 Mon Sep 17 00:00:00 2001 From: intae92 Date: Tue, 22 Mar 2022 16:50:15 +0900 Subject: [PATCH 05/44] =?UTF-8?q?test:=20=EC=9E=90=ED=8C=90=EA=B8=B0=20?= =?UTF-8?q?=EC=83=81=ED=92=88=20=EC=B6=94=EA=B0=80=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: YongRae_Kim (Usage) --- src/js/__test__/vendingMachine.test.js | 31 ++++++++++++++++++++++++++ src/js/model/VendingMachine.ts | 6 ++--- 2 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 src/js/__test__/vendingMachine.test.js diff --git a/src/js/__test__/vendingMachine.test.js b/src/js/__test__/vendingMachine.test.js new file mode 100644 index 000000000..5a346634a --- /dev/null +++ b/src/js/__test__/vendingMachine.test.js @@ -0,0 +1,31 @@ +import VendingMachine from '../model/VendingMachine' + + + +describe('자판기 기본 기능 테스트', () => { + const vendingMachine = new VendingMachine(); + + it('자판기에 상품을 추가할 수 있어야 한다.', () => { + const product = { + name: "코카콜라", + price: 1000, + amount: 5, + } + + vendingMachine.addProduct(product); + expect(vendingMachine.products.includes(product)); + }); + + it('자판기에 같은 이름의 상품은 추가할 수 없어야 한다.', () => { + const product = { + name: "코카콜라", + price: 1000, + amount: 5, + } + + expect(() => vendingMachine.addProduct(product)).toThrowError('이미 존재하는 이름의 상품입니다.'); + }); +}) + + + diff --git a/src/js/model/VendingMachine.ts b/src/js/model/VendingMachine.ts index 170333be5..27e64a58c 100644 --- a/src/js/model/VendingMachine.ts +++ b/src/js/model/VendingMachine.ts @@ -11,7 +11,7 @@ interface Product { price: number; } -class VendingMachine { +export default class VendingMachine { products: Array; // name amount price changes: Coin; // 자판기가 보유하고 있는 돈 = 잔돈 totalMoney: number; @@ -24,10 +24,10 @@ class VendingMachine { addProduct(product: Product) { const productIndex = this.findProductIndex(product.name); - const isExist = productIndex > 0; + const isExist = productIndex >= 0; if (isExist) { - alert('이미 존재하는 이름의 상품입니다.'); + throw new Error('이미 존재하는 이름의 상품입니다.'); return; } From 39dd47634667aa3baa9154ab35efd53685669ea7 Mon Sep 17 00:00:00 2001 From: intae92 Date: Tue, 22 Mar 2022 17:21:11 +0900 Subject: [PATCH 06/44] =?UTF-8?q?feat:=20=EC=83=81=ED=92=88=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=A6=9D=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit feat: 매직 넘버 상수화 Co-authored-by: YongRae_Kim (Usage) --- src/js/constants.ts | 10 ++++++++++ src/js/model/VendingMachine.ts | 21 ++++++++++++++++++++- src/js/model/validator.ts | 19 +++++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 src/js/constants.ts create mode 100644 src/js/model/validator.ts diff --git a/src/js/constants.ts b/src/js/constants.ts new file mode 100644 index 000000000..632876515 --- /dev/null +++ b/src/js/constants.ts @@ -0,0 +1,10 @@ +const RULES = { + MAX_PRODUCT_PRICE: 10000, + MIN_PRODUCT_PRICE: 100, + MAX_PRODUCT_AMOUNT: 20, + MIN_PRODUCT_AMOUNT: 0, + MINIMUM_CHARGE: 10, + MAX_LENGTH_PRODUCT_NAME: 10, +}; + +export { RULES }; diff --git a/src/js/model/VendingMachine.ts b/src/js/model/VendingMachine.ts index 27e64a58c..1d41ce5a7 100644 --- a/src/js/model/VendingMachine.ts +++ b/src/js/model/VendingMachine.ts @@ -1,3 +1,9 @@ +import { + isValidProductPrice, + isValidProductAmount, + isValidProductNameLength, +} from './validator'; + interface Coin { coin10: number; coin50: number; @@ -28,7 +34,20 @@ export default class VendingMachine { if (isExist) { throw new Error('이미 존재하는 이름의 상품입니다.'); - return; + } + + if (!isValidProductNameLength(product.name)) { + throw new Error('상품명은 최대 10글자까지 입력해주세요.'); + } + + if (!isValidProductPrice(product.price)) { + throw new Error( + '상품가격은 100원~10,000원 사이 여야 하며 10원으로 나누어 떨어져야 합니다.', + ); + } + + if (!isValidProductAmount(product.amount)) { + throw new Error('한 제품당 수량은 최대 20개 입니다.'); } this.products.push(product); diff --git a/src/js/model/validator.ts b/src/js/model/validator.ts new file mode 100644 index 000000000..6eda6aa75 --- /dev/null +++ b/src/js/model/validator.ts @@ -0,0 +1,19 @@ +import { RULES } from '../constants'; + +export const isValidProductNameLength = (name: string) => { + return name.length <= RULES.MAX_LENGTH_PRODUCT_NAME && name.length > 0; +}; + +export const isValidProductPrice = (price: number) => { + return ( + price >= RULES.MIN_PRODUCT_PRICE && + price <= RULES.MAX_PRODUCT_PRICE && + price % RULES.MINIMUM_CHARGE === 0 + ); +}; + +export const isValidProductAmount = (amount: number) => { + return ( + amount > RULES.MIN_PRODUCT_AMOUNT && amount <= RULES.MAX_PRODUCT_AMOUNT + ); +}; From 5b7fb2a5331088c34bc3e3c525f1722c660efceb Mon Sep 17 00:00:00 2001 From: intae92 Date: Tue, 22 Mar 2022 17:34:12 +0900 Subject: [PATCH 07/44] =?UTF-8?q?test:=20=EC=83=81=ED=92=88=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=A6=9D=20?= =?UTF-8?q?=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit feat: 에러메시지 상수화 적용 Co-authored-by: YongRae_Kim (Usage) --- src/js/__test__/vendingMachine.test.js | 48 ++++++++++++++++++++++++-- src/js/constants.ts | 12 ++++++- src/js/model/VendingMachine.ts | 13 ++++--- 3 files changed, 63 insertions(+), 10 deletions(-) diff --git a/src/js/__test__/vendingMachine.test.js b/src/js/__test__/vendingMachine.test.js index 5a346634a..776d7ea78 100644 --- a/src/js/__test__/vendingMachine.test.js +++ b/src/js/__test__/vendingMachine.test.js @@ -1,9 +1,11 @@ +import { ERROR_MESSAGE } from '../constants'; import VendingMachine from '../model/VendingMachine' describe('자판기 기본 기능 테스트', () => { - const vendingMachine = new VendingMachine(); + describe('자판기 상품 추가 기능 테스트', () => { + const vendingMachine = new VendingMachine(); it('자판기에 상품을 추가할 수 있어야 한다.', () => { const product = { @@ -23,9 +25,51 @@ describe('자판기 기본 기능 테스트', () => { amount: 5, } - expect(() => vendingMachine.addProduct(product)).toThrowError('이미 존재하는 이름의 상품입니다.'); + expect(() => vendingMachine.addProduct(product)).toThrowError(ERROR_MESSAGE.PRODUCT_NAME_IS_DUPLICATED); }); + + it('자판기에 추가 될 상품의 이름은 10글자를 초과할 수 없어야 한다.', () => { + const product = { + name: "코카콜라보다제로펩시가맛있다", + price: 1000, + amount: 5, + } + + expect(() => vendingMachine.addProduct(product)).toThrowError(ERROR_MESSAGE.PRODUCT_NAME_LENGTH); + }); + + it('자판기에 추가 될 상품의 가격은 100원 이상이어야 한다.', () => { + const product = { + name: "펩시", + price: 99, + amount: 5, + } + + expect(() => vendingMachine.addProduct(product)).toThrowError(ERROR_MESSAGE.PRODUCT_PRICE); + }); + + it('자판기에 추가 될 상품의 가격은 10,000원 이하여야 한다.', () => { + const product = { + name: "제로펩시", + price: 10001, + amount: 5, + } + + expect(() => vendingMachine.addProduct(product)).toThrowError(ERROR_MESSAGE.PRODUCT_PRICE); + }); + + it('자판기에 추가 될 상품의 수량은 20개 이하여야 한다.', () => { + const product = { + name: "제로코카콜라", + price: 1000, + amount: 21, + } + + expect(() => vendingMachine.addProduct(product)).toThrowError(ERROR_MESSAGE.PRODUCT_AMOUNT); + }); + }) }) + diff --git a/src/js/constants.ts b/src/js/constants.ts index 632876515..63c03ba66 100644 --- a/src/js/constants.ts +++ b/src/js/constants.ts @@ -7,4 +7,14 @@ const RULES = { MAX_LENGTH_PRODUCT_NAME: 10, }; -export { RULES }; +const ERROR_MESSAGE = { + PRODUCT_NAME_IS_DUPLICATED: '이미 존재하는 이름의 상품입니다.', + PRODUCT_NAME_LENGTH: '상품명은 최대 10글자까지 입력해주세요.', + PRODUCT_PRICE: + '상품가격은 100원~10,000원 사이 여야 하며 10원으로 나누어 떨어져야 합니다.', + PRODUCT_AMOUNT: '한 제품당 수량은 최대 20개 입니다.', + TOO_MUCH_VENDING_MACHINE_CHANGE: + '자판기가 보유할 수 있는 최대 금액은 100,000원 입니다.', +}; + +export { RULES, ERROR_MESSAGE }; diff --git a/src/js/model/VendingMachine.ts b/src/js/model/VendingMachine.ts index 1d41ce5a7..79a8e809a 100644 --- a/src/js/model/VendingMachine.ts +++ b/src/js/model/VendingMachine.ts @@ -1,3 +1,4 @@ +import { ERROR_MESSAGE } from '../constants'; import { isValidProductPrice, isValidProductAmount, @@ -33,21 +34,19 @@ export default class VendingMachine { const isExist = productIndex >= 0; if (isExist) { - throw new Error('이미 존재하는 이름의 상품입니다.'); + throw new Error(ERROR_MESSAGE.PRODUCT_NAME_IS_DUPLICATED); } if (!isValidProductNameLength(product.name)) { - throw new Error('상품명은 최대 10글자까지 입력해주세요.'); + throw new Error(ERROR_MESSAGE.PRODUCT_NAME_LENGTH); } if (!isValidProductPrice(product.price)) { - throw new Error( - '상품가격은 100원~10,000원 사이 여야 하며 10원으로 나누어 떨어져야 합니다.', - ); + throw new Error(ERROR_MESSAGE.PRODUCT_PRICE); } if (!isValidProductAmount(product.amount)) { - throw new Error('한 제품당 수량은 최대 20개 입니다.'); + throw new Error(ERROR_MESSAGE.PRODUCT_AMOUNT); } this.products.push(product); @@ -75,7 +74,7 @@ export default class VendingMachine { // 1. 돈이 10원으로 나누어지는지 -> 어디에 로직을 둘것인지 킵 if (money > 100000) { - alert('자판기가 보유할 수 있는 최대 금액은 100,000원 입니다.'); + alert(ERROR_MESSAGE.TOO_MUCH_VENDING_MACHINE_CHANGE); return; } From f07ca743dd7ec905d25944497d4380695ff29ef2 Mon Sep 17 00:00:00 2001 From: intae92 Date: Tue, 22 Mar 2022 17:50:40 +0900 Subject: [PATCH 08/44] =?UTF-8?q?test:=20=EC=9E=94=EB=8F=88=20=EC=B6=A9?= =?UTF-8?q?=EC=A0=84=20=EA=B8=B0=EB=8A=A5=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - refactor: 변수 상수화 적용 - refactor: alert -> Error 변경 Co-authored-by: YongRae_Kim (Usage) --- src/js/__test__/vendingMachine.test.js | 17 +++++++++-------- src/js/constants.ts | 1 + src/js/model/VendingMachine.ts | 7 +++---- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/js/__test__/vendingMachine.test.js b/src/js/__test__/vendingMachine.test.js index 776d7ea78..395dd90f7 100644 --- a/src/js/__test__/vendingMachine.test.js +++ b/src/js/__test__/vendingMachine.test.js @@ -1,12 +1,10 @@ import { ERROR_MESSAGE } from '../constants'; import VendingMachine from '../model/VendingMachine' - - describe('자판기 기본 기능 테스트', () => { - describe('자판기 상품 추가 기능 테스트', () => { - const vendingMachine = new VendingMachine(); + const vendingMachine = new VendingMachine(); + describe('자판기 상품 추가 기능 테스트', () => { it('자판기에 상품을 추가할 수 있어야 한다.', () => { const product = { name: "코카콜라", @@ -68,8 +66,11 @@ describe('자판기 기본 기능 테스트', () => { expect(() => vendingMachine.addProduct(product)).toThrowError(ERROR_MESSAGE.PRODUCT_AMOUNT); }); }) + + describe('잔돈 충전 기능 테스트', () => { + it('자판기가 가진 금액은 100,000원 이하여야 한다.', () => { + const money = 100010; + expect(() => vendingMachine.inputChanges(money)).toThrowError(ERROR_MESSAGE.TOO_MUCH_VENDING_MACHINE_CHANGE); + }); + }); }) - - - - diff --git a/src/js/constants.ts b/src/js/constants.ts index 63c03ba66..f8b97380f 100644 --- a/src/js/constants.ts +++ b/src/js/constants.ts @@ -5,6 +5,7 @@ const RULES = { MIN_PRODUCT_AMOUNT: 0, MINIMUM_CHARGE: 10, MAX_LENGTH_PRODUCT_NAME: 10, + MAX_VENDING_MACHINE_CHANGE: 100000, }; const ERROR_MESSAGE = { diff --git a/src/js/model/VendingMachine.ts b/src/js/model/VendingMachine.ts index 79a8e809a..1fcc71fa5 100644 --- a/src/js/model/VendingMachine.ts +++ b/src/js/model/VendingMachine.ts @@ -1,4 +1,4 @@ -import { ERROR_MESSAGE } from '../constants'; +import { ERROR_MESSAGE, RULES } from '../constants'; import { isValidProductPrice, isValidProductAmount, @@ -73,9 +73,8 @@ export default class VendingMachine { inputChanges(money: number) { // 1. 돈이 10원으로 나누어지는지 -> 어디에 로직을 둘것인지 킵 - if (money > 100000) { - alert(ERROR_MESSAGE.TOO_MUCH_VENDING_MACHINE_CHANGE); - return; + if (money > RULES.MAX_VENDING_MACHINE_CHANGE) { + throw new Error(ERROR_MESSAGE.TOO_MUCH_VENDING_MACHINE_CHANGE); } this.totalMoney += money; From 87ee77ad13d0c35c1d62c3b59f7c99324381d80c Mon Sep 17 00:00:00 2001 From: intae92 Date: Tue, 22 Mar 2022 18:32:19 +0900 Subject: [PATCH 09/44] =?UTF-8?q?feat:=20=EB=A7=88=ED=81=AC=EC=97=85=20?= =?UTF-8?q?=EC=84=A4=EA=B3=84=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: YongRae_Kim (Usage) --- index.html | 107 +++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 100 insertions(+), 7 deletions(-) diff --git a/index.html b/index.html index 454e4e55f..8cc036eaa 100644 --- a/index.html +++ b/index.html @@ -1,16 +1,109 @@ - - + 자판기 - + - +
+

🍿 자판기 🍿

+ +
+
+
+

추가할 상품 정보를 입력해주세요.

+
+ + + + +
+
+
+

자판기가 보유할 금액을 입력해주세요

+
+ + +
+

현재 보유 금액:

+
+
+
+
+

상품 현황

+
    +
  • + 상품명 + 가격 + 수량 +
  • +
  • + 콜라 + 1500 + 20 +
  • +
  • + 사이다 + 1000 + 10 +
  • +
+
+
+
+
+

자판기가 보유한 동전

+
    +
  • + 동전 + 개수 +
  • +
  • + 500원 + 0개 +
  • +
  • + 100원 + 4개 +
  • +
  • + 50원 + 1개 +
  • +
  • + 10원 + 5개 +
  • +
+
+
- - - \ No newline at end of file + + From d4a17f6daffd3616759946ee4a7aab87c743faf8 Mon Sep 17 00:00:00 2001 From: intae92 Date: Tue, 22 Mar 2022 21:04:14 +0900 Subject: [PATCH 10/44] =?UTF-8?q?style:=20=EB=A7=88=ED=81=AC=EC=97=85=20cs?= =?UTF-8?q?s=20=EC=8A=A4=ED=83=80=EC=9D=BC=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: YongRae_Kim (Usage) --- index.html | 11 +++ src/css/index.css | 178 ++++++++++++++++++++++++++++++++++++++++++++++ src/index.ts | 1 + 3 files changed, 190 insertions(+) create mode 100644 src/css/index.css diff --git a/index.html b/index.html index 8cc036eaa..2afa24304 100644 --- a/index.html +++ b/index.html @@ -63,16 +63,27 @@

상품 현황

상품명 가격 수량 +
  • 콜라 1500 20 + + +
  • 사이다 1000 10 + + +
  • diff --git a/src/css/index.css b/src/css/index.css new file mode 100644 index 000000000..9e7ea2e30 --- /dev/null +++ b/src/css/index.css @@ -0,0 +1,178 @@ +body { + width: 100vw; + height: 100vh; + margin: 0; +} + +h1 { + margin: 0; +} + +#app { + display: flex; + flex-direction: column; + width: 30%; + min-width: 465px; + margin: auto; +} + +header { + text-align: center; + margin: 40px 0 10px 0; +} + +nav { + margin: 2em 0; +} + +section { + display: flex; + flex-direction: column; + flex-wrap: wrap; + align-content: center; +} + +input[type='number']::-webkit-outer-spin-button, +input[type='number']::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} + +input[type='submit'] { + cursor: pointer; + background: #00bcd4; + color: #ffffff; + outline: 1px solid #00bcd4; + border: 1px solid #00bcd4; + border-radius: 2px; + width: 56px; + height: 28px; + margin-left: 10px; +} + +input { + background-color: #ffffff; + outline: 1px solid #cccccc; + border: 1px solid #eeeeee; + border-radius: 2px; + width: 120px; + height: 24px; + padding: 1px 2px; +} + +input:focus { + outline: 1px solid #777777; + border: 1px solid #bbbbbb; +} + +#change-add-input { + width: 300px; +} + +button { + cursor: pointer; + width: 117px; + height: 36px; + border: none; + border-radius: 5px; +} + +button:hover { + background: rgba(0, 188, 212, 0.16); +} + +h4 { + margin: 0; + font-family: 'Roboto'; + font-style: normal; + font-weight: 600; + font-size: 20px; + line-height: 24px; + text-align: center; + letter-spacing: 0.15px; + color: rgba(0, 0, 0, 0.87); +} + +#change-add-container { + margin: auto; +} + +#product-list-wrapper { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; +} +#product-list-wrapper ul { + padding: 0; +} + +#change-list-wrapper { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; +} + +#change-list-wrapper ul { + padding: 0; +} + +li { + list-style-type: none; + text-align: center; + font-family: 'Roboto'; + font-style: normal; + display: flex; + justify-content: center; + border-bottom: 1px solid #dcdcdc; +} +.product-modify-button { + padding: 0; + width: 100%; + height: 90%; + cursor: pointer; +} + +#product-list { + width: 100%; +} + +#product-list li { + width: 100%; +} + +#product-list li span { + margin: 8px 20px 4px; + width: 100%; + height: 30px; + font-size: 15px; + line-height: 24px; + letter-spacing: 0.5px; + color: rgba(0, 0, 0, 0.87); +} + +#change-list li { + width: 300px; +} + +#change-list li span { + margin: 8px 30px 0; + width: 70px; + height: 30px; + font-size: 15px; + line-height: 24px; + letter-spacing: 0.5px; + color: rgba(0, 0, 0, 0.87); +} + +.list-header { + font-weight: 600; + font-size: 15px; + line-height: 24px; + display: flex; + align-items: center; + letter-spacing: 0.5px; + color: rgba(0, 0, 0, 0.87); + border-top: 1px solid #dcdcdc; +} diff --git a/src/index.ts b/src/index.ts index e69de29bb..96f4ddc67 100644 --- a/src/index.ts +++ b/src/index.ts @@ -0,0 +1 @@ +import './css/index'; From 9556d58564f2e607882e62fedc659ad1fe8a13b0 Mon Sep 17 00:00:00 2001 From: intae92 Date: Wed, 23 Mar 2022 00:13:33 +0900 Subject: [PATCH 11/44] =?UTF-8?q?feat:=20=EB=9D=BC=EC=9A=B0=ED=84=B0=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9(url),=20ProductManage=20View=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=EC=84=A4=EA=B3=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: YongRae_Kim (Usage) --- .gitignore | 4 ++- index.html | 76 ++------------------------------------- src/css/index.css | 5 +++ src/index.ts | 33 +++++++++++++++++ src/js/ProductManage.ts | 79 +++++++++++++++++++++++++++++++++++++++++ src/js/routes.ts | 37 +++++++++++++++++++ 6 files changed, 160 insertions(+), 74 deletions(-) create mode 100644 src/js/ProductManage.ts create mode 100644 src/js/routes.ts diff --git a/.gitignore b/.gitignore index a338e5c21..426fadc86 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ # Dependency directories -node_modules/ \ No newline at end of file +node_modules/ + +dist \ No newline at end of file diff --git a/index.html b/index.html index 2afa24304..374b54da8 100644 --- a/index.html +++ b/index.html @@ -16,79 +16,9 @@

    🍿 자판기 🍿

    -
    -
    -

    추가할 상품 정보를 입력해주세요.

    -
    - - - - -
    -
    -
    -

    자판기가 보유할 금액을 입력해주세요

    -
    - - -
    -

    현재 보유 금액:

    -
    -
    -
    -
    -

    상품 현황

    -
      -
    • - 상품명 - 가격 - 수량 - -
    • -
    • - 콜라 - 1500 - 20 - - - -
    • -
    • - 사이다 - 1000 - 10 - - - -
    • -
    -
    -
    -
    +
    + +

    자판기가 보유한 동전

      diff --git a/src/css/index.css b/src/css/index.css index 9e7ea2e30..1ff13d9e1 100644 --- a/src/css/index.css +++ b/src/css/index.css @@ -14,6 +14,7 @@ h1 { width: 30%; min-width: 465px; margin: auto; + gap: 20px; } header { @@ -97,6 +98,10 @@ h4 { margin: auto; } +#product-list-container { + width: 100%; +} + #product-list-wrapper { display: flex; flex-direction: column; diff --git a/src/index.ts b/src/index.ts index 96f4ddc67..6f0841523 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1 +1,34 @@ import './css/index'; +import routes from './js/routes'; + +routes(); + +const nav = document.querySelector('nav'); + +const productManageButton = document.querySelector('#product-manage-button'); +const changeAddButton = document.querySelector('#change-add-button'); +const btn3 = document.querySelector('#product-purchase-button'); + +productManageButton.addEventListener('click', () => { + console.log('click1'); + history.pushState( + { prevPath: window.location.hash }, + '상품 관리하기', + '/#!/product-manage', + ); + routes(); +}); + +changeAddButton.addEventListener('click', () => { + console.log('click2'); + history.pushState( + { prevPath: window.location.hash }, + '잔돈 채우기', + '/#!/change-add', + ); + routes(); +}); + +window.addEventListener('popstate', function () { + console.log('popstate', history.state); +}); diff --git a/src/js/ProductManage.ts b/src/js/ProductManage.ts new file mode 100644 index 000000000..be8ccc747 --- /dev/null +++ b/src/js/ProductManage.ts @@ -0,0 +1,79 @@ +export default class ProductManage { + $inputSection: HTMLElement; + $contentsContainer: HTMLElement; + + constructor() { + this.$inputSection = document.querySelector('.input-section'); + this.$contentsContainer = document.querySelector('.contents-container'); + } + + render() { + this.$inputSection.insertAdjacentHTML('beforeend', this.inputSection()); + this.$contentsContainer.insertAdjacentHTML('beforeend', this.productList()); + } + + inputSection() { + return ` +
      +

      추가할 상품 정보를 입력해주세요.

      +
      + + + + +
      +
      `; + } + + productList() { + return ` +
      +
      +

      상품 현황

      +
        +
      • + 상품명 + 가격 + 수량 + +
      • +
      • + 콜라 + 1500 + 20 + + + +
      • +
      • + 사이다 + 1000 + 10 + + + +
      • +
      +
      +
      `; + } +} diff --git a/src/js/routes.ts b/src/js/routes.ts new file mode 100644 index 000000000..0a0f9a53e --- /dev/null +++ b/src/js/routes.ts @@ -0,0 +1,37 @@ +import ProductManage from './ProductManage'; + +const clearPurchaseBody = () => { + const $inputSection = document.querySelector('.input-section'); + const $contentsContainer = document.querySelector('.contents-container'); + + $inputSection.replaceChildren(); + $contentsContainer.replaceChildren(); +}; + +const routesCoke = () => { + const productManage = new ProductManage(); + let prevPath = ''; + + return () => { + const pathname = window.location.hash; + + if (prevPath === pathname) { + return; + } + + prevPath = pathname; + clearPurchaseBody(); + + switch (pathname) { + case '#!/product-manage': + productManage.render(); + break; + case '/#!/change-add': + break; + } + }; +}; + +const routes = routesCoke(); + +export default routes; From faca75d5d56e1190db7d75aae1bf0c93c88e2c3d Mon Sep 17 00:00:00 2001 From: intae92 Date: Wed, 23 Mar 2022 00:24:28 +0900 Subject: [PATCH 12/44] =?UTF-8?q?feat:=20ChangeAdd=20=EB=9D=BC=EC=9A=B0?= =?UTF-8?q?=ED=84=B0=EC=97=90=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: YongRae_Kim (Usage) --- index.html | 28 +-------------------- src/js/ChangeAdd.ts | 60 +++++++++++++++++++++++++++++++++++++++++++++ src/js/routes.ts | 19 ++++++++------ 3 files changed, 73 insertions(+), 34 deletions(-) create mode 100644 src/js/ChangeAdd.ts diff --git a/index.html b/index.html index 374b54da8..6f132fd23 100644 --- a/index.html +++ b/index.html @@ -18,33 +18,7 @@

      🍿 자판기 🍿

      -
      -
      -

      자판기가 보유한 동전

      -
        -
      • - 동전 - 개수 -
      • -
      • - 500원 - 0개 -
      • -
      • - 100원 - 4개 -
      • -
      • - 50원 - 1개 -
      • -
      • - 10원 - 5개 -
      • -
      -
      -
      +
    diff --git a/src/js/ChangeAdd.ts b/src/js/ChangeAdd.ts new file mode 100644 index 000000000..674aa8869 --- /dev/null +++ b/src/js/ChangeAdd.ts @@ -0,0 +1,60 @@ +export default class ChangeAdd { + $inputSection: HTMLElement; + $contentsContainer: HTMLElement; + + constructor() { + this.$inputSection = document.querySelector('.input-section'); + this.$contentsContainer = document.querySelector('.contents-container'); + } + + render() { + this.$inputSection.insertAdjacentHTML('beforeend', this.inputSection()); + this.$contentsContainer.insertAdjacentHTML('beforeend', this.changeList()); + } + + inputSection() { + return ` +
    +

    자판기가 보유할 금액을 입력해주세요

    +
    + + +
    +

    현재 보유 금액:

    +
    `; + } + + changeList() { + return ` +
    +

    자판기가 보유한 동전

    +
      +
    • + 동전 + 개수 +
    • +
    • + 500원 + 0개 +
    • +
    • + 100원 + 4개 +
    • +
    • + 50원 + 1개 +
    • +
    • + 10원 + 5개 +
    • +
    +
    `; + } +} diff --git a/src/js/routes.ts b/src/js/routes.ts index 0a0f9a53e..87bef32db 100644 --- a/src/js/routes.ts +++ b/src/js/routes.ts @@ -1,3 +1,4 @@ +import ChangeAdd from './ChangeAdd'; import ProductManage from './ProductManage'; const clearPurchaseBody = () => { @@ -8,30 +9,34 @@ const clearPurchaseBody = () => { $contentsContainer.replaceChildren(); }; -const routesCoke = () => { +const router = () => { const productManage = new ProductManage(); + const changeAdd = new ChangeAdd(); let prevPath = ''; return () => { - const pathname = window.location.hash; + const pathName = window.location.hash; - if (prevPath === pathname) { + if (prevPath === pathName) { return; } - prevPath = pathname; + prevPath = pathName; clearPurchaseBody(); - switch (pathname) { + switch (pathName) { case '#!/product-manage': productManage.render(); break; - case '/#!/change-add': + case '#!/change-add': + changeAdd.render(); + break; + default: break; } }; }; -const routes = routesCoke(); +const routes = router(); export default routes; From 2d42296f0428dcbc8f0d4665cb03eccca1c1c892 Mon Sep 17 00:00:00 2001 From: intae92 Date: Wed, 23 Mar 2022 13:43:47 +0900 Subject: [PATCH 13/44] =?UTF-8?q?feat:=20=EC=83=81=ED=92=88=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EB=B0=94=EC=9D=B8?= =?UTF-8?q?=EB=94=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - refactor: VendingMachine모델 단일화 사용 - refactor: 파일 분리 Co-authored-by: YongRae_Kim (Usage) --- src/js/__test__/vendingMachine.test.js | 5 ++-- src/js/model/VendingMachine.ts | 6 ++++- src/js/{ => pages}/ChangeAdd.ts | 0 src/js/{ => pages}/ProductManage.ts | 34 ++++++++++++++++++++++++++ src/js/routes.ts | 4 +-- 5 files changed, 44 insertions(+), 5 deletions(-) rename src/js/{ => pages}/ChangeAdd.ts (100%) rename src/js/{ => pages}/ProductManage.ts (68%) diff --git a/src/js/__test__/vendingMachine.test.js b/src/js/__test__/vendingMachine.test.js index 395dd90f7..8f2070c43 100644 --- a/src/js/__test__/vendingMachine.test.js +++ b/src/js/__test__/vendingMachine.test.js @@ -1,8 +1,9 @@ import { ERROR_MESSAGE } from '../constants'; -import VendingMachine from '../model/VendingMachine' +// import VendingMachine from '../model/VendingMachine' +import vendingMachine from '../model/VendingMachine' describe('자판기 기본 기능 테스트', () => { - const vendingMachine = new VendingMachine(); + // const vendingMachine = new VendingMachine(); describe('자판기 상품 추가 기능 테스트', () => { it('자판기에 상품을 추가할 수 있어야 한다.', () => { diff --git a/src/js/model/VendingMachine.ts b/src/js/model/VendingMachine.ts index 1fcc71fa5..f73aeeb58 100644 --- a/src/js/model/VendingMachine.ts +++ b/src/js/model/VendingMachine.ts @@ -18,7 +18,7 @@ interface Product { price: number; } -export default class VendingMachine { +class VendingMachine { products: Array; // name amount price changes: Coin; // 자판기가 보유하고 있는 돈 = 잔돈 totalMoney: number; @@ -107,3 +107,7 @@ export default class VendingMachine { return coins[index]; } } + +const vendingMachine = new VendingMachine(); + +export default vendingMachine; diff --git a/src/js/ChangeAdd.ts b/src/js/pages/ChangeAdd.ts similarity index 100% rename from src/js/ChangeAdd.ts rename to src/js/pages/ChangeAdd.ts diff --git a/src/js/ProductManage.ts b/src/js/pages/ProductManage.ts similarity index 68% rename from src/js/ProductManage.ts rename to src/js/pages/ProductManage.ts index be8ccc747..f37d1e863 100644 --- a/src/js/ProductManage.ts +++ b/src/js/pages/ProductManage.ts @@ -1,6 +1,15 @@ +import vendingMachine from '../model/VendingMachine'; + +interface Product { + name: string; + amount: number; + price: number; +} + export default class ProductManage { $inputSection: HTMLElement; $contentsContainer: HTMLElement; + $productAddForm: HTMLElement; constructor() { this.$inputSection = document.querySelector('.input-section'); @@ -10,8 +19,33 @@ export default class ProductManage { render() { this.$inputSection.insertAdjacentHTML('beforeend', this.inputSection()); this.$contentsContainer.insertAdjacentHTML('beforeend', this.productList()); + + this.$productAddForm = document.querySelector('#product-add-form'); + this.$productAddForm.addEventListener('submit', this.onSubmitNewProduct); } + onSubmitNewProduct = (e: SubmitEvent) => { + e.preventDefault(); + + const name = (( + this.$productAddForm.querySelector('#product-name-input') + )).value; + const price = (( + this.$productAddForm.querySelector('#product-price-input') + )).value; + const amount = (( + this.$productAddForm.querySelector('#product-amount-input') + )).value; + + const newProduct: Product = { + name: name, + price: parseInt(price), + amount: parseInt(amount), + }; + + vendingMachine.addProduct(newProduct); + }; + inputSection() { return `
    diff --git a/src/js/routes.ts b/src/js/routes.ts index 87bef32db..36f28fcb8 100644 --- a/src/js/routes.ts +++ b/src/js/routes.ts @@ -1,5 +1,5 @@ -import ChangeAdd from './ChangeAdd'; -import ProductManage from './ProductManage'; +import ChangeAdd from './pages/ChangeAdd'; +import ProductManage from './pages/ProductManage'; const clearPurchaseBody = () => { const $inputSection = document.querySelector('.input-section'); From 22fe134b9eae356e7a9cde17f20098638afeee88 Mon Sep 17 00:00:00 2001 From: intae92 Date: Wed, 23 Mar 2022 13:52:16 +0900 Subject: [PATCH 14/44] =?UTF-8?q?refactor:=20interface=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: YongRae_Kim (Usage) --- src/js/interfaces/VendingMachine.interface.ts | 14 ++++++++++++++ src/js/model/VendingMachine.ts | 14 +------------- src/js/pages/ProductManage.ts | 7 +------ 3 files changed, 16 insertions(+), 19 deletions(-) create mode 100644 src/js/interfaces/VendingMachine.interface.ts diff --git a/src/js/interfaces/VendingMachine.interface.ts b/src/js/interfaces/VendingMachine.interface.ts new file mode 100644 index 000000000..f306ddf83 --- /dev/null +++ b/src/js/interfaces/VendingMachine.interface.ts @@ -0,0 +1,14 @@ +interface Coin { + coin10: number; + coin50: number; + coin100: number; + coin500: number; +} + +interface Product { + name: string; + amount: number; + price: number; +} + +export { Coin, Product }; diff --git a/src/js/model/VendingMachine.ts b/src/js/model/VendingMachine.ts index f73aeeb58..9fffa4467 100644 --- a/src/js/model/VendingMachine.ts +++ b/src/js/model/VendingMachine.ts @@ -1,23 +1,11 @@ import { ERROR_MESSAGE, RULES } from '../constants'; +import { Product, Coin } from '../interfaces/VendingMachine.interface'; import { isValidProductPrice, isValidProductAmount, isValidProductNameLength, } from './validator'; -interface Coin { - coin10: number; - coin50: number; - coin100: number; - coin500: number; -} - -interface Product { - name: string; - amount: number; - price: number; -} - class VendingMachine { products: Array; // name amount price changes: Coin; // 자판기가 보유하고 있는 돈 = 잔돈 diff --git a/src/js/pages/ProductManage.ts b/src/js/pages/ProductManage.ts index f37d1e863..206d5412b 100644 --- a/src/js/pages/ProductManage.ts +++ b/src/js/pages/ProductManage.ts @@ -1,10 +1,5 @@ import vendingMachine from '../model/VendingMachine'; - -interface Product { - name: string; - amount: number; - price: number; -} +import { Product } from '../interfaces/VendingMachine.interface'; export default class ProductManage { $inputSection: HTMLElement; From c4bdca1146a54ac93586ef6cec21c017ee9e7d6b Mon Sep 17 00:00:00 2001 From: intae92 Date: Wed, 23 Mar 2022 14:43:59 +0900 Subject: [PATCH 15/44] =?UTF-8?q?chore:=20prettier=20=EB=9D=BC=EC=9D=B8?= =?UTF-8?q?=EB=B3=84=20=EA=B8=80=EC=9E=90=20=EC=88=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: YongRae_Kim (Usage) --- .prettierrc.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.prettierrc.json b/.prettierrc.json index 2a6798374..69a5ed96d 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -1,13 +1,13 @@ { - "printWidth": 80, - "tabWidth": 2, - "useTabs": false, - "semi": true, - "singleQuote": true, - "trailingComma": "all", - "bracketSpacing": true, - "jsxBracketSameLine": true, - "arrowParens": "avoid", - "vueIndentScriptAndStyle": false, - "endOfLine": "auto" + "printWidth": 120, + "tabWidth": 2, + "useTabs": false, + "semi": true, + "singleQuote": true, + "trailingComma": "all", + "bracketSpacing": true, + "jsxBracketSameLine": true, + "arrowParens": "avoid", + "vueIndentScriptAndStyle": false, + "endOfLine": "auto" } From 824925718b22c8222b0651b3781b8b4056fd4217 Mon Sep 17 00:00:00 2001 From: intae92 Date: Wed, 23 Mar 2022 14:45:02 +0900 Subject: [PATCH 16/44] =?UTF-8?q?feat:=20getProducts=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: YongRae_Kim (Usage) --- src/js/model/VendingMachine.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/js/model/VendingMachine.ts b/src/js/model/VendingMachine.ts index 9fffa4467..9980335c9 100644 --- a/src/js/model/VendingMachine.ts +++ b/src/js/model/VendingMachine.ts @@ -1,13 +1,9 @@ import { ERROR_MESSAGE, RULES } from '../constants'; import { Product, Coin } from '../interfaces/VendingMachine.interface'; -import { - isValidProductPrice, - isValidProductAmount, - isValidProductNameLength, -} from './validator'; +import { isValidProductPrice, isValidProductAmount, isValidProductNameLength } from './validator'; class VendingMachine { - products: Array; // name amount price + private products: Array; // name amount price changes: Coin; // 자판기가 보유하고 있는 돈 = 잔돈 totalMoney: number; @@ -17,6 +13,10 @@ class VendingMachine { this.totalMoney = 0; } + getProducts() { + return this.products; + } + addProduct(product: Product) { const productIndex = this.findProductIndex(product.name); const isExist = productIndex >= 0; From 7366550d2e5777ce511f633109645c8780e32538 Mon Sep 17 00:00:00 2001 From: intae92 Date: Wed, 23 Mar 2022 14:46:09 +0900 Subject: [PATCH 17/44] =?UTF-8?q?refactor:=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EC=9A=94=EC=86=8C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: YongRae_Kim (Usage) --- src/js/components/AddProductComponent.ts | 29 +++++++++++++++++++++++ src/js/components/ProductItemComponent.ts | 19 +++++++++++++++ src/js/components/ProductListComponent.ts | 18 ++++++++++++++ 3 files changed, 66 insertions(+) create mode 100644 src/js/components/AddProductComponent.ts create mode 100644 src/js/components/ProductItemComponent.ts create mode 100644 src/js/components/ProductListComponent.ts diff --git a/src/js/components/AddProductComponent.ts b/src/js/components/AddProductComponent.ts new file mode 100644 index 000000000..db99ab570 --- /dev/null +++ b/src/js/components/AddProductComponent.ts @@ -0,0 +1,29 @@ +const AddProductComponent = () => { + return ` +
    +

    추가할 상품 정보를 입력해주세요.

    +
    + + + + +
    +
    `; +}; + +export default AddProductComponent; diff --git a/src/js/components/ProductItemComponent.ts b/src/js/components/ProductItemComponent.ts new file mode 100644 index 000000000..ce9c1c1c0 --- /dev/null +++ b/src/js/components/ProductItemComponent.ts @@ -0,0 +1,19 @@ +import { Product } from '../interfaces/VendingMachine.interface'; + +const ProductItemComponent = (product: Product) => { + const { name, price, amount } = product; + return ` +
  • + ${name} + ${price} + ${amount} + + + +
  • + `; +}; + +export default ProductItemComponent; diff --git a/src/js/components/ProductListComponent.ts b/src/js/components/ProductListComponent.ts new file mode 100644 index 000000000..f8f7a45fa --- /dev/null +++ b/src/js/components/ProductListComponent.ts @@ -0,0 +1,18 @@ +const ProductListComponent = () => { + return ` +
    +
    +

    상품 현황

    +
      +
    • + 상품명 + 가격 + 수량 + +
    • +
    +
    +
    `; +}; + +export default ProductListComponent; From c669dddf3557e4419b939f904ef11eb1def7e2ce Mon Sep 17 00:00:00 2001 From: intae92 Date: Wed, 23 Mar 2022 14:46:43 +0900 Subject: [PATCH 18/44] =?UTF-8?q?feat:=20=EC=83=81=ED=92=88=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EB=AA=A9=EB=A1=9D=20=EB=A0=8C=EB=8D=94=EB=A7=81=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: YongRae_Kim (Usage) --- src/js/pages/ProductManage.ts | 101 +++++++++------------------------- 1 file changed, 27 insertions(+), 74 deletions(-) diff --git a/src/js/pages/ProductManage.ts b/src/js/pages/ProductManage.ts index 206d5412b..98c45c5c3 100644 --- a/src/js/pages/ProductManage.ts +++ b/src/js/pages/ProductManage.ts @@ -1,10 +1,14 @@ import vendingMachine from '../model/VendingMachine'; import { Product } from '../interfaces/VendingMachine.interface'; +import AddProductComponent from '../components/AddProductComponent'; +import ProductListComponent from '../components/ProductListComponent'; +import ProductItemComponent from '../components/ProductItemComponent'; export default class ProductManage { $inputSection: HTMLElement; $contentsContainer: HTMLElement; $productAddForm: HTMLElement; + $productList: HTMLElement; constructor() { this.$inputSection = document.querySelector('.input-section'); @@ -12,25 +16,22 @@ export default class ProductManage { } render() { - this.$inputSection.insertAdjacentHTML('beforeend', this.inputSection()); - this.$contentsContainer.insertAdjacentHTML('beforeend', this.productList()); + this.$inputSection.insertAdjacentHTML('beforeend', AddProductComponent()); + this.$contentsContainer.insertAdjacentHTML('beforeend', ProductListComponent()); - this.$productAddForm = document.querySelector('#product-add-form'); + this.$productAddForm = this.$inputSection.querySelector('#product-add-form'); + this.$productList = this.$contentsContainer.querySelector('#product-list'); this.$productAddForm.addEventListener('submit', this.onSubmitNewProduct); + + this.renderProducts(); } onSubmitNewProduct = (e: SubmitEvent) => { e.preventDefault(); - const name = (( - this.$productAddForm.querySelector('#product-name-input') - )).value; - const price = (( - this.$productAddForm.querySelector('#product-price-input') - )).value; - const amount = (( - this.$productAddForm.querySelector('#product-amount-input') - )).value; + const name = (this.$productAddForm.querySelector('#product-name-input')).value; + const price = (this.$productAddForm.querySelector('#product-price-input')).value; + const amount = (this.$productAddForm.querySelector('#product-amount-input')).value; const newProduct: Product = { name: name, @@ -38,71 +39,23 @@ export default class ProductManage { amount: parseInt(amount), }; - vendingMachine.addProduct(newProduct); + try { + vendingMachine.addProduct(newProduct); + this.addProductItem(newProduct); + } catch (message) { + alert(message); + } }; - inputSection() { - return ` -
    -

    추가할 상품 정보를 입력해주세요.

    -
    - - - - -
    -
    `; + renderProducts() { + const products = vendingMachine.getProducts(); + console.log('products', products); + products.forEach(product => { + this.addProductItem(product); + }); } - productList() { - return ` -
    -
    -

    상품 현황

    -
      -
    • - 상품명 - 가격 - 수량 - -
    • -
    • - 콜라 - 1500 - 20 - - - -
    • -
    • - 사이다 - 1000 - 10 - - - -
    • -
    -
    -
    `; + addProductItem(product: Product) { + this.$productList.insertAdjacentHTML('beforeend', ProductItemComponent(product)); } } From 6e11a65eb9da26cff97671880bb6a37b4c2130a4 Mon Sep 17 00:00:00 2001 From: intae92 Date: Wed, 23 Mar 2022 16:09:12 +0900 Subject: [PATCH 19/44] =?UTF-8?q?style:=20product=20modify=20css=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: YongRae_Kim (Usage) --- src/css/index.css | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/src/css/index.css b/src/css/index.css index 1ff13d9e1..5c2073856 100644 --- a/src/css/index.css +++ b/src/css/index.css @@ -132,12 +132,6 @@ li { justify-content: center; border-bottom: 1px solid #dcdcdc; } -.product-modify-button { - padding: 0; - width: 100%; - height: 90%; - cursor: pointer; -} #product-list { width: 100%; @@ -148,7 +142,7 @@ li { } #product-list li span { - margin: 8px 20px 4px; + margin: 12px 0px 8px; width: 100%; height: 30px; font-size: 15px; @@ -157,6 +151,30 @@ li { color: rgba(0, 0, 0, 0.87); } +#product-list li input { + width: 80%; + text-align: center; + outline: 1px dotted #777777; + border: 1px dotted #bbbbbb; +} + +.product-control-buttons { + display: flex; + gap: 10px; + justify-content: center; +} + +.product-control-buttons button { + width: 40%; + height: 30px; + margin-top: -4px !important; +} + +.product-modify-submit-button { + width: 90%; + margin-top: -8px !important; +} + #change-list li { width: 300px; } From 070fbf0e6609b86576470d7f8b8286a7b88c48f7 Mon Sep 17 00:00:00 2001 From: intae92 Date: Wed, 23 Mar 2022 17:59:59 +0900 Subject: [PATCH 20/44] =?UTF-8?q?feat:=20product=20=EC=A0=95=EB=B3=B4=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: YongRae_Kim (Usage) --- src/js/components/ModifyProductComponent.ts | 41 ++++++++++++++ src/js/components/ProductItemComponent.ts | 15 ++--- src/js/model/VendingMachine.ts | 48 +++++++++------- src/js/pages/ProductManage.ts | 62 ++++++++++++++++++++- 4 files changed, 137 insertions(+), 29 deletions(-) create mode 100644 src/js/components/ModifyProductComponent.ts diff --git a/src/js/components/ModifyProductComponent.ts b/src/js/components/ModifyProductComponent.ts new file mode 100644 index 000000000..aac5486ab --- /dev/null +++ b/src/js/components/ModifyProductComponent.ts @@ -0,0 +1,41 @@ +import { Product } from '../interfaces/VendingMachine.interface'; + +const ModifyProductComponent = (product: Product) => { + const { name, price, amount } = product; + return ` + + + + + + + + + + + + + `; +}; + +export default ModifyProductComponent; diff --git a/src/js/components/ProductItemComponent.ts b/src/js/components/ProductItemComponent.ts index ce9c1c1c0..0a3e81b4d 100644 --- a/src/js/components/ProductItemComponent.ts +++ b/src/js/components/ProductItemComponent.ts @@ -3,16 +3,17 @@ import { Product } from '../interfaces/VendingMachine.interface'; const ProductItemComponent = (product: Product) => { const { name, price, amount } = product; return ` -
  • - ${name} - ${price} - ${amount} - + ${name} + ${price} + ${amount} + - -
  • + + `; }; diff --git a/src/js/model/VendingMachine.ts b/src/js/model/VendingMachine.ts index 9980335c9..5b429b9f5 100644 --- a/src/js/model/VendingMachine.ts +++ b/src/js/model/VendingMachine.ts @@ -18,25 +18,7 @@ class VendingMachine { } addProduct(product: Product) { - const productIndex = this.findProductIndex(product.name); - const isExist = productIndex >= 0; - - if (isExist) { - throw new Error(ERROR_MESSAGE.PRODUCT_NAME_IS_DUPLICATED); - } - - if (!isValidProductNameLength(product.name)) { - throw new Error(ERROR_MESSAGE.PRODUCT_NAME_LENGTH); - } - - if (!isValidProductPrice(product.price)) { - throw new Error(ERROR_MESSAGE.PRODUCT_PRICE); - } - - if (!isValidProductAmount(product.amount)) { - throw new Error(ERROR_MESSAGE.PRODUCT_AMOUNT); - } - + this.checkProductValidate(product); this.products.push(product); } @@ -53,8 +35,9 @@ class VendingMachine { } } - modifyProduct(oldProduct: Product, newProduct: Product) { - const oldProductIndex = this.findProductIndex(oldProduct.name); + modifyProduct(oldProductName: string, newProduct: Product) { + const oldProductIndex = this.findProductIndex(oldProductName); + this.checkProductValidate(newProduct, oldProductIndex); this.products[oldProductIndex] = newProduct; } @@ -94,6 +77,29 @@ class VendingMachine { const index = this.getRandomInt(coins.length - 1); return coins[index]; } + + checkProductValidate(product: Product, originalIndex: number = -1) { + const productIndex = this.findProductIndex(product.name); + const isExist = productIndex >= 0; + const isAddWithDuplicatedName = isExist && originalIndex === -1; + const isModifyWithDuplicateName = isExist && originalIndex !== productIndex; + + if (isAddWithDuplicatedName || isModifyWithDuplicateName) { + throw new Error(ERROR_MESSAGE.PRODUCT_NAME_IS_DUPLICATED); + } + + if (!isValidProductNameLength(product.name)) { + throw new Error(ERROR_MESSAGE.PRODUCT_NAME_LENGTH); + } + + if (!isValidProductPrice(product.price)) { + throw new Error(ERROR_MESSAGE.PRODUCT_PRICE); + } + + if (!isValidProductAmount(product.amount)) { + throw new Error(ERROR_MESSAGE.PRODUCT_AMOUNT); + } + } } const vendingMachine = new VendingMachine(); diff --git a/src/js/pages/ProductManage.ts b/src/js/pages/ProductManage.ts index 98c45c5c3..63bad638f 100644 --- a/src/js/pages/ProductManage.ts +++ b/src/js/pages/ProductManage.ts @@ -3,6 +3,7 @@ import { Product } from '../interfaces/VendingMachine.interface'; import AddProductComponent from '../components/AddProductComponent'; import ProductListComponent from '../components/ProductListComponent'; import ProductItemComponent from '../components/ProductItemComponent'; +import ModifyProductComponent from '../components/ModifyProductComponent'; export default class ProductManage { $inputSection: HTMLElement; @@ -22,6 +23,8 @@ export default class ProductManage { this.$productAddForm = this.$inputSection.querySelector('#product-add-form'); this.$productList = this.$contentsContainer.querySelector('#product-list'); this.$productAddForm.addEventListener('submit', this.onSubmitNewProduct); + this.$productList.addEventListener('click', this.onClickModifyButton); + this.$productList.addEventListener('click', this.onSubmitModifyCompleteButton); this.renderProducts(); } @@ -47,15 +50,72 @@ export default class ProductManage { } }; + onClickModifyButton = (e: PointerEvent) => { + if ((e.target).className !== 'product-modify-button') { + return; + } + + const ul = (e.target).closest('ul'); + const oldLi = (e.target).closest('li'); + const product = { + name: (oldLi.querySelector('.product-name')).textContent, + price: parseInt((oldLi.querySelector('.product-price')).textContent), + amount: parseInt((oldLi.querySelector('.product-amount')).textContent), + }; + + ul.replaceChild(this.replaceList(product, ModifyProductComponent), oldLi); + }; + + onSubmitModifyCompleteButton = (e: PointerEvent) => { + if ((e.target).className !== 'product-modify-submit-button') { + return; + } + + const ul = (e.target).closest('ul'); + const parentList = (e.target).closest('li'); + const product = { + name: (parentList.querySelector('.product-name-modify-input')).value, + price: parseInt((parentList.querySelector('.product-price-modify-input')).value), + amount: parseInt((parentList.querySelector('.product-amount-modify-input')).value), + }; + + const prevName = (parentList.querySelector('.product-modify-submit-button')).dataset.name; + + try { + vendingMachine.modifyProduct(prevName, product); + ul.replaceChild(this.replaceList(product, ProductItemComponent), parentList); + } catch (message) { + alert(message); + } + + console.log(vendingMachine.getProducts()); + }; + + replaceList = (product: Product, component: Function) => { + const fragment = new DocumentFragment(); + const li = document.createElement('li'); + + li.insertAdjacentHTML('beforeend', component(product)); + fragment.appendChild(li); + + return fragment; + }; + renderProducts() { const products = vendingMachine.getProducts(); console.log('products', products); + products.forEach(product => { this.addProductItem(product); }); } addProductItem(product: Product) { - this.$productList.insertAdjacentHTML('beforeend', ProductItemComponent(product)); + const fragment = new DocumentFragment(); + const li = document.createElement('li'); + + li.insertAdjacentHTML('beforeend', ProductItemComponent(product)); + fragment.appendChild(li); + this.$productList.appendChild(fragment); } } From 7313f5a50b1639cd87de219388ed06640d3e289b Mon Sep 17 00:00:00 2001 From: intae92 Date: Wed, 23 Mar 2022 18:13:58 +0900 Subject: [PATCH 21/44] =?UTF-8?q?feat:=20=EC=83=81=ED=92=88=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: YongRae_Kim (Usage) --- src/js/constants.ts | 10 +++++----- src/js/model/VendingMachine.ts | 6 +++--- src/js/pages/ProductManage.ts | 18 +++++++++++++++++- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/src/js/constants.ts b/src/js/constants.ts index f8b97380f..defbdfe51 100644 --- a/src/js/constants.ts +++ b/src/js/constants.ts @@ -11,11 +11,11 @@ const RULES = { const ERROR_MESSAGE = { PRODUCT_NAME_IS_DUPLICATED: '이미 존재하는 이름의 상품입니다.', PRODUCT_NAME_LENGTH: '상품명은 최대 10글자까지 입력해주세요.', - PRODUCT_PRICE: - '상품가격은 100원~10,000원 사이 여야 하며 10원으로 나누어 떨어져야 합니다.', + PRODUCT_PRICE: '상품가격은 100원~10,000원 사이 여야 하며 10원으로 나누어 떨어져야 합니다.', PRODUCT_AMOUNT: '한 제품당 수량은 최대 20개 입니다.', - TOO_MUCH_VENDING_MACHINE_CHANGE: - '자판기가 보유할 수 있는 최대 금액은 100,000원 입니다.', + TOO_MUCH_VENDING_MACHINE_CHANGE: '자판기가 보유할 수 있는 최대 금액은 100,000원 입니다.', }; -export { RULES, ERROR_MESSAGE }; +const REMOVE_CONFIRM_MESSAGE = '정말로 삭제하시겠습니까?'; + +export { RULES, ERROR_MESSAGE, REMOVE_CONFIRM_MESSAGE }; diff --git a/src/js/model/VendingMachine.ts b/src/js/model/VendingMachine.ts index 5b429b9f5..09720e92b 100644 --- a/src/js/model/VendingMachine.ts +++ b/src/js/model/VendingMachine.ts @@ -26,9 +26,9 @@ class VendingMachine { return this.products.findIndex(product => product.name === name); } - removeProduct(product: Product) { - const productIndex = this.findProductIndex(product.name); - const isExist = productIndex > 0; + removeProduct(name: string) { + const productIndex = this.findProductIndex(name); + const isExist = productIndex >= 0; if (isExist) { this.products.splice(productIndex, 1); diff --git a/src/js/pages/ProductManage.ts b/src/js/pages/ProductManage.ts index 63bad638f..a9c752369 100644 --- a/src/js/pages/ProductManage.ts +++ b/src/js/pages/ProductManage.ts @@ -4,6 +4,7 @@ import AddProductComponent from '../components/AddProductComponent'; import ProductListComponent from '../components/ProductListComponent'; import ProductItemComponent from '../components/ProductItemComponent'; import ModifyProductComponent from '../components/ModifyProductComponent'; +import { REMOVE_CONFIRM_MESSAGE } from '../constants'; export default class ProductManage { $inputSection: HTMLElement; @@ -25,6 +26,7 @@ export default class ProductManage { this.$productAddForm.addEventListener('submit', this.onSubmitNewProduct); this.$productList.addEventListener('click', this.onClickModifyButton); this.$productList.addEventListener('click', this.onSubmitModifyCompleteButton); + this.$productList.addEventListener('click', this.onClickRemoveButton); this.renderProducts(); } @@ -87,8 +89,22 @@ export default class ProductManage { } catch (message) { alert(message); } + }; + + onClickRemoveButton = (e: PointerEvent) => { + if ((e.target).className !== 'product-remove-button') { + return; + } + + if (!window.confirm(REMOVE_CONFIRM_MESSAGE)) { + return; + } + + const parentList = (e.target).closest('li'); + const name = (parentList.querySelector('.product-name')).textContent; - console.log(vendingMachine.getProducts()); + vendingMachine.removeProduct(name); + parentList.remove(); }; replaceList = (product: Product, component: Function) => { From 7621b411e19bcf9c01bd6dcbfccee61d4dd3a912 Mon Sep 17 00:00:00 2001 From: intae92 Date: Wed, 23 Mar 2022 18:15:14 +0900 Subject: [PATCH 22/44] =?UTF-8?q?docs:=20=EA=B8=B0=EB=8A=A5=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=20=EA=B5=AC=ED=98=84=20=EC=83=81=ED=99=A9=20=EC=97=85?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: YongRae_Kim (Usage) --- src/docs/README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/docs/README.md b/src/docs/README.md index 30835b098..f66071f0c 100644 --- a/src/docs/README.md +++ b/src/docs/README.md @@ -2,12 +2,12 @@ ### 공통 -- [ ] 도메인 영역을 타입스크립트를 이용하여 구현한다. +- [x] 도메인 영역을 타입스크립트를 이용하여 구현한다. - Interface 또는 type을 이용하여, 주요 도메인 객체의 타입을 정의하고 설계한다. ### 라우팅 기능 -- [ ] Browser History Api를 이용하여 SPA처럼 라우팅을 적용한다. +- [x] Browser History Api를 이용하여 SPA처럼 라우팅을 적용한다. - 매번 페이지를 로드 하는 것이 아닌, 히스토리를 관리하고, 페이지를 url에 따라 동적으로 렌더링한다 - 상품 관리, 잔돈 충전, 상품 구매 페이지는 모두 동적으로 렌더링해야 한다. @@ -15,15 +15,15 @@ **상품 관리탭은 자판기가 보유하고 있는 상품을 추가하는 기능을 수행한다.** -- [ ] 최초 상품 목록은 비워진 상태이다. -- [ ] 상품명, 가격, 수량을 입력해 상품을 추가할 수 있다. +- [x] 최초 상품 목록은 비워진 상태이다. +- [x] 상품명, 가격, 수량을 입력해 상품을 추가할 수 있다. - 상품명은 최대 10글자까지 가능하다. - 상품 가격은 100원부터 시작하며, 최대 10,000원까지 가능하다. 그리고 10원으로 나누어 떨어져야 한다. - 한 제품당 수량은 최대 20개까지 넣을 수있다. -- [ ] 관리자는 추가한 상품을 확인할 수 있다. -- [ ] 관리자는 추가한 상품을 수정, 삭제할 수 있다. -- [ ] 수정 시 상품명, 가격, 수량 정보 영역 자체가 인풋 영역으로 변경된다. -- [ ] 삭제 시 confirm을 활용하여 사용자에게 다시 한번 확인한다. +- [x] 관리자는 추가한 상품을 확인할 수 있다. +- [x] 관리자는 추가한 상품을 수정, 삭제할 수 있다. +- [x] 수정 시 상품명, 가격, 수량 정보 영역 자체가 인풋 영역으로 변경된다. +- [x] 삭제 시 confirm을 활용하여 사용자에게 다시 한번 확인한다. ### 잔돈 충전 탭 From 1ccb5f01bf1cb3e6f987e5f7d5a0a7644522b9d8 Mon Sep 17 00:00:00 2001 From: intae92 Date: Wed, 23 Mar 2022 20:06:16 +0900 Subject: [PATCH 23/44] =?UTF-8?q?style:=20=EB=9D=BC=EC=9D=B8=20=EA=B8=80?= =?UTF-8?q?=EC=9E=90=20=EC=88=98=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/index.ts | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/index.ts b/src/index.ts index 6f0841523..187e60238 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,21 +11,13 @@ const btn3 = document.querySelector('#product-purchase-button'); productManageButton.addEventListener('click', () => { console.log('click1'); - history.pushState( - { prevPath: window.location.hash }, - '상품 관리하기', - '/#!/product-manage', - ); + history.pushState({ prevPath: window.location.hash }, '상품 관리하기', '/#!/product-manage'); routes(); }); changeAddButton.addEventListener('click', () => { console.log('click2'); - history.pushState( - { prevPath: window.location.hash }, - '잔돈 채우기', - '/#!/change-add', - ); + history.pushState({ prevPath: window.location.hash }, '잔돈 채우기', '/#!/change-add'); routes(); }); From 254962b2ea514914e3256031f75f14a62e1c386b Mon Sep 17 00:00:00 2001 From: intae92 Date: Wed, 23 Mar 2022 21:13:23 +0900 Subject: [PATCH 24/44] =?UTF-8?q?feat:=20=EC=9E=94=EB=8F=88=20=EC=B6=A9?= =?UTF-8?q?=EC=A0=84=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - feat: 투입된 금액의 유효성 검증 - feat: 투입된 금액 에러메시지 상수화 Co-authored-by: YongRae_Kim (Usage) --- src/js/components/AddChangeComponent.ts | 18 +++++ src/js/components/ChangeListComponent.ts | 30 ++++++++ src/js/constants.ts | 4 +- src/js/model/VendingMachine.ts | 56 +++++++++++--- src/js/model/validator.ts | 18 +++-- src/js/pages/ChangeAdd.ts | 94 ++++++++++++------------ 6 files changed, 154 insertions(+), 66 deletions(-) create mode 100644 src/js/components/AddChangeComponent.ts create mode 100644 src/js/components/ChangeListComponent.ts diff --git a/src/js/components/AddChangeComponent.ts b/src/js/components/AddChangeComponent.ts new file mode 100644 index 000000000..ba747eaa6 --- /dev/null +++ b/src/js/components/AddChangeComponent.ts @@ -0,0 +1,18 @@ +const AddChangeComponent = () => { + return ` +
    +

    자판기가 보유할 금액을 입력해주세요

    +
    + + +
    +

    현재 보유 금액:

    +
    `; +}; + +export default AddChangeComponent; diff --git a/src/js/components/ChangeListComponent.ts b/src/js/components/ChangeListComponent.ts new file mode 100644 index 000000000..d416a5c03 --- /dev/null +++ b/src/js/components/ChangeListComponent.ts @@ -0,0 +1,30 @@ +const ChangeListComponent = () => { + return ` +
    +

    자판기가 보유한 동전

    +
      +
    • + 동전 + 개수 +
    • +
    • + 500원 + +
    • +
    • + 100원 + +
    • +
    • + 50원 + +
    • +
    • + 10원 + +
    • +
    +
    `; +}; + +export default ChangeListComponent; diff --git a/src/js/constants.ts b/src/js/constants.ts index defbdfe51..50548c963 100644 --- a/src/js/constants.ts +++ b/src/js/constants.ts @@ -11,9 +11,11 @@ const RULES = { const ERROR_MESSAGE = { PRODUCT_NAME_IS_DUPLICATED: '이미 존재하는 이름의 상품입니다.', PRODUCT_NAME_LENGTH: '상품명은 최대 10글자까지 입력해주세요.', - PRODUCT_PRICE: '상품가격은 100원~10,000원 사이 여야 하며 10원으로 나누어 떨어져야 합니다.', + PRODUCT_PRICE: '상품가격은 100원~10,000원 사이여야 하며 10원으로 나누어 떨어져야 합니다.', PRODUCT_AMOUNT: '한 제품당 수량은 최대 20개 입니다.', TOO_MUCH_VENDING_MACHINE_CHANGE: '자판기가 보유할 수 있는 최대 금액은 100,000원 입니다.', + IS_NOT_UNIT_OF_TEN: '투입할 금액의 단위는 10원입니다.', + IS_NOT_POSITIVE_INTEGER: '투입할 금액은 0보다 큰 금액이어야 합니다.', }; const REMOVE_CONFIRM_MESSAGE = '정말로 삭제하시겠습니까?'; diff --git a/src/js/model/VendingMachine.ts b/src/js/model/VendingMachine.ts index 09720e92b..65bd89ec3 100644 --- a/src/js/model/VendingMachine.ts +++ b/src/js/model/VendingMachine.ts @@ -1,11 +1,17 @@ import { ERROR_MESSAGE, RULES } from '../constants'; import { Product, Coin } from '../interfaces/VendingMachine.interface'; -import { isValidProductPrice, isValidProductAmount, isValidProductNameLength } from './validator'; +import { + isValidProductPrice, + isValidProductAmount, + isValidProductNameLength, + isUnitOfTen, + isPositiveInteger, +} from './validator'; class VendingMachine { - private products: Array; // name amount price - changes: Coin; // 자판기가 보유하고 있는 돈 = 잔돈 - totalMoney: number; + private products: Array; + private changes: Coin; + private totalMoney: number; constructor() { this.products = []; @@ -17,6 +23,14 @@ class VendingMachine { return this.products; } + getChanges() { + return this.changes; + } + + getTotalMoney() { + return this.totalMoney; + } + addProduct(product: Product) { this.checkProductValidate(product); this.products.push(product); @@ -42,15 +56,15 @@ class VendingMachine { } inputChanges(money: number) { - // 1. 돈이 10원으로 나누어지는지 -> 어디에 로직을 둘것인지 킵 - - if (money > RULES.MAX_VENDING_MACHINE_CHANGE) { - throw new Error(ERROR_MESSAGE.TOO_MUCH_VENDING_MACHINE_CHANGE); - } - + this.checkInputChangesValidate(money); this.totalMoney += money; + this.makeChanges(money); + } + // makeChanges 과연 적절한 이름인가? 리팩토링때 다시보기 + makeChanges(money: number) { const coin = this.getChangeCoin(money); + money -= coin; switch (coin) { case 500: @@ -66,15 +80,19 @@ class VendingMachine { this.changes.coin10 += 1; break; } + + if (money >= 10) { + this.makeChanges(money); + } } getRandomInt(max: number) { - return Math.floor(Math.random() * max + 1); // 0 ~ max + return Math.floor(Math.random() * max); } getChangeCoin(money: number) { const coins = [500, 100, 50, 10].filter(coin => coin <= money); - const index = this.getRandomInt(coins.length - 1); + const index = this.getRandomInt(coins.length); return coins[index]; } @@ -100,6 +118,20 @@ class VendingMachine { throw new Error(ERROR_MESSAGE.PRODUCT_AMOUNT); } } + + checkInputChangesValidate(money: number) { + if (!isPositiveInteger(money)) { + throw new Error(ERROR_MESSAGE.IS_NOT_POSITIVE_INTEGER); + } + + if (!isUnitOfTen(money)) { + throw new Error(ERROR_MESSAGE.IS_NOT_UNIT_OF_TEN); + } + + if (this.totalMoney + money > RULES.MAX_VENDING_MACHINE_CHANGE) { + throw new Error(ERROR_MESSAGE.TOO_MUCH_VENDING_MACHINE_CHANGE); + } + } } const vendingMachine = new VendingMachine(); diff --git a/src/js/model/validator.ts b/src/js/model/validator.ts index 6eda6aa75..1f1984d02 100644 --- a/src/js/model/validator.ts +++ b/src/js/model/validator.ts @@ -4,16 +4,18 @@ export const isValidProductNameLength = (name: string) => { return name.length <= RULES.MAX_LENGTH_PRODUCT_NAME && name.length > 0; }; +export const isUnitOfTen = (price: number) => { + return price % RULES.MINIMUM_CHARGE === 0; +}; + +export const isPositiveInteger = (price: number) => { + return price > 0; +}; + export const isValidProductPrice = (price: number) => { - return ( - price >= RULES.MIN_PRODUCT_PRICE && - price <= RULES.MAX_PRODUCT_PRICE && - price % RULES.MINIMUM_CHARGE === 0 - ); + return price >= RULES.MIN_PRODUCT_PRICE && price <= RULES.MAX_PRODUCT_PRICE && isUnitOfTen(price); }; export const isValidProductAmount = (amount: number) => { - return ( - amount > RULES.MIN_PRODUCT_AMOUNT && amount <= RULES.MAX_PRODUCT_AMOUNT - ); + return amount > RULES.MIN_PRODUCT_AMOUNT && amount <= RULES.MAX_PRODUCT_AMOUNT; }; diff --git a/src/js/pages/ChangeAdd.ts b/src/js/pages/ChangeAdd.ts index 674aa8869..efa1fcceb 100644 --- a/src/js/pages/ChangeAdd.ts +++ b/src/js/pages/ChangeAdd.ts @@ -1,6 +1,17 @@ +import vendingMachine from '../model/VendingMachine'; +import AddChangeComponent from '../components/AddChangeComponent'; +import ChangeListComponent from '../components/ChangeListComponent'; + export default class ChangeAdd { $inputSection: HTMLElement; $contentsContainer: HTMLElement; + $changeAddForm: HTMLElement; + $totalChange: HTMLElement; + $changeList: HTMLElement; + $amountCoin500: HTMLElement; + $amountCoin100: HTMLElement; + $amountCoin50: HTMLElement; + $amountCoin10: HTMLElement; constructor() { this.$inputSection = document.querySelector('.input-section'); @@ -8,53 +19,46 @@ export default class ChangeAdd { } render() { - this.$inputSection.insertAdjacentHTML('beforeend', this.inputSection()); - this.$contentsContainer.insertAdjacentHTML('beforeend', this.changeList()); - } + this.$inputSection.insertAdjacentHTML('beforeend', AddChangeComponent()); + this.$contentsContainer.insertAdjacentHTML('beforeend', ChangeListComponent()); + + this.$changeAddForm = this.$inputSection.querySelector('#change-add-form'); + this.$totalChange = this.$inputSection.querySelector('#total-change'); + this.$changeList = this.$contentsContainer.querySelector('#change-list'); + + this.$amountCoin500 = this.$changeList.querySelector('#amount-coin-500'); + this.$amountCoin100 = this.$changeList.querySelector('#amount-coin-100'); + this.$amountCoin50 = this.$changeList.querySelector('#amount-coin-50'); + this.$amountCoin10 = this.$changeList.querySelector('#amount-coin-10'); + + this.$changeAddForm.addEventListener('submit', this.onSubmitChangeAdd); - inputSection() { - return ` -
    -

    자판기가 보유할 금액을 입력해주세요

    -
    - - -
    -

    현재 보유 금액:

    -
    `; + this.refreshChange(); } - changeList() { - return ` -
    -

    자판기가 보유한 동전

    -
      -
    • - 동전 - 개수 -
    • -
    • - 500원 - 0개 -
    • -
    • - 100원 - 4개 -
    • -
    • - 50원 - 1개 -
    • -
    • - 10원 - 5개 -
    • -
    -
    `; + onSubmitChangeAdd = (e: SubmitEvent) => { + e.preventDefault(); + + const inputChange = parseInt((this.$changeAddForm.querySelector('#change-add-input')).value); + console.log('inputChange component', inputChange); + + try { + vendingMachine.inputChanges(inputChange); + this.refreshChange(); + } catch (message) { + alert(message); + } + }; + + refreshChange() { + this.$totalChange.textContent = vendingMachine.getTotalMoney().toString(); + + console.log(vendingMachine.getChanges()); + const { coin10, coin50, coin100, coin500 } = vendingMachine.getChanges(); + + this.$amountCoin500.textContent = coin500 + '개'; + this.$amountCoin100.textContent = coin100 + '개'; + this.$amountCoin50.textContent = coin50 + '개'; + this.$amountCoin10.textContent = coin10 + '개'; } } From 022c496fe558a2d1de650c76db8d298da54dea8c Mon Sep 17 00:00:00 2001 From: intae92 Date: Wed, 23 Mar 2022 21:22:40 +0900 Subject: [PATCH 25/44] =?UTF-8?q?test:=20=EC=9E=94=EB=8F=88=20=EC=B6=A9?= =?UTF-8?q?=EC=A0=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: YongRae_Kim (Usage) --- src/js/__test__/vendingMachine.test.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/js/__test__/vendingMachine.test.js b/src/js/__test__/vendingMachine.test.js index 8f2070c43..3f36fc43c 100644 --- a/src/js/__test__/vendingMachine.test.js +++ b/src/js/__test__/vendingMachine.test.js @@ -1,9 +1,7 @@ import { ERROR_MESSAGE } from '../constants'; -// import VendingMachine from '../model/VendingMachine' import vendingMachine from '../model/VendingMachine' describe('자판기 기본 기능 테스트', () => { - // const vendingMachine = new VendingMachine(); describe('자판기 상품 추가 기능 테스트', () => { it('자판기에 상품을 추가할 수 있어야 한다.', () => { @@ -20,8 +18,8 @@ describe('자판기 기본 기능 테스트', () => { it('자판기에 같은 이름의 상품은 추가할 수 없어야 한다.', () => { const product = { name: "코카콜라", - price: 1000, - amount: 5, + price: 2000, + amount: 8, } expect(() => vendingMachine.addProduct(product)).toThrowError(ERROR_MESSAGE.PRODUCT_NAME_IS_DUPLICATED); @@ -73,5 +71,15 @@ describe('자판기 기본 기능 테스트', () => { const money = 100010; expect(() => vendingMachine.inputChanges(money)).toThrowError(ERROR_MESSAGE.TOO_MUCH_VENDING_MACHINE_CHANGE); }); + + it('자판기에 충전할 금액은 양수이어야 한다.', () => { + const money = -1000; + expect(() => vendingMachine.inputChanges(money)).toThrowError(ERROR_MESSAGE.IS_NOT_POSITIVE_INTEGER); + }); + + it('자판기에 충전할 수 있는 금액은 10원으로 나누어 떨어지는 금액이어야 한다.', () => { + const money = 9; + expect(() => vendingMachine.inputChanges(money)).toThrowError(ERROR_MESSAGE.IS_NOT_UNIT_OF_TEN); + }); }); }) From b3874d93939ab18d3d438bfeb52492f67514969d Mon Sep 17 00:00:00 2001 From: intae92 Date: Wed, 23 Mar 2022 21:23:16 +0900 Subject: [PATCH 26/44] =?UTF-8?q?docs:=20=EC=9A=94=EA=B5=AC=EC=82=AC?= =?UTF-8?q?=ED=95=AD=20=EB=AC=B8=EC=84=9C=20=EA=B0=B1=EC=8B=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: YongRae_Kim (Usage) --- src/docs/README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/docs/README.md b/src/docs/README.md index f66071f0c..6d5aa5933 100644 --- a/src/docs/README.md +++ b/src/docs/README.md @@ -27,13 +27,13 @@ ### 잔돈 충전 탭 -- [ ] 잔돈 충전탭은 자판기가 보유할 금액을 충전하는 기능을 수행한다. -- [ ] 잔돈 충전 탭에서 최초 자판기가 보유한 금액은 0원이며, 각 동전의 개수는 0개이다. -- [ ] 잔돈 충전 입력 요소에 충전할 금액을 입력한 후, 충전하기 버튼을 눌러 자판기 보유 금액을 충전할 수 있다. -- [ ] 잔돈은 10원으로 나누어 떨어지는 금액만 투입할 수 있다. 보유할 수 있는 최대 금액은 100,000원이다. -- [ ] 자판기 보유 금액만큼의 동전이 무작위로 생성된다. -- [ ] 자판기 보유 금액을 누적하여 충전할 수 있다. 추가 충전 금액만큼의 동전이 무작위로 생성되어 기존 동전들에 더해진다. +- [x] 잔돈 충전탭은 자판기가 보유할 금액을 충전하는 기능을 수행한다. +- [x] 잔돈 충전 탭에서 최초 자판기가 보유한 금액은 0원이며, 각 동전의 개수는 0개이다. +- [x] 잔돈 충전 입력 요소에 충전할 금액을 입력한 후, 충전하기 버튼을 눌러 자판기 보유 금액을 충전할 수 있다. +- [x] 잔돈은 10원으로 나누어 떨어지는 금액만 투입할 수 있다. 보유할 수 있는 최대 금액은 100,000원이다. +- [x] 자판기 보유 금액만큼의 동전이 무작위로 생성된다. +- [x] 자판기 보유 금액을 누적하여 충전할 수 있다. 추가 충전 금액만큼의 동전이 무작위로 생성되어 기존 동전들에 더해진다. ### 테스트 요구사항 -- [ ] 비즈니스 로직에 대한 단위 테스트를 Jest로 작성한다. +- [x] 비즈니스 로직에 대한 단위 테스트를 Jest로 작성한다. From 1e1e591db1ea97f24c93586ee3e0cf37c0446c13 Mon Sep 17 00:00:00 2001 From: intae92 Date: Wed, 23 Mar 2022 21:33:49 +0900 Subject: [PATCH 27/44] chore: git pages deploy setting Co-authored-by: YongRae_Kim (Usage) --- package-lock.json | 397 ++++++++++++++++++++++++++++++---------------- package.json | 7 +- 2 files changed, 268 insertions(+), 136 deletions(-) diff --git a/package-lock.json b/package-lock.json index dc33ccf7c..371cd935b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "clean-webpack-plugin": "^4.0.0", "css-loader": "^6.6.0", "eslint": "^8.11.0", + "gh-pages": "^3.2.3", "html-webpack-plugin": "^5.5.0", "jest": "^27.4.7", "style-loader": "^3.3.1", @@ -3647,50 +3648,6 @@ "webpack": ">=2" } }, - "node_modules/babel-loader/node_modules/find-cache-dir": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", - "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", - "dev": true, - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/avajs/find-cache-dir?sponsor=1" - } - }, - "node_modules/babel-loader/node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/babel-loader/node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/babel-plugin-dynamic-import-node": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", @@ -4238,6 +4195,12 @@ "node": ">= 0.8" } }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, "node_modules/commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -4847,6 +4810,12 @@ "integrity": "sha512-9LkRQwjW6/wnSfevR21a3k8sOJ+XWSH7kkzs9/EUenKmuDkndP3W9y1yCZpOxufwGbX3JV8glZZSDb4o95zwXQ==", "dev": true }, + "node_modules/email-addresses": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-3.1.0.tgz", + "integrity": "sha512-k0/r7GrWVL32kZlGwfPNgB2Y/mMXVTq/decgLczm/j34whdaspNrZO8CnXPf1laaHxI6ptUlsnAxN+UAPw+fzg==", + "dev": true + }, "node_modules/emittery": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", @@ -5605,6 +5574,32 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/filename-reserved-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", + "integrity": "sha1-q/c9+rc10EVECr/qLZHzieu/oik=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/filenamify": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-4.3.0.tgz", + "integrity": "sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==", + "dev": true, + "dependencies": { + "filename-reserved-regex": "^2.0.0", + "strip-outer": "^1.0.1", + "trim-repeated": "^1.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -5650,6 +5645,23 @@ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, "node_modules/find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -5749,6 +5761,20 @@ "node": ">= 0.6" } }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, "node_modules/fs-monkey": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz", @@ -5840,6 +5866,28 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gh-pages": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-3.2.3.tgz", + "integrity": "sha512-jA1PbapQ1jqzacECfjUaO9gV8uBgU6XNMV0oXLtfCX3haGLe5Atq8BxlrADhbD6/UdG9j6tZLWAkAybndOXTJg==", + "dev": true, + "dependencies": { + "async": "^2.6.1", + "commander": "^2.18.0", + "email-addresses": "^3.0.1", + "filenamify": "^4.3.0", + "find-cache-dir": "^3.3.1", + "fs-extra": "^8.1.0", + "globby": "^6.1.0" + }, + "bin": { + "gh-pages": "bin/gh-pages.js", + "gh-pages-clean": "bin/gh-pages-clean.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", @@ -6292,18 +6340,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/import-local/node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -6664,21 +6700,6 @@ "node": ">=8" } }, - "node_modules/istanbul-lib-report/node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/istanbul-lib-report/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -8530,6 +8551,15 @@ "node": ">=6" } }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -8668,6 +8698,21 @@ "node": ">=10" } }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -9324,6 +9369,18 @@ "node": ">= 6" } }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/portfinder": { "version": "1.0.28", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", @@ -10384,6 +10441,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strip-outer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", + "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/style-loader": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.1.tgz", @@ -10568,12 +10637,6 @@ "node": ">=0.10.0" } }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, "node_modules/terser/node_modules/source-map": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", @@ -10677,6 +10740,18 @@ "node": ">=8" } }, + "node_modules/trim-repeated": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", + "integrity": "sha1-42RqLqTokTEr9+rObPsFOAvAHCE=", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ts-jest": { "version": "27.1.3", "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-27.1.3.tgz", @@ -14508,37 +14583,6 @@ "loader-utils": "^1.4.0", "make-dir": "^3.1.0", "schema-utils": "^2.6.5" - }, - "dependencies": { - "find-cache-dir": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", - "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", - "dev": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - } - }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "requires": { - "semver": "^6.0.0" - } - }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "requires": { - "find-up": "^4.0.0" - } - } } }, "babel-plugin-dynamic-import-node": { @@ -14979,6 +15023,12 @@ "delayed-stream": "~1.0.0" } }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, "commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -15453,6 +15503,12 @@ "integrity": "sha512-9LkRQwjW6/wnSfevR21a3k8sOJ+XWSH7kkzs9/EUenKmuDkndP3W9y1yCZpOxufwGbX3JV8glZZSDb4o95zwXQ==", "dev": true }, + "email-addresses": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-3.1.0.tgz", + "integrity": "sha512-k0/r7GrWVL32kZlGwfPNgB2Y/mMXVTq/decgLczm/j34whdaspNrZO8CnXPf1laaHxI6ptUlsnAxN+UAPw+fzg==", + "dev": true + }, "emittery": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", @@ -16023,6 +16079,23 @@ "flat-cache": "^3.0.4" } }, + "filename-reserved-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", + "integrity": "sha1-q/c9+rc10EVECr/qLZHzieu/oik=", + "dev": true + }, + "filenamify": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-4.3.0.tgz", + "integrity": "sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==", + "dev": true, + "requires": { + "filename-reserved-regex": "^2.0.0", + "strip-outer": "^1.0.1", + "trim-repeated": "^1.0.0" + } + }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -16064,6 +16137,17 @@ } } }, + "find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + } + }, "find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -16130,6 +16214,17 @@ "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", "dev": true }, + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, "fs-monkey": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz", @@ -16196,6 +16291,21 @@ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true }, + "gh-pages": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-3.2.3.tgz", + "integrity": "sha512-jA1PbapQ1jqzacECfjUaO9gV8uBgU6XNMV0oXLtfCX3haGLe5Atq8BxlrADhbD6/UdG9j6tZLWAkAybndOXTJg==", + "dev": true, + "requires": { + "async": "^2.6.1", + "commander": "^2.18.0", + "email-addresses": "^3.0.1", + "filenamify": "^4.3.0", + "find-cache-dir": "^3.3.1", + "fs-extra": "^8.1.0", + "globby": "^6.1.0" + } + }, "glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", @@ -16531,17 +16641,6 @@ "requires": { "pkg-dir": "^4.2.0", "resolve-cwd": "^3.0.0" - }, - "dependencies": { - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "requires": { - "find-up": "^4.0.0" - } - } } }, "imurmurhash": { @@ -16802,15 +16901,6 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "requires": { - "semver": "^6.0.0" - } - }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -18198,6 +18288,15 @@ "minimist": "^1.2.5" } }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -18311,6 +18410,15 @@ "yallist": "^4.0.0" } }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + } + }, "make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -18808,6 +18916,15 @@ "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", "dev": true }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + }, "portfinder": { "version": "1.0.28", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", @@ -19624,6 +19741,15 @@ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, + "strip-outer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", + "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.2" + } + }, "style-loader": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.1.tgz", @@ -19707,12 +19833,6 @@ "source-map-support": "~0.5.20" }, "dependencies": { - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, "source-map": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", @@ -19829,6 +19949,15 @@ "punycode": "^2.1.1" } }, + "trim-repeated": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", + "integrity": "sha1-42RqLqTokTEr9+rObPsFOAvAHCE=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.2" + } + }, "ts-jest": { "version": "27.1.3", "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-27.1.3.tgz", diff --git a/package.json b/package.json index 58e076533..972e29418 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,9 @@ "scripts": { "test": "jest --watch --no-cache", "start": "webpack serve --open", - "build": "webpack" + "build": "webpack", + "predeploy": "npm run build", + "deploy": "gh-pages -d dist" }, "repository": { "type": "git", @@ -27,6 +29,7 @@ "clean-webpack-plugin": "^4.0.0", "css-loader": "^6.6.0", "eslint": "^8.11.0", + "gh-pages": "^3.2.3", "html-webpack-plugin": "^5.5.0", "jest": "^27.4.7", "style-loader": "^3.3.1", @@ -40,5 +43,5 @@ "bugs": { "url": "https://github.com/woowacourse/javascript-vendingmachine/issues" }, - "homepage": "https://github.com/woowacourse/javascript-vendingmachine#readme" + "homepage": "https://intae92.github.io/javascript-vendingmachine" } From 1d89c0cad16f8681257c35745eccb229af4a3a42 Mon Sep 17 00:00:00 2001 From: intae92 Date: Wed, 23 Mar 2022 21:43:28 +0900 Subject: [PATCH 28/44] =?UTF-8?q?fix:=20pathname=20url=20=EB=88=84?= =?UTF-8?q?=EB=9D=BD=20=EC=9D=B4=EC=8A=88=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: YongRae_Kim (Usage) --- src/index.ts | 4 ++-- src/js/routes.ts | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/index.ts b/src/index.ts index 187e60238..a74924a8b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,13 +11,13 @@ const btn3 = document.querySelector('#product-purchase-button'); productManageButton.addEventListener('click', () => { console.log('click1'); - history.pushState({ prevPath: window.location.hash }, '상품 관리하기', '/#!/product-manage'); + history.pushState({}, '상품 관리하기', window.location.pathname + '#!/product-manage'); routes(); }); changeAddButton.addEventListener('click', () => { console.log('click2'); - history.pushState({ prevPath: window.location.hash }, '잔돈 채우기', '/#!/change-add'); + history.pushState({}, '잔돈 채우기', window.location.pathname + '#!/change-add'); routes(); }); diff --git a/src/js/routes.ts b/src/js/routes.ts index 36f28fcb8..f41108714 100644 --- a/src/js/routes.ts +++ b/src/js/routes.ts @@ -15,16 +15,16 @@ const router = () => { let prevPath = ''; return () => { - const pathName = window.location.hash; + const hash = window.location.hash; - if (prevPath === pathName) { + if (prevPath === hash) { return; } - prevPath = pathName; + prevPath = hash; clearPurchaseBody(); - switch (pathName) { + switch (hash) { case '#!/product-manage': productManage.render(); break; From a5f10d6822cd0b4ead5b5881a0d93e6b783dfe18 Mon Sep 17 00:00:00 2001 From: intae92 Date: Wed, 23 Mar 2022 22:05:12 +0900 Subject: [PATCH 29/44] =?UTF-8?q?chore:=20=EB=A1=9C=EA=B7=B8=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: YongRae_Kim (Usage) --- src/index.ts | 8 ++------ src/js/pages/ChangeAdd.ts | 4 ---- src/js/pages/ProductManage.ts | 1 - 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/src/index.ts b/src/index.ts index a74924a8b..005b177f7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,24 +3,20 @@ import routes from './js/routes'; routes(); -const nav = document.querySelector('nav'); - const productManageButton = document.querySelector('#product-manage-button'); const changeAddButton = document.querySelector('#change-add-button'); -const btn3 = document.querySelector('#product-purchase-button'); +// const btn3 = document.querySelector('#product-purchase-button'); productManageButton.addEventListener('click', () => { - console.log('click1'); history.pushState({}, '상품 관리하기', window.location.pathname + '#!/product-manage'); routes(); }); changeAddButton.addEventListener('click', () => { - console.log('click2'); history.pushState({}, '잔돈 채우기', window.location.pathname + '#!/change-add'); routes(); }); window.addEventListener('popstate', function () { - console.log('popstate', history.state); + routes(); }); diff --git a/src/js/pages/ChangeAdd.ts b/src/js/pages/ChangeAdd.ts index efa1fcceb..dfda3993c 100644 --- a/src/js/pages/ChangeAdd.ts +++ b/src/js/pages/ChangeAdd.ts @@ -38,9 +38,7 @@ export default class ChangeAdd { onSubmitChangeAdd = (e: SubmitEvent) => { e.preventDefault(); - const inputChange = parseInt((this.$changeAddForm.querySelector('#change-add-input')).value); - console.log('inputChange component', inputChange); try { vendingMachine.inputChanges(inputChange); @@ -52,8 +50,6 @@ export default class ChangeAdd { refreshChange() { this.$totalChange.textContent = vendingMachine.getTotalMoney().toString(); - - console.log(vendingMachine.getChanges()); const { coin10, coin50, coin100, coin500 } = vendingMachine.getChanges(); this.$amountCoin500.textContent = coin500 + '개'; diff --git a/src/js/pages/ProductManage.ts b/src/js/pages/ProductManage.ts index a9c752369..2f8ee5f77 100644 --- a/src/js/pages/ProductManage.ts +++ b/src/js/pages/ProductManage.ts @@ -119,7 +119,6 @@ export default class ProductManage { renderProducts() { const products = vendingMachine.getProducts(); - console.log('products', products); products.forEach(product => { this.addProductItem(product); From c913b890ef6f162ab7183d81e430006ea1a98b06 Mon Sep 17 00:00:00 2001 From: intae92 Date: Wed, 23 Mar 2022 22:21:04 +0900 Subject: [PATCH 30/44] =?UTF-8?q?style:=20css=20color=20=EB=B3=80=EC=88=98?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: YongRae_Kim (Usage) --- src/css/global.css | 12 ++++++++++++ src/css/index.css | 38 ++++++++++++++++++++------------------ 2 files changed, 32 insertions(+), 18 deletions(-) create mode 100644 src/css/global.css diff --git a/src/css/global.css b/src/css/global.css new file mode 100644 index 000000000..81c4de32d --- /dev/null +++ b/src/css/global.css @@ -0,0 +1,12 @@ +:root { + --primary-color: #00bcd4; + --primary-button-text-color: #ffffff; + --primary-input-bg-color: #ffffff; + --input-outline-color: #cccccc; + --input-border-color: #eeeeee; + --input-outline-focus-color: #777777; + --input-border-focus-color: #bbbbbb; + --button-hover-color: rgba(0, 188, 212, 0.16); + --primary-text-color: rgba(0, 0, 0, 0.87); + --list-border-color: #dcdcdc; +} diff --git a/src/css/index.css b/src/css/index.css index 5c2073856..bfbabd78c 100644 --- a/src/css/index.css +++ b/src/css/index.css @@ -1,3 +1,5 @@ +@import './global.css'; + body { width: 100vw; height: 100vh; @@ -41,10 +43,10 @@ input[type='number']::-webkit-inner-spin-button { input[type='submit'] { cursor: pointer; - background: #00bcd4; - color: #ffffff; - outline: 1px solid #00bcd4; - border: 1px solid #00bcd4; + background: var(--primary-color); + color: var(--primary-button-text-color); + outline: 1px solid var(--primary-color); + border: 1px solid var(--primary-color); border-radius: 2px; width: 56px; height: 28px; @@ -52,9 +54,9 @@ input[type='submit'] { } input { - background-color: #ffffff; - outline: 1px solid #cccccc; - border: 1px solid #eeeeee; + background-color: var(--primary-input-bg-color); + outline: 1px solid var(--input-outline-color); + border: 1px solid var(--input-border-color); border-radius: 2px; width: 120px; height: 24px; @@ -62,8 +64,8 @@ input { } input:focus { - outline: 1px solid #777777; - border: 1px solid #bbbbbb; + outline: 1px solid var(--input-outline-focus-color); + border: 1px solid var(--input-border-focus-color); } #change-add-input { @@ -79,7 +81,7 @@ button { } button:hover { - background: rgba(0, 188, 212, 0.16); + background: var(--button-hover-color); } h4 { @@ -91,7 +93,7 @@ h4 { line-height: 24px; text-align: center; letter-spacing: 0.15px; - color: rgba(0, 0, 0, 0.87); + color: var(--primary-text-color); } #change-add-container { @@ -130,7 +132,7 @@ li { font-style: normal; display: flex; justify-content: center; - border-bottom: 1px solid #dcdcdc; + border-bottom: 1px solid var(--list-border-color); } #product-list { @@ -148,14 +150,14 @@ li { font-size: 15px; line-height: 24px; letter-spacing: 0.5px; - color: rgba(0, 0, 0, 0.87); + color: var(--primary-text-color); } #product-list li input { width: 80%; text-align: center; - outline: 1px dotted #777777; - border: 1px dotted #bbbbbb; + outline: 1px dotted var(--input-outline-focus-color); + border: 1px dotted var(--input-border-focus-color); } .product-control-buttons { @@ -186,7 +188,7 @@ li { font-size: 15px; line-height: 24px; letter-spacing: 0.5px; - color: rgba(0, 0, 0, 0.87); + color: var(--primary-text-color); } .list-header { @@ -196,6 +198,6 @@ li { display: flex; align-items: center; letter-spacing: 0.5px; - color: rgba(0, 0, 0, 0.87); - border-top: 1px solid #dcdcdc; + color: var(--primary-text-color); + border-top: 1px solid var(--list-border-color); } From 7107d96e0ede1313cf69febeb8396c6342a9da21 Mon Sep 17 00:00:00 2001 From: intae92 Date: Wed, 23 Mar 2022 22:29:47 +0900 Subject: [PATCH 31/44] =?UTF-8?q?refactor:=20=ED=95=A8=EC=88=98=20?= =?UTF-8?q?=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EB=B3=80=EA=B2=BD,=20=EB=A7=A4?= =?UTF-8?q?=EC=A7=81=EB=84=98=EB=B2=84=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: YongRae_Kim (Usage) --- src/js/constants.ts | 3 ++- src/js/model/VendingMachine.ts | 13 ++++++------- src/js/model/validator.ts | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/js/constants.ts b/src/js/constants.ts index 50548c963..71705409f 100644 --- a/src/js/constants.ts +++ b/src/js/constants.ts @@ -3,9 +3,10 @@ const RULES = { MIN_PRODUCT_PRICE: 100, MAX_PRODUCT_AMOUNT: 20, MIN_PRODUCT_AMOUNT: 0, - MINIMUM_CHARGE: 10, + MINIMUM_CHANGE: 10, MAX_LENGTH_PRODUCT_NAME: 10, MAX_VENDING_MACHINE_CHANGE: 100000, + NOT_EXIST_INDEX: -1, }; const ERROR_MESSAGE = { diff --git a/src/js/model/VendingMachine.ts b/src/js/model/VendingMachine.ts index 65bd89ec3..9fcc467ee 100644 --- a/src/js/model/VendingMachine.ts +++ b/src/js/model/VendingMachine.ts @@ -58,11 +58,10 @@ class VendingMachine { inputChanges(money: number) { this.checkInputChangesValidate(money); this.totalMoney += money; - this.makeChanges(money); + this.makeChangesToCoin(money); } - // makeChanges 과연 적절한 이름인가? 리팩토링때 다시보기 - makeChanges(money: number) { + makeChangesToCoin(money: number) { const coin = this.getChangeCoin(money); money -= coin; @@ -81,8 +80,8 @@ class VendingMachine { break; } - if (money >= 10) { - this.makeChanges(money); + if (money >= RULES.MINIMUM_CHANGE) { + this.makeChangesToCoin(money); } } @@ -96,10 +95,10 @@ class VendingMachine { return coins[index]; } - checkProductValidate(product: Product, originalIndex: number = -1) { + checkProductValidate(product: Product, originalIndex: number = RULES.NOT_EXIST_INDEX) { const productIndex = this.findProductIndex(product.name); const isExist = productIndex >= 0; - const isAddWithDuplicatedName = isExist && originalIndex === -1; + const isAddWithDuplicatedName = isExist && originalIndex === RULES.NOT_EXIST_INDEX; const isModifyWithDuplicateName = isExist && originalIndex !== productIndex; if (isAddWithDuplicatedName || isModifyWithDuplicateName) { diff --git a/src/js/model/validator.ts b/src/js/model/validator.ts index 1f1984d02..58b0241e6 100644 --- a/src/js/model/validator.ts +++ b/src/js/model/validator.ts @@ -5,7 +5,7 @@ export const isValidProductNameLength = (name: string) => { }; export const isUnitOfTen = (price: number) => { - return price % RULES.MINIMUM_CHARGE === 0; + return price % RULES.MINIMUM_CHANGE === 0; }; export const isPositiveInteger = (price: number) => { From 53c0614a945f5e22e6209cc5c5c13e2910d9f873 Mon Sep 17 00:00:00 2001 From: "YongRae_Kim (Usage)" Date: Sun, 27 Mar 2022 02:50:00 +0900 Subject: [PATCH 32/44] =?UTF-8?q?refactor:=20=EC=B6=95=EC=95=BD=ED=98=95?= =?UTF-8?q?=20=ED=91=9C=EA=B8=B0=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/css/global.css | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/css/global.css b/src/css/global.css index 81c4de32d..017d23ec7 100644 --- a/src/css/global.css +++ b/src/css/global.css @@ -1,11 +1,11 @@ :root { --primary-color: #00bcd4; - --primary-button-text-color: #ffffff; - --primary-input-bg-color: #ffffff; - --input-outline-color: #cccccc; - --input-border-color: #eeeeee; - --input-outline-focus-color: #777777; - --input-border-focus-color: #bbbbbb; + --primary-button-text-color: #fff; + --primary-input-bg-color: #fff; + --input-outline-color: #ccc; + --input-border-color: #eee; + --input-outline-focus-color: #777; + --input-border-focus-color: #bbb; --button-hover-color: rgba(0, 188, 212, 0.16); --primary-text-color: rgba(0, 0, 0, 0.87); --list-border-color: #dcdcdc; From c0410ebb0fb088d44298f3ac3b56cd8ed29d6718 Mon Sep 17 00:00:00 2001 From: "YongRae_Kim (Usage)" Date: Sun, 27 Mar 2022 02:50:31 +0900 Subject: [PATCH 33/44] =?UTF-8?q?chore:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EC=9D=98=20=EC=84=A4=EB=AA=85=EC=9D=84=20=EB=8C=80=EC=83=81=20?= =?UTF-8?q?=EB=8F=99=EC=9E=91=EA=B3=BC=20=EC=9D=BC=EC=B9=98=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/js/__test__/vendingMachine.test.js | 51 +++++++++++++------------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/src/js/__test__/vendingMachine.test.js b/src/js/__test__/vendingMachine.test.js index 3f36fc43c..ce9e4afe4 100644 --- a/src/js/__test__/vendingMachine.test.js +++ b/src/js/__test__/vendingMachine.test.js @@ -1,85 +1,84 @@ import { ERROR_MESSAGE } from '../constants'; -import vendingMachine from '../model/VendingMachine' +import vendingMachine from '../model/VendingMachine'; describe('자판기 기본 기능 테스트', () => { - describe('자판기 상품 추가 기능 테스트', () => { it('자판기에 상품을 추가할 수 있어야 한다.', () => { const product = { - name: "코카콜라", + name: '코카콜라', price: 1000, amount: 5, - } + }; vendingMachine.addProduct(product); expect(vendingMachine.products.includes(product)); }); - it('자판기에 같은 이름의 상품은 추가할 수 없어야 한다.', () => { + it('자판기에 같은 이름의 상품을 추가하려고 하면, 오류를 발생시킨다.', () => { const product = { - name: "코카콜라", + name: '코카콜라', price: 2000, amount: 8, - } + }; expect(() => vendingMachine.addProduct(product)).toThrowError(ERROR_MESSAGE.PRODUCT_NAME_IS_DUPLICATED); }); - it('자판기에 추가 될 상품의 이름은 10글자를 초과할 수 없어야 한다.', () => { + it('자판기에 추가 될 상품의 이름이 10글자를 초과하면, 오류를 발생시킨다.', () => { const product = { - name: "코카콜라보다제로펩시가맛있다", + name: '코카콜라보다제로펩시가맛있다', price: 1000, amount: 5, - } + }; expect(() => vendingMachine.addProduct(product)).toThrowError(ERROR_MESSAGE.PRODUCT_NAME_LENGTH); }); - it('자판기에 추가 될 상품의 가격은 100원 이상이어야 한다.', () => { + it('자판기에 추가 될 상품의 가격이 100원을 넘지 않으면, 오류를 발생시킨다.', () => { const product = { - name: "펩시", + name: '펩시', price: 99, amount: 5, - } + }; expect(() => vendingMachine.addProduct(product)).toThrowError(ERROR_MESSAGE.PRODUCT_PRICE); }); - it('자판기에 추가 될 상품의 가격은 10,000원 이하여야 한다.', () => { + it('자판기에 추가 될 상품의 가격이 10,000원이 넘으면, 오류를 발생시킨다.', () => { const product = { - name: "제로펩시", + name: '제로펩시', price: 10001, amount: 5, - } + }; expect(() => vendingMachine.addProduct(product)).toThrowError(ERROR_MESSAGE.PRODUCT_PRICE); }); - it('자판기에 추가 될 상품의 수량은 20개 이하여야 한다.', () => { + it('자판기에 추가 될 상품의 수량이 20개가 넘으면, 오류를 발생시킨다.', () => { const product = { - name: "제로코카콜라", + name: '제로코카콜라', price: 1000, amount: 21, - } + }; expect(() => vendingMachine.addProduct(product)).toThrowError(ERROR_MESSAGE.PRODUCT_AMOUNT); }); - }) - + }); + describe('잔돈 충전 기능 테스트', () => { - it('자판기가 가진 금액은 100,000원 이하여야 한다.', () => { + it('자판기가 가진 총 금액이 100,000원을 넘으면, 오류를 발생시킨다.', () => { const money = 100010; expect(() => vendingMachine.inputChanges(money)).toThrowError(ERROR_MESSAGE.TOO_MUCH_VENDING_MACHINE_CHANGE); }); - it('자판기에 충전할 금액은 양수이어야 한다.', () => { - const money = -1000; + it('자판기에 충전하려는 금액이 0원을 넘지 못하면, 오류를 발생시킨다.', () => { + const money = 0; expect(() => vendingMachine.inputChanges(money)).toThrowError(ERROR_MESSAGE.IS_NOT_POSITIVE_INTEGER); }); - it('자판기에 충전할 수 있는 금액은 10원으로 나누어 떨어지는 금액이어야 한다.', () => { + it('자판기에 충전하려는 금액이 10원으로 나누어 떨어지지 않으면, 오류를 발생시킨다.', () => { const money = 9; expect(() => vendingMachine.inputChanges(money)).toThrowError(ERROR_MESSAGE.IS_NOT_UNIT_OF_TEN); }); }); -}) +}); From 63e3be6c64a8dadf1a3d993afcb40f006238d5cd Mon Sep 17 00:00:00 2001 From: "YongRae_Kim (Usage)" Date: Sun, 27 Mar 2022 03:31:59 +0900 Subject: [PATCH 34/44] =?UTF-8?q?style:=20web=20font=20=EC=B6=94=EA=B0=80,?= =?UTF-8?q?=200=20=EB=8B=A8=EC=9C=84=EC=9D=98=20px=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/css/font.css | 9 +++++++++ src/css/index.css | 11 +++++------ 2 files changed, 14 insertions(+), 6 deletions(-) create mode 100644 src/css/font.css diff --git a/src/css/font.css b/src/css/font.css new file mode 100644 index 000000000..afd92fa18 --- /dev/null +++ b/src/css/font.css @@ -0,0 +1,9 @@ +@import url(https://hangeul.pstatic.net/hangeul_static/css/nanum-gothic.css); + +* { + font-family: inherit; +} + +body { + font-family: 'NanumGothic'; +} diff --git a/src/css/index.css b/src/css/index.css index bfbabd78c..2c32b3bbf 100644 --- a/src/css/index.css +++ b/src/css/index.css @@ -1,4 +1,5 @@ @import './global.css'; +@import './font.css'; body { width: 100vw; @@ -21,7 +22,7 @@ h1 { header { text-align: center; - margin: 40px 0 10px 0; + margin: 40px 0 10px; } nav { @@ -86,7 +87,6 @@ button:hover { h4 { margin: 0; - font-family: 'Roboto'; font-style: normal; font-weight: 600; font-size: 20px; @@ -128,7 +128,6 @@ h4 { li { list-style-type: none; text-align: center; - font-family: 'Roboto'; font-style: normal; display: flex; justify-content: center; @@ -144,7 +143,7 @@ li { } #product-list li span { - margin: 12px 0px 8px; + margin: 12px 0 8px; width: 100%; height: 30px; font-size: 15px; @@ -169,12 +168,12 @@ li { .product-control-buttons button { width: 40%; height: 30px; - margin-top: -4px !important; + margin-top: -2px; } .product-modify-submit-button { width: 90%; - margin-top: -8px !important; + margin-top: -8px; } #change-list li { From 37a0e6908e3849c83a8fa337e01dc77425432a54 Mon Sep 17 00:00:00 2001 From: "YongRae_Kim (Usage)" Date: Sun, 27 Mar 2022 04:10:28 +0900 Subject: [PATCH 35/44] =?UTF-8?q?refactor:=20routes=20=EB=A5=BC=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EA=B5=AC=EC=A1=B0=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/index.ts | 13 +++++----- src/js/routes.ts | 62 +++++++++++++++++++++++++++++------------------- 2 files changed, 43 insertions(+), 32 deletions(-) diff --git a/src/index.ts b/src/index.ts index 005b177f7..53e971905 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,22 +1,21 @@ import './css/index'; -import routes from './js/routes'; +import router from './js/routes'; -routes(); +const routes = new router(); +routes.init(); const productManageButton = document.querySelector('#product-manage-button'); const changeAddButton = document.querySelector('#change-add-button'); // const btn3 = document.querySelector('#product-purchase-button'); productManageButton.addEventListener('click', () => { - history.pushState({}, '상품 관리하기', window.location.pathname + '#!/product-manage'); - routes(); + routes.go('#!/product-manage'); }); changeAddButton.addEventListener('click', () => { - history.pushState({}, '잔돈 채우기', window.location.pathname + '#!/change-add'); - routes(); + routes.go('#!/change-add'); }); window.addEventListener('popstate', function () { - routes(); + routes.back(); }); diff --git a/src/js/routes.ts b/src/js/routes.ts index f41108714..957083e14 100644 --- a/src/js/routes.ts +++ b/src/js/routes.ts @@ -1,42 +1,54 @@ import ChangeAdd from './pages/ChangeAdd'; import ProductManage from './pages/ProductManage'; -const clearPurchaseBody = () => { - const $inputSection = document.querySelector('.input-section'); - const $contentsContainer = document.querySelector('.contents-container'); - - $inputSection.replaceChildren(); - $contentsContainer.replaceChildren(); -}; - -const router = () => { - const productManage = new ProductManage(); - const changeAdd = new ChangeAdd(); - let prevPath = ''; - - return () => { - const hash = window.location.hash; - - if (prevPath === hash) { +class router { + prevPath: string; + productManage: ProductManage; + changeAdd: ChangeAdd; + + constructor() { + this.productManage = new ProductManage(); + this.changeAdd = new ChangeAdd(); + this.prevPath = null; + } + + init() { + this.go(window.location.hash); + } + + back() { + this.go(window.location.hash); + } + + go(hash: string) { + if (this.prevPath === hash) { return; } - prevPath = hash; - clearPurchaseBody(); + this.prevPath = hash; + this.clear(); switch (hash) { case '#!/product-manage': - productManage.render(); + history.pushState({}, '상품 관리하기', window.location.pathname + hash); + this.productManage.render(); break; case '#!/change-add': - changeAdd.render(); + history.pushState({}, '잔돈 충전하기', window.location.pathname + hash); + this.changeAdd.render(); break; default: break; } - }; -}; + } + + clear() { + const $inputSection = document.querySelector('.input-section'); + const $contentsContainer = document.querySelector('.contents-container'); -const routes = router(); + $inputSection.replaceChildren(); + $contentsContainer.replaceChildren(); + } +} -export default routes; +export default router; From d586ee5c9a38c7da3365ffaad91f508b0e8fda7a Mon Sep 17 00:00:00 2001 From: "YongRae_Kim (Usage)" Date: Sun, 27 Mar 2022 16:59:20 +0900 Subject: [PATCH 36/44] =?UTF-8?q?ChangeAdd=20page=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EC=97=AD=ED=95=A0=20=EC=9E=AC=EB=B6=84?= =?UTF-8?q?=EB=8B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/js/components/AddChangeComponent.ts | 45 ++++++++++++++++-- src/js/components/ChangeListComponent.ts | 42 +++++++++++++++-- src/js/pages/ChangeAdd.ts | 58 ++++++------------------ 3 files changed, 94 insertions(+), 51 deletions(-) diff --git a/src/js/components/AddChangeComponent.ts b/src/js/components/AddChangeComponent.ts index ba747eaa6..ecfa54ea6 100644 --- a/src/js/components/AddChangeComponent.ts +++ b/src/js/components/AddChangeComponent.ts @@ -1,5 +1,44 @@ -const AddChangeComponent = () => { - return ` +import vendingMachine from '../model/VendingMachine'; + +class AddChangeComponent { + $changeAddForm: HTMLElement; + $totalChange: HTMLElement; + noticeStateChanged: Function; + parentElement: HTMLElement; + + constructor(parentElement: HTMLElement, noticeStateChanged: Function) { + this.parentElement = parentElement; + this.noticeStateChanged = noticeStateChanged; + } + + bindEventAndElement() { + this.$totalChange = document.querySelector('#total-change'); + this.$changeAddForm = document.querySelector('#change-add-form'); + this.$changeAddForm.addEventListener('submit', this.onSubmitChangeAdd); + } + + onSubmitChangeAdd = (e: SubmitEvent) => { + e.preventDefault(); + const inputChange = parseInt((this.$changeAddForm.querySelector('#change-add-input')).value); + + try { + vendingMachine.inputChanges(inputChange); + this.noticeStateChanged(); + } catch (message) { + alert(message); + } + }; + + refreshChange = () => { + this.$totalChange.textContent = vendingMachine.getTotalMoney().toString(); + }; + + render = () => { + this.parentElement.insertAdjacentHTML('beforeend', this.template()); + this.bindEventAndElement(); + }; + + template = () => `

    자판기가 보유할 금액을 입력해주세요

    @@ -13,6 +52,6 @@ const AddChangeComponent = () => {

    현재 보유 금액:

    `; -}; +} export default AddChangeComponent; diff --git a/src/js/components/ChangeListComponent.ts b/src/js/components/ChangeListComponent.ts index d416a5c03..a80f4b269 100644 --- a/src/js/components/ChangeListComponent.ts +++ b/src/js/components/ChangeListComponent.ts @@ -1,5 +1,41 @@ -const ChangeListComponent = () => { - return ` +import vendingMachine from '../model/VendingMachine'; + +class ChangeListComponent { + $changeList: HTMLElement; + $contentsContainer: HTMLElement; + $amountCoin500: HTMLElement; + $amountCoin100: HTMLElement; + $amountCoin50: HTMLElement; + $amountCoin10: HTMLElement; + parentElement: HTMLElement; + + constructor(parentElement: HTMLElement) { + this.parentElement = parentElement; + } + + bindElement = () => { + this.$changeList = document.querySelector('#change-list'); + this.$amountCoin500 = document.querySelector('#amount-coin-500'); + this.$amountCoin100 = document.querySelector('#amount-coin-100'); + this.$amountCoin50 = document.querySelector('#amount-coin-50'); + this.$amountCoin10 = document.querySelector('#amount-coin-10'); + }; + + refreshChange = () => { + const { coin10, coin50, coin100, coin500 } = vendingMachine.getChanges(); + + this.$amountCoin500.textContent = `${coin500}개`; + this.$amountCoin100.textContent = `${coin100}개`; + this.$amountCoin50.textContent = `${coin50}개`; + this.$amountCoin10.textContent = `${coin10}개`; + }; + + render = () => { + this.parentElement.insertAdjacentHTML('beforeend', this.template()); + this.bindElement(); + }; + + template = () => `

    자판기가 보유한 동전

      @@ -25,6 +61,6 @@ const ChangeListComponent = () => {
    `; -}; +} export default ChangeListComponent; diff --git a/src/js/pages/ChangeAdd.ts b/src/js/pages/ChangeAdd.ts index dfda3993c..b2b156cb9 100644 --- a/src/js/pages/ChangeAdd.ts +++ b/src/js/pages/ChangeAdd.ts @@ -1,60 +1,28 @@ -import vendingMachine from '../model/VendingMachine'; import AddChangeComponent from '../components/AddChangeComponent'; import ChangeListComponent from '../components/ChangeListComponent'; export default class ChangeAdd { + AddChangeComponent: AddChangeComponent; + ChangeListComponent: ChangeListComponent; $inputSection: HTMLElement; $contentsContainer: HTMLElement; - $changeAddForm: HTMLElement; - $totalChange: HTMLElement; - $changeList: HTMLElement; - $amountCoin500: HTMLElement; - $amountCoin100: HTMLElement; - $amountCoin50: HTMLElement; - $amountCoin10: HTMLElement; constructor() { this.$inputSection = document.querySelector('.input-section'); this.$contentsContainer = document.querySelector('.contents-container'); + this.AddChangeComponent = new AddChangeComponent(this.$inputSection, this.stateChange); + this.ChangeListComponent = new ChangeListComponent(this.$contentsContainer); } - render() { - this.$inputSection.insertAdjacentHTML('beforeend', AddChangeComponent()); - this.$contentsContainer.insertAdjacentHTML('beforeend', ChangeListComponent()); - - this.$changeAddForm = this.$inputSection.querySelector('#change-add-form'); - this.$totalChange = this.$inputSection.querySelector('#total-change'); - this.$changeList = this.$contentsContainer.querySelector('#change-list'); - - this.$amountCoin500 = this.$changeList.querySelector('#amount-coin-500'); - this.$amountCoin100 = this.$changeList.querySelector('#amount-coin-100'); - this.$amountCoin50 = this.$changeList.querySelector('#amount-coin-50'); - this.$amountCoin10 = this.$changeList.querySelector('#amount-coin-10'); - - this.$changeAddForm.addEventListener('submit', this.onSubmitChangeAdd); - - this.refreshChange(); - } - - onSubmitChangeAdd = (e: SubmitEvent) => { - e.preventDefault(); - const inputChange = parseInt((this.$changeAddForm.querySelector('#change-add-input')).value); - - try { - vendingMachine.inputChanges(inputChange); - this.refreshChange(); - } catch (message) { - alert(message); - } + render = () => { + this.AddChangeComponent.render(); + this.ChangeListComponent.render(); + this.AddChangeComponent.refreshChange(); + this.ChangeListComponent.refreshChange(); }; - refreshChange() { - this.$totalChange.textContent = vendingMachine.getTotalMoney().toString(); - const { coin10, coin50, coin100, coin500 } = vendingMachine.getChanges(); - - this.$amountCoin500.textContent = coin500 + '개'; - this.$amountCoin100.textContent = coin100 + '개'; - this.$amountCoin50.textContent = coin50 + '개'; - this.$amountCoin10.textContent = coin10 + '개'; - } + stateChange = () => { + this.ChangeListComponent.refreshChange(); + this.AddChangeComponent.refreshChange(); + }; } From 47a8b5f2c3d40a85b7736bf6a917611e96bcd11f Mon Sep 17 00:00:00 2001 From: "YongRae_Kim (Usage)" Date: Sun, 27 Mar 2022 19:04:22 +0900 Subject: [PATCH 37/44] =?UTF-8?q?refactor:=20=EC=83=81=ED=92=88=20?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=97=AD=ED=95=A0=20=EC=9E=AC?= =?UTF-8?q?=EB=B6=84=EB=B0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/js/components/AddProductComponent.ts | 54 ++++++++- src/js/components/ModifyProductComponent.ts | 72 +++++++++-- src/js/components/ProductListComponent.ts | 94 ++++++++++++++- src/js/pages/ProductManage.ts | 125 ++------------------ 4 files changed, 218 insertions(+), 127 deletions(-) diff --git a/src/js/components/AddProductComponent.ts b/src/js/components/AddProductComponent.ts index db99ab570..6adf611b4 100644 --- a/src/js/components/AddProductComponent.ts +++ b/src/js/components/AddProductComponent.ts @@ -1,5 +1,53 @@ -const AddProductComponent = () => { - return ` +import vendingMachine from '../model/VendingMachine'; +import { Product } from '../interfaces/VendingMachine.interface'; + +class AddProductComponent { + parentElement: HTMLElement; + noticeStateChanged: Function; + $productAddForm: HTMLElement; + $productList: HTMLElement; + + constructor(parentElement: HTMLElement, noticeStateChanged: Function) { + this.parentElement = parentElement; + this.noticeStateChanged = noticeStateChanged; + } + + bindEventAndElement = () => { + this.$productAddForm = this.parentElement.querySelector('#product-add-form'); + this.$productList = this.parentElement.querySelector('#product-list'); + + this.$productAddForm.addEventListener('submit', this.onSubmitNewProduct); + }; + + onSubmitNewProduct = (e: SubmitEvent) => { + e.preventDefault(); + + const name = (this.$productAddForm.querySelector('#product-name-input')).value; + const price = (this.$productAddForm.querySelector('#product-price-input')).value; + const amount = (this.$productAddForm.querySelector('#product-amount-input')).value; + + const newProduct: Product = { + name: name, + price: parseInt(price), + amount: parseInt(amount), + }; + + try { + vendingMachine.addProduct(newProduct); + this.noticeStateChanged('add', newProduct); + } catch (message) { + alert(message); + } + }; + + refreshComponent = () => {}; + + render = () => { + this.parentElement.insertAdjacentHTML('beforeend', this.template()); + this.bindEventAndElement(); + }; + + template = () => `

    추가할 상품 정보를 입력해주세요.

    @@ -24,6 +72,6 @@ const AddProductComponent = () => {
    `; -}; +} export default AddProductComponent; diff --git a/src/js/components/ModifyProductComponent.ts b/src/js/components/ModifyProductComponent.ts index aac5486ab..e53ae1da0 100644 --- a/src/js/components/ModifyProductComponent.ts +++ b/src/js/components/ModifyProductComponent.ts @@ -1,13 +1,69 @@ +import vendingMachine from '../model/VendingMachine'; +import ProductItemComponent from './ProductItemComponent'; import { Product } from '../interfaces/VendingMachine.interface'; -const ModifyProductComponent = (product: Product) => { - const { name, price, amount } = product; - return ` +class ModifyProductComponent { + name: string; + price: number; + amount: number; + parentElement: HTMLElement; + $productList: HTMLElement; + + constructor(parentElement: HTMLElement) { + this.parentElement = parentElement; + } + + bindEvent = () => { + this.$productList = this.parentElement.querySelector('#product-list'); + this.$productList.addEventListener('click', this.onSubmitModifyCompleteButton); + }; + + onSubmitModifyCompleteButton = (e: PointerEvent) => { + if ((e.target).className !== 'product-modify-submit-button') { + return; + } + + const ul = (e.target).closest('ul'); + const parentList = (e.target).closest('li'); + const product = { + name: (parentList.querySelector('.product-name-modify-input')).value, + price: parseInt((parentList.querySelector('.product-price-modify-input')).value), + amount: parseInt((parentList.querySelector('.product-amount-modify-input')).value), + }; + + const prevName = (parentList.querySelector('.product-modify-submit-button')).dataset.name; + + try { + vendingMachine.modifyProduct(prevName, product); + ul.replaceChild(this.replaceList(product, ProductItemComponent), parentList); + } catch (message) { + alert(message); + } + }; + + replaceList = (product: Product, component: Function) => { + const fragment = new DocumentFragment(); + const li = document.createElement('li'); + + li.insertAdjacentHTML('beforeend', component(product)); + fragment.appendChild(li); + + return fragment; + }; + + render = (product: Product) => { + this.name = product.name; + this.price = product.price; + this.amount = product.amount; + return this.template(); + }; + + template = () => ` @@ -16,7 +72,7 @@ const ModifyProductComponent = (product: Product) => { @@ -25,17 +81,17 @@ const ModifyProductComponent = (product: Product) => { - `; -}; +} export default ModifyProductComponent; diff --git a/src/js/components/ProductListComponent.ts b/src/js/components/ProductListComponent.ts index f8f7a45fa..a572c0bf4 100644 --- a/src/js/components/ProductListComponent.ts +++ b/src/js/components/ProductListComponent.ts @@ -1,5 +1,93 @@ -const ProductListComponent = () => { - return ` +import vendingMachine from '../model/VendingMachine'; +import ModifyProductComponent from './ModifyProductComponent'; +import ProductItemComponent from './ProductItemComponent'; +import { Product } from '../interfaces/VendingMachine.interface'; +import { REMOVE_CONFIRM_MESSAGE } from '../constants'; + +class ProductListComponent { + ModifyProductComponent: ModifyProductComponent; + parentElement: HTMLElement; + noticeStateChanged: Function; + $productList: HTMLElement; + + constructor(parentElement: HTMLElement, noticeStateChanged: Function) { + this.parentElement = parentElement; + this.noticeStateChanged = noticeStateChanged; + this.ModifyProductComponent = new ModifyProductComponent(parentElement); + } + + bindEvent = () => { + this.$productList = this.parentElement.querySelector('#product-list'); + this.$productList.addEventListener('click', this.onClickModifyButton); + this.$productList.addEventListener('click', this.onClickRemoveButton); + }; + + onClickModifyButton = (e: PointerEvent) => { + if ((e.target).className !== 'product-modify-button') { + return; + } + + const ul = (e.target).closest('ul'); + const oldLi = (e.target).closest('li'); + const product = { + name: (oldLi.querySelector('.product-name')).textContent, + price: parseInt((oldLi.querySelector('.product-price')).textContent), + amount: parseInt((oldLi.querySelector('.product-amount')).textContent), + }; + + ul.replaceChild(this.replaceList(product, this.ModifyProductComponent.render), oldLi); + this.ModifyProductComponent.bindEvent(); + }; + + onClickRemoveButton = (e: PointerEvent) => { + if ((e.target).className !== 'product-remove-button') { + return; + } + + if (!window.confirm(REMOVE_CONFIRM_MESSAGE)) { + return; + } + + const parentList = (e.target).closest('li'); + const name = (parentList.querySelector('.product-name')).textContent; + + vendingMachine.removeProduct(name); + parentList.remove(); + }; + + replaceList = (product: Product, component: Function) => { + const fragment = new DocumentFragment(); + const li = document.createElement('li'); + + li.insertAdjacentHTML('beforeend', component(product)); + fragment.appendChild(li); + + return fragment; + }; + + addProductItem(product: Product) { + const fragment = new DocumentFragment(); + const li = document.createElement('li'); + + li.insertAdjacentHTML('beforeend', ProductItemComponent(product)); + fragment.appendChild(li); + this.$productList.appendChild(fragment); + } + + refreshComponent = () => { + const products = vendingMachine.getProducts(); + + products.forEach(product => { + this.addProductItem(product); + }); + }; + + render = () => { + this.parentElement.insertAdjacentHTML('beforeend', this.template()); + this.bindEvent(); + }; + + template = () => `

    상품 현황

    @@ -13,6 +101,6 @@ const ProductListComponent = () => {
    `; -}; +} export default ProductListComponent; diff --git a/src/js/pages/ProductManage.ts b/src/js/pages/ProductManage.ts index 2f8ee5f77..395c254df 100644 --- a/src/js/pages/ProductManage.ts +++ b/src/js/pages/ProductManage.ts @@ -1,136 +1,35 @@ -import vendingMachine from '../model/VendingMachine'; import { Product } from '../interfaces/VendingMachine.interface'; import AddProductComponent from '../components/AddProductComponent'; import ProductListComponent from '../components/ProductListComponent'; -import ProductItemComponent from '../components/ProductItemComponent'; -import ModifyProductComponent from '../components/ModifyProductComponent'; -import { REMOVE_CONFIRM_MESSAGE } from '../constants'; export default class ProductManage { $inputSection: HTMLElement; $contentsContainer: HTMLElement; $productAddForm: HTMLElement; - $productList: HTMLElement; + AddProductComponent: AddProductComponent; + ProductListComponent: ProductListComponent; constructor() { this.$inputSection = document.querySelector('.input-section'); this.$contentsContainer = document.querySelector('.contents-container'); + this.AddProductComponent = new AddProductComponent(this.$inputSection, this.stateChange); + this.ProductListComponent = new ProductListComponent(this.$contentsContainer, this.stateChange); } render() { - this.$inputSection.insertAdjacentHTML('beforeend', AddProductComponent()); - this.$contentsContainer.insertAdjacentHTML('beforeend', ProductListComponent()); + this.AddProductComponent.render(); + this.ProductListComponent.render(); - this.$productAddForm = this.$inputSection.querySelector('#product-add-form'); - this.$productList = this.$contentsContainer.querySelector('#product-list'); - this.$productAddForm.addEventListener('submit', this.onSubmitNewProduct); - this.$productList.addEventListener('click', this.onClickModifyButton); - this.$productList.addEventListener('click', this.onSubmitModifyCompleteButton); - this.$productList.addEventListener('click', this.onClickRemoveButton); - - this.renderProducts(); + this.stateChange(); } - onSubmitNewProduct = (e: SubmitEvent) => { - e.preventDefault(); - - const name = (this.$productAddForm.querySelector('#product-name-input')).value; - const price = (this.$productAddForm.querySelector('#product-price-input')).value; - const amount = (this.$productAddForm.querySelector('#product-amount-input')).value; - - const newProduct: Product = { - name: name, - price: parseInt(price), - amount: parseInt(amount), - }; - - try { - vendingMachine.addProduct(newProduct); - this.addProductItem(newProduct); - } catch (message) { - alert(message); - } - }; - - onClickModifyButton = (e: PointerEvent) => { - if ((e.target).className !== 'product-modify-button') { - return; - } - - const ul = (e.target).closest('ul'); - const oldLi = (e.target).closest('li'); - const product = { - name: (oldLi.querySelector('.product-name')).textContent, - price: parseInt((oldLi.querySelector('.product-price')).textContent), - amount: parseInt((oldLi.querySelector('.product-amount')).textContent), - }; - - ul.replaceChild(this.replaceList(product, ModifyProductComponent), oldLi); - }; - - onSubmitModifyCompleteButton = (e: PointerEvent) => { - if ((e.target).className !== 'product-modify-submit-button') { + stateChange = (state: string = '', product: Product = null) => { + if (state === 'add') { + this.ProductListComponent.addProductItem(product); return; } - const ul = (e.target).closest('ul'); - const parentList = (e.target).closest('li'); - const product = { - name: (parentList.querySelector('.product-name-modify-input')).value, - price: parseInt((parentList.querySelector('.product-price-modify-input')).value), - amount: parseInt((parentList.querySelector('.product-amount-modify-input')).value), - }; - - const prevName = (parentList.querySelector('.product-modify-submit-button')).dataset.name; - - try { - vendingMachine.modifyProduct(prevName, product); - ul.replaceChild(this.replaceList(product, ProductItemComponent), parentList); - } catch (message) { - alert(message); - } + this.AddProductComponent.refreshComponent(); + this.ProductListComponent.refreshComponent(); }; - - onClickRemoveButton = (e: PointerEvent) => { - if ((e.target).className !== 'product-remove-button') { - return; - } - - if (!window.confirm(REMOVE_CONFIRM_MESSAGE)) { - return; - } - - const parentList = (e.target).closest('li'); - const name = (parentList.querySelector('.product-name')).textContent; - - vendingMachine.removeProduct(name); - parentList.remove(); - }; - - replaceList = (product: Product, component: Function) => { - const fragment = new DocumentFragment(); - const li = document.createElement('li'); - - li.insertAdjacentHTML('beforeend', component(product)); - fragment.appendChild(li); - - return fragment; - }; - - renderProducts() { - const products = vendingMachine.getProducts(); - - products.forEach(product => { - this.addProductItem(product); - }); - } - - addProductItem(product: Product) { - const fragment = new DocumentFragment(); - const li = document.createElement('li'); - - li.insertAdjacentHTML('beforeend', ProductItemComponent(product)); - fragment.appendChild(li); - this.$productList.appendChild(fragment); - } } From ab3e22d59302ef6745b2b96ce9732308ba6d6d76 Mon Sep 17 00:00:00 2001 From: "YongRae_Kim (Usage)" Date: Sun, 27 Mar 2022 19:23:52 +0900 Subject: [PATCH 38/44] =?UTF-8?q?chore:=20type-only=20export=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/js/interfaces/VendingMachine.interface.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/interfaces/VendingMachine.interface.ts b/src/js/interfaces/VendingMachine.interface.ts index f306ddf83..818f7e8ba 100644 --- a/src/js/interfaces/VendingMachine.interface.ts +++ b/src/js/interfaces/VendingMachine.interface.ts @@ -11,4 +11,4 @@ interface Product { price: number; } -export { Coin, Product }; +export type { Coin, Product }; From e4409573fd9d0422b6dab805e0bb803bb5b26338 Mon Sep 17 00:00:00 2001 From: "YongRae_Kim (Usage)" Date: Sun, 27 Mar 2022 22:12:48 +0900 Subject: [PATCH 39/44] =?UTF-8?q?refactor:=20=EC=99=B8=EB=B6=80=EC=97=90?= =?UTF-8?q?=20=EB=85=B8=EC=B6=9C=EB=90=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=EB=93=A4=EC=9D=84=20private?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/js/components/AddChangeComponent.ts | 8 ++++---- src/js/components/AddProductComponent.ts | 6 +++--- src/js/components/ChangeListComponent.ts | 4 ++-- src/js/components/ModifyProductComponent.ts | 6 +++--- src/js/components/ProductListComponent.ts | 10 +++++----- src/js/model/VendingMachine.ts | 15 ++++++--------- src/js/pages/ChangeAdd.ts | 2 +- src/js/pages/ProductManage.ts | 2 +- src/js/routes.ts | 2 +- src/js/utils/utils.ts | 3 +++ 10 files changed, 29 insertions(+), 29 deletions(-) create mode 100644 src/js/utils/utils.ts diff --git a/src/js/components/AddChangeComponent.ts b/src/js/components/AddChangeComponent.ts index ecfa54ea6..17e30e3b9 100644 --- a/src/js/components/AddChangeComponent.ts +++ b/src/js/components/AddChangeComponent.ts @@ -11,13 +11,13 @@ class AddChangeComponent { this.noticeStateChanged = noticeStateChanged; } - bindEventAndElement() { + private bindEventAndElement = () => { this.$totalChange = document.querySelector('#total-change'); this.$changeAddForm = document.querySelector('#change-add-form'); this.$changeAddForm.addEventListener('submit', this.onSubmitChangeAdd); - } + }; - onSubmitChangeAdd = (e: SubmitEvent) => { + private onSubmitChangeAdd = (e: SubmitEvent) => { e.preventDefault(); const inputChange = parseInt((this.$changeAddForm.querySelector('#change-add-input')).value); @@ -38,7 +38,7 @@ class AddChangeComponent { this.bindEventAndElement(); }; - template = () => ` + private template = () => `

    자판기가 보유할 금액을 입력해주세요

    diff --git a/src/js/components/AddProductComponent.ts b/src/js/components/AddProductComponent.ts index 6adf611b4..59c1339ea 100644 --- a/src/js/components/AddProductComponent.ts +++ b/src/js/components/AddProductComponent.ts @@ -12,14 +12,14 @@ class AddProductComponent { this.noticeStateChanged = noticeStateChanged; } - bindEventAndElement = () => { + private bindEventAndElement = () => { this.$productAddForm = this.parentElement.querySelector('#product-add-form'); this.$productList = this.parentElement.querySelector('#product-list'); this.$productAddForm.addEventListener('submit', this.onSubmitNewProduct); }; - onSubmitNewProduct = (e: SubmitEvent) => { + private onSubmitNewProduct = (e: SubmitEvent) => { e.preventDefault(); const name = (this.$productAddForm.querySelector('#product-name-input')).value; @@ -47,7 +47,7 @@ class AddProductComponent { this.bindEventAndElement(); }; - template = () => ` + private template = () => `

    추가할 상품 정보를 입력해주세요.

    diff --git a/src/js/components/ChangeListComponent.ts b/src/js/components/ChangeListComponent.ts index a80f4b269..dd1b6c913 100644 --- a/src/js/components/ChangeListComponent.ts +++ b/src/js/components/ChangeListComponent.ts @@ -13,7 +13,7 @@ class ChangeListComponent { this.parentElement = parentElement; } - bindElement = () => { + private bindElement = () => { this.$changeList = document.querySelector('#change-list'); this.$amountCoin500 = document.querySelector('#amount-coin-500'); this.$amountCoin100 = document.querySelector('#amount-coin-100'); @@ -35,7 +35,7 @@ class ChangeListComponent { this.bindElement(); }; - template = () => ` + private template = () => `

    자판기가 보유한 동전

      diff --git a/src/js/components/ModifyProductComponent.ts b/src/js/components/ModifyProductComponent.ts index e53ae1da0..57f38ab22 100644 --- a/src/js/components/ModifyProductComponent.ts +++ b/src/js/components/ModifyProductComponent.ts @@ -18,7 +18,7 @@ class ModifyProductComponent { this.$productList.addEventListener('click', this.onSubmitModifyCompleteButton); }; - onSubmitModifyCompleteButton = (e: PointerEvent) => { + private onSubmitModifyCompleteButton = (e: PointerEvent) => { if ((e.target).className !== 'product-modify-submit-button') { return; } @@ -41,7 +41,7 @@ class ModifyProductComponent { } }; - replaceList = (product: Product, component: Function) => { + private replaceList = (product: Product, component: Function) => { const fragment = new DocumentFragment(); const li = document.createElement('li'); @@ -58,7 +58,7 @@ class ModifyProductComponent { return this.template(); }; - template = () => ` + private template = () => ` { + private bindEvent = () => { this.$productList = this.parentElement.querySelector('#product-list'); this.$productList.addEventListener('click', this.onClickModifyButton); this.$productList.addEventListener('click', this.onClickRemoveButton); }; - onClickModifyButton = (e: PointerEvent) => { + private onClickModifyButton = (e: PointerEvent) => { if ((e.target).className !== 'product-modify-button') { return; } @@ -39,7 +39,7 @@ class ProductListComponent { this.ModifyProductComponent.bindEvent(); }; - onClickRemoveButton = (e: PointerEvent) => { + private onClickRemoveButton = (e: PointerEvent) => { if ((e.target).className !== 'product-remove-button') { return; } @@ -55,7 +55,7 @@ class ProductListComponent { parentList.remove(); }; - replaceList = (product: Product, component: Function) => { + private replaceList = (product: Product, component: Function) => { const fragment = new DocumentFragment(); const li = document.createElement('li'); @@ -87,7 +87,7 @@ class ProductListComponent { this.bindEvent(); }; - template = () => ` + private template = () => `

      상품 현황

      diff --git a/src/js/model/VendingMachine.ts b/src/js/model/VendingMachine.ts index 9fcc467ee..fb75b3fc8 100644 --- a/src/js/model/VendingMachine.ts +++ b/src/js/model/VendingMachine.ts @@ -1,5 +1,6 @@ import { ERROR_MESSAGE, RULES } from '../constants'; import { Product, Coin } from '../interfaces/VendingMachine.interface'; +import { getRandomInt } from '../utils/utils'; import { isValidProductPrice, isValidProductAmount, @@ -61,7 +62,7 @@ class VendingMachine { this.makeChangesToCoin(money); } - makeChangesToCoin(money: number) { + private makeChangesToCoin(money: number) { const coin = this.getChangeCoin(money); money -= coin; @@ -85,17 +86,13 @@ class VendingMachine { } } - getRandomInt(max: number) { - return Math.floor(Math.random() * max); - } - - getChangeCoin(money: number) { + private getChangeCoin(money: number) { const coins = [500, 100, 50, 10].filter(coin => coin <= money); - const index = this.getRandomInt(coins.length); + const index = getRandomInt(coins.length); return coins[index]; } - checkProductValidate(product: Product, originalIndex: number = RULES.NOT_EXIST_INDEX) { + private checkProductValidate(product: Product, originalIndex: number = RULES.NOT_EXIST_INDEX) { const productIndex = this.findProductIndex(product.name); const isExist = productIndex >= 0; const isAddWithDuplicatedName = isExist && originalIndex === RULES.NOT_EXIST_INDEX; @@ -118,7 +115,7 @@ class VendingMachine { } } - checkInputChangesValidate(money: number) { + private checkInputChangesValidate(money: number) { if (!isPositiveInteger(money)) { throw new Error(ERROR_MESSAGE.IS_NOT_POSITIVE_INTEGER); } diff --git a/src/js/pages/ChangeAdd.ts b/src/js/pages/ChangeAdd.ts index b2b156cb9..25bcf5377 100644 --- a/src/js/pages/ChangeAdd.ts +++ b/src/js/pages/ChangeAdd.ts @@ -21,7 +21,7 @@ export default class ChangeAdd { this.ChangeListComponent.refreshChange(); }; - stateChange = () => { + private stateChange = () => { this.ChangeListComponent.refreshChange(); this.AddChangeComponent.refreshChange(); }; diff --git a/src/js/pages/ProductManage.ts b/src/js/pages/ProductManage.ts index 395c254df..e0aa5b87d 100644 --- a/src/js/pages/ProductManage.ts +++ b/src/js/pages/ProductManage.ts @@ -23,7 +23,7 @@ export default class ProductManage { this.stateChange(); } - stateChange = (state: string = '', product: Product = null) => { + private stateChange = (state: string = '', product: Product = null) => { if (state === 'add') { this.ProductListComponent.addProductItem(product); return; diff --git a/src/js/routes.ts b/src/js/routes.ts index 957083e14..e004141ee 100644 --- a/src/js/routes.ts +++ b/src/js/routes.ts @@ -42,7 +42,7 @@ class router { } } - clear() { + private clear() { const $inputSection = document.querySelector('.input-section'); const $contentsContainer = document.querySelector('.contents-container'); diff --git a/src/js/utils/utils.ts b/src/js/utils/utils.ts new file mode 100644 index 000000000..2310750c4 --- /dev/null +++ b/src/js/utils/utils.ts @@ -0,0 +1,3 @@ +export function getRandomInt(max: number) { + return Math.floor(Math.random() * max); +} From 8c3b1590377a430d71041b446f208d73cfd5f2ab Mon Sep 17 00:00:00 2001 From: "YongRae_Kim (Usage)" Date: Sun, 27 Mar 2022 22:36:45 +0900 Subject: [PATCH 40/44] =?UTF-8?q?chore:=20=EC=A7=80=EC=86=8D=EC=A0=81?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=ED=95=A0=EB=8B=B9=EB=90=98=EB=8A=94=20?= =?UTF-8?q?=EB=B0=B0=EC=97=B4=EC=9D=84=20=EC=83=81=EB=8B=A8=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EB=B3=80=EC=88=98=EB=A1=9C=20=EB=AF=B8=EB=A6=AC=20?= =?UTF-8?q?=EC=84=A0=EC=96=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/js/model/VendingMachine.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/js/model/VendingMachine.ts b/src/js/model/VendingMachine.ts index fb75b3fc8..51dba59c1 100644 --- a/src/js/model/VendingMachine.ts +++ b/src/js/model/VendingMachine.ts @@ -13,9 +13,11 @@ class VendingMachine { private products: Array; private changes: Coin; private totalMoney: number; + private availableCoinTypeList: Array; constructor() { this.products = []; + this.availableCoinTypeList = [500, 100, 50, 10]; this.changes = { coin10: 0, coin50: 0, coin100: 0, coin500: 0 }; this.totalMoney = 0; } @@ -87,7 +89,7 @@ class VendingMachine { } private getChangeCoin(money: number) { - const coins = [500, 100, 50, 10].filter(coin => coin <= money); + const coins = this.availableCoinTypeList.filter(coin => coin <= money); const index = getRandomInt(coins.length); return coins[index]; } From bb987d200e36be3ce55af35eb93ed59885b6e452 Mon Sep 17 00:00:00 2001 From: "YongRae_Kim (Usage)" Date: Sun, 27 Mar 2022 23:12:49 +0900 Subject: [PATCH 41/44] =?UTF-8?q?refactor:=20=EC=A4=91=EB=B3=B5=EB=90=9C?= =?UTF-8?q?=20=EC=83=81=ED=92=88=EB=AA=85=20=ED=99=95=EC=9D=B8=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/js/model/VendingMachine.ts | 8 ++------ src/js/model/validator.ts | 8 ++++++++ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/js/model/VendingMachine.ts b/src/js/model/VendingMachine.ts index 51dba59c1..b7bc3f446 100644 --- a/src/js/model/VendingMachine.ts +++ b/src/js/model/VendingMachine.ts @@ -7,6 +7,7 @@ import { isValidProductNameLength, isUnitOfTen, isPositiveInteger, + isDuplicatedName, } from './validator'; class VendingMachine { @@ -95,12 +96,7 @@ class VendingMachine { } private checkProductValidate(product: Product, originalIndex: number = RULES.NOT_EXIST_INDEX) { - const productIndex = this.findProductIndex(product.name); - const isExist = productIndex >= 0; - const isAddWithDuplicatedName = isExist && originalIndex === RULES.NOT_EXIST_INDEX; - const isModifyWithDuplicateName = isExist && originalIndex !== productIndex; - - if (isAddWithDuplicatedName || isModifyWithDuplicateName) { + if (!isDuplicatedName(this.findProductIndex(product.name), originalIndex)) { throw new Error(ERROR_MESSAGE.PRODUCT_NAME_IS_DUPLICATED); } diff --git a/src/js/model/validator.ts b/src/js/model/validator.ts index 58b0241e6..34e36b188 100644 --- a/src/js/model/validator.ts +++ b/src/js/model/validator.ts @@ -1,5 +1,13 @@ import { RULES } from '../constants'; +export const isDuplicatedName = (productIndex: number, originalIndex: number) => { + const isExist = productIndex >= 0; + const isAddWithDuplicatedName = isExist && originalIndex === RULES.NOT_EXIST_INDEX; + const isModifyWithDuplicateName = isExist && originalIndex !== productIndex; + + return isAddWithDuplicatedName || isModifyWithDuplicateName; +}; + export const isValidProductNameLength = (name: string) => { return name.length <= RULES.MAX_LENGTH_PRODUCT_NAME && name.length > 0; }; From cc2fcaf90f56c9e3b45d1a0f919a15b98f08221a Mon Sep 17 00:00:00 2001 From: "YongRae_Kim (Usage)" Date: Mon, 28 Mar 2022 00:44:38 +0900 Subject: [PATCH 42/44] =?UTF-8?q?refactor:=20=EC=A4=91=EB=B3=B5=EB=90=9C?= =?UTF-8?q?=20=EC=83=81=ED=92=88=EB=AA=85=20=ED=99=95=EC=9D=B8=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/js/model/VendingMachine.ts | 8 ++------ src/js/model/validator.ts | 8 ++++++++ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/js/model/VendingMachine.ts b/src/js/model/VendingMachine.ts index 51dba59c1..fbd22f360 100644 --- a/src/js/model/VendingMachine.ts +++ b/src/js/model/VendingMachine.ts @@ -7,6 +7,7 @@ import { isValidProductNameLength, isUnitOfTen, isPositiveInteger, + isDuplicatedName, } from './validator'; class VendingMachine { @@ -95,12 +96,7 @@ class VendingMachine { } private checkProductValidate(product: Product, originalIndex: number = RULES.NOT_EXIST_INDEX) { - const productIndex = this.findProductIndex(product.name); - const isExist = productIndex >= 0; - const isAddWithDuplicatedName = isExist && originalIndex === RULES.NOT_EXIST_INDEX; - const isModifyWithDuplicateName = isExist && originalIndex !== productIndex; - - if (isAddWithDuplicatedName || isModifyWithDuplicateName) { + if (isDuplicatedName(this.findProductIndex(product.name), originalIndex)) { throw new Error(ERROR_MESSAGE.PRODUCT_NAME_IS_DUPLICATED); } diff --git a/src/js/model/validator.ts b/src/js/model/validator.ts index 58b0241e6..34e36b188 100644 --- a/src/js/model/validator.ts +++ b/src/js/model/validator.ts @@ -1,5 +1,13 @@ import { RULES } from '../constants'; +export const isDuplicatedName = (productIndex: number, originalIndex: number) => { + const isExist = productIndex >= 0; + const isAddWithDuplicatedName = isExist && originalIndex === RULES.NOT_EXIST_INDEX; + const isModifyWithDuplicateName = isExist && originalIndex !== productIndex; + + return isAddWithDuplicatedName || isModifyWithDuplicateName; +}; + export const isValidProductNameLength = (name: string) => { return name.length <= RULES.MAX_LENGTH_PRODUCT_NAME && name.length > 0; }; From f8dfbd55322bbe9ca7f2d66f5a32c2380cb299a9 Mon Sep 17 00:00:00 2001 From: "YongRae_Kim (Usage)" Date: Mon, 28 Mar 2022 00:47:06 +0900 Subject: [PATCH 43/44] =?UTF-8?q?chore:=20=EC=9E=94=EB=8F=88=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=9D=B4=EB=A6=84=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/js/pages/{ChangeAdd.ts => AddChange.ts} | 0 src/js/routes.ts | 8 ++++---- 2 files changed, 4 insertions(+), 4 deletions(-) rename src/js/pages/{ChangeAdd.ts => AddChange.ts} (100%) diff --git a/src/js/pages/ChangeAdd.ts b/src/js/pages/AddChange.ts similarity index 100% rename from src/js/pages/ChangeAdd.ts rename to src/js/pages/AddChange.ts diff --git a/src/js/routes.ts b/src/js/routes.ts index e004141ee..cb1a7c0c4 100644 --- a/src/js/routes.ts +++ b/src/js/routes.ts @@ -1,14 +1,14 @@ -import ChangeAdd from './pages/ChangeAdd'; +import AddChange from './pages/AddChange'; import ProductManage from './pages/ProductManage'; class router { prevPath: string; productManage: ProductManage; - changeAdd: ChangeAdd; + addChange: AddChange; constructor() { this.productManage = new ProductManage(); - this.changeAdd = new ChangeAdd(); + this.addChange = new AddChange(); this.prevPath = null; } @@ -35,7 +35,7 @@ class router { break; case '#!/change-add': history.pushState({}, '잔돈 충전하기', window.location.pathname + hash); - this.changeAdd.render(); + this.addChange.render(); break; default: break; From 69239024312f5c3fcbab66c6a3898852f07cdb1d Mon Sep 17 00:00:00 2001 From: "YongRae_Kim (Usage)" Date: Mon, 28 Mar 2022 00:52:18 +0900 Subject: [PATCH 44/44] =?UTF-8?q?refactor:=20url=20=EC=83=81=EC=88=98?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/index.ts | 5 +++-- src/js/constants.ts | 7 ++++++- src/js/routes.ts | 5 +++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/index.ts b/src/index.ts index 53e971905..c9d22b471 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ import './css/index'; +import { PATH_NAME } from './js/constants'; import router from './js/routes'; const routes = new router(); @@ -9,11 +10,11 @@ const changeAddButton = document.querySelector('#change-add-button'); // const btn3 = document.querySelector('#product-purchase-button'); productManageButton.addEventListener('click', () => { - routes.go('#!/product-manage'); + routes.go(PATH_NAME.PRODUCT_MANAGE); }); changeAddButton.addEventListener('click', () => { - routes.go('#!/change-add'); + routes.go(PATH_NAME.ADD_CHANGE); }); window.addEventListener('popstate', function () { diff --git a/src/js/constants.ts b/src/js/constants.ts index 71705409f..9637c1df2 100644 --- a/src/js/constants.ts +++ b/src/js/constants.ts @@ -1,3 +1,8 @@ +const PATH_NAME = { + PRODUCT_MANAGE: '#!/product-manage', + ADD_CHANGE: '#!/change-add', +}; + const RULES = { MAX_PRODUCT_PRICE: 10000, MIN_PRODUCT_PRICE: 100, @@ -21,4 +26,4 @@ const ERROR_MESSAGE = { const REMOVE_CONFIRM_MESSAGE = '정말로 삭제하시겠습니까?'; -export { RULES, ERROR_MESSAGE, REMOVE_CONFIRM_MESSAGE }; +export { PATH_NAME, RULES, ERROR_MESSAGE, REMOVE_CONFIRM_MESSAGE }; diff --git a/src/js/routes.ts b/src/js/routes.ts index cb1a7c0c4..e7c6c0f82 100644 --- a/src/js/routes.ts +++ b/src/js/routes.ts @@ -1,3 +1,4 @@ +import { PATH_NAME } from './constants'; import AddChange from './pages/AddChange'; import ProductManage from './pages/ProductManage'; @@ -29,11 +30,11 @@ class router { this.clear(); switch (hash) { - case '#!/product-manage': + case PATH_NAME.PRODUCT_MANAGE: history.pushState({}, '상품 관리하기', window.location.pathname + hash); this.productManage.render(); break; - case '#!/change-add': + case PATH_NAME.ADD_CHANGE: history.pushState({}, '잔돈 충전하기', window.location.pathname + hash); this.addChange.render(); break;