From 1a06992fef08a97a7a4cd4661bffc360638be023 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kadir=20Yaz=C4=B1c=C4=B1?= <47540799+kadiryazici@users.noreply.github.com> Date: Wed, 19 Oct 2022 23:42:25 +0300 Subject: [PATCH] Add unit tests (#26) * Add unit tests * Fix type check script --- .github/workflows/unit-tests.yml | 21 +++ package.json | 10 +- pnpm-lock.yaml | 275 ++++++++++++++++++++++++++++--- tests/component.test.ts | 193 ++++++++++++++++++++++ tsconfig.app.json | 2 +- tsconfig.vitest.json | 2 +- vite.config.ts | 13 +- 7 files changed, 485 insertions(+), 31 deletions(-) create mode 100644 .github/workflows/unit-tests.yml create mode 100644 tests/component.test.ts diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml new file mode 100644 index 0000000..29dccd3 --- /dev/null +++ b/.github/workflows/unit-tests.yml @@ -0,0 +1,21 @@ +name: Unit Tests + +on: push + +jobs: + publish: + runs-on: ubuntu-latest + name: Unit Tests + steps: + - name: Checkout repository + uses: actions/checkout@v2 + - name: Use Node.js + uses: actions/setup-node@v2 + with: + node-version: '16' + - name: Install pnpm + run: npm install -g pnpm + - name: Install dependencies + run: pnpm install + - name: Run tests + run: pnpm run test \ No newline at end of file diff --git a/package.json b/package.json index 37a3bda..b01b9c4 100644 --- a/package.json +++ b/package.json @@ -34,9 +34,10 @@ "build": "run-p type-check build-only", "build-dev": "vite build --mode staging", "preview": "vite preview --port 4173", - "test:unit": "vitest --environment jsdom", + "test-watch": "vitest watch --environment jsdom", + "test": "vitest run", "build-only": "vite build", - "type-check": "vue-tsc --noEmit -p tsconfig.vitest.json --composite false", + "type-check": "vue-tsc --noEmit -p tsconfig.app.json --composite false", "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore" }, "devDependencies": { @@ -44,7 +45,7 @@ "@rushstack/eslint-patch": "^1.1.4", "@types/jsdom": "^20.0.0", "@types/node": "^16.11.47", - "@vitejs/plugin-vue": "^3.1.0", + "@vitejs/plugin-vue": "^3.1.2", "@vitejs/plugin-vue-jsx": "^2.0.0", "@vue/eslint-config-prettier": "^7.0.0", "@vue/eslint-config-typescript": "^11.0.0", @@ -52,6 +53,7 @@ "@vue/tsconfig": "^0.1.3", "eslint": "^8.21.0", "eslint-plugin-vue": "^9.3.0", + "happy-dom": "^7.6.0", "hotkeys-js": "^3.9.5", "jsdom": "^20.0.0", "nanoid": "^4.0.0", @@ -67,7 +69,7 @@ "unplugin-icons": "^0.14.9", "vite": "^3.0.4", "vite-plugin-dts": "^1.4.1", - "vitest": "^0.21.0", + "vitest": "^0.21.1", "vue": "^3.2.38", "vue-selectable-items": "file:", "vue-tsc": "1.0.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bb8dd99..00f05e4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,7 +5,7 @@ specifiers: '@rushstack/eslint-patch': ^1.1.4 '@types/jsdom': ^20.0.0 '@types/node': ^16.11.47 - '@vitejs/plugin-vue': ^3.1.0 + '@vitejs/plugin-vue': ^3.1.2 '@vitejs/plugin-vue-jsx': ^2.0.0 '@vue/eslint-config-prettier': ^7.0.0 '@vue/eslint-config-typescript': ^11.0.0 @@ -13,6 +13,7 @@ specifiers: '@vue/tsconfig': ^0.1.3 eslint: ^8.21.0 eslint-plugin-vue: ^9.3.0 + happy-dom: ^7.6.0 hotkeys-js: ^3.9.5 jsdom: ^20.0.0 nanoid: ^4.0.0 @@ -28,7 +29,7 @@ specifiers: unplugin-icons: ^0.14.9 vite: ^3.0.4 vite-plugin-dts: ^1.4.1 - vitest: ^0.21.0 + vitest: ^0.21.1 vue: ^3.2.38 vue-selectable-items: 'file:' vue-tsc: 1.0.7 @@ -39,7 +40,7 @@ devDependencies: '@rushstack/eslint-patch': 1.1.4 '@types/jsdom': 20.0.0 '@types/node': 16.11.56 - '@vitejs/plugin-vue': 3.1.0_vite@3.0.9+vue@3.2.38 + '@vitejs/plugin-vue': 3.1.2_vite@3.0.9+vue@3.2.38 '@vitejs/plugin-vue-jsx': 2.0.0_vite@3.0.9+vue@3.2.38 '@vue/eslint-config-prettier': 7.0.0_bxpuzolsxufkw2ipnoyzzxnyrm '@vue/eslint-config-typescript': 11.0.0_rtxrmflxy6tcq5dwdjz7igkv5q @@ -47,6 +48,7 @@ devDependencies: '@vue/tsconfig': 0.1.3_@types+node@16.11.56 eslint: 8.23.0 eslint-plugin-vue: 9.4.0_eslint@8.23.0 + happy-dom: 7.6.0 hotkeys-js: 3.9.5 jsdom: 20.0.0 nanoid: 4.0.0 @@ -62,7 +64,7 @@ devDependencies: unplugin-icons: 0.14.9_vite@3.0.9 vite: 3.0.9_sass@1.54.8 vite-plugin-dts: 1.4.1_vite@3.0.9 - vitest: 0.21.1_jsdom@20.0.0+sass@1.54.8 + vitest: 0.21.1_v2uuuuu3zyb6zj2kmh3naav7aq vue: 3.2.38 vue-selectable-items: file:_vue@3.2.38 vue-tsc: 1.0.7_typescript@4.8.4 @@ -625,6 +627,18 @@ packages: resolution: {integrity: sha512-hC7OMnszpxhZPduX+m+nrx+uFoLkWOMiR4oa/AZF3MuSETYTZmFfJAHqZEM8MVlvfG7BEUcgvtwoCTxBp6hm3g==} dev: true + /@types/concat-stream/1.6.1: + resolution: {integrity: sha512-eHE4cQPoj6ngxBZMvVf6Hw7Mh4jMW4U9lpGmS5GBPB9RYxlFg+CHaVN7ErNY4W9XfLIEn20b4VDYaIrbq0q4uA==} + dependencies: + '@types/node': 16.11.56 + dev: true + + /@types/form-data/0.0.33: + resolution: {integrity: sha512-8BSvG1kGm83cyJITQMZSulnl6QV8jqAGreJsc5tPu1Jq0vTSOiY/k24Wx82JRpWwZSqrala6sd5rWi6aNXvqcw==} + dependencies: + '@types/node': 16.11.56 + dev: true + /@types/jsdom/20.0.0: resolution: {integrity: sha512-YfAchFs0yM1QPDrLm2VHe+WHGtqms3NXnXAMolrgrVP6fgBHHXy1ozAbo/dFtPNtZC/m66bPiCTWYmqp1F14gA==} dependencies: @@ -641,6 +655,10 @@ packages: resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==} dev: true + /@types/node/10.17.60: + resolution: {integrity: sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==} + dev: true + /@types/node/12.20.24: resolution: {integrity: sha512-yxDeaQIAJlMav7fH5AQqPH1u8YIuhYJXYBzxaQ4PifsU0GDO38MSdmEDeRlIxrKbC6NbEaaEHDanWb+y30U8SQ==} dev: true @@ -649,6 +667,10 @@ packages: resolution: {integrity: sha512-aFcUkv7EddxxOa/9f74DINReQ/celqH8DiB3fRYgVDM2Xm5QJL8sl80QKuAnGvwAsMn+H3IFA6WCrQh1CY7m1A==} dev: true + /@types/node/8.10.66: + resolution: {integrity: sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw==} + dev: true + /@types/normalize-package-data/2.4.1: resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} dev: true @@ -657,6 +679,10 @@ packages: resolution: {integrity: sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==} dev: true + /@types/qs/6.9.7: + resolution: {integrity: sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==} + dev: true + /@types/tough-cookie/4.0.2: resolution: {integrity: sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==} dev: true @@ -804,8 +830,8 @@ packages: - supports-color dev: true - /@vitejs/plugin-vue/3.1.0_vite@3.0.9+vue@3.2.38: - resolution: {integrity: sha512-fmxtHPjSOEIRg6vHYDaem+97iwCUg/uSIaTzp98lhELt2ISOQuDo2hbkBdXod0g15IhfPMQmAxh4heUks2zvDA==} + /@vitejs/plugin-vue/3.1.2_vite@3.0.9+vue@3.2.38: + resolution: {integrity: sha512-3zxKNlvA3oNaKDYX0NBclgxTQ1xaFdL7PzwF6zj9tGFziKwmBa3Q/6XcJQxudlT81WxDjEhHmevvIC4Orc1LhQ==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: vite: ^3.0.0 @@ -841,7 +867,7 @@ packages: '@volar/language-core': 1.0.7 '@volar/source-map': 1.0.7 '@vue/compiler-dom': 3.2.40 - '@vue/compiler-sfc': 3.2.40 + '@vue/compiler-sfc': 3.2.41 '@vue/reactivity': 3.2.40 '@vue/shared': 3.2.40 minimatch: 5.1.0 @@ -894,6 +920,15 @@ packages: source-map: 0.6.1 dev: true + /@vue/compiler-core/3.2.41: + resolution: {integrity: sha512-oA4mH6SA78DT+96/nsi4p9DX97PHcNROxs51lYk7gb9Z4BPKQ3Mh+BLn6CQZBw857Iuhu28BfMSRHAlPvD4vlw==} + dependencies: + '@babel/parser': 7.18.13 + '@vue/shared': 3.2.41 + estree-walker: 2.0.2 + source-map: 0.6.1 + dev: true + /@vue/compiler-dom/3.2.38: resolution: {integrity: sha512-zqX4FgUbw56kzHlgYuEEJR8mefFiiyR3u96498+zWPsLeh1WKvgIReoNE+U7gG8bCUdvsrJ0JRmev0Ky6n2O0g==} dependencies: @@ -908,6 +943,13 @@ packages: '@vue/shared': 3.2.40 dev: true + /@vue/compiler-dom/3.2.41: + resolution: {integrity: sha512-xe5TbbIsonjENxJsYRbDJvthzqxLNk+tb3d/c47zgREDa/PCp6/Y4gC/skM4H6PIuX5DAxm7fFJdbjjUH2QTMw==} + dependencies: + '@vue/compiler-core': 3.2.41 + '@vue/shared': 3.2.41 + dev: true + /@vue/compiler-sfc/3.2.38: resolution: {integrity: sha512-KZjrW32KloMYtTcHAFuw3CqsyWc5X6seb8KbkANSWt3Cz9p2qA8c1GJpSkksFP9ABb6an0FLCFl46ZFXx3kKpg==} dependencies: @@ -923,15 +965,15 @@ packages: source-map: 0.6.1 dev: true - /@vue/compiler-sfc/3.2.40: - resolution: {integrity: sha512-tzqwniIN1fu1PDHC3CpqY/dPCfN/RN1thpBC+g69kJcrl7mbGiHKNwbA6kJ3XKKy8R6JLKqcpVugqN4HkeBFFg==} + /@vue/compiler-sfc/3.2.41: + resolution: {integrity: sha512-+1P2m5kxOeaxVmJNXnBskAn3BenbTmbxBxWOtBq3mQTCokIreuMULFantBUclP0+KnzNCMOvcnKinqQZmiOF8w==} dependencies: '@babel/parser': 7.18.13 - '@vue/compiler-core': 3.2.40 - '@vue/compiler-dom': 3.2.40 - '@vue/compiler-ssr': 3.2.40 - '@vue/reactivity-transform': 3.2.40 - '@vue/shared': 3.2.40 + '@vue/compiler-core': 3.2.41 + '@vue/compiler-dom': 3.2.41 + '@vue/compiler-ssr': 3.2.41 + '@vue/reactivity-transform': 3.2.41 + '@vue/shared': 3.2.41 estree-walker: 2.0.2 magic-string: 0.25.9 postcss: 8.4.16 @@ -945,11 +987,11 @@ packages: '@vue/shared': 3.2.38 dev: true - /@vue/compiler-ssr/3.2.40: - resolution: {integrity: sha512-80cQcgasKjrPPuKcxwuCx7feq+wC6oFl5YaKSee9pV3DNq+6fmCVwEEC3vvkf/E2aI76rIJSOYHsWSEIxK74oQ==} + /@vue/compiler-ssr/3.2.41: + resolution: {integrity: sha512-Y5wPiNIiaMz/sps8+DmhaKfDm1xgj6GrH99z4gq2LQenfVQcYXmHIOBcs5qPwl7jaW3SUQWjkAPKMfQemEQZwQ==} dependencies: - '@vue/compiler-dom': 3.2.40 - '@vue/shared': 3.2.40 + '@vue/compiler-dom': 3.2.41 + '@vue/shared': 3.2.41 dev: true /@vue/eslint-config-prettier/7.0.0_bxpuzolsxufkw2ipnoyzzxnyrm: @@ -995,12 +1037,12 @@ packages: magic-string: 0.25.9 dev: true - /@vue/reactivity-transform/3.2.40: - resolution: {integrity: sha512-HQUCVwEaacq6fGEsg2NUuGKIhUveMCjOk8jGHqLXPI2w6zFoPrlQhwWEaINTv5kkZDXKEnCijAp+4gNEHG03yw==} + /@vue/reactivity-transform/3.2.41: + resolution: {integrity: sha512-mK5+BNMsL4hHi+IR3Ft/ho6Za+L3FA5j8WvreJ7XzHrqkPq8jtF/SMo7tuc9gHjLDwKZX1nP1JQOKo9IEAn54A==} dependencies: '@babel/parser': 7.18.13 - '@vue/compiler-core': 3.2.40 - '@vue/shared': 3.2.40 + '@vue/compiler-core': 3.2.41 + '@vue/shared': 3.2.41 estree-walker: 2.0.2 magic-string: 0.25.9 dev: true @@ -1050,6 +1092,10 @@ packages: resolution: {integrity: sha512-0PLQ6RUtZM0vO3teRfzGi4ltLUO5aO+kLgwh4Um3THSR03rpQWLTuRCkuO5A41ITzwdWeKdPHtSARuPkoo5pCQ==} dev: true + /@vue/shared/3.2.41: + resolution: {integrity: sha512-W9mfWLHmJhkfAmV+7gDjcHeAWALQtgGT3JErxULl0oz6R6+3ug91I7IErs93eCFhPCZPHBs4QJS7YWEV7A3sxw==} + dev: true + /@vue/test-utils/2.0.2_vue@3.2.38: resolution: {integrity: sha512-E2P4oXSaWDqTZNbmKZFVLrNN/siVN78YkEqs7pHryWerrlZR9bBFLWdJwRoguX45Ru6HxIflzKl4vQvwRMwm5g==} peerDependencies: @@ -1179,6 +1225,10 @@ packages: engines: {node: '>=0.10.0'} dev: true + /asap/2.0.6: + resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + dev: true + /assertion-error/1.1.0: resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} dev: true @@ -1244,6 +1294,10 @@ packages: update-browserslist-db: 1.0.5_browserslist@4.21.3 dev: true + /buffer-from/1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + dev: true + /call-bind/1.0.2: resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} dependencies: @@ -1279,6 +1333,10 @@ packages: resolution: {integrity: sha512-BBWt57kqWbc0GYZXb47wTXpmAgqr5LSibPzNjk/AWMdmJMQhLqOl3c/Kd4OAU/tu4NLfYkMx8Tlq3RVBkOBolQ==} dev: true + /caseless/0.12.0: + resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} + dev: true + /chai/4.3.6: resolution: {integrity: sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==} engines: {node: '>=4'} @@ -1379,12 +1437,26 @@ packages: resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} dev: true + /concat-stream/1.6.2: + resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==} + engines: {'0': node >= 0.8} + dependencies: + buffer-from: 1.1.2 + inherits: 2.0.4 + readable-stream: 2.3.7 + typedarray: 0.0.6 + dev: true + /convert-source-map/1.8.0: resolution: {integrity: sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==} dependencies: safe-buffer: 5.1.2 dev: true + /core-util-is/1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + dev: true + /cosmiconfig/7.0.1: resolution: {integrity: sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==} engines: {node: '>=10'} @@ -1436,6 +1508,10 @@ packages: engines: {node: '>= 6'} dev: true + /css.escape/1.5.1: + resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} + dev: true + /cssesc/3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} @@ -2144,6 +2220,15 @@ packages: resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==} dev: true + /form-data/2.5.1: + resolution: {integrity: sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==} + engines: {node: '>= 0.12'} + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + dev: true + /form-data/4.0.0: resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} engines: {node: '>= 6'} @@ -2222,6 +2307,11 @@ packages: has-symbols: 1.0.3 dev: true + /get-port/3.2.0: + resolution: {integrity: sha512-x5UJKlgeUiNT8nyo/AcnwLnZuZNcSjSw0kogRB+Whd1fjjFq4B1hySFxSFWWSn4mIBzg3sRNUDFYc4g5gjPoLg==} + engines: {node: '>=4'} + dev: true + /get-stream/6.0.1: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} @@ -2312,6 +2402,20 @@ packages: resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} dev: true + /happy-dom/7.6.0: + resolution: {integrity: sha512-QnNsiblZdyVDzW5ts6E7ub79JnabqHJeJgt+1WGNq9fSYqS/r/RzzTVXCZSDl6EVkipdwI48B4bgXAnMZPecIw==} + dependencies: + css.escape: 1.5.1 + he: 1.2.0 + node-fetch: 2.6.7 + sync-request: 6.1.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 2.0.0 + whatwg-mimetype: 3.0.0 + transitivePeerDependencies: + - encoding + dev: true + /hard-rejection/2.1.0: resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} engines: {node: '>=6'} @@ -2388,6 +2492,16 @@ packages: engines: {node: '>=8'} dev: true + /http-basic/8.1.3: + resolution: {integrity: sha512-/EcDMwJZh3mABI2NhGfHOGOeOZITqfkEO4p/xK+l3NpyncIHUQBoMvCSF/b5GqvKtySC2srL/GGG3+EtlqlmCw==} + engines: {node: '>=6.0.0'} + dependencies: + caseless: 0.12.0 + concat-stream: 1.6.2 + http-response-object: 3.0.2 + parse-cache-control: 1.0.1 + dev: true + /http-proxy-agent/5.0.0: resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==} engines: {node: '>= 6'} @@ -2399,6 +2513,12 @@ packages: - supports-color dev: true + /http-response-object/3.0.2: + resolution: {integrity: sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==} + dependencies: + '@types/node': 10.17.60 + dev: true + /https-proxy-agent/5.0.1: resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} engines: {node: '>= 6'} @@ -2607,6 +2727,10 @@ packages: call-bind: 1.0.2 dev: true + /isarray/1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + dev: true + /isexe/2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} dev: true @@ -2947,6 +3071,18 @@ packages: resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==} dev: true + /node-fetch/2.6.7: + resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + dependencies: + whatwg-url: 5.0.0 + dev: true + /node-html-parser/5.4.2: resolution: {integrity: sha512-RaBPP3+51hPne/OolXxcz89iYvQvKOydaqoePpOgXcrOKZhjVIzmpKZz+Hd/RBO2/zN2q6CNJhQzucVz+u3Jyw==} dependencies: @@ -3111,6 +3247,10 @@ packages: callsites: 3.1.0 dev: true + /parse-cache-control/1.0.1: + resolution: {integrity: sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg==} + dev: true + /parse-json/4.0.0: resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==} engines: {node: '>=4'} @@ -3277,6 +3417,16 @@ packages: hasBin: true dev: true + /process-nextick-args/2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + dev: true + + /promise/8.2.0: + resolution: {integrity: sha512-+CMAlLHqwRYwBMXKCP+o8ns7DN+xHDUiI+0nArsiJ9y+kJVPLFxEaSw6Ha9s9H0tftxg2Yzl25wqj9G7m5wLZg==} + dependencies: + asap: 2.0.6 + dev: true + /psl/1.9.0: resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} dev: true @@ -3286,6 +3436,13 @@ packages: engines: {node: '>=6'} dev: true + /qs/6.11.0: + resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} + engines: {node: '>=0.6'} + dependencies: + side-channel: 1.0.4 + dev: true + /querystringify/2.2.0: resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} dev: true @@ -3327,6 +3484,18 @@ packages: type-fest: 0.6.0 dev: true + /readable-stream/2.3.7: + resolution: {integrity: sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==} + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + dev: true + /readdirp/3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -3599,6 +3768,12 @@ packages: es-abstract: 1.20.1 dev: true + /string_decoder/1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + dependencies: + safe-buffer: 5.1.2 + dev: true + /strip-ansi/6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -3751,6 +3926,21 @@ packages: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} dev: true + /sync-request/6.1.0: + resolution: {integrity: sha512-8fjNkrNlNCrVc/av+Jn+xxqfCjYaBoHqCsDz6mt030UMxJGr+GSfCV1dQt2gRtlL63+VPidwDVLr7V2OcTSdRw==} + engines: {node: '>=8.0.0'} + dependencies: + http-response-object: 3.0.2 + sync-rpc: 1.3.6 + then-request: 6.0.2 + dev: true + + /sync-rpc/1.3.6: + resolution: {integrity: sha512-J8jTXuZzRlvU7HemDgHi3pGnh/rkoqR/OZSjhTyyZrEkkYQbk7Z33AXp37mkPfPpfdOuj7Ex3H/TJM1z48uPQw==} + dependencies: + get-port: 3.2.0 + dev: true + /table/6.8.0: resolution: {integrity: sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==} engines: {node: '>=10.0.0'} @@ -3766,6 +3956,23 @@ packages: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} dev: true + /then-request/6.0.2: + resolution: {integrity: sha512-3ZBiG7JvP3wbDzA9iNY5zJQcHL4jn/0BWtXIkagfz7QgOL/LqjCEOBQuJNZfu0XYnv5JhKh+cDxCPM4ILrqruA==} + engines: {node: '>=6.0.0'} + dependencies: + '@types/concat-stream': 1.6.1 + '@types/form-data': 0.0.33 + '@types/node': 8.10.66 + '@types/qs': 6.9.7 + caseless: 0.12.0 + concat-stream: 1.6.2 + form-data: 2.5.1 + http-basic: 8.1.3 + http-response-object: 3.0.2 + promise: 8.2.0 + qs: 6.11.0 + dev: true + /tinypool/0.2.4: resolution: {integrity: sha512-Vs3rhkUH6Qq1t5bqtb816oT+HeJTXfwt2cbPH17sWHIYKTotQIFPk3tf2fgqRrVyMDVOc1EnPgzIxfIulXVzwQ==} engines: {node: '>=14.0.0'} @@ -3798,6 +4005,10 @@ packages: url-parse: 1.5.10 dev: true + /tr46/0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + dev: true + /tr46/3.0.0: resolution: {integrity: sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==} engines: {node: '>=12'} @@ -3870,6 +4081,10 @@ packages: engines: {node: '>=8'} dev: true + /typedarray/0.0.6: + resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} + dev: true + /typescript/4.7.4: resolution: {integrity: sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==} engines: {node: '>=4.2.0'} @@ -4052,7 +4267,7 @@ packages: fsevents: 2.3.2 dev: true - /vitest/0.21.1_jsdom@20.0.0+sass@1.54.8: + /vitest/0.21.1_v2uuuuu3zyb6zj2kmh3naav7aq: resolution: {integrity: sha512-WBIxuFmIDPuK47GO6Lu9eNeRMqHj/FWL3dk73OHH3eyPPWPiu+UB3QHLkLK2PEggCqJW4FaWoWg8R68S7p9+9Q==} engines: {node: '>=v14.16.0'} hasBin: true @@ -4082,6 +4297,7 @@ packages: '@types/node': 16.11.56 chai: 4.3.6 debug: 4.3.4 + happy-dom: 7.6.0 jsdom: 20.0.0 local-pkg: 0.4.2 tinypool: 0.2.4 @@ -4154,6 +4370,10 @@ packages: xml-name-validator: 4.0.0 dev: true + /webidl-conversions/3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + dev: true + /webidl-conversions/7.0.0: resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} engines: {node: '>=12'} @@ -4188,6 +4408,13 @@ packages: webidl-conversions: 7.0.0 dev: true + /whatwg-url/5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + dev: true + /which-boxed-primitive/1.0.2: resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} dependencies: @@ -4295,7 +4522,7 @@ packages: resolution: {directory: '', type: directory} id: 'file:' name: vue-selectable-items - version: 0.1.1 + version: 0.3.0 peerDependencies: vue: ^3.2.38 dependencies: diff --git a/tests/component.test.ts b/tests/component.test.ts new file mode 100644 index 0000000..fa3f6c2 --- /dev/null +++ b/tests/component.test.ts @@ -0,0 +1,193 @@ +import { beforeEach, describe, vi, expect, it } from 'vitest'; +import { item } from '../src/index'; +import { nanoid } from 'nanoid'; +import { createTextVNode, Slot } from 'vue'; +import { mount } from '@vue/test-utils'; +import SelectableItems, { Context } from '../src/Component'; + +const mockItems = [ + item({ + key: nanoid(), + meta: 'Item 1', + }), + item({ + key: nanoid(), + meta: 'Item 2', + }), + item({ + key: nanoid(), + meta: 'Item 3', + }), +]; + +describe('main tests', () => { + const onItemFocusSpy = vi.fn(); + const onItemUnfocusSpy = vi.fn(); + const onItemDOMFocusSpy = vi.fn(); + const onSelectSpy = vi.fn(); + + const mockFns = [onItemFocusSpy, onItemUnfocusSpy, onItemDOMFocusSpy, onSelectSpy]; + const resetFnMocks = () => mockFns.forEach((fn) => fn.mockReset()); + + const createDefaultWrapper = ({ + props = {}, + slots = {}, + }: { + props?: Partial['$props']>; + slots?: Record; + } = {}) => + mount(SelectableItems, { + props: { + items: mockItems, + onItemFocus: onItemFocusSpy, + onItemUnfocus: onItemUnfocusSpy, + onItemDOMFocus: onItemDOMFocusSpy, + onSelect: onSelectSpy, + ...props, + }, + slots: { + render: (meta) => createTextVNode(meta), + ...slots, + }, + }); + + beforeEach(() => { + document.head.innerHTML = ''; + document.body.innerHTML = ''; + resetFnMocks(); + }); + + it('should test events', async () => { + const wrapper = createDefaultWrapper(); + + expect(onItemFocusSpy).not.toHaveBeenCalled(); + expect(onItemUnfocusSpy).not.toHaveBeenCalled(); + expect(onItemDOMFocusSpy).not.toHaveBeenCalled(); + expect(onSelectSpy).not.toHaveBeenCalled(); + + const items = wrapper.findAll('.vue-selectable-items-item'); + + expect(items.length).toBe(3); + + await items[0].trigger('mouseenter'); + + expect(onItemFocusSpy).toHaveBeenCalled(); + expect(onItemUnfocusSpy).not.toHaveBeenCalled(); + expect(onItemDOMFocusSpy).not.toHaveBeenCalled(); + expect(onSelectSpy).not.toHaveBeenCalled(); + + resetFnMocks(); + + await items[1].trigger('mouseenter'); + + expect(onItemFocusSpy).toHaveBeenCalled(); + expect(onItemUnfocusSpy).toHaveBeenCalled(); + expect(onItemDOMFocusSpy).not.toHaveBeenCalled(); + expect(onSelectSpy).not.toHaveBeenCalled(); + + resetFnMocks(); + + await items[1].trigger('click'); + + expect(onItemFocusSpy).not.toHaveBeenCalled(); + expect(onItemUnfocusSpy).not.toHaveBeenCalled(); + expect(onItemDOMFocusSpy).not.toHaveBeenCalled(); + expect(onSelectSpy).toHaveBeenCalled(); + + resetFnMocks(); + + await items[1].trigger('focus'); + + expect(onItemFocusSpy).not.toHaveBeenCalled(); + expect(onItemUnfocusSpy).not.toHaveBeenCalled(); + expect(onItemDOMFocusSpy).toHaveBeenCalled(); + expect(onSelectSpy).not.toHaveBeenCalled(); + }); + + it('should test context', async () => { + let context: Context; + + const wrapper = createDefaultWrapper({ + props: { + setup: (ctx) => (context = ctx), + }, + }); + + expect(context).not.toBe(undefined); + + context.onFocus(onItemFocusSpy); + context.onDOMFocus(onItemDOMFocusSpy); + context.onSelect(onSelectSpy); + context.onUnfocus(onItemUnfocusSpy); + + context.focusNext(); + + expect(onItemFocusSpy).toHaveBeenCalled(); + expect(onItemUnfocusSpy).not.toHaveBeenCalled(); + expect(onItemDOMFocusSpy).not.toHaveBeenCalled(); + expect(onSelectSpy).not.toHaveBeenCalled(); + + context.selectFocusedElement(); + expect(onSelectSpy).toHaveBeenCalled(); + expect(context.getFocusedItemElement()).toBeInstanceOf(HTMLElement); + expect(context.getFocusedItemElement().classList.contains('vue-selectable-items-item')).toBe( + true, + ); + + const itemWrapper = wrapper.findAll('.vue-selectable-items-item'); + await itemWrapper[/* current item */ 0].trigger('focus'); + expect(onItemDOMFocusSpy).toHaveBeenCalled(); + + resetFnMocks(); + + context.clearFocus(); + expect(onItemUnfocusSpy).toHaveBeenCalled(); + context.focusNext(); + expect(context.getFocusedItem()?.key).toBe(mockItems[0].key); + context.focusPrevious(); + expect(context.getFocusedItem()?.key).toBe(mockItems[2].key); + context.focusNext(); + expect(context.getFocusedItem()?.key).toBe(mockItems[0].key); + + { + const items = [mockItems[1], mockItems[2]]; + await wrapper.setProps({ items }); + + expect(context.getFocusedItem()).toBe(undefined); + + context.setFocusByIndex(1); + expect(context.getFocusedItem()?.key).toBe(items[1].key); + } + + { + const itemOnSelectSpy = vi.fn(); + const items = [ + item({ + key: 'nanoid', + meta: 'Item -1', + disabled: true, + onSelect: itemOnSelectSpy, + }), + ...mockItems, + ]; + + await wrapper.setProps({ items }); + resetFnMocks(); + + context.focusNext(); + expect(context.getFocusedItem()?.key).toBe(items[1].key); + expect(itemOnSelectSpy).not.toHaveBeenCalled(); + + context.focusPrevious(); + expect(context.getFocusedItem()?.key).toBe(items[items.length - 1].key); + expect(itemOnSelectSpy).not.toHaveBeenCalled(); + + items[0].disabled = false; + await wrapper.setProps({ items }); + context.clearFocus(); + context.focusNext(); + context.selectFocusedElement(); + expect(itemOnSelectSpy).toHaveBeenCalled(); + } + }); +}); diff --git a/tsconfig.app.json b/tsconfig.app.json index 63d047b..bde9d0a 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -1,5 +1,5 @@ { "extends": "@vue/tsconfig/tsconfig.web.json", "include": ["env.d.ts", "src/**/*", "playground/**/*"], - "exclude": ["src/**/__tests__/*"], + "exclude": ["src/**/__tests__/*", "tests"] } diff --git a/tsconfig.vitest.json b/tsconfig.vitest.json index 06f6ddf..af80b6d 100644 --- a/tsconfig.vitest.json +++ b/tsconfig.vitest.json @@ -4,6 +4,6 @@ "compilerOptions": { "composite": true, "lib": [], - "types": ["node", "jsdom"], + "types": ["node"] } } diff --git a/vite.config.ts b/vite.config.ts index c72d029..685a92a 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,9 +1,9 @@ -import { defineConfig, type UserConfigExport } from 'vite'; import VueJSX from '@vitejs/plugin-vue-jsx'; import path from 'path'; import ViteDTS from 'vite-plugin-dts'; import Vue from '@vitejs/plugin-vue'; import Icons from 'unplugin-icons/vite'; +import { defineConfig, type UserConfigExport } from 'vitest/config'; const devConfig: UserConfigExport = { plugins: [ @@ -43,9 +43,20 @@ const prodConfig: UserConfigExport = { }, }; +const testConfig: UserConfigExport = { + plugins: [VueJSX(), Vue()], + test: { + environment: 'jsdom', + transformMode: { + web: [/\.[jt]sx$/], + }, + }, +}; + // https://vitejs.dev/config/ export default defineConfig(({ mode }) => { if (mode === 'production') return prodConfig; + if (mode === 'test') return testConfig; if (mode === 'development') return devConfig; if (mode === 'staging') return { ...devConfig, base: '/vue-selectable-items' };