From a23eab522fcfa393cb2f3602161960626161bb9c Mon Sep 17 00:00:00 2001 From: Daniil Chudo Date: Sun, 31 Mar 2024 15:07:32 +0400 Subject: [PATCH] feature: rewrite to Composition API (#446) --- docs/others/v3.md | 8 +- package-lock.json | 330 +++--- package.json | 11 +- .../{all-countries.js => all-countries.ts} | 8 +- src/components/vue-tel-input.test.js | 261 ----- src/components/vue-tel-input.test.ts | 359 +++++++ src/components/vue-tel-input.vue | 983 +++++++++--------- src/types/index.ts | 50 + src/{utils.js => utils.ts} | 25 +- tsconfig.json | 8 +- vite.config.ts | 6 +- 11 files changed, 1106 insertions(+), 943 deletions(-) rename src/assets/{all-countries.js => all-countries.ts} (99%) delete mode 100644 src/components/vue-tel-input.test.js create mode 100644 src/components/vue-tel-input.test.ts create mode 100644 src/types/index.ts rename src/{utils.js => utils.ts} (92%) diff --git a/docs/others/v3.md b/docs/others/v3.md index a6af2e6f..dfbac887 100644 --- a/docs/others/v3.md +++ b/docs/others/v3.md @@ -135,10 +135,10 @@ Read more on `vue-form-generator`'s [instruction page](https://icebob.gitbooks.i | ----- | --------- | ----------- | ----- | | `input` | `String`, `Object` | Fires when the input changes with the argument is the object includes `{ number, isValid, country }` | `onInput` deprecated | | `validate` | `Object` | Fires when the correctness of the phone number changes (from `true` to `false` or vice-versa) and when the component is mounted `{ number, isValid, country }` | `onValidate` deprecated | - | `blur` | | Fires on blur event | `onBlur` deprecated | - | `focus` | | Fires on focus event | | - | `space` | | Fires on keyup.space event | `onSpace` deprecated | - | `enter` | | Fires on keyup.enter event | `onEnter` deprecated | + | `blur` | `FocusEvent` | Fires on blur event | `onBlur` deprecated | + | `focus` | `FocusEvent` | Fires on focus event | | + | `space` | `KeyboardEvent` | Fires on keyup.space event | `onSpace` deprecated | + | `enter` | `KeyboardEvent` | Fires on keyup.enter event | `onEnter` deprecated | | `open` | | Fires when the flags dropdown opens | | | `close` | | Fires when the flags dropdown closes | | | `country-changed` | `Object` | Fires when country changed (even for the first time) | Available from [v2.4.2](https://github.com/iamstevendao/vue-tel-input/releases/tag/v2.4.2) | diff --git a/package-lock.json b/package-lock.json index 1408f82e..a8b2a2df 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "libphonenumber-js": "^1.10.51", - "vue": "^3.3.9" + "vue": "^3.4.21" }, "devDependencies": { "@vitejs/plugin-vue": "^4.5.0", @@ -195,9 +195,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.4.tgz", - "integrity": "sha512-vf3Xna6UEprW+7t6EtOmFpHNAuxw3xqPZghy+brsnusscJRW5BMUzzHZc5ICjULee81WeUV2jjakG09MDglJXQ==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.0.tgz", + "integrity": "sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg==", "bin": { "parser": "bin/babel-parser.js" }, @@ -866,49 +866,49 @@ } }, "node_modules/@vue/compiler-core": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.9.tgz", - "integrity": "sha512-+/Lf68Vr/nFBA6ol4xOtJrW+BQWv3QWKfRwGSm70jtXwfhZNF4R/eRgyVJYoxFRhdCTk/F6g99BP0ffPgZihfQ==", + "version": "3.4.21", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.21.tgz", + "integrity": "sha512-MjXawxZf2SbZszLPYxaFCjxfibYrzr3eYbKxwpLR9EQN+oaziSu3qKVbwBERj1IFIB8OLUewxB5m/BFzi613og==", "dependencies": { - "@babel/parser": "^7.23.3", - "@vue/shared": "3.3.9", + "@babel/parser": "^7.23.9", + "@vue/shared": "3.4.21", + "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.0.2" } }, "node_modules/@vue/compiler-dom": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.3.9.tgz", - "integrity": "sha512-nfWubTtLXuT4iBeDSZ5J3m218MjOy42Vp2pmKVuBKo2/BLcrFUX8nCSr/bKRFiJ32R8qbdnnnBgRn9AdU5v0Sg==", + "version": "3.4.21", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.21.tgz", + "integrity": "sha512-IZC6FKowtT1sl0CR5DpXSiEB5ayw75oT2bma1BEhV7RRR1+cfwLrxc2Z8Zq/RGFzJ8w5r9QtCOvTjQgdn0IKmA==", "dependencies": { - "@vue/compiler-core": "3.3.9", - "@vue/shared": "3.3.9" + "@vue/compiler-core": "3.4.21", + "@vue/shared": "3.4.21" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.3.9.tgz", - "integrity": "sha512-wy0CNc8z4ihoDzjASCOCsQuzW0A/HP27+0MDSSICMjVIFzk/rFViezkR3dzH+miS2NDEz8ywMdbjO5ylhOLI2A==", - "dependencies": { - "@babel/parser": "^7.23.3", - "@vue/compiler-core": "3.3.9", - "@vue/compiler-dom": "3.3.9", - "@vue/compiler-ssr": "3.3.9", - "@vue/reactivity-transform": "3.3.9", - "@vue/shared": "3.3.9", + "version": "3.4.21", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.21.tgz", + "integrity": "sha512-me7epoTxYlY+2CUM7hy9PCDdpMPfIwrOvAXud2Upk10g4YLv9UBW7kL798TvMeDhPthkZ0CONNrK2GoeI1ODiQ==", + "dependencies": { + "@babel/parser": "^7.23.9", + "@vue/compiler-core": "3.4.21", + "@vue/compiler-dom": "3.4.21", + "@vue/compiler-ssr": "3.4.21", + "@vue/shared": "3.4.21", "estree-walker": "^2.0.2", - "magic-string": "^0.30.5", - "postcss": "^8.4.31", + "magic-string": "^0.30.7", + "postcss": "^8.4.35", "source-map-js": "^1.0.2" } }, "node_modules/@vue/compiler-ssr": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.3.9.tgz", - "integrity": "sha512-NO5oobAw78R0G4SODY5A502MGnDNiDjf6qvhn7zD7TJGc8XDeIEw4fg6JU705jZ/YhuokBKz0A5a/FL/XZU73g==", + "version": "3.4.21", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.21.tgz", + "integrity": "sha512-M5+9nI2lPpAsgXOGQobnIueVqc9sisBFexh5yMIMRAPYLa7+5wEJs8iqOZc1WAa9WQbx9GR2twgznU8LTIiZ4Q==", "dependencies": { - "@vue/compiler-dom": "3.3.9", - "@vue/shared": "3.3.9" + "@vue/compiler-dom": "3.4.21", + "@vue/shared": "3.4.21" } }, "node_modules/@vue/devtools-api": { @@ -942,60 +942,48 @@ } }, "node_modules/@vue/reactivity": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.3.9.tgz", - "integrity": "sha512-VmpIqlNp+aYDg2X0xQhJqHx9YguOmz2UxuUJDckBdQCNkipJvfk9yA75woLWElCa0Jtyec3lAAt49GO0izsphw==", + "version": "3.4.21", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.21.tgz", + "integrity": "sha512-UhenImdc0L0/4ahGCyEzc/pZNwVgcglGy9HVzJ1Bq2Mm9qXOpP8RyNTjookw/gOCUlXSEtuZ2fUg5nrHcoqJcw==", "dependencies": { - "@vue/shared": "3.3.9" - } - }, - "node_modules/@vue/reactivity-transform": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.3.9.tgz", - "integrity": "sha512-HnUFm7Ry6dFa4Lp63DAxTixUp8opMtQr6RxQCpDI1vlh12rkGIeYqMvJtK+IKyEfEOa2I9oCkD1mmsPdaGpdVg==", - "dependencies": { - "@babel/parser": "^7.23.3", - "@vue/compiler-core": "3.3.9", - "@vue/shared": "3.3.9", - "estree-walker": "^2.0.2", - "magic-string": "^0.30.5" + "@vue/shared": "3.4.21" } }, "node_modules/@vue/runtime-core": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.3.9.tgz", - "integrity": "sha512-xxaG9KvPm3GTRuM4ZyU8Tc+pMVzcu6eeoSRQJ9IE7NmCcClW6z4B3Ij6L4EDl80sxe/arTtQ6YmgiO4UZqRc+w==", + "version": "3.4.21", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.21.tgz", + "integrity": "sha512-pQthsuYzE1XcGZznTKn73G0s14eCJcjaLvp3/DKeYWoFacD9glJoqlNBxt3W2c5S40t6CCcpPf+jG01N3ULyrA==", "dependencies": { - "@vue/reactivity": "3.3.9", - "@vue/shared": "3.3.9" + "@vue/reactivity": "3.4.21", + "@vue/shared": "3.4.21" } }, "node_modules/@vue/runtime-dom": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.3.9.tgz", - "integrity": "sha512-e7LIfcxYSWbV6BK1wQv9qJyxprC75EvSqF/kQKe6bdZEDNValzeRXEVgiX7AHI6hZ59HA4h7WT5CGvm69vzJTQ==", + "version": "3.4.21", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.21.tgz", + "integrity": "sha512-gvf+C9cFpevsQxbkRBS1NpU8CqxKw0ebqMvLwcGQrNpx6gqRDodqKqA+A2VZZpQ9RpK2f9yfg8VbW/EpdFUOJw==", "dependencies": { - "@vue/runtime-core": "3.3.9", - "@vue/shared": "3.3.9", - "csstype": "^3.1.2" + "@vue/runtime-core": "3.4.21", + "@vue/shared": "3.4.21", + "csstype": "^3.1.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.3.9.tgz", - "integrity": "sha512-w0zT/s5l3Oa3ZjtLW88eO4uV6AQFqU8X5GOgzq7SkQQu6vVr+8tfm+OI2kDBplS/W/XgCBuFXiPw6T5EdwXP0A==", + "version": "3.4.21", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.21.tgz", + "integrity": "sha512-aV1gXyKSN6Rz+6kZ6kr5+Ll14YzmIbeuWe7ryJl5muJ4uwSwY/aStXTixx76TwkZFJLm1aAlA/HSWEJ4EyiMkg==", "dependencies": { - "@vue/compiler-ssr": "3.3.9", - "@vue/shared": "3.3.9" + "@vue/compiler-ssr": "3.4.21", + "@vue/shared": "3.4.21" }, "peerDependencies": { - "vue": "3.3.9" + "vue": "3.4.21" } }, "node_modules/@vue/shared": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.9.tgz", - "integrity": "sha512-ZE0VTIR0LmYgeyhurPTpy4KzKsuDyQbMSdM49eKkMnT5X4VfFBLysMzjIZhLEFQYjjOVVfbvUDHckwjDFiO2eA==" + "version": "3.4.21", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.21.tgz", + "integrity": "sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g==" }, "node_modules/@vueuse/core": { "version": "10.6.1", @@ -1261,9 +1249,9 @@ "dev": true }, "node_modules/csstype": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", - "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "node_modules/de-indent": { "version": "1.0.2", @@ -1297,7 +1285,6 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, "engines": { "node": ">=0.12" }, @@ -1538,9 +1525,9 @@ } }, "node_modules/magic-string": { - "version": "0.30.5", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", - "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", + "version": "0.30.8", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz", + "integrity": "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==", "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" }, @@ -1700,9 +1687,9 @@ "dev": true }, "node_modules/nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", "funding": [ { "type": "github", @@ -1740,9 +1727,9 @@ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" }, "node_modules/postcss": { - "version": "8.4.31", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", - "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "version": "8.4.35", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", + "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", "funding": [ { "type": "opencollective", @@ -1758,7 +1745,7 @@ } ], "dependencies": { - "nanoid": "^3.3.6", + "nanoid": "^3.3.7", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" }, @@ -2120,15 +2107,15 @@ } }, "node_modules/vue": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.3.9.tgz", - "integrity": "sha512-sy5sLCTR8m6tvUk1/ijri3Yqzgpdsmxgj6n6yl7GXXCXqVbmW2RCXe9atE4cEI6Iv7L89v5f35fZRRr5dChP9w==", + "version": "3.4.21", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.21.tgz", + "integrity": "sha512-5hjyV/jLEIKD/jYl4cavMcnzKwjMKohureP8ejn3hhEjwhWIhWeuzL2kJAjzl/WyVsgPY56Sy4Z40C3lVshxXA==", "dependencies": { - "@vue/compiler-dom": "3.3.9", - "@vue/compiler-sfc": "3.3.9", - "@vue/runtime-dom": "3.3.9", - "@vue/server-renderer": "3.3.9", - "@vue/shared": "3.3.9" + "@vue/compiler-dom": "3.4.21", + "@vue/compiler-sfc": "3.4.21", + "@vue/runtime-dom": "3.4.21", + "@vue/server-renderer": "3.4.21", + "@vue/shared": "3.4.21" }, "peerDependencies": { "typescript": "*" @@ -2359,9 +2346,9 @@ } }, "@babel/parser": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.4.tgz", - "integrity": "sha512-vf3Xna6UEprW+7t6EtOmFpHNAuxw3xqPZghy+brsnusscJRW5BMUzzHZc5ICjULee81WeUV2jjakG09MDglJXQ==" + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.0.tgz", + "integrity": "sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg==" }, "@docsearch/css": { "version": "3.5.2", @@ -2728,49 +2715,49 @@ } }, "@vue/compiler-core": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.9.tgz", - "integrity": "sha512-+/Lf68Vr/nFBA6ol4xOtJrW+BQWv3QWKfRwGSm70jtXwfhZNF4R/eRgyVJYoxFRhdCTk/F6g99BP0ffPgZihfQ==", + "version": "3.4.21", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.21.tgz", + "integrity": "sha512-MjXawxZf2SbZszLPYxaFCjxfibYrzr3eYbKxwpLR9EQN+oaziSu3qKVbwBERj1IFIB8OLUewxB5m/BFzi613og==", "requires": { - "@babel/parser": "^7.23.3", - "@vue/shared": "3.3.9", + "@babel/parser": "^7.23.9", + "@vue/shared": "3.4.21", + "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.0.2" } }, "@vue/compiler-dom": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.3.9.tgz", - "integrity": "sha512-nfWubTtLXuT4iBeDSZ5J3m218MjOy42Vp2pmKVuBKo2/BLcrFUX8nCSr/bKRFiJ32R8qbdnnnBgRn9AdU5v0Sg==", + "version": "3.4.21", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.21.tgz", + "integrity": "sha512-IZC6FKowtT1sl0CR5DpXSiEB5ayw75oT2bma1BEhV7RRR1+cfwLrxc2Z8Zq/RGFzJ8w5r9QtCOvTjQgdn0IKmA==", "requires": { - "@vue/compiler-core": "3.3.9", - "@vue/shared": "3.3.9" + "@vue/compiler-core": "3.4.21", + "@vue/shared": "3.4.21" } }, "@vue/compiler-sfc": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.3.9.tgz", - "integrity": "sha512-wy0CNc8z4ihoDzjASCOCsQuzW0A/HP27+0MDSSICMjVIFzk/rFViezkR3dzH+miS2NDEz8ywMdbjO5ylhOLI2A==", - "requires": { - "@babel/parser": "^7.23.3", - "@vue/compiler-core": "3.3.9", - "@vue/compiler-dom": "3.3.9", - "@vue/compiler-ssr": "3.3.9", - "@vue/reactivity-transform": "3.3.9", - "@vue/shared": "3.3.9", + "version": "3.4.21", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.21.tgz", + "integrity": "sha512-me7epoTxYlY+2CUM7hy9PCDdpMPfIwrOvAXud2Upk10g4YLv9UBW7kL798TvMeDhPthkZ0CONNrK2GoeI1ODiQ==", + "requires": { + "@babel/parser": "^7.23.9", + "@vue/compiler-core": "3.4.21", + "@vue/compiler-dom": "3.4.21", + "@vue/compiler-ssr": "3.4.21", + "@vue/shared": "3.4.21", "estree-walker": "^2.0.2", - "magic-string": "^0.30.5", - "postcss": "^8.4.31", + "magic-string": "^0.30.7", + "postcss": "^8.4.35", "source-map-js": "^1.0.2" } }, "@vue/compiler-ssr": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.3.9.tgz", - "integrity": "sha512-NO5oobAw78R0G4SODY5A502MGnDNiDjf6qvhn7zD7TJGc8XDeIEw4fg6JU705jZ/YhuokBKz0A5a/FL/XZU73g==", + "version": "3.4.21", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.21.tgz", + "integrity": "sha512-M5+9nI2lPpAsgXOGQobnIueVqc9sisBFexh5yMIMRAPYLa7+5wEJs8iqOZc1WAa9WQbx9GR2twgznU8LTIiZ4Q==", "requires": { - "@vue/compiler-dom": "3.3.9", - "@vue/shared": "3.3.9" + "@vue/compiler-dom": "3.4.21", + "@vue/shared": "3.4.21" } }, "@vue/devtools-api": { @@ -2796,57 +2783,45 @@ } }, "@vue/reactivity": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.3.9.tgz", - "integrity": "sha512-VmpIqlNp+aYDg2X0xQhJqHx9YguOmz2UxuUJDckBdQCNkipJvfk9yA75woLWElCa0Jtyec3lAAt49GO0izsphw==", + "version": "3.4.21", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.21.tgz", + "integrity": "sha512-UhenImdc0L0/4ahGCyEzc/pZNwVgcglGy9HVzJ1Bq2Mm9qXOpP8RyNTjookw/gOCUlXSEtuZ2fUg5nrHcoqJcw==", "requires": { - "@vue/shared": "3.3.9" - } - }, - "@vue/reactivity-transform": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.3.9.tgz", - "integrity": "sha512-HnUFm7Ry6dFa4Lp63DAxTixUp8opMtQr6RxQCpDI1vlh12rkGIeYqMvJtK+IKyEfEOa2I9oCkD1mmsPdaGpdVg==", - "requires": { - "@babel/parser": "^7.23.3", - "@vue/compiler-core": "3.3.9", - "@vue/shared": "3.3.9", - "estree-walker": "^2.0.2", - "magic-string": "^0.30.5" + "@vue/shared": "3.4.21" } }, "@vue/runtime-core": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.3.9.tgz", - "integrity": "sha512-xxaG9KvPm3GTRuM4ZyU8Tc+pMVzcu6eeoSRQJ9IE7NmCcClW6z4B3Ij6L4EDl80sxe/arTtQ6YmgiO4UZqRc+w==", + "version": "3.4.21", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.21.tgz", + "integrity": "sha512-pQthsuYzE1XcGZznTKn73G0s14eCJcjaLvp3/DKeYWoFacD9glJoqlNBxt3W2c5S40t6CCcpPf+jG01N3ULyrA==", "requires": { - "@vue/reactivity": "3.3.9", - "@vue/shared": "3.3.9" + "@vue/reactivity": "3.4.21", + "@vue/shared": "3.4.21" } }, "@vue/runtime-dom": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.3.9.tgz", - "integrity": "sha512-e7LIfcxYSWbV6BK1wQv9qJyxprC75EvSqF/kQKe6bdZEDNValzeRXEVgiX7AHI6hZ59HA4h7WT5CGvm69vzJTQ==", + "version": "3.4.21", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.21.tgz", + "integrity": "sha512-gvf+C9cFpevsQxbkRBS1NpU8CqxKw0ebqMvLwcGQrNpx6gqRDodqKqA+A2VZZpQ9RpK2f9yfg8VbW/EpdFUOJw==", "requires": { - "@vue/runtime-core": "3.3.9", - "@vue/shared": "3.3.9", - "csstype": "^3.1.2" + "@vue/runtime-core": "3.4.21", + "@vue/shared": "3.4.21", + "csstype": "^3.1.3" } }, "@vue/server-renderer": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.3.9.tgz", - "integrity": "sha512-w0zT/s5l3Oa3ZjtLW88eO4uV6AQFqU8X5GOgzq7SkQQu6vVr+8tfm+OI2kDBplS/W/XgCBuFXiPw6T5EdwXP0A==", + "version": "3.4.21", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.21.tgz", + "integrity": "sha512-aV1gXyKSN6Rz+6kZ6kr5+Ll14YzmIbeuWe7ryJl5muJ4uwSwY/aStXTixx76TwkZFJLm1aAlA/HSWEJ4EyiMkg==", "requires": { - "@vue/compiler-ssr": "3.3.9", - "@vue/shared": "3.3.9" + "@vue/compiler-ssr": "3.4.21", + "@vue/shared": "3.4.21" } }, "@vue/shared": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.9.tgz", - "integrity": "sha512-ZE0VTIR0LmYgeyhurPTpy4KzKsuDyQbMSdM49eKkMnT5X4VfFBLysMzjIZhLEFQYjjOVVfbvUDHckwjDFiO2eA==" + "version": "3.4.21", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.21.tgz", + "integrity": "sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g==" }, "@vueuse/core": { "version": "10.6.1", @@ -2981,9 +2956,9 @@ "dev": true }, "csstype": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", - "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "de-indent": { "version": "1.0.2", @@ -3009,8 +2984,7 @@ "entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==" }, "esbuild": { "version": "0.19.8", @@ -3193,9 +3167,9 @@ } }, "magic-string": { - "version": "0.30.5", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", - "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", + "version": "0.30.8", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz", + "integrity": "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==", "requires": { "@jridgewell/sourcemap-codec": "^1.4.15" } @@ -3289,9 +3263,9 @@ "dev": true }, "nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==" + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==" }, "parse5": { "version": "7.1.2", @@ -3314,11 +3288,11 @@ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" }, "postcss": { - "version": "8.4.31", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", - "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "version": "8.4.35", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", + "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", "requires": { - "nanoid": "^3.3.6", + "nanoid": "^3.3.7", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } @@ -3544,15 +3518,15 @@ } }, "vue": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.3.9.tgz", - "integrity": "sha512-sy5sLCTR8m6tvUk1/ijri3Yqzgpdsmxgj6n6yl7GXXCXqVbmW2RCXe9atE4cEI6Iv7L89v5f35fZRRr5dChP9w==", - "requires": { - "@vue/compiler-dom": "3.3.9", - "@vue/compiler-sfc": "3.3.9", - "@vue/runtime-dom": "3.3.9", - "@vue/server-renderer": "3.3.9", - "@vue/shared": "3.3.9" + "version": "3.4.21", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.21.tgz", + "integrity": "sha512-5hjyV/jLEIKD/jYl4cavMcnzKwjMKohureP8ejn3hhEjwhWIhWeuzL2kJAjzl/WyVsgPY56Sy4Z40C3lVshxXA==", + "requires": { + "@vue/compiler-dom": "3.4.21", + "@vue/compiler-sfc": "3.4.21", + "@vue/runtime-dom": "3.4.21", + "@vue/server-renderer": "3.4.21", + "@vue/shared": "3.4.21" } }, "vue-template-compiler": { diff --git a/package.json b/package.json index 42552966..93be18ae 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "module": "./dist/vue-tel-input.js", "scripts": { "dev": "vitepress dev docs", + "test": "vitest --ui", "build": "vue-tsc && vite build", "preview": "vite preview", "docs:dev": "vitepress dev docs", @@ -29,17 +30,21 @@ }, "dependencies": { "libphonenumber-js": "^1.10.51", - "vue": "^3.3.9" + "vue": "^3.4.21" }, "files": [ "dist" ], "devDependencies": { - "@vitejs/plugin-vue": "^4.5.0", + "@vitejs/plugin-vue": "^5.0.4", + "@vitest/ui": "^1.4.0", + "@vue/test-utils": "^2.4.5", + "jsdom": "^24.0.0", "typescript": "^5.3.2", "vite": "^5.0.2", "vitepress": "^1.0.0-rc.31", - "vue-tsc": "^1.8.22" + "vitest": "^1.4.0", + "vue-tsc": "^2.0.6" }, "keywords": [ "vue", diff --git a/src/assets/all-countries.js b/src/assets/all-countries.ts similarity index 99% rename from src/assets/all-countries.js rename to src/assets/all-countries.ts index 22cf16b9..b5867a62 100644 --- a/src/assets/all-countries.js +++ b/src/assets/all-countries.ts @@ -1,5 +1,8 @@ // A fork of https://github.com/jackocnr/intl-tel-input/blob/master/src/js/data.js +import type { Country, CountryObject } from "../types"; + + // Array of country objects for the flag dropdown. // Here is the criteria for the plugin to support a given country/territory @@ -16,7 +19,8 @@ // Order (if >1 country with same dial code), // Area codes // ] -const allCountries = [ + +const allCountries: Country[] = [ [ 'Afghanistan (‫افغانستان‬‎)', 'af', @@ -1330,4 +1334,4 @@ export default allCountries.map(([name, iso2, dialCode, priority = 0, areaCodes dialCode, priority, areaCodes, -})); +})) as CountryObject[]; diff --git a/src/components/vue-tel-input.test.js b/src/components/vue-tel-input.test.js deleted file mode 100644 index 1db6dc17..00000000 --- a/src/components/vue-tel-input.test.js +++ /dev/null @@ -1,261 +0,0 @@ -import Vue from 'vue'; -import { shallowMount } from '@vue/test-utils'; -import VueTelInput from './vue-tel-input.vue'; -import * as utils from '../utils'; - -describe('vue-tel-input', () => { - beforeEach(() => { - jest.spyOn(utils, 'getCountry').mockImplementation(() => Promise.resolve('au')); - }); - - it('renders without crash', () => { - const wrapper = shallowMount(VueTelInput); - - expect(wrapper.find('.vue-tel-input').exists()).toBeTruthy(); - }); - // TODO: Test validation of some specific phone numbers -}); - -describe('Props', () => { - beforeEach(() => { - jest.spyOn(utils, 'getCountry').mockImplementation(() => Promise.resolve('au')); - }); - - describe(':allCountries', () => { - it('overrides all pre-defined countries', async () => { - const wrapper = shallowMount(VueTelInput, { - propsData: { - allCountries: [{ iso2: 'AU' }], - autoDefaultCountry: false, - }, - }); - await Vue.nextTick(); - expect(wrapper.vm.sortedCountries).toHaveLength(1); - expect(wrapper.findAll('.vti__dropdown-list li')).toHaveLength(1); - expect(wrapper.find('.vti__selection > .vti__flag').classes()).toContain('au'); - }); - }); - // describe(':customValidate', () => { - // it('tests custom validation on type', () => { - // const wrapper = shallowMount(VueTelInput, { - // propsData: { - // customValidate: new RegExp('^[()-+0-9s]*$'), - // }, - // }); - // wrapper.vm.testCustomValidate = jest.fn(); - // wrapper.vm.onInput(); - // expect(wrapper.vm.testCustomValidate).toHaveBeenCalledTimes(1); - // }); - // }); - describe(':defaultCountry', () => { - it('shows correct default country', async () => { - const wrapper = shallowMount(VueTelInput, { - propsData: { - defaultCountry: 'AU', - }, - }); - await Vue.nextTick(); - expect(wrapper.find('.vti__selection > .vti__flag').classes()).toContain('au'); - }); - }); - describe(':defaultCountryByDialCode', () => { - it('shows correct default country by dial code', async () => { - const wrapper = shallowMount(VueTelInput, { - propsData: { - defaultCountry: 48, - }, - }); - await Vue.nextTick(); - expect(wrapper.find('.vti__selection > .vti__flag').classes()).toContain('pl'); - }); - }); - describe(':disabled', () => { - it('adds disabled class to component', () => { - const wrapper = shallowMount(VueTelInput, { - propsData: { - disabled: true, - }, - }); - - expect(wrapper.find('.vue-tel-input').classes()).toContain('disabled'); - expect(wrapper.find('.vti__input').attributes('disabled')).toBe('disabled'); - }); - it('stops showing dropdown list when true', () => { - const wrapper = shallowMount(VueTelInput, { - propsData: { - disabled: true, - }, - }); - - wrapper.find('.vti__dropdown').trigger('click'); - expect(wrapper.vm.open).toBe(false); - }); - }); - describe(':autoDefaultCountry', () => { - it('doesn\'t fetch the country by IP when set FALSE', async () => { - utils.getCountry.mockReset(); - const wrapper = shallowMount(VueTelInput, { - propsData: { - autoDefaultCountry: false, - preferredCountries: ['AU'], - }, - }); - await Vue.nextTick(); - expect(utils.getCountry).not.toHaveBeenCalled(); - expect(wrapper.find('.vti__selection > .vti__flag').classes()).toContain('au'); - }); - }); - describe(':dropdownOptions', () => { - it('.tabindex sets tabindex for dropdown', () => { - const wrapper = shallowMount(VueTelInput, { - propsData: { - dropdownOptions: { - tabindex: 1, - }, - }, - }); - - expect(wrapper.find('.vti__dropdown').attributes('tabindex')).toBe('1'); - }); - it('.showDialCodeInList hides dial code in the dropdown', () => { - const wrapper = shallowMount(VueTelInput, { - propsData: { - dropdownOptions: { - showDialCodeInList: false, - }, - }, - }); - - expect(wrapper.find('.vti__dropdown-item span').exists()).toBeFalsy(); - }); - it('.showDialCodeInSelection shows country code in the selection if TRUE', async () => { - const wrapper = shallowMount(VueTelInput, { - propsData: { - dropdownOptions: { - showDialCodeInSelection: true, - }, - defaultCountry: 'au', - }, - }); - await Vue.nextTick(); - expect(wrapper.find('.vti__selection > .vti__country-code').text()).toBe('+61'); - }); - it('.showFlags hides flags in the dropdown list if FALSE', () => { - const wrapper = shallowMount(VueTelInput, { - propsData: { - dropdownOptions: { - showFlags: false, - }, - }, - }); - - expect(wrapper.find('.vti__dropdown-item .vti__flag').exists()).toBeFalsy(); - }); - }); - describe(':ignoredCountries', () => { - it('hides countries from the list', () => { - const wrapper = shallowMount(VueTelInput, { - propsData: { - ignoredCountries: ['AU'], - }, - }); - - expect(wrapper.vm.filteredCountries.find(({ iso2 }) => iso2 === 'AU')).toBeUndefined(); - expect(wrapper.find('.vti__dropdown-item > .vti__flag.au').exists()).toBeFalsy(); - }); - }); - describe(':inputOptions', () => { - it('.id sets `id` native attribute of input', () => { - const wrapper = shallowMount(VueTelInput, { - propsData: { - inputOptions: { id: 'test' }, - }, - }); - - expect(wrapper.find('.vti__input').attributes('id')).toBe('test'); - }); - it('.maxlength sets `maxlength` native attribute of input', () => { - const wrapper = shallowMount(VueTelInput, { - propsData: { - inputOptions: { maxlength: 20 }, - }, - }); - - expect(wrapper.find('.vti__input').attributes('maxlength')).toBe('20'); - }); - it('.name sets `name` native attribute of input', () => { - const wrapper = shallowMount(VueTelInput, { - propsData: { - inputOptions: { name: 'test' }, - }, - }); - - expect(wrapper.find('.vti__input').attributes('name')).toBe('test'); - }); - it('.styleClasses sets classes along side with .vti__input', () => { - const wrapper = shallowMount(VueTelInput, { - propsData: { - inputOptions: { styleClasses: ['test'] }, - }, - }); - - expect(wrapper.find('.vti__input').classes()).toContain('test'); - }); - // it('.dynamicPlaceholder generates a random number as placeholder', async () => { - // const wrapper = shallowMount(VueTelInput, { - // propsData: { - // inputOptions: { dynamicPlaceholder: true }, - // defaultCountry: 'au', - // }, - // }); - // await new Promise((res) => setTimeout(() => { - // expect(wrapper.find('.vti__input').attributes('placeholder')).toContain('+61 '); - // res(); - // }, 2000)); - // }); - }); - describe(':invalid-msg', () => { - // TODO - }); - describe(':mode', () => { - // TODO - }); - describe(':onlyCountries', () => { - it('limits the countries to be used', () => { - const wrapper = shallowMount(VueTelInput, { - propsData: { - onlyCountries: ['AU'], - }, - }); - - expect(wrapper.findAll('.vti__dropdown-item')).toHaveLength(1); - expect(wrapper.vm.filteredCountries).toHaveLength(1); - }); - }); - describe(':preferredCountries', () => { - it('are highlighted and be on top of the dropdown', () => { - const wrapper = shallowMount(VueTelInput, { - propsData: { - preferredCountries: ['AU'], - }, - }); - - expect(wrapper.vm.sortedCountries[0].iso2).toBe('AU'); - expect(wrapper.find('.vti__dropdown-item > .vti__flag.au').element.parentElement.getAttribute('class')).toContain('preferred'); - }); - }); - describe(':validCharactersOnly', () => { - // TODO - }); - describe(':styleClasses', () => { - it('sets classes along side with .vue-tel-input', () => { - const wrapper = shallowMount(VueTelInput, { - propsData: { - styleClasses: ['test'], - }, - }); - - expect(wrapper.find('.vue-tel-input').classes()).toContain('test'); - }); - }); -}); diff --git a/src/components/vue-tel-input.test.ts b/src/components/vue-tel-input.test.ts new file mode 100644 index 00000000..cc8e75f4 --- /dev/null +++ b/src/components/vue-tel-input.test.ts @@ -0,0 +1,359 @@ +import { shallowMount } from '@vue/test-utils'; +import { describe, beforeEach, it, expect, vi } from 'vitest' + +import VueTelInput from './vue-tel-input.vue'; +import * as utils from '../utils'; + +describe('vue-tel-input', () => { + beforeEach(() => { + vi.spyOn(utils, 'getCountry').mockImplementation(() => Promise.resolve('au')); + }); + + it('renders without crash', () => { + const wrapper = shallowMount(VueTelInput); + + expect(wrapper.find('.vue-tel-input').exists()).toBeTruthy(); + }); + // TODO: Test validation of some specific phone numbers + + describe('Dropdown interaction', () => { + it('toggles dropdown on click', async () => { + const wrapper = shallowMount(VueTelInput); + + await wrapper.find('.vti__dropdown').trigger('click'); + expect(wrapper.vm.data.open).toBe(true); + + await wrapper.find('.vti__dropdown').trigger('click'); + expect(wrapper.vm.data.open).toBe(false); + }); + + it('closes dropdown when clicked outside', async () => { + const wrapper = shallowMount(VueTelInput); + + // Simulate opening the dropdown + await wrapper.find('.vti__dropdown').trigger('click'); + wrapper.vm.$nextTick(() => { + expect(wrapper.vm.data.open).toBe(true); + }) + + // Simulate clicking outside + await wrapper.vm.$el.click(); + wrapper.vm.$nextTick(() => { + expect(wrapper.vm.data.open).toBe(false); + }) + }); + }); + + describe('Keyboard navigation', () => { + it('navigates through countries using arrow keys', async () => { + const wrapper = shallowMount(VueTelInput, { + props: { + allCountries: [{ iso2: 'US' }, { iso2: 'CA' }], + preferredCountries: ['US'] + } + }); + await wrapper.find('.vti__dropdown').trigger('keydown.down'); // Simulate arrow down press + wrapper.vm.$nextTick(() => { + expect(wrapper.vm.data.selectedIndex).toBe(0); // Assuming the first country is selected + }) + + await wrapper.find('.vti__dropdown').trigger('keydown.up'); // Simulate arrow up press + wrapper.vm.$nextTick(() => { + expect(wrapper.vm.data.selectedIndex).toBe(1); // Assuming it cycles back to the last country + }) + }); + }); + + describe('Search functionality', () => { + it('filters countries based on input', async () => { + const wrapper = shallowMount(VueTelInput, { + props: { + allCountries: [{ iso2: 'US', name: 'United States' }, { iso2: 'CA', name: 'Canada' }], + dropdownOptions: { showSearchBox: true } + } + }); + wrapper.vm.data.searchQuery = 'Uni'; + wrapper.vm.$nextTick(() => { + expect(wrapper.vm.sortedCountries).toHaveLength(1); + expect(wrapper.vm.sortedCountries[0].iso2).toBe('US'); + }) + }); + }); +}); + +describe('Props', () => { + beforeEach(() => { + vi.spyOn(utils, 'getCountry').mockImplementation(() => Promise.resolve('au')); + }); + + describe(':allCountries', () => { + it('overrides all pre-defined countries', async () => { + const wrapper = shallowMount(VueTelInput, { + props: { + allCountries: [{ iso2: 'AU' }], + autoDefaultCountry: false, + }, + }); + wrapper.vm.$nextTick(() => { + expect(wrapper.vm.sortedCountries).toHaveLength(1); + expect(wrapper.findAll('.vti__dropdown-list li')).toHaveLength(1); + expect(wrapper.find('.vti__selection > .vti__flag').classes()).toContain('au'); + }); + }); + }); + // describe(':customValidate', () => { + // it('tests custom validation on type', () => { + // const wrapper = shallowMount(VueTelInput, { + // props: { + // customValidate: new RegExp('^[()-+0-9s]*$'), + // }, + // }); + // wrapper.vm.testCustomValidate = vi.fn(); + // wrapper.vm.onInput(); + // expect(wrapper.vm.testCustomValidate).toHaveBeenCalledTimes(1); + // }); + // }); + describe(':defaultCountry', () => { + it('shows correct default country', async () => { + const wrapper = shallowMount(VueTelInput, { + props: { + defaultCountry: 'AU', + }, + }); + wrapper.vm.$nextTick(() => { + expect(wrapper.find('.vti__selection > .vti__flag').classes()).toContain('au'); + }); + }); + }); + describe(':defaultCountryByDialCode', () => { + it('shows correct default country by dial code', async () => { + const wrapper = shallowMount(VueTelInput, { + props: { + defaultCountry: 48, + }, + }); + wrapper.vm.$nextTick(() => { + expect(wrapper.find('.vti__selection > .vti__flag').classes()).toContain('pl'); + }); + }); + }); + describe(':disabled', () => { + it('adds disabled class to component', () => { + const wrapper = shallowMount(VueTelInput, { + props: { + disabled: true, + }, + }); + + wrapper.vm.$nextTick(() => { + expect(wrapper.find('.vue-tel-input').classes()).toContain('disabled'); + expect(wrapper.find('.vti__phone').attributes('disabled')).toBe('disabled'); + }) + }); + it('stops showing dropdown list when true', () => { + const wrapper = shallowMount(VueTelInput, { + props: { + disabled: true, + }, + }); + + wrapper.vm.$nextTick(() => { + console.log('wrapper.vm.data :>> ', wrapper.vm.data); + wrapper.find('.vti__dropdown').trigger('click'); + expect(wrapper.vm.data.open).toBe(false); + }) + }); + }); + describe(':autoDefaultCountry', () => { + it('doesn\'t fetch the country by IP when set FALSE', async () => { + utils.getCountry.mockReset(); + const wrapper = shallowMount(VueTelInput, { + props: { + autoDefaultCountry: false, + preferredCountries: ['AU'], + }, + }); + wrapper.vm.$nextTick(() => { + expect(utils.getCountry).not.toHaveBeenCalled(); + expect(wrapper.find('.vti__selection > .vti__flag').classes()).toContain('au'); + }); + }); + }); + describe(':dropdownOptions', () => { + it('.tabindex sets tabindex for dropdown', () => { + const wrapper = shallowMount(VueTelInput, { + props: { + dropdownOptions: { + tabindex: 1, + }, + }, + }); + + wrapper.vm.$nextTick(() => { + expect(wrapper.find('.vti__dropdown').attributes('tabindex')).toBe('1'); + }) + }); + it('.showDialCodeInList hides dial code in the dropdown', () => { + const wrapper = shallowMount(VueTelInput, { + props: { + dropdownOptions: { + showDialCodeInList: false, + }, + }, + }); + + wrapper.vm.$nextTick(() => { + expect(wrapper.find('.vti__dropdown-item span').exists()).toBeFalsy(); + }) + }); + it('.showDialCodeInSelection shows country code in the selection if TRUE', async () => { + const wrapper = shallowMount(VueTelInput, { + props: { + dropdownOptions: { + showDialCodeInSelection: true, + }, + defaultCountry: 'au', + }, + }); + wrapper.vm.$nextTick(() => { + expect(wrapper.find('.vti__selection > .vti__country-code').text()).toBe('+61'); + }); + }); + it('.showFlags hides flags in the dropdown list if FALSE', () => { + const wrapper = shallowMount(VueTelInput, { + props: { + dropdownOptions: { + showFlags: false, + }, + }, + }); + + wrapper.vm.$nextTick(() => { + expect(wrapper.find('.vti__dropdown-item .vti__flag').exists()).toBeFalsy(); + }) + }); + }); + describe(':ignoredCountries', () => { + it('hides countries from the list', () => { + const wrapper = shallowMount(VueTelInput, { + props: { + ignoredCountries: ['AU'], + }, + }); + + wrapper.vm.$nextTick(() => { + + expect(wrapper.vm.filteredCountries.find(({ iso2 }) => iso2 === 'AU')).toBeUndefined(); + expect(wrapper.find('.vti__dropdown-item > .vti__flag.au').exists()).toBeFalsy(); + }) + }); + }); + describe(':inputOptions', () => { + it('.id sets `id` native attribute of input', () => { + const wrapper = shallowMount(VueTelInput, { + props: { + inputOptions: { id: 'test' }, + }, + }); + + wrapper.vm.$nextTick(() => { + expect(wrapper.find('.vti__input').attributes('id')).toBe('test'); + }) + }); + it('.maxlength sets `maxlength` native attribute of input', () => { + const wrapper = shallowMount(VueTelInput, { + props: { + inputOptions: { maxlength: 20 }, + }, + }); + + wrapper.vm.$nextTick(() => { + expect(wrapper.find('.vti__input').attributes('maxlength')).toBe('20'); + }) + }); + it('.name sets `name` native attribute of input', () => { + const wrapper = shallowMount(VueTelInput, { + props: { + inputOptions: { name: 'test' }, + }, + }); + + wrapper.vm.$nextTick(() => { + expect(wrapper.find('.vti__input').attributes('name')).toBe('test'); + }) + }); + it('.styleClasses sets classes along side with .vti__input', () => { + const wrapper = shallowMount(VueTelInput, { + props: { + inputOptions: { styleClasses: ['test'] }, + }, + }); + + wrapper.vm.$nextTick(() => { + expect(wrapper.find('.vti__input').classes()).toContain('test'); + }) + }); + // it('.dynamicPlaceholder generates a random number as placeholder', async () => { + // const wrapper = shallowMount(VueTelInput, { + // props: { + // inputOptions: { dynamicPlaceholder: true }, + // defaultCountry: 'au', + // }, + // }); + // await new Promise((res) => setTimeout(() => { + // expect(wrapper.find('.vti__input').attributes('placeholder')).toContain('+61 '); + // res(); + // }, 2000)); + // }); + }); + describe(':invalid-msg', () => { + // TODO + }); + describe(':mode', () => { + // TODO + }); + describe(':onlyCountries', () => { + it('limits the countries to be used', () => { + const wrapper = shallowMount(VueTelInput, { + props: { + onlyCountries: ['AU'], + }, + }); + + wrapper.vm.$nextTick(() => { + expect(wrapper.findAll('.vti__dropdown-item')).toHaveLength(1); + expect(wrapper.vm.filteredCountries).toHaveLength(1); + }) + }); + }); + describe(':preferredCountries', () => { + it('are highlighted and be on top of the dropdown', () => { + const wrapper = shallowMount(VueTelInput, { + props: { + preferredCountries: ['AU'], + }, + }); + + wrapper.vm.$nextTick(() => { + expect(wrapper.vm.sortedCountries[0].iso2).toBe('AU'); + expect(wrapper.find('.vti__dropdown-item > .vti__flag.au').element.parentElement.getAttribute('class')).toContain('preferred'); + }) + }); + }); + describe(':validCharactersOnly', () => { + // TODO + }); + describe(':styleClasses', () => { + it('sets classes along side with .vue-tel-input', () => { + const wrapper = shallowMount(VueTelInput, { + props: { + styleClasses: ['test'], + }, + }); + + wrapper.vm.$nextTick(() => { + expect(wrapper.find('.vue-tel-input').classes()).toContain('test'); + }) + }); + }); +}); diff --git a/src/components/vue-tel-input.vue b/src/components/vue-tel-input.vue index 2bb9786a..e5ca8dbc 100644 --- a/src/components/vue-tel-input.vue +++ b/src/components/vue-tel-input.vue @@ -1,11 +1,11 @@ - - - + + diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 00000000..0bbca7ad --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,50 @@ +import type { CountryCode, PhoneNumber } from "libphonenumber-js"; + +export type Country = [ + CountryName: string, + Iso2: Lowercase, + DialCode: string, + Priority?: number, + AreaCodes?: string[] +]; + +export interface CountryObject { + name: string; + iso2: CountryCode; + dialCode: string; + priority: number; + areaCodes: string[] | null; +} + +export type PhoneMeta = { + country: PhoneNumber['country'], + countryCode: PhoneNumber['country'], + valid: boolean, + formatted: string +}; + +export interface DropdownOptions { + disabled?: boolean + showDialCodeInList?: boolean + showDialCodeInSelection?: boolean + showFlags?: boolean + showSearchBox?: boolean + tabindex?: number +} + +export interface InputOptions { + autocomplete?: string + autofocus?: boolean + // dynamicPlaceholder?: boolean + 'aria-describedby'?: string + id?: string + maxlength?: number + name?: string + showDialCode?: boolean + placeholder?: string + readonly?: boolean + required?: boolean + tabindex?: number + type?: string + styleClasses?: string | any[] | Record +} diff --git a/src/utils.js b/src/utils.ts similarity index 92% rename from src/utils.js rename to src/utils.ts index 503d48e7..1108e20f 100644 --- a/src/utils.js +++ b/src/utils.ts @@ -15,14 +15,14 @@ export function getCountry() { } // Credits: http://blog.vishalon.net/index.php/javascript-getting-and-setting-caret-position-in-textarea/ -export function setCaretPosition(ctrl, pos) { +export function setCaretPosition(ctrl: HTMLInputElement, pos: number) { // Modern browsers if (ctrl.setSelectionRange) { ctrl.focus(); ctrl.setSelectionRange(pos, pos); // IE8 and below - } else if (ctrl.createTextRange) { + } else if ('createTextRange' in ctrl && typeof ctrl.createTextRange === 'function') { const range = ctrl.createTextRange(); range.collapse(true); range.moveEnd('character', pos); @@ -291,8 +291,25 @@ export const defaultOptions = [...allProps] Object.assign(prv, { [crr.name]: crr.default }); } return prv; - }, {}); + }, {} as Record); -export default { +const utils = { options: { ...defaultOptions }, }; +export default utils; + +export function getDefault(key: string) { + const value = utils.options[key]; + if (typeof value === 'undefined') { + return utils.options[key]; + } + return value; +} + +export function toLowerCase (str: T) { + return str?.toLowerCase() as Lowercase; +} + +export function toUpperCase (str: T) { + return str?.toUpperCase() as Uppercase; +} diff --git a/tsconfig.json b/tsconfig.json index 022ede55..aa475d34 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,10 +18,14 @@ "noEmit": true, "jsx": "preserve", /* Linting */ - "strict": true, + "strict": false, "noUnusedLocals": true, "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + "types": [ + "libphonenumber-js", + "vitest" + ], }, "include": [ "src/**/*.ts", diff --git a/vite.config.ts b/vite.config.ts index 9fef0d87..611e1f6d 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,6 +1,6 @@ import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' -import { resolve } from 'path' +import { resolve } from 'node:path' // https://vitejs.dev/config/ export default defineConfig({ @@ -31,4 +31,8 @@ export default defineConfig({ }, }, }, + test: { + globals: true, + environment: 'jsdom', + } })