diff --git a/package-lock.json b/package-lock.json index 9f104c32f..505fa462e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,18 +19,19 @@ "@biomejs/biome": "~2.3.11", "@custom-elements-manifest/analyzer": "^0.11.0", "@igniteui/material-icons-extended": "^3.1.0", - "@open-wc/testing": "^4.0.0", + "@open-wc/semantic-dom-diff": "^0.20.1", "@storybook/addon-a11y": "^10.1.11", "@storybook/addon-docs": "^10.1.11", "@storybook/addon-links": "^10.1.11", "@storybook/web-components-vite": "^10.1.11", - "@types/mocha": "^10.0.10", - "@web/dev-server-esbuild": "^1.0.4", - "@web/test-runner": "^0.20.2", - "@web/test-runner-playwright": "^0.11.1", + "@types/chai-dom": "^1.11.3", + "@vitest/browser-playwright": "^4.0.14", + "@vitest/coverage-v8": "^4.0.15", "autoprefixer": "^10.4.23", "browser-sync": "^3.0.4", "cem-plugin-expanded-types": "^1.4.0", + "chai-a11y-axe": "^1.5.0", + "chai-dom": "^1.12.1", "concurrently": "^9.2.1", "custom-element-jet-brains-integration": "^1.7.0", "custom-element-vs-code-integration": "^1.5.0", @@ -49,7 +50,6 @@ "prettier": "^3.7.4", "rimraf": "^6.1.2", "sass-embedded": "~1.93.3", - "sinon": "^21.0.1", "storybook": "^10.1.11", "stylelint": "^16.26.1", "stylelint-config-standard-scss": "^16.0.0", @@ -151,6 +151,16 @@ "node": ">=6.9.0" } }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@biomejs/biome": { "version": "2.3.11", "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.3.11.tgz", @@ -315,34 +325,34 @@ } }, "node_modules/@bufbuild/protobuf": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.10.1.tgz", - "integrity": "sha512-ckS3+vyJb5qGpEYv/s1OebUHDi/xSNtfgw1wqKZo7MR9F2z+qXr0q5XagafAG/9O0QPVIUfST0smluYSTpYFkg==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.10.2.tgz", + "integrity": "sha512-uFsRXwIGyu+r6AMdz+XijIIZJYpoWeYzILt5yZ2d3mCjQrWUTVpVD9WL/jZAbvp+Ed04rOhrsk7FiTcEDseB5A==", "dev": true, "license": "(Apache-2.0 AND BSD-3-Clause)" }, "node_modules/@cacheable/memory": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@cacheable/memory/-/memory-2.0.6.tgz", - "integrity": "sha512-7e8SScMocHxcAb8YhtkbMhGG+EKLRIficb1F5sjvhSYsWTZGxvg4KIDp8kgxnV2PUJ3ddPe6J9QESjKvBWRDkg==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@cacheable/memory/-/memory-2.0.7.tgz", + "integrity": "sha512-RbxnxAMf89Tp1dLhXMS7ceft/PGsDl1Ip7T20z5nZ+pwIAsQ1p2izPjVG69oCLv/jfQ7HDPHTWK0c9rcAWXN3A==", "dev": true, "license": "MIT", "dependencies": { - "@cacheable/utils": "^2.3.2", + "@cacheable/utils": "^2.3.3", "@keyv/bigmap": "^1.3.0", - "hookified": "^1.13.0", - "keyv": "^5.5.4" + "hookified": "^1.14.0", + "keyv": "^5.5.5" } }, "node_modules/@cacheable/utils": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/@cacheable/utils/-/utils-2.3.2.tgz", - "integrity": "sha512-8kGE2P+HjfY8FglaOiW+y8qxcaQAfAhVML+i66XJR3YX5FtyDqn6Txctr3K2FrbxLKixRRYYBWMbuGciOhYNDg==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/@cacheable/utils/-/utils-2.3.3.tgz", + "integrity": "sha512-JsXDL70gQ+1Vc2W/KUFfkAJzgb4puKwwKehNLuB+HrNKWf91O736kGfxn4KujXCCSuh6mRRL4XEB0PkAFjWS0A==", "dev": true, "license": "MIT", "dependencies": { - "hashery": "^1.2.0", - "keyv": "^5.5.4" + "hashery": "^1.3.0", + "keyv": "^5.5.5" } }, "node_modules/@csstools/css-parser-algorithms": { @@ -369,9 +379,9 @@ } }, "node_modules/@csstools/css-syntax-patches-for-csstree": { - "version": "1.0.20", - "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.20.tgz", - "integrity": "sha512-8BHsjXfSciZxjmHQOuVdW2b8WLUPts9a+mfL13/PzEviufUEW2xnvQuOlKs9dRBHgRqJ53SF/DUoK9+MZk72oQ==", + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.22.tgz", + "integrity": "sha512-qBcx6zYlhleiFfdtzkRgwNC7VVoAwfK76Vmsw5t+PbvtdknO9StgRk7ROvq9so1iqbdW4uLIDAsXRsTfUrIoOw==", "dev": true, "funding": [ { @@ -614,9 +624,9 @@ } }, "node_modules/@emnapi/core": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz", - "integrity": "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", + "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==", "dev": true, "license": "MIT", "optional": true, @@ -626,9 +636,9 @@ } }, "node_modules/@emnapi/runtime": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz", - "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", "dev": true, "license": "MIT", "optional": true, @@ -648,9 +658,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", "cpu": [ "ppc64" ], @@ -665,9 +675,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", "cpu": [ "arm" ], @@ -682,9 +692,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", "cpu": [ "arm64" ], @@ -699,9 +709,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", "cpu": [ "x64" ], @@ -716,9 +726,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", "cpu": [ "arm64" ], @@ -733,9 +743,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", "cpu": [ "x64" ], @@ -750,9 +760,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", "cpu": [ "arm64" ], @@ -767,9 +777,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", "cpu": [ "x64" ], @@ -784,9 +794,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", "cpu": [ "arm" ], @@ -801,9 +811,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", "cpu": [ "arm64" ], @@ -818,9 +828,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", "cpu": [ "ia32" ], @@ -835,9 +845,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", "cpu": [ "loong64" ], @@ -852,9 +862,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", "cpu": [ "mips64el" ], @@ -869,9 +879,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", "cpu": [ "ppc64" ], @@ -886,9 +896,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", "cpu": [ "riscv64" ], @@ -903,9 +913,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", "cpu": [ "s390x" ], @@ -920,9 +930,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", "cpu": [ "x64" ], @@ -937,9 +947,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", "cpu": [ "arm64" ], @@ -954,9 +964,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", "cpu": [ "x64" ], @@ -971,9 +981,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", "cpu": [ "arm64" ], @@ -988,9 +998,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", "cpu": [ "x64" ], @@ -1005,9 +1015,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", "cpu": [ "arm64" ], @@ -1022,9 +1032,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", "cpu": [ "x64" ], @@ -1039,9 +1049,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", "cpu": [ "arm64" ], @@ -1056,9 +1066,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", "cpu": [ "ia32" ], @@ -1073,9 +1083,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", "cpu": [ "x64" ], @@ -1089,16 +1099,6 @@ "node": ">=18" } }, - "node_modules/@esm-bundle/chai": { - "version": "4.3.4-fix.0", - "resolved": "https://registry.npmjs.org/@esm-bundle/chai/-/chai-4.3.4-fix.0.tgz", - "integrity": "sha512-26SKdM4uvDWlY8/OOOxSB1AqQWeBosCX3wRYUZO7enTAj03CtVxIiCimYVG2WpULcyV51qapK4qTovwkUr5Mlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/chai": "^4.2.12" - } - }, "node_modules/@floating-ui/core": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", @@ -1125,16 +1125,16 @@ "license": "MIT" }, "node_modules/@gerrit0/mini-shiki": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.19.0.tgz", - "integrity": "sha512-ZSlWfLvr8Nl0T4iA3FF/8VH8HivYF82xQts2DY0tJxZd4wtXJ8AA0nmdW9lmO4hlrh3f9xNwEPtOgqETPqKwDA==", + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.20.0.tgz", + "integrity": "sha512-Wa57i+bMpK6PGJZ1f2myxo3iO+K/kZikcyvH8NIqNNZhQUbDav7V9LQmWOXhf946mz5c1NZ19WMsGYiDKTryzQ==", "dev": true, "license": "MIT", "dependencies": { - "@shikijs/engine-oniguruma": "^3.19.0", - "@shikijs/langs": "^3.19.0", - "@shikijs/themes": "^3.19.0", - "@shikijs/types": "^3.19.0", + "@shikijs/engine-oniguruma": "^3.20.0", + "@shikijs/langs": "^3.20.0", + "@shikijs/themes": "^3.20.0", + "@shikijs/types": "^3.20.0", "@shikijs/vscode-textmate": "^10.0.2" } }, @@ -1257,9 +1257,9 @@ "license": "MIT" }, "node_modules/@lit-labs/ssr-dom-shim": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.4.0.tgz", - "integrity": "sha512-ficsEARKnmmW5njugNYKipTm4SFnbik7CXtoencDZzmzo/dQ+2Q0bgkzJuoJP20Aj0F+izzJjOqsnkd6F/o1bw==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.5.0.tgz", + "integrity": "sha512-HLomZXMmrCFHSRKESF5vklAKsDY7/fsT/ZhqCu3V0UoW/Qbv8wxmO4W9bx4KnCCF2Zak4yuk+AGraK/bPmI4kA==", "license": "BSD-3-Clause" }, "node_modules/@lit-labs/virtualizer": { @@ -1282,21 +1282,14 @@ } }, "node_modules/@lit/reactive-element": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.1.1.tgz", - "integrity": "sha512-N+dm5PAYdQ8e6UlywyyrgI2t++wFGXfHx+dSJ1oBrg6FAxUj40jId++EaRm80MKX5JnlH1sBsyZ5h0bcZKemCg==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.1.2.tgz", + "integrity": "sha512-pbCDiVMnne1lYUIaYNN5wrwQXDtHaYtg7YEFPeW+hws6U47WeFvISGUWekPGKWOP1ygrs0ef0o1VJMk1exos5A==", "license": "BSD-3-Clause", "dependencies": { - "@lit-labs/ssr-dom-shim": "^1.4.0" + "@lit-labs/ssr-dom-shim": "^1.5.0" } }, - "node_modules/@mdn/browser-compat-data": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-4.2.1.tgz", - "integrity": "sha512-EWUguj2kd7ldmrF9F+vI5hUOralPd+sdsUnYbRy33vZTuZkduC1shE9TtEMEjAQwyfyMb4ole5KtjF8MsnQOlA==", - "dev": true, - "license": "CC0-1.0" - }, "node_modules/@mdx-js/react": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.1.1.tgz", @@ -1316,9 +1309,9 @@ } }, "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.0.tgz", - "integrity": "sha512-Fq6DJW+Bb5jaWE69/qOE0D1TUN9+6uWhCeZpdnSBk14pjLcCWR7Q8n49PTSPHazM37JqrsdpEthXy2xn6jWWiA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", + "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==", "dev": true, "license": "MIT", "optional": true, @@ -1326,6 +1319,10 @@ "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" } }, "node_modules/@nodelib/fs.scandir": { @@ -1366,24 +1363,6 @@ "node": ">= 8" } }, - "node_modules/@open-wc/dedupe-mixin": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@open-wc/dedupe-mixin/-/dedupe-mixin-2.0.1.tgz", - "integrity": "sha512-+R4VxvceUxHAUJXJQipkkoV9fy10vNo+OnUnGKZnVmcwxMl460KLzytnUM4S35SI073R0yZQp9ra0MbPUwVcEA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@open-wc/scoped-elements": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@open-wc/scoped-elements/-/scoped-elements-3.0.6.tgz", - "integrity": "sha512-w1ayJaUUmBw8tALtqQ6cBueld+op+bufujzbrOdH0uCTXnSQkONYZzOH+9jyQ8auVgKLqcxZ8oU6SzfqQhQkPg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@open-wc/dedupe-mixin": "^2.0.0", - "lit": "^3.0.0" - } - }, "node_modules/@open-wc/semantic-dom-diff": { "version": "0.20.1", "resolved": "https://registry.npmjs.org/@open-wc/semantic-dom-diff/-/semantic-dom-diff-0.20.1.tgz", @@ -1395,37 +1374,10 @@ "@web/test-runner-commands": "^0.9.0" } }, - "node_modules/@open-wc/testing": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@open-wc/testing/-/testing-4.0.0.tgz", - "integrity": "sha512-KI70O0CJEpBWs3jrTju4BFCy7V/d4tFfYWkg8pMzncsDhD7TYNHLw5cy+s1FHXIgVFetnMDhPpwlKIPvtTQW7w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@esm-bundle/chai": "^4.3.4-fix.0", - "@open-wc/semantic-dom-diff": "^0.20.0", - "@open-wc/testing-helpers": "^3.0.0", - "@types/chai-dom": "^1.11.0", - "@types/sinon-chai": "^3.2.3", - "chai-a11y-axe": "^1.5.0" - } - }, - "node_modules/@open-wc/testing-helpers": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@open-wc/testing-helpers/-/testing-helpers-3.0.1.tgz", - "integrity": "sha512-hyNysSatbgT2FNxHJsS3rGKcLEo6+HwDFu1UQL6jcSQUabp/tj3PyX7UnXL3H5YGv0lJArdYLSnvjLnjn3O2fw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@open-wc/scoped-elements": "^3.0.2", - "lit": "^2.0.0 || ^3.0.0", - "lit-html": "^2.0.0 || ^3.0.0" - } - }, "node_modules/@oxc-resolver/binding-android-arm-eabi": { - "version": "11.15.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm-eabi/-/binding-android-arm-eabi-11.15.0.tgz", - "integrity": "sha512-Q+lWuFfq7whNelNJIP1dhXaVz4zO9Tu77GcQHyxDWh3MaCoO2Bisphgzmsh4ZoUe2zIchQh6OvQL99GlWHg9Tw==", + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm-eabi/-/binding-android-arm-eabi-11.16.2.tgz", + "integrity": "sha512-lVJbvydLQIDZHKUb6Zs9Rq80QVTQ9xdCQE30eC9/cjg4wsMoEOg65QZPymUAIVJotpUAWJD0XYcwE7ugfxx5kQ==", "cpu": [ "arm" ], @@ -1437,9 +1389,9 @@ ] }, "node_modules/@oxc-resolver/binding-android-arm64": { - "version": "11.15.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm64/-/binding-android-arm64-11.15.0.tgz", - "integrity": "sha512-vbdBttesHR0W1oJaxgWVTboyMUuu+VnPsHXJ6jrXf4czELzB6GIg5DrmlyhAmFBhjwov+yJH/DfTnHS+2sDgOw==", + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm64/-/binding-android-arm64-11.16.2.tgz", + "integrity": "sha512-fEk+g/g2rJ6LnBVPqeLcx+/alWZ/Db1UlXG+ZVivip0NdrnOzRL48PAmnxTMGOrLwsH1UDJkwY3wOjrrQltCqg==", "cpu": [ "arm64" ], @@ -1451,9 +1403,9 @@ ] }, "node_modules/@oxc-resolver/binding-darwin-arm64": { - "version": "11.15.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-arm64/-/binding-darwin-arm64-11.15.0.tgz", - "integrity": "sha512-R67lsOe1UzNjqVBCwCZX1rlItTsj/cVtBw4Uy19CvTicqEWvwaTn8t34zLD75LQwDDPCY3C8n7NbD+LIdw+ZoA==", + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-arm64/-/binding-darwin-arm64-11.16.2.tgz", + "integrity": "sha512-Pkbp1qi7kdUX6k3Fk1PvAg6p7ruwaWKg1AhOlDgrg2vLXjtv9ZHo7IAQN6kLj0W771dPJZWqNxoqTPacp2oYWA==", "cpu": [ "arm64" ], @@ -1465,9 +1417,9 @@ ] }, "node_modules/@oxc-resolver/binding-darwin-x64": { - "version": "11.15.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-x64/-/binding-darwin-x64-11.15.0.tgz", - "integrity": "sha512-77mya5F8WV0EtCxI0MlVZcqkYlaQpfNwl/tZlfg4jRsoLpFbaTeWv75hFm6TE84WULVlJtSgvf7DhoWBxp9+ZQ==", + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-x64/-/binding-darwin-x64-11.16.2.tgz", + "integrity": "sha512-FYCGcU1iSoPkADGLfQbuj0HWzS+0ItjDCt9PKtu2Hzy6T0dxO4Y1enKeCOxCweOlmLEkSxUlW5UPT4wvT3LnAg==", "cpu": [ "x64" ], @@ -1479,9 +1431,9 @@ ] }, "node_modules/@oxc-resolver/binding-freebsd-x64": { - "version": "11.15.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-freebsd-x64/-/binding-freebsd-x64-11.15.0.tgz", - "integrity": "sha512-X1Sz7m5PC+6D3KWIDXMUtux+0Imj6HfHGdBStSvgdI60OravzI1t83eyn6eN0LPTrynuPrUgjk7tOnOsBzSWHw==", + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-freebsd-x64/-/binding-freebsd-x64-11.16.2.tgz", + "integrity": "sha512-1zHCoK6fMcBjE54P2EG/z70rTjcRxvyKfvk4E/QVrWLxNahuGDFZIxoEoo4kGnnEcmPj41F0c2PkrQbqlpja5g==", "cpu": [ "x64" ], @@ -1493,9 +1445,9 @@ ] }, "node_modules/@oxc-resolver/binding-linux-arm-gnueabihf": { - "version": "11.15.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-11.15.0.tgz", - "integrity": "sha512-L1x/wCaIRre+18I4cH/lTqSAymlV0k4HqfSYNNuI9oeL28Ks86lI6O5VfYL6sxxWYgjuWB98gNGo7tq7d4GarQ==", + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-11.16.2.tgz", + "integrity": "sha512-+ucLYz8EO5FDp6kZ4o1uDmhoP+M98ysqiUW4hI3NmfiOJQWLrAzQjqaTdPfIOzlCXBU9IHp5Cgxu6wPjVb8dbA==", "cpu": [ "arm" ], @@ -1507,9 +1459,9 @@ ] }, "node_modules/@oxc-resolver/binding-linux-arm-musleabihf": { - "version": "11.15.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-11.15.0.tgz", - "integrity": "sha512-abGXd/zMGa0tH8nKlAXdOnRy4G7jZmkU0J85kMKWns161bxIgGn/j7zxqh3DKEW98wAzzU9GofZMJ0P5YCVPVw==", + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-11.16.2.tgz", + "integrity": "sha512-qq+TpNXyw1odDgoONRpMLzH4hzhwnEw55398dL8rhKGvvYbio71WrJ00jE+hGlEi7H1Gkl11KoPJRaPlRAVGPw==", "cpu": [ "arm" ], @@ -1521,9 +1473,9 @@ ] }, "node_modules/@oxc-resolver/binding-linux-arm64-gnu": { - "version": "11.15.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-11.15.0.tgz", - "integrity": "sha512-SVjjjtMW66Mza76PBGJLqB0KKyFTBnxmtDXLJPbL6ZPGSctcXVmujz7/WAc0rb9m2oV0cHQTtVjnq6orQnI/jg==", + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-11.16.2.tgz", + "integrity": "sha512-xlMh4gNtplNQEwuF5icm69udC7un0WyzT5ywOeHrPMEsghKnLjXok2wZgAA7ocTm9+JsI+nVXIQa5XO1x+HPQg==", "cpu": [ "arm64" ], @@ -1535,9 +1487,9 @@ ] }, "node_modules/@oxc-resolver/binding-linux-arm64-musl": { - "version": "11.15.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-musl/-/binding-linux-arm64-musl-11.15.0.tgz", - "integrity": "sha512-JDv2/AycPF2qgzEiDeMJCcSzKNDm3KxNg0KKWipoKEMDFqfM7LxNwwSVyAOGmrYlE4l3dg290hOMsr9xG7jv9g==", + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-musl/-/binding-linux-arm64-musl-11.16.2.tgz", + "integrity": "sha512-OZs33QTMi0xmHv/4P0+RAKXJTBk7UcMH5tpTaCytWRXls/DGaJ48jOHmriQGK2YwUqXl+oneuNyPOUO0obJ+Hg==", "cpu": [ "arm64" ], @@ -1549,9 +1501,9 @@ ] }, "node_modules/@oxc-resolver/binding-linux-ppc64-gnu": { - "version": "11.15.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-11.15.0.tgz", - "integrity": "sha512-zbu9FhvBLW4KJxo7ElFvZWbSt4vP685Qc/Gyk/Ns3g2gR9qh2qWXouH8PWySy+Ko/qJ42+HJCLg+ZNcxikERfg==", + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-11.16.2.tgz", + "integrity": "sha512-UVyuhaV32dJGtF6fDofOcBstg9JwB2Jfnjfb8jGlu3xcG+TsubHRhuTwQ6JZ1sColNT1nMxBiu7zdKUEZi1kwg==", "cpu": [ "ppc64" ], @@ -1563,9 +1515,9 @@ ] }, "node_modules/@oxc-resolver/binding-linux-riscv64-gnu": { - "version": "11.15.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-11.15.0.tgz", - "integrity": "sha512-Kfleehe6B09C2qCnyIU01xLFqFXCHI4ylzkicfX/89j+gNHh9xyNdpEvit88Kq6i5tTGdavVnM6DQfOE2qNtlg==", + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-11.16.2.tgz", + "integrity": "sha512-YZZS0yv2q5nE1uL/Fk4Y7m9018DSEmDNSG8oJzy1TJjA1jx5HL52hEPxi98XhU6OYhSO/vC1jdkJeE8TIHugug==", "cpu": [ "riscv64" ], @@ -1577,9 +1529,9 @@ ] }, "node_modules/@oxc-resolver/binding-linux-riscv64-musl": { - "version": "11.15.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-11.15.0.tgz", - "integrity": "sha512-J7LPiEt27Tpm8P+qURDwNc8q45+n+mWgyys4/V6r5A8v5gDentHRGUx3iVk5NxdKhgoGulrzQocPTZVosq25Eg==", + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-11.16.2.tgz", + "integrity": "sha512-9VYuypwtx4kt1lUcwJAH4dPmgJySh4/KxtAPdRoX2BTaZxVm/yEXHq0mnl/8SEarjzMvXKbf7Cm6UBgptm3DZw==", "cpu": [ "riscv64" ], @@ -1591,9 +1543,9 @@ ] }, "node_modules/@oxc-resolver/binding-linux-s390x-gnu": { - "version": "11.15.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-11.15.0.tgz", - "integrity": "sha512-+8/d2tAScPjVJNyqa7GPGnqleTB/XW9dZJQ2D/oIM3wpH3TG+DaFEXBbk4QFJ9K9AUGBhvQvWU2mQyhK/yYn3Q==", + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-11.16.2.tgz", + "integrity": "sha512-3gbwQ+xlL5gpyzgSDdC8B4qIM4mZaPDLaFOi3c/GV7CqIdVJc5EZXW4V3T6xwtPBOpXPXfqQLbhTnUD4SqwJtA==", "cpu": [ "s390x" ], @@ -1605,9 +1557,9 @@ ] }, "node_modules/@oxc-resolver/binding-linux-x64-gnu": { - "version": "11.15.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-gnu/-/binding-linux-x64-gnu-11.15.0.tgz", - "integrity": "sha512-xtvSzH7Nr5MCZI2FKImmOdTl9kzuQ51RPyLh451tvD2qnkg3BaqI9Ox78bTk57YJhlXPuxWSOL5aZhKAc9J6qg==", + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-gnu/-/binding-linux-x64-gnu-11.16.2.tgz", + "integrity": "sha512-m0WcK0j54tSwWa+hQaJMScZdWneqE7xixp/vpFqlkbhuKW9dRHykPAFvSYg1YJ3MJgu9ZzVNpYHhPKJiEQq57Q==", "cpu": [ "x64" ], @@ -1619,9 +1571,9 @@ ] }, "node_modules/@oxc-resolver/binding-linux-x64-musl": { - "version": "11.15.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-musl/-/binding-linux-x64-musl-11.15.0.tgz", - "integrity": "sha512-14YL1zuXj06+/tqsuUZuzL0T425WA/I4nSVN1kBXeC5WHxem6lQ+2HGvG+crjeJEqHgZUT62YIgj88W+8E7eyg==", + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-musl/-/binding-linux-x64-musl-11.16.2.tgz", + "integrity": "sha512-ZjUm3w96P2t47nWywGwj1A2mAVBI/8IoS7XHhcogWCfXnEI3M6NPIRQPYAZW4s5/u3u6w1uPtgOwffj2XIOb/g==", "cpu": [ "x64" ], @@ -1633,9 +1585,9 @@ ] }, "node_modules/@oxc-resolver/binding-openharmony-arm64": { - "version": "11.15.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-openharmony-arm64/-/binding-openharmony-arm64-11.15.0.tgz", - "integrity": "sha512-/7Qli+1Wk93coxnrQaU8ySlICYN8HsgyIrzqjgIkQEpI//9eUeaeIHZptNl2fMvBGeXa7k2QgLbRNaBRgpnvMw==", + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-openharmony-arm64/-/binding-openharmony-arm64-11.16.2.tgz", + "integrity": "sha512-OFVQ2x3VenTp13nIl6HcQ/7dmhFmM9dg2EjKfHcOtYfrVLQdNR6THFU7GkMdmc8DdY1zLUeilHwBIsyxv5hkwQ==", "cpu": [ "arm64" ], @@ -1647,9 +1599,9 @@ ] }, "node_modules/@oxc-resolver/binding-wasm32-wasi": { - "version": "11.15.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-wasm32-wasi/-/binding-wasm32-wasi-11.15.0.tgz", - "integrity": "sha512-q5rn2eIMQLuc/AVGR2rQKb2EVlgreATGG8xXg8f4XbbYCVgpxaq+dgMbiPStyNywW1MH8VU2T09UEm30UtOQvg==", + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-wasm32-wasi/-/binding-wasm32-wasi-11.16.2.tgz", + "integrity": "sha512-+O1sY3RrGyA2AqDnd3yaDCsqZqCblSTEpY7TbbaOaw0X7iIbGjjRLdrQk9StG3QSiZuBy9FdFwotIiSXtwvbAQ==", "cpu": [ "wasm32" ], @@ -1664,9 +1616,9 @@ } }, "node_modules/@oxc-resolver/binding-win32-arm64-msvc": { - "version": "11.15.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-11.15.0.tgz", - "integrity": "sha512-yCAh2RWjU/8wWTxQDgGPgzV9QBv0/Ojb5ej1c/58iOjyTuy/J1ZQtYi2SpULjKmwIxLJdTiCHpMilauWimE31w==", + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-11.16.2.tgz", + "integrity": "sha512-jMrMJL+fkx6xoSMFPOeyQ1ctTFjavWPOSZEKUY5PebDwQmC9cqEr4LhdTnGsOtFrWYLXlEU4xWeMdBoc/XKkOA==", "cpu": [ "arm64" ], @@ -1678,9 +1630,9 @@ ] }, "node_modules/@oxc-resolver/binding-win32-ia32-msvc": { - "version": "11.15.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-11.15.0.tgz", - "integrity": "sha512-lmXKb6lvA6M6QIbtYfgjd+AryJqExZVSY2bfECC18OPu7Lv1mHFF171Mai5l9hG3r4IhHPPIwT10EHoilSCYeA==", + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-11.16.2.tgz", + "integrity": "sha512-tl0xDA5dcQplG2yg2ZhgVT578dhRFafaCfyqMEAXq8KNpor85nJ53C3PLpfxD2NKzPioFgWEexNsjqRi+kW2Mg==", "cpu": [ "ia32" ], @@ -1692,9 +1644,9 @@ ] }, "node_modules/@oxc-resolver/binding-win32-x64-msvc": { - "version": "11.15.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-x64-msvc/-/binding-win32-x64-msvc-11.15.0.tgz", - "integrity": "sha512-HZsfne0s/tGOcJK9ZdTGxsNU2P/dH0Shf0jqrPvsC6wX0Wk+6AyhSpHFLQCnLOuFQiHHU0ePfM8iYsoJb5hHpQ==", + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-x64-msvc/-/binding-win32-x64-msvc-11.16.2.tgz", + "integrity": "sha512-M7z0xjYQq1HdJk2DxTSLMvRMyBSI4wn4FXGcVQBsbAihgXevAReqwMdb593nmCK/OiFwSNcOaGIzUvzyzQ+95w==", "cpu": [ "x64" ], @@ -2015,6 +1967,13 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "dev": true, + "license": "MIT" + }, "node_modules/@prettier/sync": { "version": "0.5.5", "resolved": "https://registry.npmjs.org/@prettier/sync/-/sync-0.5.5.tgz", @@ -2031,125 +1990,10 @@ "prettier": "*" } }, - "node_modules/@puppeteer/browsers": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.11.0.tgz", - "integrity": "sha512-n6oQX6mYkG8TRPuPXmbPidkUbsSRalhmaaVAQxvH1IkQy63cwsH+kOjB3e4cpCDHg0aSvsiX9bQ4s2VB6mGWUQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "debug": "^4.4.3", - "extract-zip": "^2.0.1", - "progress": "^2.0.3", - "proxy-agent": "^6.5.0", - "semver": "^7.7.3", - "tar-fs": "^3.1.1", - "yargs": "^17.7.2" - }, - "bin": { - "browsers": "lib/cjs/main-cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@puppeteer/browsers/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@puppeteer/browsers/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/plugin-node-resolve": { - "version": "15.3.1", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.1.tgz", - "integrity": "sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "@types/resolve": "1.20.2", - "deepmerge": "^4.2.2", - "is-module": "^1.0.0", - "resolve": "^1.22.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^2.78.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/pluginutils": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", - "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/pluginutils/node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/pluginutils/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", - "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.1.tgz", + "integrity": "sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==", "cpu": [ "arm" ], @@ -2161,9 +2005,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz", - "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.55.1.tgz", + "integrity": "sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==", "cpu": [ "arm64" ], @@ -2175,9 +2019,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz", - "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.55.1.tgz", + "integrity": "sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==", "cpu": [ "arm64" ], @@ -2189,9 +2033,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz", - "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.55.1.tgz", + "integrity": "sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==", "cpu": [ "x64" ], @@ -2203,9 +2047,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz", - "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.55.1.tgz", + "integrity": "sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==", "cpu": [ "arm64" ], @@ -2217,9 +2061,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz", - "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.55.1.tgz", + "integrity": "sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==", "cpu": [ "x64" ], @@ -2231,9 +2075,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz", - "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.55.1.tgz", + "integrity": "sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==", "cpu": [ "arm" ], @@ -2245,9 +2089,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz", - "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.55.1.tgz", + "integrity": "sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==", "cpu": [ "arm" ], @@ -2259,9 +2103,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", - "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.55.1.tgz", + "integrity": "sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==", "cpu": [ "arm64" ], @@ -2273,9 +2117,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz", - "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.55.1.tgz", + "integrity": "sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==", "cpu": [ "arm64" ], @@ -2287,9 +2131,23 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz", - "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.55.1.tgz", + "integrity": "sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.55.1.tgz", + "integrity": "sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==", "cpu": [ "loong64" ], @@ -2301,9 +2159,23 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz", - "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.55.1.tgz", + "integrity": "sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.55.1.tgz", + "integrity": "sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==", "cpu": [ "ppc64" ], @@ -2315,9 +2187,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz", - "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.55.1.tgz", + "integrity": "sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==", "cpu": [ "riscv64" ], @@ -2329,9 +2201,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz", - "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.55.1.tgz", + "integrity": "sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==", "cpu": [ "riscv64" ], @@ -2343,9 +2215,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz", - "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.55.1.tgz", + "integrity": "sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==", "cpu": [ "s390x" ], @@ -2357,9 +2229,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz", - "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.55.1.tgz", + "integrity": "sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==", "cpu": [ "x64" ], @@ -2371,9 +2243,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz", - "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.55.1.tgz", + "integrity": "sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==", "cpu": [ "x64" ], @@ -2384,10 +2256,24 @@ "linux" ] }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.55.1.tgz", + "integrity": "sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz", - "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.55.1.tgz", + "integrity": "sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==", "cpu": [ "arm64" ], @@ -2399,9 +2285,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz", - "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.55.1.tgz", + "integrity": "sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==", "cpu": [ "arm64" ], @@ -2413,9 +2299,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz", - "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.55.1.tgz", + "integrity": "sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==", "cpu": [ "ia32" ], @@ -2427,9 +2313,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz", - "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.55.1.tgz", + "integrity": "sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==", "cpu": [ "x64" ], @@ -2441,9 +2327,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz", - "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.55.1.tgz", + "integrity": "sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==", "cpu": [ "x64" ], @@ -2536,47 +2422,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.1.0.tgz", - "integrity": "sha512-cqfapCxwTGsrR80FEgOoPsTonoefMBY7dnUEbQ+GRcved0jvkJLzvX6F4WtN+HBqbPX/SiFsIRUp+IrCW/2I2w==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.1" - } - }, - "node_modules/@sinonjs/samsam": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.3.tgz", - "integrity": "sha512-hw6HbX+GyVZzmaYNh82Ecj1vdGZrqVIn/keDTg63IgAwiQPO+xCz99uG6Woqgb4tM0mUiFENKZ4cqd7IX94AXQ==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.1", - "type-detect": "^4.1.0" - } - }, - "node_modules/@sinonjs/samsam/node_modules/type-detect": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", - "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/@socket.io/component-emitter": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", @@ -2584,6 +2429,14 @@ "dev": true, "license": "MIT" }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/@storybook/addon-a11y": { "version": "10.1.11", "resolved": "https://registry.npmjs.org/@storybook/addon-a11y/-/addon-a11y-10.1.11.tgz", @@ -2837,13 +2690,6 @@ "@testing-library/dom": ">=7.21.4" } }, - "node_modules/@tootallnate/quickjs-emscripten": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", - "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", - "dev": true, - "license": "MIT" - }, "node_modules/@ts-graphviz/adapter": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@ts-graphviz/adapter/-/adapter-2.0.6.tgz", @@ -3009,13 +2855,6 @@ "@types/qs": "*" } }, - "node_modules/@types/command-line-args": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@types/command-line-args/-/command-line-args-5.2.3.tgz", - "integrity": "sha512-uv0aG6R0Y8WHZLTamZwtfsDLVRnOa+n+n5rEvFWL5Na5gZ8V2Teab/duDPFzIIIhs9qizDpcavCusCLJZu62Kw==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/connect": { "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", @@ -3210,17 +3049,10 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/mocha": { - "version": "10.0.10", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", - "integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/node": { - "version": "25.0.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.1.tgz", - "integrity": "sha512-czWPzKIAXucn9PtsttxmumiQ9N0ok9FrBwgRWrwmVLlp86BrMExzvXRLFYRJ+Ex3g6yqj+KuaxfX1JTgV2lpfg==", + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.3.tgz", + "integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==", "dev": true, "license": "MIT", "dependencies": { @@ -3259,13 +3091,6 @@ "csstype": "^3.2.2" } }, - "node_modules/@types/resolve": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", - "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/send": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", @@ -3287,34 +3112,6 @@ "@types/node": "*" } }, - "node_modules/@types/sinon": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-21.0.0.tgz", - "integrity": "sha512-+oHKZ0lTI+WVLxx1IbJDNmReQaIsQJjN2e7UUrJHEeByG7bFeKJYsv1E75JxTQ9QKJDp21bAa/0W2Xo4srsDnw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/sinonjs__fake-timers": "*" - } - }, - "node_modules/@types/sinon-chai": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/@types/sinon-chai/-/sinon-chai-3.2.12.tgz", - "integrity": "sha512-9y0Gflk3b0+NhQZ/oxGtaAJDvRywCa5sIyaVnounqLvmf93yBF4EgIRspePtkMs3Tr844nCclYMlcCNmLCvjuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/chai": "*", - "@types/sinon": "*" - } - }, - "node_modules/@types/sinonjs__fake-timers": { - "version": "15.0.1", - "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-15.0.1.tgz", - "integrity": "sha512-Ko2tjWJq8oozHzHV+reuvS5KYIRAokHnGbDwGh/J64LntgpbuylF74ipEL24HCyRjf9FOlBiBHWBR1RlVKsI1w==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/trusted-types": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", @@ -3337,26 +3134,15 @@ "@types/node": "*" } }, - "node_modules/@types/yauzl": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", - "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@typescript-eslint/project-service": { - "version": "8.49.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.49.0.tgz", - "integrity": "sha512-/wJN0/DKkmRUMXjZUXYZpD1NEQzQAAn9QWfGwo+Ai8gnzqH7tvqS7oNVdTjKqOcPyVIdZdyCMoqN66Ia789e7g==", + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.51.0.tgz", + "integrity": "sha512-Luv/GafO07Z7HpiI7qeEW5NW8HUtZI/fo/kE0YbtQEFpJRUuR0ajcWfCE5bnMvL7QQFrmT/odMe8QZww8X2nfQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.49.0", - "@typescript-eslint/types": "^8.49.0", + "@typescript-eslint/tsconfig-utils": "^8.51.0", + "@typescript-eslint/types": "^8.51.0", "debug": "^4.3.4" }, "engines": { @@ -3396,9 +3182,9 @@ "license": "MIT" }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.49.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.49.0.tgz", - "integrity": "sha512-8prixNi1/6nawsRYxet4YOhnbW+W9FK/bQPxsGB1D3ZrDzbJ5FXw5XmzxZv82X3B+ZccuSxo/X8q9nQ+mFecWA==", + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.51.0.tgz", + "integrity": "sha512-Qi5bSy/vuHeWyir2C8u/uqGMIlIDu8fuiYWv48ZGlZ/k+PRPHtaAu7erpc7p5bzw2WNNSniuxoMSO4Ar6V9OXw==", "dev": true, "license": "MIT", "engines": { @@ -3413,9 +3199,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.49.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.49.0.tgz", - "integrity": "sha512-e9k/fneezorUo6WShlQpMxXh8/8wfyc+biu6tnAqA81oWrEic0k21RHzP9uqqpyBBeBKu4T+Bsjy9/b8u7obXQ==", + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.51.0.tgz", + "integrity": "sha512-TizAvWYFM6sSscmEakjY3sPqGwxZRSywSsPEiuZF6d5GmGD9Gvlsv0f6N8FvAAA0CD06l3rIcWNbsN1e5F/9Ag==", "dev": true, "license": "MIT", "engines": { @@ -3427,21 +3213,21 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.49.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.49.0.tgz", - "integrity": "sha512-jrLdRuAbPfPIdYNppHJ/D0wN+wwNfJ32YTAm10eJVsFmrVpXQnDWBn8niCSMlWjvml8jsce5E/O+86IQtTbJWA==", + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.51.0.tgz", + "integrity": "sha512-1qNjGqFRmlq0VW5iVlcyHBbCjPB7y6SxpBkrbhNWMy/65ZoncXCEPJxkRZL8McrseNH6lFhaxCIaX+vBuFnRng==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.49.0", - "@typescript-eslint/tsconfig-utils": "8.49.0", - "@typescript-eslint/types": "8.49.0", - "@typescript-eslint/visitor-keys": "8.49.0", + "@typescript-eslint/project-service": "8.51.0", + "@typescript-eslint/tsconfig-utils": "8.51.0", + "@typescript-eslint/types": "8.51.0", + "@typescript-eslint/visitor-keys": "8.51.0", "debug": "^4.3.4", "minimatch": "^9.0.4", "semver": "^7.6.0", "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.1.0" + "ts-api-utils": "^2.2.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3506,13 +3292,13 @@ "license": "MIT" }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.49.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.49.0.tgz", - "integrity": "sha512-LlKaciDe3GmZFphXIc79THF/YYBugZ7FS1pO581E/edlVVNbZKDy93evqmrfQ9/Y4uN0vVhX4iuchq26mK/iiA==", + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.51.0.tgz", + "integrity": "sha512-mM/JRQOzhVN1ykejrvwnBRV3+7yTKK8tVANVN3o1O0t0v7o+jqdVu9crPy5Y9dov15TJk/FTIgoUGHrTOVL3Zg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.49.0", + "@typescript-eslint/types": "8.51.0", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -3530,6 +3316,159 @@ "license": "ISC", "peer": true }, + "node_modules/@vitest/browser": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-4.0.16.tgz", + "integrity": "sha512-t4toy8X/YTnjYEPoY0pbDBg3EvDPg1elCDrfc+VupPHwoN/5/FNQ8Z+xBYIaEnOE2vVEyKwqYBzZ9h9rJtZVcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/mocker": "4.0.16", + "@vitest/utils": "4.0.16", + "magic-string": "^0.30.21", + "pixelmatch": "7.1.0", + "pngjs": "^7.0.0", + "sirv": "^3.0.2", + "tinyrainbow": "^3.0.3", + "ws": "^8.18.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "vitest": "4.0.16" + } + }, + "node_modules/@vitest/browser-playwright": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/browser-playwright/-/browser-playwright-4.0.16.tgz", + "integrity": "sha512-I2Fy/ANdphi1yI46d15o0M1M4M0UJrUiVKkH5oKeRZZCdPg0fw/cfTKZzv9Ge9eobtJYp4BGblMzXdXH0vcl5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/browser": "4.0.16", + "@vitest/mocker": "4.0.16", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "playwright": "*", + "vitest": "4.0.16" + }, + "peerDependenciesMeta": { + "playwright": { + "optional": false + } + } + }, + "node_modules/@vitest/browser-playwright/node_modules/@vitest/mocker": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.16.tgz", + "integrity": "sha512-yb6k4AZxJTB+q9ycAvsoxGn+j/po0UaPgajllBgt1PzoMAAmJGYFdDk0uCcRcxb3BrME34I6u8gHZTQlkqSZpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.0.16", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/browser-playwright/node_modules/@vitest/spy": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.16.tgz", + "integrity": "sha512-4jIOWjKP0ZUaEmJm00E0cOBLU+5WE0BpeNr3XN6TEF05ltro6NJqHWxXD0kA8/Zc8Nh23AT8WQxwNG+WeROupw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/browser/node_modules/@vitest/mocker": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.16.tgz", + "integrity": "sha512-yb6k4AZxJTB+q9ycAvsoxGn+j/po0UaPgajllBgt1PzoMAAmJGYFdDk0uCcRcxb3BrME34I6u8gHZTQlkqSZpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.0.16", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/browser/node_modules/@vitest/spy": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.16.tgz", + "integrity": "sha512-4jIOWjKP0ZUaEmJm00E0cOBLU+5WE0BpeNr3XN6TEF05ltro6NJqHWxXD0kA8/Zc8Nh23AT8WQxwNG+WeROupw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/coverage-v8": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.16.tgz", + "integrity": "sha512-2rNdjEIsPRzsdu6/9Eq0AYAzYdpP6Bx9cje9tL3FE5XzXRQF1fNU9pe/1yE8fCrS0HD+fBtt6gLPh6LI57tX7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.0.16", + "ast-v8-to-istanbul": "^0.3.8", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.6", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.1", + "obug": "^2.1.1", + "std-env": "^3.10.0", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "4.0.16", + "vitest": "4.0.16" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, "node_modules/@vitest/expect": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", @@ -3558,6 +3497,61 @@ "assertion-error": "^2.0.1" } }, + "node_modules/@vitest/expect/node_modules/@vitest/pretty-format": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/expect/node_modules/@vitest/utils": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", + "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/expect/node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/expect/node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@vitest/mocker": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", @@ -3586,13 +3580,44 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", - "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.16.tgz", + "integrity": "sha512-eNCYNsSty9xJKi/UdVD8Ou16alu7AYiS2fCPRs0b1OdhJiV89buAXQLpTbe+X8V9L6qrs9CqyvU7OaAopJYPsA==", "dev": true, "license": "MIT", "dependencies": { - "tinyrainbow": "^2.0.0" + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.16.tgz", + "integrity": "sha512-VWEDm5Wv9xEo80ctjORcTQRJ539EGPB3Pb9ApvVRAY1U/WkHXmmYISqU5E79uCwcW7xYUV38gwZD+RV755fu3Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@vitest/utils": "4.0.16", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.16.tgz", + "integrity": "sha512-sf6NcrYhYBsSYefxnry+DR8n3UV4xWZwWxYbCJUt2YdvtqzSPR7VfGrY0zsv090DAbjFZsi7ZaMi1KnSRyK1XA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@vitest/pretty-format": "4.0.16", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" }, "funding": { "url": "https://opencollective.com/vitest" @@ -3612,15 +3637,14 @@ } }, "node_modules/@vitest/utils": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", - "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.16.tgz", + "integrity": "sha512-h8z9yYhV3e1LEfaQ3zdypIrnAg/9hguReGZoS7Gl0aBG5xgA410zBqECqmaF/+RkTggRsfnzc1XaAHA6bmUufA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.2.4", - "loupe": "^3.1.4", - "tinyrainbow": "^2.0.0" + "@vitest/pretty-format": "4.0.16", + "tinyrainbow": "^3.0.3" }, "funding": { "url": "https://opencollective.com/vitest" @@ -3634,15 +3658,15 @@ "license": "MIT" }, "node_modules/@vue/compiler-core": { - "version": "3.5.25", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.25.tgz", - "integrity": "sha512-vay5/oQJdsNHmliWoZfHPoVZZRmnSWhug0BYT34njkYTPqClh3DNWLkZNJBVSjsNMrg0CCrBfoKkjZQPM/QVUw==", + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.26.tgz", + "integrity": "sha512-vXyI5GMfuoBCnv5ucIT7jhHKl55Y477yxP6fc4eUswjP8FG3FFVFd41eNDArR+Uk3QKn2Z85NavjaxLxOC19/w==", "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.28.5", - "@vue/shared": "3.5.25", - "entities": "^4.5.0", + "@vue/shared": "3.5.26", + "entities": "^7.0.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } @@ -3655,28 +3679,28 @@ "license": "MIT" }, "node_modules/@vue/compiler-dom": { - "version": "3.5.25", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.25.tgz", - "integrity": "sha512-4We0OAcMZsKgYoGlMjzYvaoErltdFI2/25wqanuTu+S4gismOTRTBPi4IASOjxWdzIwrYSjnqONfKvuqkXzE2Q==", + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.26.tgz", + "integrity": "sha512-y1Tcd3eXs834QjswshSilCBnKGeQjQXB6PqFn/1nxcQw4pmG42G8lwz+FZPAZAby6gZeHSt/8LMPfZ4Rb+Bd/A==", "dev": true, "license": "MIT", "dependencies": { - "@vue/compiler-core": "3.5.25", - "@vue/shared": "3.5.25" + "@vue/compiler-core": "3.5.26", + "@vue/shared": "3.5.26" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.5.25", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.25.tgz", - "integrity": "sha512-PUgKp2rn8fFsI++lF2sO7gwO2d9Yj57Utr5yEsDf3GNaQcowCLKL7sf+LvVFvtJDXUp/03+dC6f2+LCv5aK1ag==", + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.26.tgz", + "integrity": "sha512-egp69qDTSEZcf4bGOSsprUr4xI73wfrY5oRs6GSgXFTiHrWj4Y3X5Ydtip9QMqiCMCPVwLglB9GBxXtTadJ3mA==", "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.28.5", - "@vue/compiler-core": "3.5.25", - "@vue/compiler-dom": "3.5.25", - "@vue/compiler-ssr": "3.5.25", - "@vue/shared": "3.5.25", + "@vue/compiler-core": "3.5.26", + "@vue/compiler-dom": "3.5.26", + "@vue/compiler-ssr": "3.5.26", + "@vue/shared": "3.5.26", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.6", @@ -3691,20 +3715,20 @@ "license": "MIT" }, "node_modules/@vue/compiler-ssr": { - "version": "3.5.25", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.25.tgz", - "integrity": "sha512-ritPSKLBcParnsKYi+GNtbdbrIE1mtuFEJ4U1sWeuOMlIziK5GtOL85t5RhsNy4uWIXPgk+OUdpnXiTdzn8o3A==", + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.26.tgz", + "integrity": "sha512-lZT9/Y0nSIRUPVvapFJEVDbEXruZh2IYHMk2zTtEgJSlP5gVOqeWXH54xDKAaFS4rTnDeDBQUYDtxKyoW9FwDw==", "dev": true, "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.25", - "@vue/shared": "3.5.25" + "@vue/compiler-dom": "3.5.26", + "@vue/shared": "3.5.26" } }, "node_modules/@vue/shared": { - "version": "3.5.25", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.25.tgz", - "integrity": "sha512-AbOPdQQnAnzs58H2FrrDxYj/TJfmeS2jdfEEhgiKINy+bnOANmVizIEgq1r+C5zsbs6l1CCQxtcj71rwNQ4jWg==", + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.26.tgz", + "integrity": "sha512-7Z6/y3uFI5PRoKeorTOSXKcDj0MSasfNNltcslbFrPpcw6aXRUALq4IfJlaTRspiWIUOEZbrpM+iQGmCOiWe4A==", "dev": true, "license": "MIT" }, @@ -3734,36 +3758,6 @@ "node": ">=10.0.0" } }, - "node_modules/@web/dev-server": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/@web/dev-server/-/dev-server-0.4.6.tgz", - "integrity": "sha512-jj/1bcElAy5EZet8m2CcUdzxT+CRvUjIXGh8Lt7vxtthkN9PzY9wlhWx/9WOs5iwlnG1oj0VGo6f/zvbPO0s9w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.12.11", - "@types/command-line-args": "^5.0.0", - "@web/config-loader": "^0.3.0", - "@web/dev-server-core": "^0.7.2", - "@web/dev-server-rollup": "^0.6.1", - "camelcase": "^6.2.0", - "command-line-args": "^5.1.1", - "command-line-usage": "^7.0.1", - "debounce": "^1.2.0", - "deepmerge": "^4.2.2", - "internal-ip": "^6.2.0", - "nanocolors": "^0.2.1", - "open": "^8.0.2", - "portfinder": "^1.0.32" - }, - "bin": { - "wds": "dist/bin.js", - "web-dev-server": "dist/bin.js" - }, - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/@web/dev-server-core": { "version": "0.7.5", "resolved": "https://registry.npmjs.org/@web/dev-server-core/-/dev-server-core-0.7.5.tgz", @@ -3824,49 +3818,26 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@web/dev-server-esbuild": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@web/dev-server-esbuild/-/dev-server-esbuild-1.0.4.tgz", - "integrity": "sha512-ia1LxBwwRiQBYhJ7/RtLenHyPjzle3SvTw3jOZaeGv8UGXVPOkQV8fR05caOtW/DPPZaZovNAybzRKVnNiYIZg==", + "node_modules/@web/dev-server-core/node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "dev": true, "license": "MIT", - "dependencies": { - "@mdn/browser-compat-data": "^4.0.0", - "@web/dev-server-core": "^0.7.4", - "esbuild": "^0.25.0", - "parse5": "^6.0.1", - "ua-parser-js": "^1.0.33" - }, "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@web/dev-server-rollup": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/@web/dev-server-rollup/-/dev-server-rollup-0.6.4.tgz", - "integrity": "sha512-sJZfTGCCrdku5xYnQQG51odGI092hKY9YFM0X3Z0tRY3iXKXcYRaLZrErw5KfCxr6g0JRuhe4BBhqXTA5Q2I3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/plugin-node-resolve": "^15.0.1", - "@web/dev-server-core": "^0.7.2", - "nanocolors": "^0.2.1", - "parse5": "^6.0.1", - "rollup": "^4.4.0", - "whatwg-url": "^14.0.0" + "node": ">=8.3.0" }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@web/dev-server/node_modules/@web/config-loader": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@web/config-loader/-/config-loader-0.3.3.tgz", - "integrity": "sha512-ilzeQzrPpPLWZhzFCV+4doxKDGm7oKVfdKpW9wiUNVgive34NSzCw+WzXTvjE4Jgr5CkyTDIObEmMrqQEjhT0g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.0.0" + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, "node_modules/@web/parse5-utils": { @@ -3883,54 +3854,6 @@ "node": ">=18.0.0" } }, - "node_modules/@web/test-runner": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@web/test-runner/-/test-runner-0.20.2.tgz", - "integrity": "sha512-zfEGYEDnS0EI8qgoWFjmtkIXhqP15W40NW3dCaKtbxj5eU0a7E53f3GV/tZGD0GlZKF8d4Fyw+AFrwOJU9Z4GA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@web/browser-logs": "^0.4.0", - "@web/config-loader": "^0.3.0", - "@web/dev-server": "^0.4.0", - "@web/test-runner-chrome": "^0.18.1", - "@web/test-runner-commands": "^0.9.0", - "@web/test-runner-core": "^0.13.0", - "@web/test-runner-mocha": "^0.9.0", - "camelcase": "^6.2.0", - "command-line-args": "^5.1.1", - "command-line-usage": "^7.0.1", - "convert-source-map": "^2.0.0", - "diff": "^5.0.0", - "globby": "^11.0.1", - "nanocolors": "^0.2.1", - "portfinder": "^1.0.32", - "source-map": "^0.7.3" - }, - "bin": { - "web-test-runner": "dist/bin.js", - "wtr": "dist/bin.js" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@web/test-runner-chrome": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/@web/test-runner-chrome/-/test-runner-chrome-0.18.1.tgz", - "integrity": "sha512-eO6ctCaqSguGM6G3cFobGHnrEs9wlv9Juj/Akyr4XLjeEMTheNULdvOXw9Bygi+QC/ir/0snMmt+/YKnfy8rYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@web/test-runner-core": "^0.13.0", - "@web/test-runner-coverage-v8": "^0.8.0", - "chrome-launcher": "^0.15.0", - "puppeteer-core": "^24.0.0" - }, - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/@web/test-runner-commands": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/@web/test-runner-commands/-/test-runner-commands-0.9.0.tgz", @@ -4054,102 +3977,6 @@ "node": ">=8" } }, - "node_modules/@web/test-runner-coverage-v8": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@web/test-runner-coverage-v8/-/test-runner-coverage-v8-0.8.0.tgz", - "integrity": "sha512-PskiucYpjUtgNfR2zF2AWqWwjXL7H3WW/SnCAYmzUrtob7X9o/+BjdyZ4wKbOxWWSbJO4lEdGIDLu+8X2Xw+lA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@web/test-runner-core": "^0.13.0", - "istanbul-lib-coverage": "^3.0.0", - "lru-cache": "^8.0.4", - "picomatch": "^2.2.2", - "v8-to-istanbul": "^9.0.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@web/test-runner-mocha": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@web/test-runner-mocha/-/test-runner-mocha-0.9.0.tgz", - "integrity": "sha512-ZL9F6FXd0DBQvo/h/+mSfzFTSRVxzV9st/AHhpgABtUtV/AIpVE9to6+xdkpu6827kwjezdpuadPfg+PlrBWqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@web/test-runner-core": "^0.13.0" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@web/test-runner-playwright": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/@web/test-runner-playwright/-/test-runner-playwright-0.11.1.tgz", - "integrity": "sha512-l9tmX0LtBqMaKAApS4WshpB87A/M8sOHZyfCobSGuYqnREgz5rqQpX314yx+4fwHXLLTa5N64mTrawsYkLjliw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@web/test-runner-core": "^0.13.0", - "@web/test-runner-coverage-v8": "^0.8.0", - "playwright": "^1.53.0" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@web/test-runner/node_modules/@web/config-loader": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@web/config-loader/-/config-loader-0.3.3.tgz", - "integrity": "sha512-ilzeQzrPpPLWZhzFCV+4doxKDGm7oKVfdKpW9wiUNVgive34NSzCw+WzXTvjE4Jgr5CkyTDIObEmMrqQEjhT0g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@web/test-runner/node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "license": "MIT", - "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/@web/test-runner/node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/@web/test-runner/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/@xn-sakina/rml-darwin-arm64": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/@xn-sakina/rml-darwin-arm64/-/rml-darwin-arm64-2.6.0.tgz", @@ -4330,16 +4157,6 @@ "node": ">=0.4.0" } }, - "node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, "node_modules/ajv": { "version": "8.17.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", @@ -4485,9 +4302,9 @@ } }, "node_modules/ast-types": { - "version": "0.13.4", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", - "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", + "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", "dev": true, "license": "MIT", "dependencies": { @@ -4497,6 +4314,25 @@ "node": ">=4" } }, + "node_modules/ast-v8-to-istanbul": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.10.tgz", + "integrity": "sha512-p4K7vMz2ZSk3wN8l5o3y2bJAoZXT3VuJI5OLTATY/01CYWumWvwkUw0SqDBnNq6IiTO3qDa1eSQDibAV8g7XOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^9.0.1" + } + }, + "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, "node_modules/astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", @@ -4508,11 +4344,14 @@ } }, "node_modules/async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "lodash": "^4.17.14" + } }, "node_modules/async-each-series": { "version": "0.1.1", @@ -4571,21 +4410,6 @@ "node": ">=4" } }, - "node_modules/b4a": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz", - "integrity": "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==", - "dev": true, - "license": "Apache-2.0", - "peerDependencies": { - "react-native-b4a": "*" - }, - "peerDependenciesMeta": { - "react-native-b4a": { - "optional": true - } - } - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -4593,103 +4417,6 @@ "dev": true, "license": "MIT" }, - "node_modules/bare-events": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", - "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", - "dev": true, - "license": "Apache-2.0", - "peerDependencies": { - "bare-abort-controller": "*" - }, - "peerDependenciesMeta": { - "bare-abort-controller": { - "optional": true - } - } - }, - "node_modules/bare-fs": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.2.tgz", - "integrity": "sha512-veTnRzkb6aPHOvSKIOy60KzURfBdUflr5VReI+NSaPL6xf+XLdONQgZgpYvUuZLVQ8dCqxpBAudaOM1+KpAUxw==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "bare-events": "^2.5.4", - "bare-path": "^3.0.0", - "bare-stream": "^2.6.4", - "bare-url": "^2.2.2", - "fast-fifo": "^1.3.2" - }, - "engines": { - "bare": ">=1.16.0" - }, - "peerDependencies": { - "bare-buffer": "*" - }, - "peerDependenciesMeta": { - "bare-buffer": { - "optional": true - } - } - }, - "node_modules/bare-os": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.2.tgz", - "integrity": "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "engines": { - "bare": ">=1.14.0" - } - }, - "node_modules/bare-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", - "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "bare-os": "^3.0.1" - } - }, - "node_modules/bare-stream": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.7.0.tgz", - "integrity": "sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "streamx": "^2.21.0" - }, - "peerDependencies": { - "bare-buffer": "*", - "bare-events": "*" - }, - "peerDependenciesMeta": { - "bare-buffer": { - "optional": true - }, - "bare-events": { - "optional": true - } - } - }, - "node_modules/bare-url": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.3.2.tgz", - "integrity": "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "bare-path": "^3.0.0" - } - }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -4722,25 +4449,15 @@ } }, "node_modules/baseline-browser-mapping": { - "version": "2.9.6", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.6.tgz", - "integrity": "sha512-v9BVVpOTLB59C9E7aSnmIF8h7qRsFpx+A2nugVMTszEOMcfjlZMsXRm4LF23I3Z9AJxc8ANpIvzbzONoX9VJlg==", + "version": "2.9.11", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz", + "integrity": "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==", "dev": true, "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.js" } }, - "node_modules/basic-ftp": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", - "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/batch": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", @@ -4944,16 +4661,6 @@ "dev": true, "license": "MIT/X11" }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, "node_modules/bundle-name": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", @@ -4995,17 +4702,17 @@ } }, "node_modules/cacheable": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-2.3.0.tgz", - "integrity": "sha512-HHiAvOBmlcR2f3SQ7kdlYD8+AUJG+wlFZ/Ze8tl1Vzvz0MdOh8IYA/EFU4ve8t1/sZ0j4MGi7ST5MoTwHessQA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-2.3.1.tgz", + "integrity": "sha512-yr+FSHWn1ZUou5LkULX/S+jhfgfnLbuKQjE40tyEd4fxGZVMbBL5ifno0J0OauykS8UiCSgHi+DV/YD+rjFxFg==", "dev": true, "license": "MIT", "dependencies": { "@cacheable/memory": "^2.0.6", "@cacheable/utils": "^2.3.2", - "hookified": "^1.13.0", - "keyv": "^5.5.4", - "qified": "^0.5.2" + "hookified": "^1.14.0", + "keyv": "^5.5.5", + "qified": "^0.5.3" } }, "node_modules/call-bind-apply-helpers": { @@ -5049,23 +4756,10 @@ "node": ">=6" } }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/caniuse-lite": { - "version": "1.0.30001760", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001760.tgz", - "integrity": "sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==", + "version": "1.0.30001762", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001762.tgz", + "integrity": "sha512-PxZwGNvH7Ak8WX5iXzoK1KPZttBXNPuaOvI2ZYU7NrlM+d9Ov+TUvlLOBNGzVXAntMSMMlJPd+jY6ovrVjSmUw==", "dev": true, "funding": [ { @@ -5103,18 +4797,12 @@ "license": "MIT" }, "node_modules/chai": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", - "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", "dev": true, "license": "MIT", - "dependencies": { - "assertion-error": "^2.0.1", - "check-error": "^2.1.1", - "deep-eql": "^5.0.1", - "loupe": "^3.1.0", - "pathval": "^2.0.0" - }, + "peer": true, "engines": { "node": ">=18" } @@ -5129,7 +4817,20 @@ "axe-core": "^4.3.3" } }, - "node_modules/chalk": { + "node_modules/chai-dom": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/chai-dom/-/chai-dom-1.12.1.tgz", + "integrity": "sha512-tvz+D0PJue2VHXRec3udgP/OeeXBiePU3VH6JhEnHQJYzvNzR2nUvEykA9dXVS76JvaUENSOYH8Ufr0kZSnlCQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.12.0" + }, + "peerDependencies": { + "chai": ">= 3" + } + }, + "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", @@ -5146,22 +4847,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/chalk-template": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz", - "integrity": "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/chalk-template?sponsor=1" - } - }, "node_modules/character-entities-html4": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", @@ -5216,46 +4901,6 @@ "fsevents": "~2.3.2" } }, - "node_modules/chrome-launcher": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz", - "integrity": "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/node": "*", - "escape-string-regexp": "^4.0.0", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^1.0.0" - }, - "bin": { - "print-chrome-path": "bin/print-chrome-path.js" - }, - "engines": { - "node": ">=12.13.0" - } - }, - "node_modules/chromium-bidi": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-11.0.0.tgz", - "integrity": "sha512-cM3DI+OOb89T3wO8cpPSro80Q9eKYJ7hGVXoGS3GkDPxnYSqiv+6xwpIf6XERyJ9Tdsl09hmNmY94BkgZdVekw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "mitt": "^3.0.1", - "zod": "^3.24.1" - }, - "peerDependencies": { - "devtools-protocol": "*" - } - }, - "node_modules/chromium-bidi/node_modules/mitt": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", - "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", - "dev": true, - "license": "MIT" - }, "node_modules/cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -5463,32 +5108,6 @@ "node": ">=4.0.0" } }, - "node_modules/command-line-usage": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-7.0.3.tgz", - "integrity": "sha512-PqMLy5+YGwhMh1wS04mVG44oqDsgyLRSKJBdOo1bnYhMKBW65gZF1dRp2OZRhiTjgUHljy99qkO7bsctLaw35Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-back": "^6.2.2", - "chalk-template": "^0.4.0", - "table-layout": "^4.1.0", - "typical": "^7.1.1" - }, - "engines": { - "node": ">=12.20.0" - } - }, - "node_modules/command-line-usage/node_modules/typical": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/typical/-/typical-7.3.0.tgz", - "integrity": "sha512-ya4mg/30vm+DOWfBg4YK3j2WD6TWtRkCbasOJr40CseYENzCUby/7rIvXA99JGsQHeNxLbnXdyLLxKSv3tauFw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.17" - } - }, "node_modules/commander": { "version": "14.0.2", "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", @@ -5779,16 +5398,6 @@ "dev": true, "license": "BSD-3-Clause" }, - "node_modules/data-uri-to-buffer": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", - "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, "node_modules/debounce": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", @@ -5833,16 +5442,6 @@ "node": ">=4.0.0" } }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/default-browser": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.4.0.tgz", @@ -5919,21 +5518,6 @@ "node": ">=8" } }, - "node_modules/degenerator": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", - "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ast-types": "^0.13.4", - "escodegen": "^2.1.0", - "esprima": "^4.0.1" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", @@ -6191,13 +5775,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/devtools-protocol": { - "version": "0.0.1534754", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1534754.tgz", - "integrity": "sha512-26T91cV5dbOYnXdJi5qQHoTtUoNEqwkHcAyu/IKtjIAxiEqPMrDiRkDOPWVsGfNZGmlQVHQbZRSjD8sxagWVsQ==", - "dev": true, - "license": "BSD-3-Clause" - }, "node_modules/didyoumean2": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/didyoumean2/-/didyoumean2-4.1.0.tgz", @@ -6213,16 +5790,6 @@ "node": ">=10.13" } }, - "node_modules/diff": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", - "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -6324,20 +5891,10 @@ "node": ">= 0.8" } }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, "node_modules/engine.io": { - "version": "6.6.4", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz", - "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==", + "version": "6.6.5", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.5.tgz", + "integrity": "sha512-2RZdgEbXmp5+dVbRm0P7HQUImZpICccJy7rN7Tv+SFa55pH+lxnuw6/K1ZxxBfHoYpSkHLAO92oa8O4SwFXA2A==", "dev": true, "license": "MIT", "dependencies": { @@ -6347,32 +5904,32 @@ "base64id": "2.0.0", "cookie": "~0.7.2", "cors": "~2.8.5", - "debug": "~4.3.1", + "debug": "~4.4.1", "engine.io-parser": "~5.2.1", - "ws": "~8.17.1" + "ws": "~8.18.3" }, "engines": { "node": ">=10.2.0" } }, "node_modules/engine.io-client": { - "version": "6.6.3", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz", - "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==", + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.4.tgz", + "integrity": "sha512-+kjUJnZGwzewFDw951CDWcwj35vMNf2fcj7xQWOctq1F2i1jkDdVvdFG9kM/BEChymCH36KgjnW0NsL58JYRxw==", "dev": true, "license": "MIT", "dependencies": { "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.1", + "debug": "~4.4.1", "engine.io-parser": "~5.2.1", - "ws": "~8.17.1", + "ws": "~8.18.3", "xmlhttprequest-ssl": "~2.1.1" } }, "node_modules/engine.io-client/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "license": "MIT", "dependencies": { @@ -6394,28 +5951,6 @@ "dev": true, "license": "MIT" }, - "node_modules/engine.io-client/node_modules/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/engine.io-parser": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", @@ -6427,9 +5962,9 @@ } }, "node_modules/engine.io/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "license": "MIT", "dependencies": { @@ -6451,28 +5986,6 @@ "dev": true, "license": "MIT" }, - "node_modules/engine.io/node_modules/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/enhanced-resolve": { "version": "5.18.4", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz", @@ -6488,9 +6001,9 @@ } }, "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.0.tgz", + "integrity": "sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -6581,9 +6094,9 @@ } }, "node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -6594,32 +6107,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" } }, "node_modules/escalade": { @@ -6640,16 +6153,13 @@ "license": "MIT" }, "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==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true, "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=0.8.0" } }, "node_modules/escodegen": { @@ -6759,16 +6269,6 @@ "dev": true, "license": "MIT" }, - "node_modules/events-universal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", - "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "bare-events": "^2.7.0" - } - }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -6793,68 +6293,17 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "bin": { - "extract-zip": "cli.js" - }, - "engines": { - "node": ">= 10.17.0" - }, - "optionalDependencies": { - "@types/yauzl": "^2.9.1" - } - }, - "node_modules/extract-zip/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/extract-zip/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", "dev": true, - "license": "MIT", - "dependencies": { - "pump": "^3.0.0" - }, + "license": "Apache-2.0", + "peer": true, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=12.0.0" } }, - "node_modules/extract-zip/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -6869,13 +6318,6 @@ "dev": true, "license": "Apache-2.0" }, - "node_modules/fast-fifo": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", - "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", - "dev": true, - "license": "MIT" - }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", @@ -6921,25 +6363,15 @@ } }, "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", "dev": true, "license": "ISC", "dependencies": { "reusify": "^1.0.4" } }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "pend": "~1.2.0" - } - }, "node_modules/file-entry-cache": { "version": "11.1.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-11.1.1.tgz", @@ -7255,60 +6687,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/get-uri": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", - "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "basic-ftp": "^5.0.2", - "data-uri-to-buffer": "^6.0.2", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/get-uri/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/get-uri/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" }, "engines": { "node": "*" @@ -7475,13 +6867,13 @@ } }, "node_modules/hashery": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/hashery/-/hashery-1.3.0.tgz", - "integrity": "sha512-fWltioiy5zsSAs9ouEnvhsVJeAXRybGCNNv0lvzpzNOSDbULXRy7ivFWwCCv4I5Am6kSo75hmbsCduOoc2/K4w==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/hashery/-/hashery-1.4.0.tgz", + "integrity": "sha512-Wn2i1In6XFxl8Az55kkgnFRiAlIAushzh26PTjL2AKtQcEfXrcLa7Hn5QOWGZEf3LU057P9TwwZjFyxfS1VuvQ==", "dev": true, "license": "MIT", "dependencies": { - "hookified": "^1.13.0" + "hookified": "^1.14.0" }, "engines": { "node": ">=20" @@ -7539,9 +6931,9 @@ } }, "node_modules/hookified": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.14.0.tgz", - "integrity": "sha512-pi1ynXIMFx/uIIwpWJ/5CEtOHLGtnUB0WhGeeYT+fKcQ+WCQbm3/rrkAXnpfph++PgepNqPdTC2WTj8A6k6zoQ==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.15.0.tgz", + "integrity": "sha512-51w+ZZGt7Zw5q7rM3nC4t3aLn/xvKDETsXqMczndvwyVQhAHfUmUuFBRFcos8Iyebtk7OAE9dL26wFNzZVVOkw==", "dev": true, "license": "MIT" }, @@ -7642,84 +7034,6 @@ "node": ">=8.0.0" } }, - "node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/http-proxy-agent/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/http-proxy-agent/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/https-proxy-agent/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/https-proxy-agent/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -7947,16 +7261,6 @@ "url": "https://github.com/sindresorhus/internal-ip?sponsor=1" } }, - "node_modules/ip-address": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", - "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, "node_modules/ip-regex": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", @@ -8146,13 +7450,6 @@ "node": ">=8" } }, - "node_modules/is-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", - "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", - "dev": true, - "license": "MIT" - }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -8339,6 +7636,46 @@ "node": ">=10" } }, + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, "node_modules/istanbul-reports": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", @@ -8646,17 +7983,6 @@ "node": ">=6" } }, - "node_modules/lighthouse-logger": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz", - "integrity": "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "debug": "^2.6.9", - "marky": "^1.2.2" - } - }, "node_modules/limiter": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", @@ -8934,16 +8260,6 @@ "dev": true, "license": "MIT" }, - "node_modules/lit-analyzer/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/lit-analyzer/node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -8975,20 +8291,20 @@ } }, "node_modules/lit-element": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.2.1.tgz", - "integrity": "sha512-WGAWRGzirAgyphK2urmYOV72tlvnxw7YfyLDgQ+OZnM9vQQBQnumQ7jUJe6unEzwGU3ahFOjuz1iz1jjrpCPuw==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.2.2.tgz", + "integrity": "sha512-aFKhNToWxoyhkNDmWZwEva2SlQia+jfG0fjIWV//YeTaWrVnOxD89dPKfigCUspXFmjzOEUQpOkejH5Ly6sG0w==", "license": "BSD-3-Clause", "dependencies": { - "@lit-labs/ssr-dom-shim": "^1.4.0", + "@lit-labs/ssr-dom-shim": "^1.5.0", "@lit/reactive-element": "^2.1.0", "lit-html": "^3.3.0" } }, "node_modules/lit-html": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.3.1.tgz", - "integrity": "sha512-S9hbyDu/vs1qNrithiNyeyv64c9yqiW9l+DBgI18fL+MTvOtWoFR0FWiyq1TxaYef5wNlpEmzlXoBlZEO+WjoA==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.3.2.tgz", + "integrity": "sha512-Qy9hU88zcmaxBXcc10ZpdK7cOLXvXpRoBxERdtqV9QOrfpMZZ6pSYP91LhpPtap3sFMUiL7Tw2RImbe0Al2/kw==", "license": "BSD-3-Clause", "dependencies": { "@types/trusted-types": "^2.0.2" @@ -9242,6 +8558,18 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/magicast": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.1.tgz", + "integrity": "sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "source-map-js": "^1.2.1" + } + }, "node_modules/make-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", @@ -9286,6 +8614,19 @@ "markdown-it": "bin/markdown-it.mjs" } }, + "node_modules/markdown-it/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/marked": { "version": "17.0.1", "resolved": "https://registry.npmjs.org/marked/-/marked-17.0.1.tgz", @@ -9310,13 +8651,6 @@ "shiki": ">=1.0.0" } }, - "node_modules/marky": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/marky/-/marky-1.3.0.tgz", - "integrity": "sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==", - "dev": true, - "license": "Apache-2.0" - }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -9690,6 +9024,16 @@ "node": ">=18" } }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -9746,16 +9090,6 @@ "node": ">= 0.6" } }, - "node_modules/netmask": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", - "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/node-addon-api": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", @@ -9840,6 +9174,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, "node_modules/on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", @@ -9970,35 +9315,35 @@ } }, "node_modules/oxc-resolver": { - "version": "11.15.0", - "resolved": "https://registry.npmjs.org/oxc-resolver/-/oxc-resolver-11.15.0.tgz", - "integrity": "sha512-Hk2J8QMYwmIO9XTCUiOH00+Xk2/+aBxRUnhrSlANDyCnLYc32R1WSIq1sU2yEdlqd53FfMpPEpnBYIKQMzliJw==", + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/oxc-resolver/-/oxc-resolver-11.16.2.tgz", + "integrity": "sha512-Uy76u47vwhhF7VAmVY61Srn+ouiOobf45MU9vGct9GD2ARy6hKoqEElyHDB0L+4JOM6VLuZ431KiLwyjI/A21g==", "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/Boshen" }, "optionalDependencies": { - "@oxc-resolver/binding-android-arm-eabi": "11.15.0", - "@oxc-resolver/binding-android-arm64": "11.15.0", - "@oxc-resolver/binding-darwin-arm64": "11.15.0", - "@oxc-resolver/binding-darwin-x64": "11.15.0", - "@oxc-resolver/binding-freebsd-x64": "11.15.0", - "@oxc-resolver/binding-linux-arm-gnueabihf": "11.15.0", - "@oxc-resolver/binding-linux-arm-musleabihf": "11.15.0", - "@oxc-resolver/binding-linux-arm64-gnu": "11.15.0", - "@oxc-resolver/binding-linux-arm64-musl": "11.15.0", - "@oxc-resolver/binding-linux-ppc64-gnu": "11.15.0", - "@oxc-resolver/binding-linux-riscv64-gnu": "11.15.0", - "@oxc-resolver/binding-linux-riscv64-musl": "11.15.0", - "@oxc-resolver/binding-linux-s390x-gnu": "11.15.0", - "@oxc-resolver/binding-linux-x64-gnu": "11.15.0", - "@oxc-resolver/binding-linux-x64-musl": "11.15.0", - "@oxc-resolver/binding-openharmony-arm64": "11.15.0", - "@oxc-resolver/binding-wasm32-wasi": "11.15.0", - "@oxc-resolver/binding-win32-arm64-msvc": "11.15.0", - "@oxc-resolver/binding-win32-ia32-msvc": "11.15.0", - "@oxc-resolver/binding-win32-x64-msvc": "11.15.0" + "@oxc-resolver/binding-android-arm-eabi": "11.16.2", + "@oxc-resolver/binding-android-arm64": "11.16.2", + "@oxc-resolver/binding-darwin-arm64": "11.16.2", + "@oxc-resolver/binding-darwin-x64": "11.16.2", + "@oxc-resolver/binding-freebsd-x64": "11.16.2", + "@oxc-resolver/binding-linux-arm-gnueabihf": "11.16.2", + "@oxc-resolver/binding-linux-arm-musleabihf": "11.16.2", + "@oxc-resolver/binding-linux-arm64-gnu": "11.16.2", + "@oxc-resolver/binding-linux-arm64-musl": "11.16.2", + "@oxc-resolver/binding-linux-ppc64-gnu": "11.16.2", + "@oxc-resolver/binding-linux-riscv64-gnu": "11.16.2", + "@oxc-resolver/binding-linux-riscv64-musl": "11.16.2", + "@oxc-resolver/binding-linux-s390x-gnu": "11.16.2", + "@oxc-resolver/binding-linux-x64-gnu": "11.16.2", + "@oxc-resolver/binding-linux-x64-musl": "11.16.2", + "@oxc-resolver/binding-openharmony-arm64": "11.16.2", + "@oxc-resolver/binding-wasm32-wasi": "11.16.2", + "@oxc-resolver/binding-win32-arm64-msvc": "11.16.2", + "@oxc-resolver/binding-win32-ia32-msvc": "11.16.2", + "@oxc-resolver/binding-win32-x64-msvc": "11.16.2" } }, "node_modules/p-event": { @@ -10040,65 +9385,6 @@ "node": ">=8" } }, - "node_modules/pac-proxy-agent": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", - "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@tootallnate/quickjs-emscripten": "^0.23.0", - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "get-uri": "^6.0.1", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.6", - "pac-resolver": "^7.0.1", - "socks-proxy-agent": "^8.0.5" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/pac-proxy-agent/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/pac-proxy-agent/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/pac-resolver": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", - "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", - "dev": true, - "license": "MIT", - "dependencies": { - "degenerator": "^5.0.0", - "netmask": "^2.0.2" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -10229,6 +9515,14 @@ "node": ">=8" } }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/pathval": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", @@ -10239,13 +9533,6 @@ "node": ">= 14.16" } }, - "node_modules/pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "dev": true, - "license": "MIT" - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -10279,6 +9566,19 @@ "node": ">=0.10" } }, + "node_modules/pixelmatch": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-7.1.0.tgz", + "integrity": "sha512-1wrVzJ2STrpmONHKBy228LM1b84msXDUoAzVEl0R8Mz4Ce6EPr+IVtxm8+yvrqLYMHswREkjYFaMxnyGnaY3Ng==", + "dev": true, + "license": "ISC", + "dependencies": { + "pngjs": "^7.0.0" + }, + "bin": { + "pixelmatch": "bin/pixelmatch" + } + }, "node_modules/playwright": { "version": "1.57.0", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz", @@ -10336,45 +9636,16 @@ "node": ">=4" } }, - "node_modules/portfinder": { - "version": "1.0.38", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.38.tgz", - "integrity": "sha512-rEwq/ZHlJIKw++XtLAO8PPuOQA/zaPJOZJ37BVuN97nLpMJeuDVLVGRwbFoBgLudgdTMP2hdRJP++H+8QOA3vg==", - "dev": true, - "license": "MIT", - "dependencies": { - "async": "^3.2.6", - "debug": "^4.3.6" - }, - "engines": { - "node": ">= 10.12" - } - }, - "node_modules/portfinder/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "node_modules/pngjs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", + "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", "dev": true, "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=14.19.0" } }, - "node_modules/portfinder/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, "node_modules/portscanner": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/portscanner/-/portscanner-2.2.0.tgz", @@ -10390,16 +9661,6 @@ "npm": ">=1.0.0" } }, - "node_modules/portscanner/node_modules/async": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "lodash": "^4.17.14" - } - }, "node_modules/postcss": { "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", @@ -10593,9 +9854,9 @@ } }, "node_modules/prettier-linter-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.1.tgz", + "integrity": "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==", "dev": true, "license": "MIT", "dependencies": { @@ -10651,16 +9912,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/property-information": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", @@ -10672,89 +9923,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/proxy-agent": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", - "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "http-proxy-agent": "^7.0.1", - "https-proxy-agent": "^7.0.6", - "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.1.0", - "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.5" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/proxy-agent/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/proxy-agent/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/proxy-agent/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true, - "license": "MIT" - }, - "node_modules/pump": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", - "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", - "dev": true, - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/punycode.js": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", @@ -10765,72 +9933,6 @@ "node": ">=6" } }, - "node_modules/puppeteer-core": { - "version": "24.33.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.33.0.tgz", - "integrity": "sha512-tPTxVg+Qdj/8av4cy6szv3GlhxeOoNhiiMZ955fjxQyvPQE/6DjCa6ZyF/x0WJrlgBZtaLSP8TQgJb7FdLDXXA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@puppeteer/browsers": "2.11.0", - "chromium-bidi": "11.0.0", - "debug": "^4.4.3", - "devtools-protocol": "0.0.1534754", - "typed-query-selector": "^2.12.0", - "webdriver-bidi-protocol": "0.3.9", - "ws": "^8.18.3" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/puppeteer-core/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/puppeteer-core/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/puppeteer-core/node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/qified": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/qified/-/qified-0.5.3.tgz", @@ -11037,19 +10139,6 @@ "node": ">= 4" } }, - "node_modules/recast/node_modules/ast-types": { - "version": "0.16.1", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", - "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", - "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/recast/node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -11361,9 +10450,9 @@ } }, "node_modules/rollup": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz", - "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.1.tgz", + "integrity": "sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==", "dev": true, "license": "MIT", "dependencies": { @@ -11377,28 +10466,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.53.3", - "@rollup/rollup-android-arm64": "4.53.3", - "@rollup/rollup-darwin-arm64": "4.53.3", - "@rollup/rollup-darwin-x64": "4.53.3", - "@rollup/rollup-freebsd-arm64": "4.53.3", - "@rollup/rollup-freebsd-x64": "4.53.3", - "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", - "@rollup/rollup-linux-arm-musleabihf": "4.53.3", - "@rollup/rollup-linux-arm64-gnu": "4.53.3", - "@rollup/rollup-linux-arm64-musl": "4.53.3", - "@rollup/rollup-linux-loong64-gnu": "4.53.3", - "@rollup/rollup-linux-ppc64-gnu": "4.53.3", - "@rollup/rollup-linux-riscv64-gnu": "4.53.3", - "@rollup/rollup-linux-riscv64-musl": "4.53.3", - "@rollup/rollup-linux-s390x-gnu": "4.53.3", - "@rollup/rollup-linux-x64-gnu": "4.53.3", - "@rollup/rollup-linux-x64-musl": "4.53.3", - "@rollup/rollup-openharmony-arm64": "4.53.3", - "@rollup/rollup-win32-arm64-msvc": "4.53.3", - "@rollup/rollup-win32-ia32-msvc": "4.53.3", - "@rollup/rollup-win32-x64-gnu": "4.53.3", - "@rollup/rollup-win32-x64-msvc": "4.53.3", + "@rollup/rollup-android-arm-eabi": "4.55.1", + "@rollup/rollup-android-arm64": "4.55.1", + "@rollup/rollup-darwin-arm64": "4.55.1", + "@rollup/rollup-darwin-x64": "4.55.1", + "@rollup/rollup-freebsd-arm64": "4.55.1", + "@rollup/rollup-freebsd-x64": "4.55.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.55.1", + "@rollup/rollup-linux-arm-musleabihf": "4.55.1", + "@rollup/rollup-linux-arm64-gnu": "4.55.1", + "@rollup/rollup-linux-arm64-musl": "4.55.1", + "@rollup/rollup-linux-loong64-gnu": "4.55.1", + "@rollup/rollup-linux-loong64-musl": "4.55.1", + "@rollup/rollup-linux-ppc64-gnu": "4.55.1", + "@rollup/rollup-linux-ppc64-musl": "4.55.1", + "@rollup/rollup-linux-riscv64-gnu": "4.55.1", + "@rollup/rollup-linux-riscv64-musl": "4.55.1", + "@rollup/rollup-linux-s390x-gnu": "4.55.1", + "@rollup/rollup-linux-x64-gnu": "4.55.1", + "@rollup/rollup-linux-x64-musl": "4.55.1", + "@rollup/rollup-openbsd-x64": "4.55.1", + "@rollup/rollup-openharmony-arm64": "4.55.1", + "@rollup/rollup-win32-arm64-msvc": "4.55.1", + "@rollup/rollup-win32-ia32-msvc": "4.55.1", + "@rollup/rollup-win32-x64-gnu": "4.55.1", + "@rollup/rollup-win32-x64-msvc": "4.55.1", "fsevents": "~2.3.2" } }, @@ -12006,45 +11098,59 @@ } }, "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", "dev": true, "license": "MIT", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "range-parser": "~1.2.1", - "statuses": "2.0.1" + "statuses": "~2.0.2" }, "engines": { "node": ">= 0.8.0" } }, - "node_modules/send/node_modules/http-errors": { + "node_modules/send/node_modules/encodeurl": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "dev": true, "license": "MIT", "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, "engines": { "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/send/node_modules/ms": { @@ -12068,9 +11174,9 @@ } }, "node_modules/send/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "dev": true, "license": "MIT", "engines": { @@ -12147,16 +11253,16 @@ } }, "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", "dev": true, "license": "MIT", "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.19.0" + "send": "~0.19.1" }, "engines": { "node": ">= 0.8.0" @@ -12315,6 +11421,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC", + "peer": true + }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -12322,32 +11436,19 @@ "dev": true, "license": "ISC" }, - "node_modules/sinon": { - "version": "21.0.1", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-21.0.1.tgz", - "integrity": "sha512-Z0NVCW45W8Mg5oC/27/+fCqIHFnW8kpkFOq0j9XJIev4Ld0mKmERaZv5DMLAb9fGCevjKwaEeIQz5+MBXfZcDw==", + "node_modules/sirv": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", + "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", "dependencies": { - "@sinonjs/commons": "^3.0.1", - "@sinonjs/fake-timers": "^15.1.0", - "@sinonjs/samsam": "^8.0.3", - "diff": "^8.0.2", - "supports-color": "^7.2.0" + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/sinon" - } - }, - "node_modules/sinon/node_modules/diff": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.2.tgz", - "integrity": "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==", - "dev": true, - "license": "BSD-3-Clause", "engines": { - "node": ">=0.3.1" + "node": ">=18" } }, "node_modules/slash": { @@ -12393,28 +11494,17 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, "node_modules/socket.io": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", - "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.3.tgz", + "integrity": "sha512-2Dd78bqzzjE6KPkD5fHZmDAKRNe3J15q+YHDrIsy9WEkqttc7GY+kT9OBLSMaPbQaEd0x1BjcmtMtXkfpc+T5A==", "dev": true, "license": "MIT", "dependencies": { "accepts": "~1.3.4", "base64id": "~2.0.0", "cors": "~2.8.5", - "debug": "~4.3.2", + "debug": "~4.4.1", "engine.io": "~6.6.0", "socket.io-adapter": "~2.5.2", "socket.io-parser": "~4.2.4" @@ -12424,20 +11514,20 @@ } }, "node_modules/socket.io-adapter": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", - "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.6.tgz", + "integrity": "sha512-DkkO/dz7MGln0dHn5bmN3pPy+JmywNICWrJqVWiVOyvXjWQFIv9c2h24JrQLLFJ2aQVQf/Cvl1vblnd4r2apLQ==", "dev": true, "license": "MIT", "dependencies": { - "debug": "~4.3.4", - "ws": "~8.17.1" + "debug": "~4.4.1", + "ws": "~8.18.3" } }, "node_modules/socket.io-adapter/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "license": "MIT", "dependencies": { @@ -12459,37 +11549,15 @@ "dev": true, "license": "MIT" }, - "node_modules/socket.io-adapter/node_modules/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/socket.io-client": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz", - "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.3.tgz", + "integrity": "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==", "dev": true, "license": "MIT", "dependencies": { "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.2", + "debug": "~4.4.1", "engine.io-client": "~6.6.1", "socket.io-parser": "~4.2.4" }, @@ -12498,9 +11566,9 @@ } }, "node_modules/socket.io-client/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "license": "MIT", "dependencies": { @@ -12523,48 +11591,23 @@ "license": "MIT" }, "node_modules/socket.io-parser": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", - "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.5.tgz", + "integrity": "sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ==", "dev": true, "license": "MIT", "dependencies": { "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.1" + "debug": "~4.4.1" }, "engines": { "node": ">=10.0.0" } }, "node_modules/socket.io-parser/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/socket.io-parser/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/socket.io/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "license": "MIT", "dependencies": { @@ -12579,44 +11622,14 @@ } } }, - "node_modules/socket.io/node_modules/ms": { + "node_modules/socket.io-parser/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, "license": "MIT" }, - "node_modules/socks": { - "version": "2.8.7", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", - "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ip-address": "^10.0.1", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks-proxy-agent": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", - "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "socks": "^2.8.3" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/socks-proxy-agent/node_modules/debug": { + "node_modules/socket.io/node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", @@ -12634,7 +11647,7 @@ } } }, - "node_modules/socks-proxy-agent/node_modules/ms": { + "node_modules/socket.io/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", @@ -12672,6 +11685,14 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/statuses": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", @@ -12682,6 +11703,13 @@ "node": ">= 0.6" } }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, "node_modules/storybook": { "version": "10.1.11", "resolved": "https://registry.npmjs.org/storybook/-/storybook-10.1.11.tgz", @@ -12750,28 +11778,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/storybook/node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/stream-throttle": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/stream-throttle/-/stream-throttle-0.1.3.tgz", @@ -12806,18 +11812,6 @@ "any-promise": "^1.1.0" } }, - "node_modules/streamx": { - "version": "2.23.0", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", - "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "events-universal": "^1.0.0", - "fast-fifo": "^1.3.2", - "text-decoder": "^1.1.0" - } - }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -13397,20 +12391,6 @@ "node": ">=10.0.0" } }, - "node_modules/table-layout": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-4.1.1.tgz", - "integrity": "sha512-iK5/YhZxq5GO5z8wb0bY1317uDF3Zjpha0QFFLA8/trAoiLbQD0HUbMesEaxyzUgDxi2QlcbM8IvqOlEjgoXBA==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-back": "^6.2.2", - "wordwrapjs": "^5.1.0" - }, - "engines": { - "node": ">=12.17" - } - }, "node_modules/table/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -13468,50 +12448,32 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/tar-fs": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.1.tgz", - "integrity": "sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==", + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", "dev": true, - "license": "MIT", - "dependencies": { - "pump": "^3.0.0", - "tar-stream": "^3.1.5" - }, - "optionalDependencies": { - "bare-fs": "^4.0.1", - "bare-path": "^3.0.0" - } + "license": "MIT" }, - "node_modules/tar-stream": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", - "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", "dev": true, "license": "MIT", - "dependencies": { - "b4a": "^1.6.4", - "fast-fifo": "^1.2.0", - "streamx": "^2.15.0" - } + "peer": true }, - "node_modules/text-decoder": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", - "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", + "node_modules/tinyexec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "b4a": "^1.6.4" + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" } }, - "node_modules/tiny-invariant": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", - "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", - "dev": true, - "license": "MIT" - }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -13561,9 +12523,9 @@ } }, "node_modules/tinyrainbow": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", - "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", "dev": true, "license": "MIT", "engines": { @@ -13603,17 +12565,14 @@ "node": ">=0.6" } }, - "node_modules/tr46": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", - "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", "dev": true, "license": "MIT", - "dependencies": { - "punycode": "^2.3.1" - }, "engines": { - "node": ">=18" + "node": ">=6" } }, "node_modules/tree-kill": { @@ -13638,9 +12597,9 @@ } }, "node_modules/ts-api-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", "dev": true, "license": "MIT", "engines": { @@ -13735,16 +12694,6 @@ "node": ">=0.6.x" } }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/type-fest": { "version": "0.21.3", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", @@ -13772,13 +12721,6 @@ "node": ">= 0.6" } }, - "node_modules/typed-query-selector": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz", - "integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==", - "dev": true, - "license": "MIT" - }, "node_modules/typedoc": { "version": "0.28.15", "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.28.15.tgz", @@ -14078,9 +13020,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.2.tgz", - "integrity": "sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "dev": true, "funding": [ { @@ -14135,21 +13077,6 @@ "node": ">= 0.4.0" } }, - "node_modules/v8-to-istanbul": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", - "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", - "dev": true, - "license": "ISC", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^2.0.0" - }, - "engines": { - "node": ">=10.12.0" - } - }, "node_modules/varint": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", @@ -14272,514 +13199,193 @@ } } }, - "node_modules/vite/node_modules/@esbuild/aix-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", - "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", - "cpu": [ - "ppc64" - ], + "node_modules/vite/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "aix" - ], "engines": { - "node": ">=18" + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, - "node_modules/vite/node_modules/@esbuild/android-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", - "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", - "cpu": [ - "arm" - ], + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], "engines": { - "node": ">=18" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/vite/node_modules/@esbuild/android-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", - "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", - "cpu": [ - "arm64" - ], + "node_modules/vitest": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.16.tgz", + "integrity": "sha512-E4t7DJ9pESL6E3I8nFjPa4xGUd3PmiWDLsDztS2qXSJWfHtbQnwAWylaBvSNY48I3vr8PTqIZlyK8TE3V3CA4Q==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "peer": true, + "dependencies": { + "@vitest/expect": "4.0.16", + "@vitest/mocker": "4.0.16", + "@vitest/pretty-format": "4.0.16", + "@vitest/runner": "4.0.16", + "@vitest/snapshot": "4.0.16", + "@vitest/spy": "4.0.16", + "@vitest/utils": "4.0.16", + "es-module-lexer": "^1.7.0", + "expect-type": "^1.2.2", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^3.10.0", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, "engines": { - "node": ">=18" + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.0.16", + "@vitest/browser-preview": "4.0.16", + "@vitest/browser-webdriverio": "4.0.16", + "@vitest/ui": "4.0.16", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } } }, - "node_modules/vite/node_modules/@esbuild/android-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", - "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", - "cpu": [ - "x64" - ], + "node_modules/vitest/node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" + "peer": true, + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" } }, - "node_modules/vite/node_modules/@esbuild/darwin-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", - "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/darwin-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", - "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", - "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/freebsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", - "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", - "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", - "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", - "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-loong64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", - "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-mips64el": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", - "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", - "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-riscv64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", - "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-s390x": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", - "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", - "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", - "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/netbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", - "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", - "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/openbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", - "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", - "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/sunos-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", - "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", - "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", - "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", - "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/esbuild": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", - "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "node_modules/vitest/node_modules/@vitest/expect": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.16.tgz", + "integrity": "sha512-eshqULT2It7McaJkQGLkPjPjNph+uevROGuIMJdG3V+0BSR2w9u6J9Lwu+E8cK5TETlfou8GRijhafIMhXsimA==", "dev": true, - "hasInstallScript": true, "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" + "peer": true, + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.0.16", + "@vitest/utils": "4.0.16", + "chai": "^6.2.1", + "tinyrainbow": "^3.0.3" }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.2", - "@esbuild/android-arm": "0.27.2", - "@esbuild/android-arm64": "0.27.2", - "@esbuild/android-x64": "0.27.2", - "@esbuild/darwin-arm64": "0.27.2", - "@esbuild/darwin-x64": "0.27.2", - "@esbuild/freebsd-arm64": "0.27.2", - "@esbuild/freebsd-x64": "0.27.2", - "@esbuild/linux-arm": "0.27.2", - "@esbuild/linux-arm64": "0.27.2", - "@esbuild/linux-ia32": "0.27.2", - "@esbuild/linux-loong64": "0.27.2", - "@esbuild/linux-mips64el": "0.27.2", - "@esbuild/linux-ppc64": "0.27.2", - "@esbuild/linux-riscv64": "0.27.2", - "@esbuild/linux-s390x": "0.27.2", - "@esbuild/linux-x64": "0.27.2", - "@esbuild/netbsd-arm64": "0.27.2", - "@esbuild/netbsd-x64": "0.27.2", - "@esbuild/openbsd-arm64": "0.27.2", - "@esbuild/openbsd-x64": "0.27.2", - "@esbuild/openharmony-arm64": "0.27.2", - "@esbuild/sunos-x64": "0.27.2", - "@esbuild/win32-arm64": "0.27.2", - "@esbuild/win32-ia32": "0.27.2", - "@esbuild/win32-x64": "0.27.2" + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/vite/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "node_modules/vitest/node_modules/@vitest/mocker": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.16.tgz", + "integrity": "sha512-yb6k4AZxJTB+q9ycAvsoxGn+j/po0UaPgajllBgt1PzoMAAmJGYFdDk0uCcRcxb3BrME34I6u8gHZTQlkqSZpg==", "dev": true, "license": "MIT", - "engines": { - "node": ">=12.0.0" + "peer": true, + "dependencies": { + "@vitest/spy": "4.0.16", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "picomatch": "^3 || ^4" + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0-0" }, "peerDependenciesMeta": { - "picomatch": { + "msw": { + "optional": true + }, + "vite": { "optional": true } } }, - "node_modules/vite/node_modules/picomatch": { + "node_modules/vitest/node_modules/@vitest/spy": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.16.tgz", + "integrity": "sha512-4jIOWjKP0ZUaEmJm00E0cOBLU+5WE0BpeNr3XN6TEF05ltro6NJqHWxXD0kA8/Zc8Nh23AT8WQxwNG+WeROupw==", + "dev": true, + "license": "MIT", + "peer": true, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest/node_modules/picomatch": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -14892,23 +13498,6 @@ "node": ">=14.17" } }, - "node_modules/webdriver-bidi-protocol": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.3.9.tgz", - "integrity": "sha512-uIYvlRQ0PwtZR1EzHlTMol1G0lAlmOe6wPykF9a77AK3bkpvZHzIVxRE2ThOx5vjy2zISe0zhwf5rzuUfbo1PQ==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - } - }, "node_modules/webpack-virtual-modules": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", @@ -14916,20 +13505,6 @@ "dev": true, "license": "MIT" }, - "node_modules/whatwg-url": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", - "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "tr46": "^5.1.0", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -14946,14 +13521,22 @@ "node": ">= 8" } }, - "node_modules/wordwrapjs": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-5.1.1.tgz", - "integrity": "sha512-0yweIbkINJodk27gX9LBGMzyQdBDan3s/dEAiwBOj+Mf0PPyWL6/rikalkv8EeD0E8jm4o5RXEOrFTP3NXbhJg==", + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", "dev": true, "license": "MIT", + "peer": true, + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, "engines": { - "node": ">=12.17" + "node": ">=8" } }, "node_modules/wrap-ansi": { @@ -15076,17 +13659,17 @@ } }, "node_modules/ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", "dev": true, "license": "MIT", "engines": { - "node": ">=8.3.0" + "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { "bufferutil": { @@ -15218,17 +13801,6 @@ "node": ">=8" } }, - "node_modules/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, "node_modules/ylru": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/ylru/-/ylru-1.4.0.tgz", @@ -15239,16 +13811,6 @@ "node": ">= 4.0.0" } }, - "node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", diff --git a/package.json b/package.json index aed6bb6ef..a4257f309 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "url": "https://github.com/IgniteUI/igniteui-webcomponents/issues" }, "scripts": { + "vitest:debug": "vitest --no-file-parallelism --isolate false", "start": "npm run storybook", "build:publish": "npm run cem && node scripts/build.mjs", "cem": "cem analyze --config cem.config.mjs", @@ -40,8 +41,9 @@ "lint:styles": "stylelint \"src/**/*.scss\"", "format": "biome check --fix && prettier \"**/*.ts\" --write --ignore-path .gitignore", "release": "node scripts/gen-changelog.mjs", - "test": "npm run build:styles && wtr --coverage", - "test:watch": "npm run build:styles && concurrently -k -r \"npm:watch-scss\" \"wtr --watch\"", + "test": "npm run build:styles && vitest --watch false --coverage", + "test:watch": "npm run build:styles && concurrently -k -r \"npm:watch-scss\" \"vitest\"", + "test:debug": "npm run build:styles && concurrently -k -r \"npm:watch-scss\" \"npm:vitest:debug\"", "storybook": "npm run build:styles && concurrently -k -r \"npm run cem:watch\" \"npm:watch-scss\" \"npm:watch-meta\" \"storybook dev -p 8000\"", "storybook:build": "npm run build:styles && storybook build -o ./storybook-static", "build:typedoc:export": "node scripts/build-typedoc.js export", @@ -63,18 +65,19 @@ "@biomejs/biome": "~2.3.11", "@custom-elements-manifest/analyzer": "^0.11.0", "@igniteui/material-icons-extended": "^3.1.0", - "@open-wc/testing": "^4.0.0", + "@open-wc/semantic-dom-diff": "^0.20.1", "@storybook/addon-a11y": "^10.1.11", "@storybook/addon-docs": "^10.1.11", "@storybook/addon-links": "^10.1.11", "@storybook/web-components-vite": "^10.1.11", - "@types/mocha": "^10.0.10", - "@web/dev-server-esbuild": "^1.0.4", - "@web/test-runner": "^0.20.2", - "@web/test-runner-playwright": "^0.11.1", + "@types/chai-dom": "^1.11.3", + "@vitest/browser-playwright": "^4.0.14", + "@vitest/coverage-v8": "^4.0.15", "autoprefixer": "^10.4.23", "browser-sync": "^3.0.4", "cem-plugin-expanded-types": "^1.4.0", + "chai-a11y-axe": "^1.5.0", + "chai-dom": "^1.12.1", "concurrently": "^9.2.1", "custom-element-jet-brains-integration": "^1.7.0", "custom-element-vs-code-integration": "^1.5.0", @@ -93,7 +96,6 @@ "prettier": "^3.7.4", "rimraf": "^6.1.2", "sass-embedded": "~1.93.3", - "sinon": "^21.0.1", "storybook": "^10.1.11", "stylelint": "^16.26.1", "stylelint-config-standard-scss": "^16.0.0", diff --git a/scripts/build.mjs b/scripts/build.mjs index 371d14d00..b8dc6634c 100644 --- a/scripts/build.mjs +++ b/scripts/build.mjs @@ -11,7 +11,6 @@ import { import customElements from '../custom-elements.json' with { type: 'json' }; import report from './report.mjs'; import { buildComponents, buildThemes } from './sass.mjs'; -import { globby } from 'globby'; const exec = promisify(_exec); @@ -51,20 +50,9 @@ async function runTask(tag, cmd) { ); await runTask('Copying release files', async () => { - const pngFiles = await globby('src/components/**/*.png'); - pngFiles.forEach( - async (dest) => - await mkdir(path.dirname(DEST_DIR(dest.replace('src', ''))), { - recursive: true, - }) - ); - Promise.all([ copyFile('scripts/_package.json', DEST_DIR('package.json')), ...RELEASE_FILES.map((file) => copyFile(file, DEST_DIR(file))), - ...pngFiles.map((file) => - copyFile(file, DEST_DIR(file.replace('src', ''))) - ), ]); }); diff --git a/scripts/tsconfig.prod.json b/scripts/tsconfig.prod.json index 597e1689a..a1c0bf3d5 100644 --- a/scripts/tsconfig.prod.json +++ b/scripts/tsconfig.prod.json @@ -2,6 +2,7 @@ "extends": "../tsconfig.json", "exclude": [ "../vite.config.ts", + "../vitest.setup.ts", "../**/*.spec.ts", "../stories" ], @@ -12,7 +13,5 @@ "declarationMap": false, "removeComments": true }, - "include": [ - "../**/*.ts" - ] + "include": ["../**/*.ts"] } diff --git a/src/animations/player.spec.ts b/src/animations/player.spec.ts index 0760dc56f..15f1aca20 100644 --- a/src/animations/player.spec.ts +++ b/src/animations/player.spec.ts @@ -1,11 +1,11 @@ +import { css, LitElement } from 'lit'; +import { beforeAll, beforeEach, describe, expect, it } from 'vitest'; import { defineCE, - expect, fixture, html, unsafeStatic, -} from '@open-wc/testing'; -import { css, LitElement } from 'lit'; +} from '../components/common/helpers.spec.js'; import { EaseOut } from './easings.js'; import { addAnimationController } from './player.js'; @@ -27,7 +27,7 @@ describe('Animations Player', () => { let tag: string; let el: HTMLElement & { player: ReturnType }; - before(() => { + beforeAll(() => { tag = defineCE( class extends LitElement { public static override styles = css` diff --git a/src/components/accordion/accordion.spec.ts b/src/components/accordion/accordion.spec.ts index c2bd48bc5..ab658bf2c 100644 --- a/src/components/accordion/accordion.spec.ts +++ b/src/components/accordion/accordion.spec.ts @@ -1,5 +1,4 @@ -import { elementUpdated, expect, fixture, html } from '@open-wc/testing'; - +import { beforeAll, beforeEach, describe, expect, it } from 'vitest'; import { altKey, arrowDown, @@ -9,12 +8,13 @@ import { shiftKey, } from '../common/controllers/key-bindings.js'; import { defineComponents } from '../common/definitions/defineComponents.js'; +import { elementUpdated, fixture, html } from '../common/helpers.spec.js'; import { simulateClick, simulateKeyboard } from '../common/utils.spec.js'; import IgcExpansionPanelComponent from '../expansion-panel/expansion-panel.js'; import IgcAccordionComponent from './accordion.js'; describe('Accordion', () => { - before(() => { + beforeAll(() => { defineComponents(IgcAccordionComponent); }); diff --git a/src/components/avatar/avatar.spec.ts b/src/components/avatar/avatar.spec.ts index 44d81bdad..21bfdd163 100644 --- a/src/components/avatar/avatar.spec.ts +++ b/src/components/avatar/avatar.spec.ts @@ -1,6 +1,6 @@ -import { elementUpdated, expect, fixture, html } from '@open-wc/testing'; - +import { beforeAll, describe, expect, it } from 'vitest'; import { defineComponents } from '../common/definitions/defineComponents.js'; +import { elementUpdated, fixture, html } from '../common/helpers.spec.js'; import IgcAvatarComponent from './avatar.js'; describe('Avatar', () => { @@ -8,7 +8,7 @@ describe('Avatar', () => { ignoreAttributes: ['style'], }; - before(() => { + beforeAll(() => { defineComponents(IgcAvatarComponent); }); diff --git a/src/components/badge/badge.spec.ts b/src/components/badge/badge.spec.ts index 81a8fd291..fd4fe228c 100644 --- a/src/components/badge/badge.spec.ts +++ b/src/components/badge/badge.spec.ts @@ -1,10 +1,10 @@ -import { elementUpdated, expect, fixture, html } from '@open-wc/testing'; - +import { beforeAll, describe, expect, it } from 'vitest'; import { defineComponents } from '../common/definitions/defineComponents.js'; +import { elementUpdated, fixture, html } from '../common/helpers.spec.js'; import IgcBadgeComponent from './badge.js'; describe('Badge', () => { - before(() => { + beforeAll(() => { defineComponents(IgcBadgeComponent); }); diff --git a/src/components/badge/badge.ts b/src/components/badge/badge.ts index 9e274d354..2965728d8 100644 --- a/src/components/badge/badge.ts +++ b/src/components/badge/badge.ts @@ -1,8 +1,8 @@ import { html, LitElement, type PropertyValues } from 'lit'; -import { property } from 'lit/decorators.js'; +import { property, state } from 'lit/decorators.js'; import { addThemingController } from '../../theming/theming-controller.js'; import { addInternalsController } from '../common/controllers/internals.js'; -import { addSlotController } from '../common/controllers/slot.js'; +import { addSlotController, setSlots } from '../common/controllers/slot.js'; import { registerComponent } from '../common/definitions/register.js'; import { partMap } from '../common/part-map.js'; import type { BadgeShape, StyleVariant } from '../types.js'; @@ -29,24 +29,18 @@ export default class IgcBadgeComponent extends LitElement { registerComponent(IgcBadgeComponent); } - private _iconPart = false; + private readonly _internals = addInternalsController(this, { + initialARIA: { role: 'status' }, + }); private readonly _slots = addSlotController(this, { + slots: setSlots(), onChange: this._handleSlotChange, + initial: true, }); - protected _handleSlotChange(): void { - const assignedNodes = this._slots.getAssignedNodes('[default]', true); - this._iconPart = assignedNodes.some( - (node) => - node.nodeType === Node.ELEMENT_NODE && - (node as Element).tagName.toLowerCase() === 'igc-icon' - ); - } - - private readonly _internals = addInternalsController(this, { - initialARIA: { role: 'status' }, - }); + @state() + private _iconPart = false; /** * The type of badge. @@ -87,6 +81,13 @@ export default class IgcBadgeComponent extends LitElement { } } + protected _handleSlotChange(): void { + this._iconPart = this._slots.hasAssignedElements('[default]', { + flatten: true, + selector: 'igc-icon', + }); + } + protected override render() { return html` diff --git a/src/components/banner/banner.spec.ts b/src/components/banner/banner.spec.ts index f786930da..faeae8f71 100644 --- a/src/components/banner/banner.spec.ts +++ b/src/components/banner/banner.spec.ts @@ -1,19 +1,17 @@ +import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; +import { defineComponents } from '../common/definitions/defineComponents.js'; import { elementUpdated, - expect, fixture, html, nextFrame, -} from '@open-wc/testing'; -import { spy } from 'sinon'; - -import { defineComponents } from '../common/definitions/defineComponents.js'; +} from '../common/helpers.spec.js'; import { finishAnimationsFor, simulateClick } from '../common/utils.spec.js'; import IgcIconComponent from '../icon/icon.js'; import IgcBannerComponent from './banner.js'; describe('Banner', () => { - before(() => { + beforeAll(() => { defineComponents(IgcBannerComponent, IgcIconComponent); }); @@ -246,7 +244,7 @@ describe('Banner', () => { }); it('should emit correct event sequence for the default action button', async () => { - const eventSpy = spy(banner, 'emitEvent'); + const spy = vi.spyOn(banner, 'emitEvent'); const button = banner.renderRoot.querySelector('igc-button')!; expect(banner.open).to.be.false; @@ -256,19 +254,19 @@ describe('Banner', () => { simulateClick(button); - expect(eventSpy.callCount).to.equal(1); - expect(eventSpy).calledWith('igcClosing', { cancelable: true }); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith('igcClosing', { cancelable: true }); - eventSpy.resetHistory(); + spy.mockClear(); await elementUpdated(banner); await clickHideComplete(); - expect(eventSpy).calledWith('igcClosed'); + expect(spy).toHaveBeenCalledWith('igcClosed'); expect(banner.open).to.be.false; }); it('can cancel `igcClosing` event', async () => { - const eventSpy = spy(banner, 'emitEvent'); + const spy = vi.spyOn(banner, 'emitEvent'); const button = banner.renderRoot.querySelector('igc-button')!; banner.addEventListener('igcClosing', (event) => { @@ -282,8 +280,8 @@ describe('Banner', () => { await elementUpdated(banner); await clickHideComplete(); - expect(eventSpy).calledWith('igcClosing'); - expect(eventSpy).not.calledWith('igcClosed'); + expect(spy).toHaveBeenCalledWith('igcClosing', { cancelable: true }); + expect(spy).not.toHaveBeenCalledWith('igcClosed'); expect(banner.open).to.be.true; }); }); diff --git a/src/components/button-group/button-group.spec.ts b/src/components/button-group/button-group.spec.ts index 77cff5fa2..efe1033b7 100644 --- a/src/components/button-group/button-group.spec.ts +++ b/src/components/button-group/button-group.spec.ts @@ -1,20 +1,16 @@ +import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; +import { defineComponents } from '../common/definitions/defineComponents.js'; import { elementUpdated, - expect, fixture, html, unsafeStatic, -} from '@open-wc/testing'; -import { spy } from 'sinon'; - -import { - defineComponents, - IgcButtonGroupComponent, - IgcToggleButtonComponent, -} from '../../index.js'; +} from '../common/helpers.spec.js'; +import IgcButtonGroupComponent from './button-group.js'; +import IgcToggleButtonComponent from './toggle-button.js'; describe('Button Group', () => { - before(() => { + beforeAll(() => { defineComponents(IgcButtonGroupComponent); }); @@ -600,14 +596,14 @@ describe('Button Group', () => { }); it('should emit `igcSelect` event on select', async () => { - const eventSpy = spy(buttonGroup, 'emitEvent'); + const spy = vi.spyOn(buttonGroup, 'emitEvent'); buttons[0].dispatchEvent(new MouseEvent('click', { bubbles: true })); await elementUpdated(buttonGroup); const args = { detail: buttons[0].value }; - expect(eventSpy).calledWith('igcSelect', args); + expect(spy).toHaveBeenCalledWith('igcSelect', args); buttonGroup.addEventListener('igcSelect', (event) => { expect(buttonGroup.selectedItems.length).to.equal(1); @@ -618,7 +614,7 @@ describe('Button Group', () => { }); it('should emit `igcDeselect` event on deselect', async () => { - const eventSpy = spy(buttonGroup, 'emitEvent'); + const spy = vi.spyOn(buttonGroup, 'emitEvent'); buttons[0].selected = true; await elementUpdated(buttonGroup); @@ -629,7 +625,7 @@ describe('Button Group', () => { const args = { detail: buttons[0].value }; - expect(eventSpy).calledWith('igcDeselect', args); + expect(spy).toHaveBeenCalledWith('igcDeselect', args); buttonGroup.addEventListener('igcDeselect', () => { expect(buttonGroup.selectedItems.length).to.equal(0); @@ -637,7 +633,7 @@ describe('Button Group', () => { }); it('events are correctly emitted on user interaction (single mode)', async () => { - const eventSpy = spy(buttonGroup, 'emitEvent'); + const spy = vi.spyOn(buttonGroup, 'emitEvent'); const selectArgs = { detail: '' }; const deselectArgs = { detail: '' }; @@ -647,7 +643,7 @@ describe('Button Group', () => { selectArgs.detail = buttons[0].value; - expect(eventSpy).calledWith('igcSelect', selectArgs); + expect(spy).toHaveBeenCalledWith('igcSelect', selectArgs); expect(buttonGroup.selectedItems.length).to.equal(1); expect(buttonGroup.selectedItems).to.have.same.members([ buttons[0].value, @@ -660,8 +656,8 @@ describe('Button Group', () => { selectArgs.detail = buttons[1].value; deselectArgs.detail = buttons[0].value; - expect(eventSpy).calledWith('igcDeselect', deselectArgs); - expect(eventSpy).calledWith('igcSelect', selectArgs); + expect(spy).toHaveBeenCalledWith('igcDeselect', deselectArgs); + expect(spy).toHaveBeenCalledWith('igcSelect', selectArgs); expect(buttonGroup.selectedItems.length).to.equal(1); expect(buttonGroup.selectedItems).to.have.same.members([ buttons[1].value, @@ -669,7 +665,7 @@ describe('Button Group', () => { }); it('events are correctly emitted on user interaction (single-required mode)', async () => { - const eventSpy = spy(buttonGroup, 'emitEvent'); + const spy = vi.spyOn(buttonGroup, 'emitEvent'); const selectArgs = { detail: '' }; const deselectArgs = { detail: '' }; @@ -682,21 +678,21 @@ describe('Button Group', () => { selectArgs.detail = buttons[0].value; - expect(eventSpy).calledWith('igcSelect', selectArgs); + expect(spy).toHaveBeenCalledWith('igcSelect', selectArgs); expect(buttonGroup.selectedItems.length).to.equal(1); expect(buttonGroup.selectedItems).to.have.same.members([ buttons[0].value, ]); - eventSpy.resetHistory(); + spy.mockClear(); // deselect first button // should not emit events when interacting with an already selected button buttons[0].dispatchEvent(new MouseEvent('click', { bubbles: true })); await elementUpdated(buttonGroup); - expect(eventSpy).not.calledWith('igcDeselect'); - expect(eventSpy).not.calledWith('igcSelect'); + expect(spy).not.toHaveBeenCalledWith('igcDeselect'); + expect(spy).not.toHaveBeenCalledWith('igcSelect'); expect(buttonGroup.selectedItems.length).to.equal(1); expect(buttonGroup.selectedItems).to.have.same.members([ buttons[0].value, @@ -709,8 +705,8 @@ describe('Button Group', () => { selectArgs.detail = buttons[1].value; deselectArgs.detail = buttons[0].value; - expect(eventSpy).calledWith('igcDeselect', deselectArgs); - expect(eventSpy).calledWith('igcSelect', selectArgs); + expect(spy).toHaveBeenCalledWith('igcDeselect', deselectArgs); + expect(spy).toHaveBeenCalledWith('igcSelect', selectArgs); expect(buttonGroup.selectedItems.length).to.equal(1); expect(buttonGroup.selectedItems).to.have.same.members([ buttons[1].value, @@ -718,7 +714,7 @@ describe('Button Group', () => { }); it('events are correctly emitted on user interaction (multiple mode)', async () => { - const eventSpy = spy(buttonGroup, 'emitEvent'); + const spy = vi.spyOn(buttonGroup, 'emitEvent'); const selectArgs = { detail: '' }; const deselectArgs = { detail: '' }; @@ -731,7 +727,7 @@ describe('Button Group', () => { selectArgs.detail = buttons[0].value; - expect(eventSpy).calledWith('igcSelect', selectArgs); + expect(spy).toHaveBeenCalledWith('igcSelect', selectArgs); expect(buttonGroup.selectedItems.length).to.equal(1); expect(buttonGroup.selectedItems).to.have.same.members([ buttons[0].value, @@ -743,8 +739,8 @@ describe('Button Group', () => { selectArgs.detail = buttons[1].value; - expect(eventSpy).not.calledWith('igcDeselect'); - expect(eventSpy).calledWith('igcSelect', selectArgs); + expect(spy).not.toHaveBeenCalledWith('igcDeselect'); + expect(spy).toHaveBeenCalledWith('igcSelect', selectArgs); expect(buttonGroup.selectedItems.length).to.equal(2); expect(buttonGroup.selectedItems).to.have.same.members([ buttons[0].value, @@ -757,7 +753,7 @@ describe('Button Group', () => { deselectArgs.detail = buttons[0].value; - expect(eventSpy).calledWith('igcDeselect', deselectArgs); + expect(spy).toHaveBeenCalledWith('igcDeselect', deselectArgs); expect(buttonGroup.selectedItems.length).to.equal(1); expect(buttonGroup.selectedItems).to.have.same.members([ buttons[1].value, diff --git a/src/components/button-group/toggle-button.spec.ts b/src/components/button-group/toggle-button.spec.ts index 7581a6a1e..7aff22552 100644 --- a/src/components/button-group/toggle-button.spec.ts +++ b/src/components/button-group/toggle-button.spec.ts @@ -1,15 +1,15 @@ +import { beforeAll, beforeEach, describe, expect, it } from 'vitest'; +import { defineComponents } from '../common/definitions/defineComponents.js'; import { elementUpdated, - expect, fixture, html, unsafeStatic, -} from '@open-wc/testing'; - -import { defineComponents, IgcToggleButtonComponent } from '../../index.js'; +} from '../common/helpers.spec.js'; +import IgcToggleButtonComponent from './toggle-button.js'; describe('Toggle Button', () => { - before(() => { + beforeAll(() => { defineComponents(IgcToggleButtonComponent); }); diff --git a/src/components/button/button.spec.ts b/src/components/button/button.spec.ts index 590d8cf09..e43739164 100644 --- a/src/components/button/button.spec.ts +++ b/src/components/button/button.spec.ts @@ -1,6 +1,6 @@ -import { elementUpdated, expect, fixture, html } from '@open-wc/testing'; - +import { beforeAll, beforeEach, describe, expect, it } from 'vitest'; import { defineComponents } from '../common/definitions/defineComponents.js'; +import { elementUpdated, fixture, html } from '../common/helpers.spec.js'; import { createFormAssociatedTestBed, isFocused, @@ -18,7 +18,9 @@ const Types: Array = ['button', 'reset', 'submit']; describe('Button tests', () => { let button: IgcButtonComponent; - before(() => defineComponents(IgcButtonComponent, IgcInputComponent)); + beforeAll(() => { + defineComponents(IgcButtonComponent, IgcInputComponent); + }); describe('Button component', () => { const ignored_DOM_parts = { diff --git a/src/components/button/icon-button.spec.ts b/src/components/button/icon-button.spec.ts index 13c89d334..a83828c0a 100644 --- a/src/components/button/icon-button.spec.ts +++ b/src/components/button/icon-button.spec.ts @@ -1,15 +1,14 @@ +import { beforeAll, beforeEach, describe, expect, it } from 'vitest'; +import { defineComponents, IgcIconButtonComponent } from '../../index.js'; import { elementUpdated, - expect, fixture, html, unsafeStatic, -} from '@open-wc/testing'; - -import { defineComponents, IgcIconButtonComponent } from '../../index.js'; +} from '../common/helpers.spec.js'; describe('IconButton component', () => { - before(() => { + beforeAll(() => { defineComponents(IgcIconButtonComponent); }); diff --git a/src/components/calendar/calendar-keyboard-navigation.spec.ts b/src/components/calendar/calendar-keyboard-navigation.spec.ts index 4e8aa4a86..d1c1cd763 100644 --- a/src/components/calendar/calendar-keyboard-navigation.spec.ts +++ b/src/components/calendar/calendar-keyboard-navigation.spec.ts @@ -1,5 +1,4 @@ -import { elementUpdated, expect, fixture, html } from '@open-wc/testing'; - +import { beforeAll, beforeEach, describe, expect, it } from 'vitest'; import { arrowDown, arrowLeft, @@ -14,6 +13,7 @@ import { spaceBar, } from '../common/controllers/key-bindings.js'; import { defineComponents } from '../common/definitions/defineComponents.js'; +import { elementUpdated, fixture, html } from '../common/helpers.spec.js'; import { asNumber, first } from '../common/util.js'; import { simulateClick, simulateKeyboard } from '../common/utils.spec.js'; import IgcCalendarComponent from './calendar.js'; @@ -26,7 +26,7 @@ import { DateRangeType } from './types.js'; import type IgcYearsViewComponent from './years-view/years-view.js'; describe('Calendar keyboard interaction', () => { - before(() => defineComponents(IgcCalendarComponent)); + beforeAll(() => defineComponents(IgcCalendarComponent)); let calendar: IgcCalendarComponent; const activeDate = new Date(2021, 6, 17); diff --git a/src/components/calendar/calendar-rendering.spec.ts b/src/components/calendar/calendar-rendering.spec.ts index ef7ee1ca3..7db0ccba2 100644 --- a/src/components/calendar/calendar-rendering.spec.ts +++ b/src/components/calendar/calendar-rendering.spec.ts @@ -1,7 +1,7 @@ -import { elementUpdated, expect, fixture, html } from '@open-wc/testing'; import type { TemplateResult } from 'lit'; - +import { beforeAll, beforeEach, describe, expect, it } from 'vitest'; import { defineComponents } from '../common/definitions/defineComponents.js'; +import { elementUpdated, fixture, html } from '../common/helpers.spec.js'; import { first } from '../common/util.js'; import IgcCalendarComponent from './calendar.js'; import { getCalendarDOM, getDayViewDOM, getDOMDate } from './helpers.spec.js'; @@ -9,7 +9,7 @@ import { CalendarDay } from './model.js'; import { type DateRangeDescriptor, DateRangeType } from './types.js'; describe('Calendar Rendering', () => { - before(() => { + beforeAll(() => { defineComponents(IgcCalendarComponent); }); diff --git a/src/components/calendar/calendar.interaction.spec.ts b/src/components/calendar/calendar.interaction.spec.ts index 308ac5674..a5cd65d33 100644 --- a/src/components/calendar/calendar.interaction.spec.ts +++ b/src/components/calendar/calendar.interaction.spec.ts @@ -1,9 +1,9 @@ -import { elementUpdated, expect, fixture, html } from '@open-wc/testing'; -import { spy } from 'sinon'; - -import { defineComponents, IgcCalendarComponent } from '../../index.js'; +import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; +import { defineComponents } from '../common/definitions/defineComponents.js'; +import { elementUpdated, fixture, html } from '../common/helpers.spec.js'; import { first, last } from '../common/util.js'; import { simulateClick } from '../common/utils.spec.js'; +import IgcCalendarComponent from './calendar.js'; import type IgcDaysViewComponent from './days-view/days-view.js'; import { calendarRange, @@ -19,7 +19,7 @@ describe('Calendar interactions', () => { let calendar: IgcCalendarComponent; let daysView: IgcDaysViewComponent; - before(() => defineComponents(IgcCalendarComponent)); + beforeAll(() => defineComponents(IgcCalendarComponent)); beforeEach(async () => { const value = new CalendarDay({ year: 2021, month: 8, date: 29 }); @@ -177,7 +177,7 @@ describe('Calendar interactions', () => { }); it('single selection', async () => { - const eventSpy = spy(calendar, 'emitEvent'); + const spy = vi.spyOn(calendar, 'emitEvent'); const current = CalendarDay.from(calendar.value!); const previous = current.add('day', -1); @@ -187,7 +187,7 @@ describe('Calendar interactions', () => { simulateClick(previousDOM); await elementUpdated(calendar); - expect(eventSpy).calledOnceWithExactly('igcChange', { + expect(spy).toHaveBeenCalledExactlyOnceWith('igcChange', { detail: previous.native, }); @@ -196,7 +196,7 @@ describe('Calendar interactions', () => { }); it('issue-1443', async () => { - const eventSpy = spy(calendar, 'emitEvent'); + const spy = vi.spyOn(calendar, 'emitEvent'); const anchor = new CalendarDay({ year: 681, month: 0, @@ -214,7 +214,7 @@ describe('Calendar interactions', () => { simulateClick(previousDOM); await elementUpdated(calendar); - expect(eventSpy).calledOnceWithExactly('igcChange', { + expect(spy).toHaveBeenCalledExactlyOnceWith('igcChange', { detail: previous.native, }); @@ -235,7 +235,7 @@ describe('Calendar interactions', () => { }); it('multiple selection', async () => { - const eventSpy = spy(calendar, 'emitEvent'); + const spy = vi.spyOn(calendar, 'emitEvent'); const start = CalendarDay.from(calendar.value!).set({ date: 15 }); const dates = Array.from(calendarRange({ start, end: 7 })); const elements = dates.map((date) => getDOMDate(date, daysView)); @@ -247,12 +247,14 @@ describe('Calendar interactions', () => { for (const element of elements) { simulateClick(element); - expect(eventSpy).calledWith('igcChange', { detail: calendar.values }); + expect(spy).toHaveBeenCalledWith('igcChange', { + detail: calendar.values, + }); } await elementUpdated(calendar); expect(calendar.values).lengthOf(7); - expect(eventSpy.callCount).equal(7); + expect(spy).toHaveBeenCalledTimes(7); expect(first(dates).equalTo(first(calendar.values))).to.be.true; expect(last(dates).equalTo(last(calendar.values))).to.be.true; @@ -305,23 +307,25 @@ describe('Calendar interactions', () => { } }); - it('should emit `igcACtiveDateChange` event when the active date is selected', async () => { - const eventSpy = spy(daysView, 'emitEvent'); + it('should emit `igcActiveDateChange` event when the active date is selected', async () => { + const spy = vi.spyOn(daysView, 'emitEvent'); const date = CalendarDay.from(calendar.value!).set({ date: 28 }); const element = getDOMDate(date, daysView); simulateClick(element); await elementUpdated(calendar); - const argDate = CalendarDay.from(eventSpy.getCall(1).lastArg.detail); + const argDate = CalendarDay.from(spy.mock.lastCall?.[1]?.detail as Date); - expect(eventSpy).calledWith('igcActiveDateChange'); + expect(spy).toBeCalledWith('igcActiveDateChange', { + detail: argDate.native, + }); expect(argDate.equalTo(date)).to.be.true; expect(argDate.equalTo(calendar.activeDate)).to.be.true; }); it('should emit `igcRangePreviewDateChange` event', async () => { - const eventSpy = spy(daysView, 'emitEvent'); + const spy = vi.spyOn(daysView, 'emitEvent'); const start = CalendarDay.from(calendar.value!).set({ date: 26 }); const dates = Array.from(calendarRange({ start, end: 7 })); const elements = dates.map((date) => getDOMDate(date, daysView)); @@ -333,7 +337,7 @@ describe('Calendar interactions', () => { last(elements).focus(); await elementUpdated(calendar); - expect(eventSpy).calledWithExactly('igcRangePreviewDateChange', { + expect(spy).toHaveBeenCalledWith('igcRangePreviewDateChange', { detail: last(dates).native, }); }); diff --git a/src/components/calendar/model.spec.ts b/src/components/calendar/model.spec.ts index 2142eebfd..95908dece 100644 --- a/src/components/calendar/model.spec.ts +++ b/src/components/calendar/model.spec.ts @@ -1,5 +1,4 @@ -import { expect } from '@open-wc/testing'; - +import { describe, expect, it } from 'vitest'; import { first, last } from '../common/util.js'; import { calendarRange, isDateInRanges } from './helpers.js'; import { CalendarDay } from './model.js'; diff --git a/src/components/card/card.spec.ts b/src/components/card/card.spec.ts index 4c4eea13c..e29f93163 100644 --- a/src/components/card/card.spec.ts +++ b/src/components/card/card.spec.ts @@ -1,9 +1,10 @@ -import { elementUpdated, expect, fixture, html } from '@open-wc/testing'; - -import { defineComponents, IgcCardComponent } from '../../index.js'; +import { beforeAll, describe, expect, it } from 'vitest'; +import { defineComponents } from '../common/definitions/defineComponents.js'; +import { elementUpdated, fixture, html } from '../common/helpers.spec.js'; +import IgcCardComponent from './card.js'; describe('Card Component', () => { - before(() => { + beforeAll(() => { defineComponents(IgcCardComponent); }); diff --git a/src/components/carousel/carousel-indicator-container.spec.ts b/src/components/carousel/carousel-indicator-container.spec.ts index 4294664c0..857d19ebd 100644 --- a/src/components/carousel/carousel-indicator-container.spec.ts +++ b/src/components/carousel/carousel-indicator-container.spec.ts @@ -1,7 +1,7 @@ -import { elementUpdated, expect, fixture, html } from '@open-wc/testing'; - +import { beforeAll, beforeEach, describe, expect, it } from 'vitest'; import { tabKey } from '../common/controllers/key-bindings.js'; import { defineComponents } from '../common/definitions/defineComponents.js'; +import { elementUpdated, fixture, html } from '../common/helpers.spec.js'; import { first } from '../common/util.js'; import { simulateClick, @@ -13,7 +13,7 @@ import IgcCarouselIndicatorComponent from './carousel-indicator.js'; import IgcCarouselIndicatorContainerComponent from './carousel-indicator-container.js'; describe('Carousel Indicator Container', () => { - before(() => { + beforeAll(() => { defineComponents( IgcCarouselIndicatorContainerComponent, IgcCarouselIndicatorComponent diff --git a/src/components/carousel/carousel.spec.ts b/src/components/carousel/carousel.spec.ts index 0eb28c214..44ab51811 100644 --- a/src/components/carousel/carousel.spec.ts +++ b/src/components/carousel/carousel.spec.ts @@ -1,13 +1,12 @@ import { - elementUpdated, + afterEach, + beforeAll, + beforeEach, + describe, expect, - fixture, - html, - nextFrame, - waitUntil, -} from '@open-wc/testing'; - -import { type SinonFakeTimers, spy, stub, useFakeTimers } from 'sinon'; + it, + vi, +} from 'vitest'; import IgcButtonComponent from '../button/button.js'; import { arrowLeft, @@ -19,7 +18,17 @@ import { } from '../common/controllers/key-bindings.js'; import { defineComponents } from '../common/definitions/defineComponents.js'; import { + elementUpdated, + fixture, + html, + nextFrame, + waitUntil, +} from '../common/helpers.spec.js'; +import { + eventArgsMatch, + eventMatch, finishAnimationsFor, + getEvents, simulateClick, simulateKeyboard, simulateLostPointerCapture, @@ -31,7 +40,7 @@ import IgcCarouselIndicatorComponent from './carousel-indicator.js'; import IgcCarouselSlideComponent from './carousel-slide.js'; describe('Carousel', () => { - before(() => { + beforeAll(() => { defineComponents(IgcCarouselComponent); }); @@ -70,7 +79,6 @@ describe('Carousel', () => { let nextButton: IgcButtonComponent; let prevButton: IgcButtonComponent; let defaultIndicators: IgcCarouselIndicatorComponent[]; - let clock: SinonFakeTimers; beforeEach(async () => { carousel = await fixture(createCarouselComponent()); @@ -494,71 +502,69 @@ describe('Carousel', () => { describe('Interactions', () => { describe('Click', () => { it('should change slide when clicking next button', async () => { - const eventSpy = spy(carousel, 'emitEvent'); + const spy = vi.spyOn(carousel, 'emitEvent'); expect(carousel.current).to.equal(0); expect(defaultIndicators[0].active).to.be.true; simulateClick(nextButton!); - await waitUntil(() => eventSpy.calledWith('igcSlideChanged')); + await waitUntil(() => eventMatch(spy, 'igcSlideChanged')); expect(carousel.current).to.equal(1); expect(defaultIndicators[0].active).to.be.false; expect(defaultIndicators[1].active).to.be.true; - expect(eventSpy.firstCall).calledWith('igcSlideChanged', { detail: 1 }); + expect(spy).toHaveBeenCalledWith('igcSlideChanged', { detail: 1 }); }); it('should change slide when clicking previous button', async () => { - const eventSpy = spy(carousel, 'emitEvent'); + const spy = vi.spyOn(carousel, 'emitEvent'); expect(carousel.current).to.equal(0); expect(defaultIndicators[0].active).to.be.true; simulateClick(prevButton!); - await waitUntil(() => eventSpy.calledWith('igcSlideChanged')); + await waitUntil(() => eventMatch(spy, 'igcSlideChanged')); expect(carousel.current).to.equal(2); expect(defaultIndicators[0].active).to.be.false; expect(defaultIndicators[2].active).to.be.true; - expect(eventSpy.firstCall).calledWith('igcSlideChanged', { detail: 2 }); + expect(spy).toHaveBeenCalledWith('igcSlideChanged', { detail: 2 }); }); it('should change slide when clicking indicators', async () => { - const eventSpy = spy(carousel, 'emitEvent'); + const spy = vi.spyOn(carousel, 'emitEvent'); expect(carousel.current).to.equal(0); expect(defaultIndicators[0].active).to.be.true; // select second slide simulateClick(defaultIndicators[1]); await waitUntil(() => - eventSpy.calledWith('igcSlideChanged', { detail: 1 }) + eventArgsMatch(spy, 'igcSlideChanged', { detail: 1 }) ); expect(carousel.current).to.equal(1); expect(defaultIndicators[0].active).to.be.false; expect(defaultIndicators[1].active).to.be.true; - expect(eventSpy.firstCall).calledWith('igcSlideChanged', { detail: 1 }); + expect(spy).toHaveBeenCalledWith('igcSlideChanged', { detail: 1 }); // select first slide simulateClick(defaultIndicators[0]); await waitUntil(() => - eventSpy.calledWith('igcSlideChanged', { detail: 0 }) + eventArgsMatch(spy, 'igcSlideChanged', { detail: 0 }) ); expect(carousel.current).to.equal(0); expect(defaultIndicators[0].active).to.be.true; expect(defaultIndicators[1].active).to.be.false; - expect(eventSpy.secondCall).calledWith('igcSlideChanged', { + expect(spy).toHaveBeenCalledWith('igcSlideChanged', { detail: 0, }); }); it('should properly call `igcSlideChanged` event', async () => { - const eventSpy = spy(carousel, 'emitEvent'); + const spy = vi.spyOn(carousel, 'emitEvent'); - stub(carousel, 'select') - .onFirstCall() - .resolves(true) - .onSecondCall() - .resolves(false); + vi.spyOn(carousel, 'select') + .mockResolvedValueOnce(true) + .mockResolvedValueOnce(false); // select second indicator simulateClick(defaultIndicators[1]); @@ -568,7 +574,7 @@ describe('Carousel', () => { simulateClick(defaultIndicators[1]); await slideChangeComplete(slides[0], slides[1]); - expect(eventSpy.callCount).to.equal(1); + expect(getEvents(spy, 'igcSlideChanged').length).to.equal(1); }); }); @@ -658,10 +664,10 @@ describe('Carousel', () => { describe('Automatic rotation', () => { beforeEach(async () => { - clock = useFakeTimers({ toFake: ['setInterval'] }); + vi.useFakeTimers({ toFake: ['setInterval', 'clearInterval'] }); }); - afterEach(() => clock.restore()); + afterEach(() => vi.useRealTimers()); it('should automatically change slides', async () => { expect(carousel.current).to.equal(0); @@ -669,14 +675,14 @@ describe('Carousel', () => { carousel.interval = 200; await elementUpdated(carousel); - await clock.tickAsync(200); + await vi.advanceTimersByTimeAsync(200); await slideChangeComplete(slides[0], slides[1]); expect(carousel.current).to.equal(1); }); it('should properly call `igcSlideChanged` event', async () => { - const eventSpy = spy(carousel, 'emitEvent'); + const spy = vi.spyOn(carousel, 'emitEvent'); carousel.disableLoop = true; carousel.interval = 100; @@ -684,15 +690,15 @@ describe('Carousel', () => { expect(carousel.current).to.equal(0); - await clock.tickAsync(300); + await vi.advanceTimersByTimeAsync(300); expect(carousel.current).to.equal(2); - expect(eventSpy.callCount).to.equal(2); + expect(getEvents(spy, 'igcSlideChanged').length).to.equal(2); }); it('should pause/play on pointerenter/pointerleave', async () => { - const eventSpy = spy(carousel, 'emitEvent'); - const divContainer = carousel.shadowRoot?.querySelector( + const spy = vi.spyOn(carousel, 'emitEvent'); + const divContainer = carousel.renderRoot.querySelector( 'div[aria-live]' ) as HTMLDivElement; @@ -719,14 +725,14 @@ describe('Carousel', () => { expect(carousel.isPaused).to.be.false; expect(divContainer.ariaLive).to.equal('off'); - expect(eventSpy.callCount).to.equal(2); - expect(eventSpy.firstCall).calledWith('igcPaused'); - expect(eventSpy.secondCall).calledWith('igcPlaying'); + expect(spy.mock.calls.length).to.equal(2); + expect(spy).toHaveBeenNthCalledWith(1, 'igcPaused'); + expect(spy).toHaveBeenNthCalledWith(2, 'igcPlaying'); }); it('should pause/play on keyboard interaction', async () => { - const eventSpy = spy(carousel, 'emitEvent'); - const divContainer = carousel.shadowRoot?.querySelector( + const spy = vi.spyOn(carousel, 'emitEvent'); + const divContainer = carousel.renderRoot.querySelector( 'div[aria-live]' ) as HTMLDivElement; @@ -778,16 +784,16 @@ describe('Carousel', () => { expect(carousel.isPaused).to.be.false; expect(divContainer.ariaLive).to.equal('off'); - expect(eventSpy.callCount).to.equal(2); - expect(eventSpy.firstCall).calledWith('igcPaused'); - expect(eventSpy.secondCall).calledWith('igcPlaying'); + expect(spy.mock.calls.length).to.equal(2); + expect(spy).toHaveBeenNthCalledWith(1, 'igcPaused'); + expect(spy).toHaveBeenNthCalledWith(2, 'igcPlaying'); }); it('should pause when focusing an interactive element - issue #1731', async () => { carousel.interval = 200; await elementUpdated(carousel); - await clock.tickAsync(199); + await vi.advanceTimersByTimeAsync(199); expect(carousel.isPlaying).to.be.true; expect(carousel.isPaused).to.be.false; @@ -797,7 +803,7 @@ describe('Carousel', () => { carousel.dispatchEvent(new PointerEvent('pointerenter')); await elementUpdated(carousel); - await clock.tickAsync(1); + await vi.advanceTimersByTimeAsync(1); expect(carousel.isPlaying).to.be.false; expect(carousel.isPaused).to.be.true; @@ -811,7 +817,7 @@ describe('Carousel', () => { carousel.dispatchEvent(new PointerEvent('pointerleave')); await elementUpdated(carousel); - await clock.tickAsync(200); + await vi.advanceTimersByTimeAsync(200); // an interactive element is focused // -> should not start rotation on pointerleave @@ -823,7 +829,11 @@ describe('Carousel', () => { carousel.dispatchEvent(new FocusEvent('focusout')); await elementUpdated(carousel); - await clock.tickAsync(200); + await vi.advanceTimersByTimeAsync(200); + await slideChangeComplete(slides[0], slides[1]); + + await vi.advanceTimersByTimeAsync(200); + await slideChangeComplete(slides[1], slides[2]); // the interactive element loses focus // -> should start rotation @@ -833,7 +843,7 @@ describe('Carousel', () => { }); it('should not pause on interaction if `disablePauseOnInteraction` is true', async () => { - const eventSpy = spy(carousel, 'emitEvent'); + const spy = vi.spyOn(carousel, 'emitEvent'); const divContainer = carousel.shadowRoot?.querySelector( 'div[aria-live]' ) as HTMLDivElement; @@ -862,7 +872,7 @@ describe('Carousel', () => { expect(carousel.isPaused).to.be.false; expect(divContainer.ariaLive).to.equal('off'); - expect(eventSpy.callCount).to.equal(0); + expect(spy.mock.calls.length).to.equal(0); }); }); @@ -1073,13 +1083,13 @@ describe('Carousel', () => { 'div[aria-live="polite"]' ) as Element; - const eventSpy = spy(carousel, 'emitEvent'); - - const prevStub = stub(carousel, 'prev'); - const nextStub = stub(carousel, 'next'); + const spy = vi.spyOn(carousel, 'emitEvent'); - prevStub.resolves(false); - nextStub.onFirstCall().resolves(true).onSecondCall().resolves(false); + const prevStub = vi.spyOn(carousel, 'prev').mockResolvedValue(false); + const nextStub = vi + .spyOn(carousel, 'next') + .mockResolvedValueOnce(true) + .mockResolvedValueOnce(false); carousel.disableLoop = true; await elementUpdated(carousel); @@ -1104,19 +1114,19 @@ describe('Carousel', () => { simulateLostPointerCapture(carouselSlidesContainer); await slideChangeComplete(slides[0], slides[1]); - expect(eventSpy.callCount).to.equal(1); + expect(getEvents(spy, 'igcSlideChanged').length).to.equal(1); - eventSpy.resetHistory(); - prevStub.resetHistory(); - nextStub.resetHistory(); + spy.mockClear(); + prevStub.mockClear(); + nextStub.mockClear(); - prevStub.resolves(false); - nextStub.onFirstCall().resolves(true).onSecondCall().resolves(false); + prevStub.mockResolvedValue(false); + nextStub.mockResolvedValueOnce(true).mockResolvedValueOnce(false); carousel.vertical = true; await elementUpdated(carousel); - expect(eventSpy.callCount).to.equal(0); + expect(spy.mock.calls.length).to.equal(0); // swipe down - disabled simulatePointerDown(carouselSlidesContainer); @@ -1136,7 +1146,7 @@ describe('Carousel', () => { simulateLostPointerCapture(carouselSlidesContainer); await slideChangeComplete(slides[1], slides[0]); - expect(eventSpy.callCount).to.equal(1); + expect(getEvents(spy, 'igcSlideChanged').length).to.equal(1); }); }); }); diff --git a/src/components/chat/chat-input.ts b/src/components/chat/chat-input.ts index baecb42d1..c957557f8 100644 --- a/src/components/chat/chat-input.ts +++ b/src/components/chat/chat-input.ts @@ -98,7 +98,7 @@ export default class IgcChatInputComponent extends LitElement { private _userIsTyping = false; private _userLastTypeTime = Date.now(); - private _typingTimeout = 0; + private _typingTimeout?: ReturnType; private readonly _adoptedStyles = addAdoptedStylesController(this); diff --git a/src/components/chat/chat.spec.ts b/src/components/chat/chat.spec.ts index e4d073ab5..6bee093da 100644 --- a/src/components/chat/chat.spec.ts +++ b/src/components/chat/chat.spec.ts @@ -1,13 +1,23 @@ -import { elementUpdated, expect, fixture } from '@open-wc/testing'; import { html, nothing } from 'lit'; -import { spy, stub, useFakeTimers } from 'sinon'; +import { + afterEach, + beforeAll, + beforeEach, + describe, + expect, + it, + vi, +} from 'vitest'; import { configureTheme } from '../../theming/config.js'; import type IgcIconButtonComponent from '../button/icon-button.js'; import IgcChipComponent from '../chip/chip.js'; import { enterKey, tabKey } from '../common/controllers/key-bindings.js'; import { defineComponents } from '../common/definitions/defineComponents.js'; +import { elementUpdated, fixture } from '../common/helpers.spec.js'; import { first, last } from '../common/util.js'; import { + expectCalledWith, + getEvents, isFocused, simulateBlur, simulateClick, @@ -31,7 +41,7 @@ import type { } from './types.js'; describe('Chat', () => { - before(() => { + beforeAll(() => { defineComponents(IgcChatComponent, IgcInputComponent); }); @@ -617,7 +627,7 @@ describe('Chat', () => { describe('Interactions', () => { describe('Click', () => { it('should update messages properly on send button click', async () => { - const eventSpy = spy(chat, 'emitEvent'); + const spy = vi.spyOn(chat, 'emitEvent'); const { textarea, sendButton } = getChatDOM(chat).input!; textarea.setAttribute('value', 'Hello!'); textarea.dispatchEvent( @@ -628,8 +638,11 @@ describe('Chat', () => { simulateClick(sendButton); await elementUpdated(chat); - expect(eventSpy).calledWith('igcMessageCreated'); - const eventArgs = eventSpy.getCall(1).args[1]?.detail as IgcChatMessage; + expect(spy).toHaveBeenCalledWith( + 'igcMessageCreated', + expect.anything() + ); + const eventArgs = spy.mock.calls[1]?.[1]?.detail as IgcChatMessage; const args = { ...eventArgs, text: 'Hello!', sender: 'user' }; expect(eventArgs).to.deep.equal(args); expect(chat.messages.length).to.equal(1); @@ -640,7 +653,7 @@ describe('Chat', () => { }); it('should update messages properly on suggestion chip click', async () => { - const eventSpy = spy(chat, 'emitEvent'); + const spy = vi.spyOn(chat, 'emitEvent'); chat.options = { suggestions: ['Suggestion 1', 'Suggestion 2'], }; @@ -654,8 +667,11 @@ describe('Chat', () => { simulateClick(suggestionItems[0]); await elementUpdated(chat); - expect(eventSpy).calledWith('igcMessageCreated'); - const eventArgs = eventSpy.getCall(0).args[1]?.detail; + expect(spy).toHaveBeenCalledWith( + 'igcMessageCreated', + expect.anything() + ); + const eventArgs = spy.mock.calls[0]?.[1]?.detail; const args = eventArgs && typeof eventArgs === 'object' ? { ...eventArgs, text: 'Suggestion 1', sender: 'user' } @@ -670,13 +686,16 @@ describe('Chat', () => { }); it('should remove attachment on chip remove button click', async () => { - const eventSpy = spy(chat, 'emitEvent'); + const spy = vi.spyOn(chat, 'emitEvent'); const fileInput = getChatDOM(chat).input.fileInput; simulateFileUpload(fileInput, files); await elementUpdated(chat); - expect(eventSpy).calledOnce; - expect(eventSpy.calledWith('igcAttachmentAdded')).to.be.true; + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith( + 'igcAttachmentAdded', + expect.anything() + ); const attachments = getChatDOM(chat).input.chips; expect(attachments.length).to.equal(2); @@ -686,9 +705,12 @@ describe('Chat', () => { simulateClick(removeFileButton); await elementUpdated(chat); - expect(eventSpy).calledTwice; - expect(eventSpy.calledWith('igcAttachmentRemoved')).to.be.true; - const detail = eventSpy.getCall(1).args[1]?.detail; + expect(spy).toHaveBeenCalledTimes(2); + expect(spy).toHaveBeenCalledWith( + 'igcAttachmentRemoved', + expect.anything() + ); + const detail = spy.mock.calls[1]?.[1]?.detail; expect((detail as IgcChatMessageAttachment).name).to.equal( files[1].name ); @@ -761,14 +783,13 @@ describe('Chat', () => { }); it('should handle the copy action properly', async () => { - const clipboardWriteText = stub( - navigator.clipboard, - 'writeText' - ).resolves(); + const clipboardWriteText = vi + .spyOn(navigator.clipboard, 'writeText') + .mockResolvedValue(); chat.messages = [messages[0]]; await elementUpdated(chat); - expect(clipboardWriteText.called).to.be.false; + expect(clipboardWriteText).not.toHaveBeenCalled(); const firstMessage = chat.shadowRoot?.querySelectorAll('igc-chat-message')[0]; // click on copy icon @@ -777,7 +798,7 @@ describe('Chat', () => { ) as HTMLElement; simulateClick(copyIcon); await elementUpdated(chat); - expect(clipboardWriteText.called).to.be.true; + expect(clipboardWriteText).toHaveBeenCalled(); }); }); @@ -792,7 +813,7 @@ describe('Chat', () => { }); it('should be able to drag & drop files based on the types listed in `acceptedFiles`', async () => { - const eventSpy = spy(chat, 'emitEvent'); + const spy = vi.spyOn(chat, 'emitEvent'); const inputArea = getChatDOM(chat).input.self!; const dropZone = inputArea?.renderRoot.querySelector( `div[part='input-container']` @@ -817,8 +838,7 @@ describe('Chat', () => { dropZone?.dispatchEvent(dragEnterEvent); await elementUpdated(chat); - expect(eventSpy).calledOnce; - expect(eventSpy).calledWith('igcAttachmentDrag'); + expectCalledWith(spy, 'igcAttachmentDrag'); const dropEvent = new DragEvent('drop', { bubbles: true, @@ -831,19 +851,21 @@ describe('Chat', () => { dropZone.dispatchEvent(dropEvent); await elementUpdated(chat); - expect(eventSpy).calledWith('igcAttachmentDrop'); + expectCalledWith(spy, 'igcAttachmentDrop'); const attachments = getChatDOM(chat).input.chips; expect(attachments?.length).to.equal(1); expect(attachments?.[0]?.textContent?.trim()).to.equal('test.txt'); - expect(eventSpy).calledWith('igcAttachmentDrop'); - expect(eventSpy).calledWith('igcAttachmentAdded'); + expect(spy).toHaveBeenCalledWith( + 'igcAttachmentAdded', + expect.anything() + ); } }); }); describe('Keyboard', () => { it('should update messages properly on `Enter` keypress when the textarea is focused', async () => { - const eventSpy = spy(chat, 'emitEvent'); + const spy = vi.spyOn(chat, 'emitEvent'); const textArea = getChatDOM(chat).input.textarea; textArea.setAttribute('value', 'Hello!'); @@ -855,8 +877,11 @@ describe('Chat', () => { simulateKeyboard(textArea, enterKey); await elementUpdated(chat); - expect(eventSpy).calledWith('igcMessageCreated'); - const eventArgs = eventSpy.getCall(2).args[1]?.detail; + expect(spy).toHaveBeenCalledWith( + 'igcMessageCreated', + expect.anything() + ); + const eventArgs = spy.mock.calls[2]?.[1]?.detail; const args = eventArgs && typeof eventArgs === 'object' ? { ...eventArgs, text: 'Hello!', sender: 'user' } @@ -874,7 +899,7 @@ describe('Chat', () => { describe('Events', () => { it('emits igcAttachmentClick', async () => { - const eventSpy = spy(chat, 'emitEvent'); + const spy = vi.spyOn(chat, 'emitEvent'); chat.messages = [messages[1]]; await elementUpdated(chat); @@ -883,35 +908,37 @@ describe('Chat', () => { const attachmentHeader = getChatAttachmentDOM(attachment).header; simulateClick(attachmentHeader); - expect(eventSpy).calledWith('igcAttachmentClick', { + expect(spy).toHaveBeenCalledWith('igcAttachmentClick', { detail: { ...messages[1].attachments?.at(0) }, }); }); it('emits igcTypingChange', async () => { - const clock = useFakeTimers({ now: 0, toFake: ['Date', 'setTimeout'] }); + vi.useFakeTimers({ now: 0, toFake: ['Date', 'setTimeout'] }); - const eventSpy = spy(chat, 'emitEvent'); + const spy = vi.spyOn(chat, 'emitEvent'); const textArea = getChatDOM(chat).input.textarea; chat.options = { stopTypingDelay: 2500 }; simulateKeyboard(textArea, 'a', 15); await elementUpdated(chat); - expect(eventSpy).calledWith('igcTypingChange'); - expect(eventSpy.firstCall.args[1]?.detail).to.be.true; + expect(spy).toHaveBeenCalledWith('igcTypingChange', expect.anything()); + expect(last(first(getEvents(spy, 'igcTypingChange'))).detail).to.be + .true; - clock.setSystemTime(2501); - await clock.runAllAsync(); + vi.setSystemTime(2501); + await vi.runAllTimersAsync(); - expect(eventSpy).calledWith('igcTypingChange'); - expect(eventSpy.lastCall.args[1]?.detail).to.be.false; + expect(spy).toHaveBeenCalledWith('igcTypingChange', expect.anything()); + expect(last(last(getEvents(spy, 'igcTypingChange'))).detail).to.be + .false; - clock.restore(); + vi.useRealTimers(); }); it('emits igcTypingChange after sending a message', async () => { - const eventSpy = spy(chat, 'emitEvent'); + const spy = vi.spyOn(chat, 'emitEvent'); const textArea = getChatDOM(chat).input.textarea; const internalInput = textArea.renderRoot.querySelector('textarea')!; @@ -941,12 +968,12 @@ describe('Chat', () => { ]; for (const [idx, event] of expectedEventSequence.entries()) { - expect(eventSpy.getCall(idx).firstArg).to.equal(event); + expect(spy.mock.calls[idx][0]).to.equal(event); } }); it('should not emit igcTypingChange on Tab key', async () => { - const eventSpy = spy(chat, 'emitEvent'); + const spy = vi.spyOn(chat, 'emitEvent'); const textArea = getChatDOM(chat).input.textarea; const internalInput = textArea.renderRoot.querySelector('textarea')!; @@ -955,37 +982,37 @@ describe('Chat', () => { simulateKeyboard(internalInput, tabKey); await elementUpdated(chat); - expect(eventSpy.getCalls()).is.empty; + expect(spy.mock.calls).to.be.empty; }); it('emits igcInputFocus', async () => { - const eventSpy = spy(chat, 'emitEvent'); + const spy = vi.spyOn(chat, 'emitEvent'); simulateFocus(getChatDOM(chat).input.textarea); - expect(eventSpy).calledWith('igcInputFocus'); + expectCalledWith(spy, 'igcInputFocus'); }); it('emits igcInputBlur', async () => { - const eventSpy = spy(chat, 'emitEvent'); + const spy = vi.spyOn(chat, 'emitEvent'); simulateBlur(getChatDOM(chat).input.textarea); - expect(eventSpy).calledWith('igcInputBlur'); + expectCalledWith(spy, 'igcInputBlur'); }); it('emits igcInputChange', async () => { - const eventSpy = spy(chat, 'emitEvent'); + const spy = vi.spyOn(chat, 'emitEvent'); const textArea = getChatDOM(chat).input.textarea!; textArea.setAttribute('value', 'Hello!'); textArea.dispatchEvent(new CustomEvent('igcInput', { detail: 'Hello!' })); await elementUpdated(chat); - expect(eventSpy).calledWith('igcInputChange', { + expect(spy).toHaveBeenCalledWith('igcInputChange', { detail: { value: 'Hello!' }, }); }); it('emits igcMessageReact', async () => { - const eventSpy = spy(chat, 'emitEvent'); + const spy = vi.spyOn(chat, 'emitEvent'); chat.messages = [messages[0]]; await elementUpdated(chat); @@ -994,7 +1021,7 @@ describe('Chat', () => { getChatMessageDOM(messageElement).defaultActionButtons[1]; simulateClick(likeIcon); - expect(eventSpy).calledWith('igcMessageReact', { + expect(spy).toHaveBeenCalledWith('igcMessageReact', { detail: { message: messages[0], reaction: 'thumb_up_active' }, }); }); diff --git a/src/components/checkbox/checkbox.spec.ts b/src/components/checkbox/checkbox.spec.ts index bf13e12db..382948a58 100644 --- a/src/components/checkbox/checkbox.spec.ts +++ b/src/components/checkbox/checkbox.spec.ts @@ -1,7 +1,7 @@ -import { elementUpdated, expect, fixture, html } from '@open-wc/testing'; -import { spy } from 'sinon'; +import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; import { configureTheme } from '../../theming/config.js'; import { defineComponents } from '../common/definitions/defineComponents.js'; +import { elementUpdated, fixture, html } from '../common/helpers.spec.js'; import { createFormAssociatedTestBed, isFocused, @@ -13,7 +13,7 @@ import { import IgcCheckboxComponent from './checkbox.js'; describe('Checkbox', () => { - before(() => { + beforeAll(() => { defineComponents(IgcCheckboxComponent); }); @@ -171,21 +171,21 @@ describe('Checkbox', () => { }); it('should emit click event only once', async () => { - const eventSpy = spy(element, 'click'); + const spy = vi.spyOn(element, 'click'); - element.addEventListener('click', eventSpy); + element.addEventListener('click', spy); element.click(); await elementUpdated(element); - expect(eventSpy.callCount).to.equal(1); + expect(spy.mock.calls.length).to.equal(1); }); it('should emit igcChange event when the checkbox checked state changes', async () => { - const eventSpy = spy(element, 'emitEvent'); + const spy = vi.spyOn(element, 'emitEvent'); element.click(); await elementUpdated(element); - expect(eventSpy).calledWithExactly('igcChange', { + expect(spy).toHaveBeenCalledWith('igcChange', { detail: { checked: true, value: undefined }, }); }); diff --git a/src/components/checkbox/switch.spec.ts b/src/components/checkbox/switch.spec.ts index 815d647bc..9129f8375 100644 --- a/src/components/checkbox/switch.spec.ts +++ b/src/components/checkbox/switch.spec.ts @@ -1,6 +1,6 @@ -import { elementUpdated, expect, fixture, html } from '@open-wc/testing'; -import { spy } from 'sinon'; +import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; import { defineComponents } from '../common/definitions/defineComponents.js'; +import { elementUpdated, fixture, html } from '../common/helpers.spec.js'; import { createFormAssociatedTestBed, isFocused, @@ -8,7 +8,7 @@ import { import IgcSwitchComponent from './switch.js'; describe('Switch', () => { - before(() => { + beforeAll(() => { defineComponents(IgcSwitchComponent); }); @@ -133,11 +133,11 @@ describe('Switch', () => { }); it('should emit igcChange event when the switch checked state changes', async () => { - const eventSpy = spy(element, 'emitEvent'); + const spy = vi.spyOn(element, 'emitEvent'); element.click(); await elementUpdated(element); - expect(eventSpy).calledWithExactly('igcChange', { + expect(spy).toHaveBeenCalledWith('igcChange', { detail: { checked: true, value: undefined }, }); }); diff --git a/src/components/chip/chip.spec.ts b/src/components/chip/chip.spec.ts index b4dbf7684..7abfc27b1 100644 --- a/src/components/chip/chip.spec.ts +++ b/src/components/chip/chip.spec.ts @@ -1,5 +1,6 @@ -import { elementUpdated, expect, fixture, html } from '@open-wc/testing'; +import { beforeAll, describe, expect, it } from 'vitest'; import { defineComponents } from '../common/definitions/defineComponents.js'; +import { elementUpdated, fixture, html } from '../common/helpers.spec.js'; import IgcChipComponent from './chip.js'; describe('Chip', () => { @@ -7,7 +8,7 @@ describe('Chip', () => { ignoreAttributes: ['style'], }; - before(() => { + beforeAll(() => { defineComponents(IgcChipComponent); }); diff --git a/src/components/combo/combo.spec.ts b/src/components/combo/combo.spec.ts index fb0d1a1b0..1e2caf921 100644 --- a/src/components/combo/combo.spec.ts +++ b/src/components/combo/combo.spec.ts @@ -1,6 +1,4 @@ -import { elementUpdated, expect, fixture, html } from '@open-wc/testing'; -import { spy } from 'sinon'; - +import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; import { altKey, arrowDown, @@ -13,9 +11,12 @@ import { tabKey, } from '../common/controllers/key-bindings.js'; import { defineComponents } from '../common/definitions/defineComponents.js'; +import { elementUpdated, fixture, html } from '../common/helpers.spec.js'; import { first } from '../common/util.js'; import { createFormAssociatedTestBed, + expectCalledWith, + expectNotCalledWith, isFocused, simulateClick, simulateKeyboard, @@ -174,7 +175,7 @@ describe('Combo', () => { .shadowRoot!.querySelector('igc-combo-list')! .querySelectorAll('[part~="group-header"]'), ] as IgcComboHeaderComponent[]; - before(() => { + beforeAll(() => { defineComponents(IgcComboComponent); }); @@ -279,25 +280,25 @@ describe('Combo', () => { }); it('should open the menu upon clicking on the input', async () => { - const eventSpy = spy(combo, 'emitEvent'); + const spy = vi.spyOn(combo, 'emitEvent'); simulateClick(input); await elementUpdated(combo); - expect(eventSpy).calledWith('igcOpening'); - expect(eventSpy).calledWith('igcOpened'); + expectCalledWith(spy, 'igcOpening'); + expectCalledWith(spy, 'igcOpened'); expect(combo.open).to.be.true; }); it('should hide the menu upon clicking on the input', async () => { - const eventSpy = spy(combo, 'emitEvent'); + const spy = vi.spyOn(combo, 'emitEvent'); await combo.show(); simulateClick(input); await elementUpdated(combo); - expect(eventSpy).calledWith('igcClosing'); - expect(eventSpy).calledWith('igcClosed'); + expectCalledWith(spy, 'igcClosing'); + expectCalledWith(spy, 'igcClosed'); expect(combo.open).to.be.false; }); @@ -306,15 +307,15 @@ describe('Combo', () => { combo.addEventListener('igcOpening', (event: CustomEvent) => { event.preventDefault(); }); - const eventSpy = spy(combo, 'emitEvent'); + const spy = vi.spyOn(combo, 'emitEvent'); simulateClick(input); await elementUpdated(combo); - expect(eventSpy).calledOnceWithExactly('igcOpening', { + expect(spy).toHaveBeenCalledExactlyOnceWith('igcOpening', { cancelable: true, }); - expect(eventSpy).not.calledWith('igcOpened'); + expectNotCalledWith(spy, 'igcOpened'); }); it('should be able to cancel the igcClosing event', async () => { @@ -322,15 +323,15 @@ describe('Combo', () => { combo.addEventListener('igcClosing', (event: CustomEvent) => { event.preventDefault(); }); - const eventSpy = spy(combo, 'emitEvent'); + const spy = vi.spyOn(combo, 'emitEvent'); simulateClick(input); await elementUpdated(combo); - expect(eventSpy).calledOnceWithExactly('igcClosing', { + expect(spy).toHaveBeenCalledExactlyOnceWith('igcClosing', { cancelable: true, }); - expect(eventSpy).not.calledWith('igcClosed'); + expectNotCalledWith(spy, 'igcClosed'); }); it('should focus the input when the host is focused', async () => { @@ -507,12 +508,12 @@ describe('Combo', () => { event.preventDefault() ); - const eventSpy = spy(combo, 'emitEvent'); - expect(eventSpy).not.calledWith('igcChange'); + const spy = vi.spyOn(combo, 'emitEvent'); + expectNotCalledWith(spy, 'igcChange'); }); it('should fire igcChange selection type event on mouse click', async () => { - const eventSpy = spy(combo, 'emitEvent'); + const spy = vi.spyOn(combo, 'emitEvent'); const args = { cancelable: true, detail: { @@ -528,11 +529,11 @@ describe('Combo', () => { first(items(combo)).click(); expect(combo.value).to.eql(['BG01']); - expect(eventSpy).calledWithExactly('igcChange', args); + expect(spy).toHaveBeenCalledWith('igcChange', args); }); it('should fire igcChange deselection type event on mouse click', async () => { - const eventSpy = spy(combo, 'emitEvent'); + const spy = vi.spyOn(combo, 'emitEvent'); const args = { cancelable: true, detail: { @@ -553,14 +554,14 @@ describe('Combo', () => { await elementUpdated(combo); expect(combo.value).to.eql(['BG02', 'BG03']); - expect(eventSpy).calledWithExactly('igcChange', args); + expect(spy).toHaveBeenCalledWith('igcChange', args); }); it('should be able to cancel the selection event', async () => { combo.addEventListener('igcChange', (event: CustomEvent) => { event.preventDefault(); }); - const eventSpy = spy(combo, 'emitEvent'); + const spy = vi.spyOn(combo, 'emitEvent'); combo.open = true; await elementUpdated(combo); @@ -569,7 +570,7 @@ describe('Combo', () => { first(items(combo)).click(); await elementUpdated(combo); - expect(eventSpy).calledWith('igcChange'); + expectCalledWith(spy, 'igcChange'); expect(combo.value).to.be.empty; }); @@ -577,7 +578,7 @@ describe('Combo', () => { combo.addEventListener('igcChange', (event: CustomEvent) => { event.preventDefault(); }); - const eventSpy = spy(combo, 'emitEvent'); + const spy = vi.spyOn(combo, 'emitEvent'); combo.select(['BG01', 'BG02']); combo.open = true; @@ -587,7 +588,7 @@ describe('Combo', () => { first(items(combo)).click(); await elementUpdated(combo); - expect(eventSpy).calledWith('igcChange'); + expectCalledWith(spy, 'igcChange'); expect(combo.value).lengthOf(2); }); diff --git a/src/components/common/controllers/drag.spec.ts b/src/components/common/controllers/drag.spec.ts index 162afb862..85c045c70 100644 --- a/src/components/common/controllers/drag.spec.ts +++ b/src/components/common/controllers/drag.spec.ts @@ -1,13 +1,21 @@ +import { css, LitElement } from 'lit'; +import { + afterEach, + beforeAll, + beforeEach, + describe, + expect, + it, + type MockInstance, + vi, +} from 'vitest'; import { defineCE, elementUpdated, - expect, fixture, html, unsafeStatic, -} from '@open-wc/testing'; -import { css, LitElement } from 'lit'; -import { type SinonSpy, spy } from 'sinon'; +} from '../helpers.spec.js'; import { getCenterPoint, last } from '../util.js'; import { compareStyles, @@ -27,7 +35,7 @@ describe('Drag controller', () => { let tag: string; let instance: DragElement; - before(() => { + beforeAll(() => { tag = defineCE( class extends LitElement { public static override styles = css` @@ -48,7 +56,7 @@ describe('Drag controller', () => { }); describe('Immediate mode - basic element dragging', () => { - const dragStart = spy(); + const dragStart = vi.fn(); beforeEach(async () => { const tagName = unsafeStatic(tag); @@ -66,7 +74,7 @@ describe('Drag controller', () => { }); afterEach(() => { - dragStart.resetHistory(); + dragStart.mockClear(); }); it('should not start drag operation when disabled', async () => { @@ -75,7 +83,7 @@ describe('Drag controller', () => { simulatePointerDown(instance); await elementUpdated(instance); - expect(dragStart.called).is.false; + expect(dragStart).not.toHaveBeenCalled(); }); it('should not start a drag operation on a non-primary button interaction', async () => { @@ -84,18 +92,18 @@ describe('Drag controller', () => { simulatePointerDown(instance, { button: 1 }); await elementUpdated(instance); - expect(dragStart.called).is.false; + expect(dragStart).not.toHaveBeenCalled(); }); it('should not start a drag operation when a skip callback returns true', async () => { - const skip = spy(() => true); + const skip = vi.fn(() => true); instance.controller.set({ skip, start: dragStart }); simulatePointerDown(instance); await elementUpdated(instance); - expect(skip.called).is.true; - expect(dragStart.called).is.false; + expect(skip).toHaveBeenCalled(); + expect(dragStart).not.toHaveBeenCalled(); }); it('should apply correct internal styles on drag operation', async () => { @@ -109,25 +117,25 @@ describe('Drag controller', () => { }); it('should not create a ghost element in "immediate" mode', async () => { - const ghost = spy(); + const ghost = vi.fn(); instance.controller.set({ start: dragStart, ghost }); simulatePointerDown(instance); await elementUpdated(instance); - expect(dragStart.called).is.true; - expect(ghost.called).is.false; + expect(dragStart).toHaveBeenCalled(); + expect(ghost).not.toHaveBeenCalled(); }); it('should not invoke the `layer` configuration callback in "immediate" mode', async () => { - const layer = spy(); + const layer = vi.fn(); instance.controller.set({ start: dragStart, layer }); simulatePointerDown(instance); await elementUpdated(instance); - expect(dragStart.called).is.true; - expect(layer.called).is.false; + expect(dragStart).toHaveBeenCalled(); + expect(layer).not.toHaveBeenCalled(); }); it('should invoke start callback on drag operation', async () => { @@ -136,23 +144,23 @@ describe('Drag controller', () => { simulatePointerDown(instance); await elementUpdated(instance); - expect(dragStart.called).is.true; - expect(dragStart.callCount).to.equal(1); + expect(dragStart).toHaveBeenCalled(); + expect(dragStart).toHaveBeenCalledTimes(1); }); it('should not invoke move unless a start is invoked', async () => { - const dragMove = spy(); + const dragMove = vi.fn(); instance.controller.set({ start: dragStart, move: dragMove }); simulatePointerMove(instance); await elementUpdated(instance); - expect(dragStart.called).is.false; - expect(dragMove.called).is.false; + expect(dragStart).not.toHaveBeenCalled(); + expect(dragMove).not.toHaveBeenCalled(); }); it('should invoke move when moving the dragged element around the viewport', async () => { - const dragMove = spy(); + const dragMove = vi.fn(); instance.controller.set({ start: dragStart, move: dragMove }); simulatePointerDown(instance); @@ -164,55 +172,55 @@ describe('Drag controller', () => { ); await elementUpdated(instance); - expect(dragStart.called).is.true; - expect(dragMove.called).is.true; - expect(dragMove.callCount).to.equal(10); + expect(dragStart).toHaveBeenCalled(); + expect(dragMove).toHaveBeenCalled(); + expect(dragMove).toHaveBeenCalledTimes(10); }); it('should invoke end when releasing the dragged element', async () => { - const dragEnd = spy(); + const dragEnd = vi.fn(); instance.controller.set({ start: dragStart, end: dragEnd }); simulatePointerDown(instance); simulateLostPointerCapture(instance); await elementUpdated(instance); - expect(dragStart.callCount).to.equal(1); - expect(dragEnd.callCount).to.equal(1); + expect(dragStart).toHaveBeenCalledTimes(1); + expect(dragEnd).toHaveBeenCalledTimes(1); }); it('should invoke cancel when pressing Escape during a drag operation', async () => { - const dragCancel = spy(); + const dragCancel = vi.fn(); instance.controller.set({ start: dragStart, cancel: dragCancel }); simulatePointerDown(instance); await elementUpdated(instance); - expect(dragStart.called).is.true; + expect(dragStart).toHaveBeenCalled(); simulateKeyboard(instance, escapeKey); await elementUpdated(instance); - expect(dragCancel.called).is.true; + expect(dragCancel).toHaveBeenCalled(); }); it('should not invoke cancel when pressing Escape outside of drag operation', async () => { // Sanity check since the Escape key handler is a root level dynamic listener. - const dragCancel = spy(); + const dragCancel = vi.fn(); instance.controller.set({ cancel: dragCancel }); simulateKeyboard(instance, escapeKey); await elementUpdated(instance); - expect(dragCancel.called).is.false; + expect(dragCancel).not.toHaveBeenCalled(); }); }); describe('Immediate mode - advanced element dragging', () => { - const dragStart = spy(); + const dragStart = vi.fn(); - function getCallbackArgs(fn: SinonSpy) { - return last(last(fn.args)) as DragCallbackParameters; + function getCallbackArgs(fn: MockInstance) { + return last(last(fn.mock.calls)) as DragCallbackParameters; } beforeEach(async () => { @@ -234,45 +242,45 @@ describe('Drag controller', () => { }); afterEach(() => { - dragStart.resetHistory(); + dragStart.mockClear(); }); it('should not start a drag operation when `skip` is set and returns true', async () => { const button = instance.querySelector('button')!; - const skip = spy((event: PointerEvent) => event.target === button); + const skip = vi.fn((event: PointerEvent) => event.target === button); instance.controller.set({ start: dragStart, skip }); simulatePointerDown(button); await elementUpdated(instance); - expect(dragStart.called).is.false; - expect(skip.called).is.true; - expect(skip.returned(true)).is.true; + expect(dragStart).not.toHaveBeenCalled(); + expect(skip).toHaveBeenCalled(); + expect(skip).toHaveReturnedWith(true); simulatePointerDown(instance); await elementUpdated(instance); - expect(dragStart.called).is.true; - expect(skip.called).is.true; - expect(skip.returned(false)).is.true; + expect(dragStart).toHaveBeenCalled(); + expect(skip).toHaveBeenCalled(); + expect(skip).toHaveReturnedWith(false); }); it('should start a drag operation only from the element returned from the `trigger` invocation', async () => { const button = instance.querySelector('button')!; - const trigger = spy(() => button); + const trigger = vi.fn(() => button); instance.controller.set({ start: dragStart, trigger }); simulatePointerDown(instance); await elementUpdated(instance); - expect(dragStart.called).is.false; + expect(dragStart).not.toHaveBeenCalled(); simulatePointerDown(button); await elementUpdated(instance); - expect(dragStart.called).is.true; + expect(dragStart).toHaveBeenCalled(); }); it('should adhere to `snapToCursor` option on drag start', async () => { @@ -321,7 +329,7 @@ describe('Drag controller', () => { }); it('should pass correct parameter state in the move callback', async () => { - const move = spy(); + const move = vi.fn(); instance.controller.set({ start: dragStart, move }); const { x: clientX, y: clientY } = getCenterPoint(instance); @@ -341,7 +349,7 @@ describe('Drag controller', () => { }); it('should pass correct parameter state in end callback', async () => { - const end = spy(); + const end = vi.fn(); instance.controller.set({ end }); const { x: clientX, y: clientY } = getCenterPoint(instance); @@ -359,8 +367,8 @@ describe('Drag controller', () => { it('should invoke the `enter` callback when the `match` callback returns true', async () => { const target = document.querySelector('.target')!; - const matchTarget = spy((element: Element) => target === element); - const enter = spy(); + const matchTarget = vi.fn((element: Element) => target === element); + const enter = vi.fn(); const { x: clientX, y: clientY } = target.getBoundingClientRect(); @@ -370,16 +378,16 @@ describe('Drag controller', () => { simulatePointerMove(instance, { clientX, clientY }); await elementUpdated(instance); - expect(matchTarget.called).is.true; - expect(enter.called).is.true; + expect(matchTarget).toHaveBeenCalled(); + expect(enter).toHaveBeenCalled(); expect(getCallbackArgs(enter).state.element).to.eql(target); }); it('should not invoke the `enter` callback when the `match` callback returns false', async () => { const target = document.querySelector('.target')!; - const matchTarget = spy(() => false); - const enter = spy(); + const matchTarget = vi.fn(() => false); + const enter = vi.fn(); const { x: clientX, y: clientY } = target.getBoundingClientRect(); @@ -389,15 +397,15 @@ describe('Drag controller', () => { simulatePointerMove(instance, { clientX, clientY }); await elementUpdated(instance); - expect(matchTarget.called).is.true; - expect(enter.called).is.false; + expect(matchTarget).toHaveBeenCalled(); + expect(enter).not.toHaveBeenCalled(); }); it('should invoke the `leave` callback when leaving the boundaries of a matched element', async () => { const target = document.querySelector('.target')!; - const matchTarget = spy((element: Element) => target === element); - const enter = spy(); - const leave = spy(); + const matchTarget = vi.fn((element: Element) => target === element); + const enter = vi.fn(); + const leave = vi.fn(); const { x: clientX, y: clientY } = target.getBoundingClientRect(); @@ -407,22 +415,22 @@ describe('Drag controller', () => { simulatePointerMove(instance, { clientX, clientY }); await elementUpdated(instance); - expect(matchTarget.called).is.true; - expect(enter.called).is.true; + expect(matchTarget).toHaveBeenCalled(); + expect(enter).toHaveBeenCalled(); expect(getCallbackArgs(enter).state.element).to.eql(target); simulatePointerMove(instance, { clientX: 0, clientY: 0 }); await elementUpdated(instance); - expect(leave.called).is.true; + expect(leave).toHaveBeenCalled(); expect(getCallbackArgs(leave).state.element).to.eql(target); }); it('should invoke the `over` callback while dragging over a matched element', async () => { const target = document.querySelector('.target')!; - const matchTarget = spy((element: Element) => target === element); - const over = spy(); + const matchTarget = vi.fn((element: Element) => target === element); + const over = vi.fn(); instance.controller.set({ matchTarget, over }); @@ -433,19 +441,19 @@ describe('Drag controller', () => { await elementUpdated(instance); // Not called on initial drag enter - expect(over.called).is.false; + expect(over).not.toHaveBeenCalled(); // 5 pointer moves over the matched element simulatePointerMove(instance, { clientX, clientY }, { x: 5, y: 5 }, 5); await elementUpdated(instance); - expect(over.callCount).to.equal(5); + expect(over).toHaveBeenCalledTimes(5); expect(getCallbackArgs(over).state.element).to.eql(target); }); }); describe('Deferred mode - basic element dragging', () => { - const dragStart = spy(); + const dragStart = vi.fn(); function createGhost() { const clone = instance.cloneNode() as HTMLElement; @@ -479,7 +487,7 @@ describe('Drag controller', () => { }); afterEach(() => { - dragStart.resetHistory(); + dragStart.mockClear(); }); it('should not start drag operation when disabled', async () => { @@ -488,7 +496,7 @@ describe('Drag controller', () => { simulatePointerDown(instance); await elementUpdated(instance); - expect(dragStart.called).is.false; + expect(dragStart).not.toHaveBeenCalled(); }); it('should not start a drag operation on a non-primary button interaction', async () => { @@ -497,18 +505,18 @@ describe('Drag controller', () => { simulatePointerDown(instance, { button: 1 }); await elementUpdated(instance); - expect(dragStart.called).is.false; + expect(dragStart).not.toHaveBeenCalled(); }); it('should not start a drag operation when a skip callback returns true', async () => { - const skip = spy(() => true); + const skip = vi.fn(() => true); instance.controller.set({ skip, start: dragStart }); simulatePointerDown(instance); await elementUpdated(instance); - expect(skip.called).is.true; - expect(dragStart.called).is.false; + expect(skip).toHaveBeenCalled(); + expect(dragStart).not.toHaveBeenCalled(); }); it('should apply correct internal styles on drag operation', async () => { @@ -553,7 +561,7 @@ describe('Drag controller', () => { }); it('should correctly fallback to the host as a container if the layer callbacks return falsy', async () => { - const layer = spy((): HTMLElement => null as unknown as HTMLElement); + const layer = vi.fn((): HTMLElement => null as unknown as HTMLElement); instance.controller.set({ layer }); simulatePointerDown(instance); @@ -563,7 +571,7 @@ describe('Drag controller', () => { }); it('should correctly place the ghost element in the configured layer container', async () => { - const layer = spy(() => instance.parentElement!); + const layer = vi.fn(() => instance.parentElement!); instance.controller.set({ layer }); simulatePointerDown(instance); @@ -578,23 +586,23 @@ describe('Drag controller', () => { simulatePointerDown(instance); await elementUpdated(instance); - expect(dragStart.called).is.true; - expect(dragStart.callCount).to.equal(1); + expect(dragStart).toHaveBeenCalled(); + expect(dragStart).toHaveBeenCalledTimes(1); }); it('should not invoke move unless a start is invoked', async () => { - const dragMove = spy(); + const dragMove = vi.fn(); instance.controller.set({ start: dragStart, move: dragMove }); simulatePointerMove(instance); await elementUpdated(instance); - expect(dragStart.called).is.false; - expect(dragMove.called).is.false; + expect(dragStart).not.toHaveBeenCalled(); + expect(dragMove).not.toHaveBeenCalled(); }); it('should invoke move when moving the dragged element around the viewport', async () => { - const dragMove = spy(); + const dragMove = vi.fn(); instance.controller.set({ start: dragStart, move: dragMove }); simulatePointerDown(instance); @@ -606,57 +614,57 @@ describe('Drag controller', () => { ); await elementUpdated(instance); - expect(dragStart.called).is.true; - expect(dragMove.called).is.true; - expect(dragMove.callCount).to.equal(10); + expect(dragStart).toHaveBeenCalled(); + expect(dragMove).toHaveBeenCalled(); + expect(dragMove).toHaveBeenCalledTimes(10); }); it('should invoke end when releasing the dragged element', async () => { - const dragEnd = spy(); + const dragEnd = vi.fn(); instance.controller.set({ start: dragStart, end: dragEnd }); simulatePointerDown(instance); simulateLostPointerCapture(instance); await elementUpdated(instance); - expect(dragStart.callCount).to.equal(1); - expect(dragEnd.callCount).to.equal(1); + expect(dragStart).toHaveBeenCalledTimes(1); + expect(dragEnd).toHaveBeenCalledTimes(1); expect(getDefaultGhost()).is.null; }); it('should invoke cancel when pressing Escape during a drag operation', async () => { - const dragCancel = spy(() => instance.controller.dispose()); + const dragCancel = vi.fn(() => instance.controller.dispose()); instance.controller.set({ start: dragStart, cancel: dragCancel }); simulatePointerDown(instance); await elementUpdated(instance); - expect(dragStart.called).is.true; + expect(dragStart).toHaveBeenCalled(); simulateKeyboard(instance, escapeKey); await elementUpdated(instance); - expect(dragCancel.called).is.true; + expect(dragCancel).toHaveBeenCalled(); expect(getDefaultGhost()).is.null; }); it('should not invoke cancel when pressing Escape outside of drag operation', async () => { // Sanity check since the Escape key handler is a root level dynamic listener. - const dragCancel = spy(); + const dragCancel = vi.fn(); instance.controller.set({ cancel: dragCancel }); simulateKeyboard(instance, escapeKey); await elementUpdated(instance); - expect(dragCancel.called).is.false; + expect(dragCancel).not.toHaveBeenCalled(); }); }); describe('Deferred mode - advanced element dragging', () => { - const dragStart = spy(); + const dragStart = vi.fn(); - function getCallbackArgs(fn: SinonSpy) { - return last(last(fn.args)) as DragCallbackParameters; + function getCallbackArgs(fn: MockInstance) { + return last(last(fn.mock.calls)) as DragCallbackParameters; } beforeEach(async () => { @@ -678,45 +686,45 @@ describe('Drag controller', () => { }); afterEach(() => { - dragStart.resetHistory(); + dragStart.mockClear(); }); it('should not start a drag operation when `skip` is set and returns true', async () => { const button = instance.querySelector('button')!; - const skip = spy((event: PointerEvent) => event.target === button); + const skip = vi.fn((event: PointerEvent) => event.target === button); instance.controller.set({ start: dragStart, skip }); simulatePointerDown(button); await elementUpdated(instance); - expect(dragStart.called).is.false; - expect(skip.called).is.true; - expect(skip.returned(true)).is.true; + expect(dragStart).not.toHaveBeenCalled(); + expect(skip).toHaveBeenCalled(); + expect(skip).toHaveReturnedWith(true); simulatePointerDown(instance); await elementUpdated(instance); - expect(dragStart.called).is.true; - expect(skip.called).is.true; - expect(skip.returned(false)).is.true; + expect(dragStart).toHaveBeenCalled(); + expect(skip).toHaveBeenCalled(); + expect(skip).toHaveReturnedWith(false); }); it('should start a drag operation only from the element returned from the `trigger` invocation', async () => { const button = instance.querySelector('button')!; - const trigger = spy(() => button); + const trigger = vi.fn(() => button); instance.controller.set({ start: dragStart, trigger }); simulatePointerDown(instance); await elementUpdated(instance); - expect(dragStart.called).is.false; + expect(dragStart).not.toHaveBeenCalled(); simulatePointerDown(button); await elementUpdated(instance); - expect(dragStart.called).is.true; + expect(dragStart).toHaveBeenCalled(); }); it('should adhere to `snapToCursor` option on drag start', async () => { @@ -765,7 +773,7 @@ describe('Drag controller', () => { }); it('should pass correct parameter state in the move callback', async () => { - const move = spy(); + const move = vi.fn(); instance.controller.set({ start: dragStart, move }); const { x: clientX, y: clientY } = getCenterPoint(instance); @@ -785,7 +793,7 @@ describe('Drag controller', () => { }); it('should pass correct parameter state in end callback', async () => { - const end = spy(); + const end = vi.fn(); instance.controller.set({ end }); const { x: clientX, y: clientY } = getCenterPoint(instance); @@ -803,8 +811,8 @@ describe('Drag controller', () => { it('should invoke the `enter` callback when the `match` callback returns true', async () => { const target = document.querySelector('.target')!; - const matchTarget = spy((element: Element) => target === element); - const enter = spy(); + const matchTarget = vi.fn((element: Element) => target === element); + const enter = vi.fn(); const { x: clientX, y: clientY } = target.getBoundingClientRect(); @@ -814,16 +822,16 @@ describe('Drag controller', () => { simulatePointerMove(instance, { clientX, clientY }); await elementUpdated(instance); - expect(matchTarget.called).is.true; - expect(enter.called).is.true; + expect(matchTarget).toHaveBeenCalled(); + expect(enter).toHaveBeenCalled(); expect(getCallbackArgs(enter).state.element).to.eql(target); }); it('should not invoke the `enter` callback when the `match` callback returns false', async () => { const target = document.querySelector('.target')!; - const matchTarget = spy(() => false); - const enter = spy(); + const matchTarget = vi.fn(() => false); + const enter = vi.fn(); const { x: clientX, y: clientY } = target.getBoundingClientRect(); @@ -833,15 +841,15 @@ describe('Drag controller', () => { simulatePointerMove(instance, { clientX, clientY }); await elementUpdated(instance); - expect(matchTarget.called).is.true; - expect(enter.called).is.false; + expect(matchTarget).toHaveBeenCalled(); + expect(enter).not.toHaveBeenCalled(); }); it('should invoke the `leave` callback when leaving the boundaries of a matched element', async () => { const target = document.querySelector('.target')!; - const matchTarget = spy((element: Element) => target === element); - const enter = spy(); - const leave = spy(); + const matchTarget = vi.fn((element: Element) => target === element); + const enter = vi.fn(); + const leave = vi.fn(); const { x: clientX, y: clientY } = target.getBoundingClientRect(); @@ -851,22 +859,22 @@ describe('Drag controller', () => { simulatePointerMove(instance, { clientX, clientY }); await elementUpdated(instance); - expect(matchTarget.called).is.true; - expect(enter.called).is.true; + expect(matchTarget).toHaveBeenCalled(); + expect(enter).toHaveBeenCalled(); expect(getCallbackArgs(enter).state.element).to.eql(target); simulatePointerMove(instance, { clientX: 0, clientY: 0 }); await elementUpdated(instance); - expect(leave.called).is.true; + expect(leave).toHaveBeenCalled(); expect(getCallbackArgs(leave).state.element).to.eql(target); }); it('should invoke the `over` callback while dragging over a matched element', async () => { const target = document.querySelector('.target')!; - const matchTarget = spy((element: Element) => target === element); - const over = spy(); + const matchTarget = vi.fn((element: Element) => target === element); + const over = vi.fn(); instance.controller.set({ matchTarget, over }); @@ -877,13 +885,13 @@ describe('Drag controller', () => { await elementUpdated(instance); // Not called on initial drag enter - expect(over.called).is.false; + expect(over).not.toHaveBeenCalled(); // 5 pointer moves over the matched element simulatePointerMove(instance, { clientX, clientY }, { x: 5, y: 5 }, 5); await elementUpdated(instance); - expect(over.callCount).to.equal(5); + expect(over).toHaveBeenCalledTimes(5); expect(getCallbackArgs(over).state.element).to.eql(target); }); }); diff --git a/src/components/common/controllers/focus-ring.spec.ts b/src/components/common/controllers/focus-ring.spec.ts index cb3a9fb05..8232a63a1 100644 --- a/src/components/common/controllers/focus-ring.spec.ts +++ b/src/components/common/controllers/focus-ring.spec.ts @@ -1,12 +1,12 @@ +import { css, LitElement } from 'lit'; +import { beforeAll, beforeEach, describe, expect, it } from 'vitest'; import { defineCE, elementUpdated, - expect, fixture, html, unsafeStatic, -} from '@open-wc/testing'; -import { css, LitElement } from 'lit'; +} from '../../common/helpers.spec.js'; import { partMap } from '../part-map.js'; import { simulateClick, @@ -21,7 +21,7 @@ describe('Focus ring controller', () => { let tag: string; let instance: LitElement & { button: HTMLButtonElement }; - before(() => { + beforeAll(() => { tag = defineCE( class extends LitElement { public static override styles = css` diff --git a/src/components/common/controllers/gestures.spec.ts b/src/components/common/controllers/gestures.spec.ts index b616fab99..2b69e895c 100644 --- a/src/components/common/controllers/gestures.spec.ts +++ b/src/components/common/controllers/gestures.spec.ts @@ -1,12 +1,14 @@ +import { css, LitElement } from 'lit'; import { - defineCE, + afterEach, + beforeAll, + beforeEach, + describe, expect, - fixture, - html, - unsafeStatic, -} from '@open-wc/testing'; -import { css, LitElement } from 'lit'; -import { type SinonFakeTimers, useFakeTimers } from 'sinon'; + it, + vi, +} from 'vitest'; +import { defineCE, fixture, html, unsafeStatic } from '../helpers.spec.js'; import { simulateLostPointerCapture, simulatePointerDown, @@ -15,14 +17,13 @@ import { import { addGesturesController, type SwipeEvent } from './gestures.js'; describe('Gestures controller', () => { - let clock: SinonFakeTimers; let tag: string; let instance: LitElement & { gestures: ReturnType; events: SwipeEvent[]; }; - before(() => { + beforeAll(() => { tag = defineCE( class extends LitElement { public static override styles = css` @@ -64,10 +65,10 @@ describe('Gestures controller', () => { }); describe('Options', () => { - afterEach(() => clock.restore()); + afterEach(() => vi.useRealTimers()); beforeEach(async () => { - clock = useFakeTimers({ toFake: ['Date'] }); + vi.useFakeTimers({ toFake: ['Date'] }); }); it('correct default options', () => { @@ -97,14 +98,14 @@ describe('Gestures controller', () => { it('correctly takes `thresholdTime` into account', () => { instance.gestures.updateOptions({ thresholdTime: 350 }); simulatePointerDown(instance); - clock.tick(500); + vi.advanceTimersByTime(500); simulatePointerMove(instance, {}, { x: 250 }); simulateLostPointerCapture(instance); expect(instance.events).lengthOf(0); simulatePointerDown(instance); - clock.tick(350); + vi.advanceTimersByTime(350); simulatePointerMove(instance, {}, { x: 250 }); simulateLostPointerCapture(instance); diff --git a/src/components/common/controllers/key-bindings.spec.ts b/src/components/common/controllers/key-bindings.spec.ts index aa6d1eaf3..9c8532d95 100644 --- a/src/components/common/controllers/key-bindings.spec.ts +++ b/src/components/common/controllers/key-bindings.spec.ts @@ -1,11 +1,6 @@ -import { - defineCE, - expect, - fixture, - html, - unsafeStatic, -} from '@open-wc/testing'; import { LitElement } from 'lit'; +import { beforeAll, beforeEach, describe, expect, it } from 'vitest'; +import { defineCE, fixture, html, unsafeStatic } from '../helpers.spec.js'; import { simulateKeyboard } from '../utils.spec.js'; import { addKeybindings, @@ -24,7 +19,7 @@ describe('Key bindings controller', () => { input: HTMLInputElement; }; - before(() => { + beforeAll(() => { tag = defineCE( class extends LitElement { public key?: string; diff --git a/src/components/common/controllers/slot.spec.ts b/src/components/common/controllers/slot.spec.ts index 263258f24..8c6971831 100644 --- a/src/components/common/controllers/slot.spec.ts +++ b/src/components/common/controllers/slot.spec.ts @@ -1,12 +1,12 @@ +import { LitElement } from 'lit'; +import { beforeAll, beforeEach, describe, expect, it } from 'vitest'; import { defineCE, elementUpdated, - expect, fixture, html, unsafeStatic, -} from '@open-wc/testing'; -import { LitElement } from 'lit'; +} from '../helpers.spec.js'; import { first, last } from '../util.js'; import { addSlotController, @@ -27,7 +27,7 @@ describe('Slots controller', () => { }; let slots: TestSlotController; - before(() => { + beforeAll(() => { tag = defineCE( class extends LitElement { public readonly slotsCallbackResults: SlotChangeCallbackParameters< @@ -162,11 +162,9 @@ describe('Slots controller', () => { describe('Slot onChange callback', () => { beforeEach(async () => { - beforeEach(async () => { - const tagName = unsafeStatic(tag); - instance = await fixture(html`<${tagName}> { diff --git a/src/components/common/equal.spec.ts b/src/components/common/equal.spec.ts index 10ea4e3be..0fc9ed91f 100644 --- a/src/components/common/equal.spec.ts +++ b/src/components/common/equal.spec.ts @@ -1,4 +1,4 @@ -import { expect } from '@open-wc/testing'; +import { describe, expect, it } from 'vitest'; import { equal } from './util.js'; describe('equal', () => { diff --git a/src/components/common/helpers.spec.ts b/src/components/common/helpers.spec.ts new file mode 100644 index 000000000..e59fbd28f --- /dev/null +++ b/src/components/common/helpers.spec.ts @@ -0,0 +1,145 @@ +import { type RenderOptions, render, type TemplateResult } from 'lit'; +import { html, unsafeStatic } from 'lit/static-html.js'; +import { beforeEach } from 'vitest'; +import { page } from 'vitest/browser'; +import type { Constructor } from './mixins/constructor.js'; + +interface LitTemplateResult { + processor: any; + strings: TemplateStringsArray; + type: string; + values: readonly unknown[]; +} + +type RenderResult = + | LitTemplateResult + | TemplateResult + | TemplateResult[] + | Node + | Node[] + | string + | string[] + | number + | number[] + | boolean + | boolean[] + | null + | undefined; + +type FixtureOptions = { + container?: HTMLElement; + options?: RenderOptions; +}; + +let defineCECounter = 0; +const containers = new Set(); + +export async function fixture( + template: RenderResult, + options: FixtureOptions = {} +) { + const container = + options?.container ?? + document.body.appendChild(document.createElement('div')); + + containers.add(container); + render(template, container, options.options); + + const element = container.firstElementChild as T; + await elementUpdated(element); + + return element; +} + +/** Clean up all rendered fixtures */ +export function cleanup(): void { + for (const container of containers) { + container.remove(); + } + containers.clear(); +} + +/** Define a custom element with a unique tag name for testing */ +export function defineCE( + _class: Constructor +): string { + const uniqueTagName = `test-${defineCECounter}`; + customElements.define(uniqueTagName, _class); + defineCECounter += 1; + return uniqueTagName; +} + +/** Wait for the next animation frame */ +export async function nextFrame(): Promise { + return new Promise((resolve) => requestAnimationFrame(() => resolve())); +} + +/** Wait for a specific amount of time */ +export function aTimeout(timeout: number): Promise { + return new Promise((resolve) => setTimeout(() => resolve(), timeout)); +} + +/** Wait for an element to finish updating */ +export async function elementUpdated( + element: T +): Promise { + let hasSpecificUpdate = false; + + if (element && 'updateComplete' in element) { + await element.updateComplete; + hasSpecificUpdate = true; + } + + if (!hasSpecificUpdate) { + await nextFrame(); + } + + return element; +} + +/** Wait until a predicate is true or a timeout occurs */ +export function waitUntil( + predicate: () => unknown | Promise, + message?: string, + options: { interval?: number; timeout?: number } = {} +): Promise { + const { interval = 50, timeout = 1000 } = options; + const { stack } = new Error(); + + return new Promise((resolve, reject) => { + let timeoutId: ReturnType | undefined; + + setTimeout(() => { + clearTimeout(timeoutId); + + const error = new Error( + message + ? `Timeout: ${message}` + : `waitUntil timed out after ${timeout}ms` + ); + error.stack = stack ?? ''; + reject(error); + }, timeout); + + async function nextInterval() { + try { + if (await predicate()) { + resolve(); + } else { + timeoutId = setTimeout(() => { + nextInterval(); + }, interval); + } + } catch (error) { + reject(error); + } + } + + nextInterval(); + }); +} + +page.extend({ fixture, [Symbol.for('vitest:component-cleanup')]: cleanup }); +beforeEach(() => cleanup()); + +export { html, unsafeStatic }; diff --git a/src/components/common/i18n/i18n.spec.ts b/src/components/common/i18n/i18n.spec.ts index 2d622e89c..05fbc0d50 100644 --- a/src/components/common/i18n/i18n.spec.ts +++ b/src/components/common/i18n/i18n.spec.ts @@ -1,11 +1,3 @@ -import { - defineCE, - elementUpdated, - expect, - fixture, - html, - unsafeStatic, -} from '@open-wc/testing'; import { ComboResourceStringsEN, getI18nManager, @@ -16,6 +8,14 @@ import { } from 'igniteui-i18n-core'; import { ResourceStringsBG } from 'igniteui-i18n-resources'; import { LitElement } from 'lit'; +import { beforeAll, beforeEach, describe, expect, it } from 'vitest'; +import { + defineCE, + elementUpdated, + fixture, + html, + unsafeStatic, +} from '../helpers.spec.js'; import { type IgcDateRangePickerResourceStrings, IgcDateRangePickerResourceStringsEN, @@ -56,7 +56,7 @@ describe('Localization', () => { i18nController: I18nController; }; - before(() => { + beforeAll(() => { tagOld = defineCE( class extends TestLocalizedClass { public override get defaultEN() { diff --git a/src/components/common/mixins/form-associated.spec.ts b/src/components/common/mixins/form-associated.spec.ts index fded52c85..dadb8cb5d 100644 --- a/src/components/common/mixins/form-associated.spec.ts +++ b/src/components/common/mixins/form-associated.spec.ts @@ -1,13 +1,8 @@ -import { - defineCE, - expect, - fixture, - html, - unsafeStatic, -} from '@open-wc/testing'; import { LitElement } from 'lit'; import { ifDefined } from 'lit/directives/if-defined.js'; import type { StaticValue } from 'lit/static-html.js'; +import { beforeAll, describe, expect, it } from 'vitest'; +import { defineCE, fixture, html, unsafeStatic } from '../helpers.spec.js'; import { maxLengthValidator, minLengthValidator, @@ -39,7 +34,7 @@ describe('Form associated mixin tests', () => { let tagName: StaticValue; let instance: FormAssociatedTestInstance; - before(() => { + beforeAll(() => { tag = defineCE( class Foo extends FormAssociatedRequiredMixin(LitElement) { static override properties = { diff --git a/src/components/common/utils.spec.ts b/src/components/common/utils.spec.ts index 168267606..9af65323d 100644 --- a/src/components/common/utils.spec.ts +++ b/src/components/common/utils.spec.ts @@ -1,15 +1,10 @@ -import { - elementUpdated, - expect, - fixture, - html, - nextFrame, -} from '@open-wc/testing'; -import type { TemplateResult } from 'lit'; +import { html, type TemplateResult } from 'lit'; +import { expect, type MockInstance } from 'vitest'; import { type CalendarDay, toCalendarDay } from '../calendar/model.js'; import { parseKeys } from './controllers/key-bindings.js'; +import { elementUpdated, fixture, nextFrame } from './helpers.spec.js'; import type { IgcFormControl } from './mixins/forms/types.js'; -import { toKebabCase } from './util.js'; +import { equal, toKebabCase } from './util.js'; export function createFormAssociatedTestBed( template: TemplateResult @@ -464,3 +459,55 @@ export function compareStyles( export function checkDatesEqual(a: CalendarDay | Date, b: CalendarDay | Date) { expect(toCalendarDay(a).equalTo(toCalendarDay(b))).to.be.true; } + +/** * Checks whether a spy was called with a specific event name. */ +export function eventMatch(spy: MockInstance, eventName: string): boolean { + return spy.mock.calls.some((call: unknown[]) => call[0] === eventName); +} + +/** * Checks whether a spy was called with a specific event name and arguments. */ +export function eventArgsMatch( + spy: MockInstance, + eventName: string, + args: unknown +): boolean { + return spy.mock.calls.some((call: unknown[]) => { + return call[0] === eventName && equal(call[1], args); + }); +} + +/** * Returns all calls of a spy with a specific event name. */ +export function getEvents(spy: MockInstance, eventName: string): any[] { + return spy.mock.calls.filter((call: unknown[]) => call[0] === eventName); +} + +/** * Expects a spy to have been called with a specific event name. */ +export function expectCalledWith(spy: MockInstance, eventName: string): void { + expect(eventMatch(spy, eventName)).to.be.true; +} + +/** * Expects a spy to not have been called with a specific event name. */ +export function expectNotCalledWith( + spy: MockInstance, + eventName: string +): void { + expect(eventMatch(spy, eventName)).to.be.false; +} + +/** * Expects a spy to have been called with a specific event name and arguments. */ +export function expectCalledWithArgs( + spy: MockInstance, + eventName: string, + args: unknown +): void { + expect(eventArgsMatch(spy, eventName, args)).to.be.true; +} + +/** * Expects a spy to not have been called with a specific event name and arguments. */ +export function expectNotCalledWithArgs( + spy: MockInstance, + eventName: string, + args: unknown +): void { + expect(eventArgsMatch(spy, eventName, args)).to.be.false; +} diff --git a/src/components/common/validity-helpers.spec.ts b/src/components/common/validity-helpers.spec.ts index 649372d8c..5cf88bf6e 100644 --- a/src/components/common/validity-helpers.spec.ts +++ b/src/components/common/validity-helpers.spec.ts @@ -1,6 +1,7 @@ -import { elementUpdated, expect } from '@open-wc/testing'; +import { expect } from 'vitest'; import IgcValidationContainerComponent from '../validation-container/validation-container.js'; import type { IgniteComponent } from './definitions/register.js'; +import { elementUpdated } from './helpers.spec.js'; import type { Constructor } from './mixins/constructor.js'; import type { IgcFormControl } from './mixins/forms/types.js'; import { isEmpty, toKebabCase } from './util.js'; diff --git a/src/components/date-picker/date-picker-form.spec.ts b/src/components/date-picker/date-picker-form.spec.ts index 404ed710c..68a5224c1 100644 --- a/src/components/date-picker/date-picker-form.spec.ts +++ b/src/components/date-picker/date-picker-form.spec.ts @@ -1,7 +1,8 @@ -import { elementUpdated, expect, fixture, html } from '@open-wc/testing'; +import { beforeAll, beforeEach, describe, expect, it } from 'vitest'; import { CalendarDay, toCalendarDay } from '../calendar/model.js'; import { type DateRangeDescriptor, DateRangeType } from '../calendar/types.js'; import { defineComponents } from '../common/definitions/defineComponents.js'; +import { elementUpdated, fixture, html } from '../common/helpers.spec.js'; import { equal } from '../common/util.js'; import { createFormAssociatedTestBed, @@ -16,7 +17,7 @@ import IgcDateTimeInputComponent from '../date-time-input/date-time-input.js'; import IgcDatePickerComponent from './date-picker.js'; describe('igc-datepicker form integration', () => { - before(() => defineComponents(IgcDatePickerComponent)); + beforeAll(() => defineComponents(IgcDatePickerComponent)); function checkDatesEqual(a: CalendarDay | Date, b: CalendarDay | Date) { expect(equal(toCalendarDay(a), toCalendarDay(b))).to.be.true; diff --git a/src/components/date-picker/date-picker.spec.ts b/src/components/date-picker/date-picker.spec.ts index 9f0b8e3d7..fc1454b15 100644 --- a/src/components/date-picker/date-picker.spec.ts +++ b/src/components/date-picker/date-picker.spec.ts @@ -1,5 +1,4 @@ -import { elementUpdated, expect, fixture, html } from '@open-wc/testing'; -import { spy } from 'sinon'; +import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; import IgcCalendarComponent from '../calendar/calendar.js'; import { getCalendarDOM, @@ -15,8 +14,11 @@ import { escapeKey, } from '../common/controllers/key-bindings.js'; import { defineComponents } from '../common/definitions/defineComponents.js'; +import { elementUpdated, fixture, html } from '../common/helpers.spec.js'; import { equal } from '../common/util.js'; import { + expectCalledWith, + expectNotCalledWith, isFocused, simulateClick, simulateKeyboard, @@ -25,7 +27,7 @@ import IgcDateTimeInputComponent from '../date-time-input/date-time-input.js'; import IgcDatePickerComponent from './date-picker.js'; describe('Date picker', () => { - before(() => defineComponents(IgcDatePickerComponent)); + beforeAll(() => defineComponents(IgcDatePickerComponent)); const pickerShowIcon = 'today'; const pickerClearIcon = 'input_clear'; @@ -307,17 +309,17 @@ describe('Date picker', () => { it('should show/hide the picker based on the value of the open attribute', async () => { expect(picker.open).to.equal(false); picker.open = true; - const eventSpy = spy(picker, 'emitEvent'); + const spy = vi.spyOn(picker, 'emitEvent'); await elementUpdated(picker); expect(picker.open).to.equal(true); - expect(eventSpy).not.called; + expect(spy).not.toHaveBeenCalled(); picker.open = false; await elementUpdated(picker); expect(picker.open).to.equal(false); - expect(eventSpy).not.called; + expect(spy).not.toHaveBeenCalled(); }); it('should set prompt char correctly', async () => { @@ -334,12 +336,12 @@ describe('Date picker', () => { await picker.show(); - const eventSpy = spy(picker, 'emitEvent'); + const spy = vi.spyOn(picker, 'emitEvent'); selectCurrentDate(calendar); await elementUpdated(picker); - expect(eventSpy).calledOnce; + expect(spy).toHaveBeenCalledTimes(1); checkDatesEqual(picker.value as Date, currentDate); checkDatesEqual(calendar.value as Date, currentDate); @@ -369,19 +371,19 @@ describe('Date picker', () => { await picker.show(); - const eventSpy = spy(picker, 'emitEvent'); + const spy = vi.spyOn(picker, 'emitEvent'); selectCurrentDate(calendar); await elementUpdated(picker); - expect(eventSpy).calledWith('igcChange'); + expectCalledWith(spy, 'igcChange'); checkDatesEqual(picker.value as Date, currentDate); - eventSpy.resetHistory(); + spy.mockClear(); simulateKeyboard(dateTimeInput, '1'); await elementUpdated(picker); - expect(eventSpy).not.called; + expect(spy).not.toHaveBeenCalled(); checkDatesEqual(picker.value as Date, currentDate); }); @@ -394,22 +396,22 @@ describe('Date picker', () => { await picker.show(); - const eventSpy = spy(picker, 'emitEvent'); - const calendarEventSpy = spy(calendar, 'emitEvent'); + const spy = vi.spyOn(picker, 'emitEvent'); + const calendarEventSpy = vi.spyOn(calendar, 'emitEvent'); selectCurrentDate(calendar); await elementUpdated(picker); - expect(eventSpy).not.calledWith('igcChange'); - expect(calendarEventSpy).calledWith('igcChange'); + expect(spy).not.toHaveBeenCalledWith('igcChange'); + expectCalledWith(calendarEventSpy, 'igcChange'); checkDatesEqual(picker.value as Date, tomorrowDate); checkDatesEqual(calendar.value as Date, tomorrowDate); - eventSpy.resetHistory(); + spy.mockClear(); simulateKeyboard(dateTimeInput, '1'); await elementUpdated(picker); - expect(eventSpy).not.called; + expect(spy).not.toHaveBeenCalled(); checkDatesEqual(picker.value as Date, tomorrowDate); }); @@ -569,7 +571,7 @@ describe('Date picker', () => { it('should default inputFormat to whatever Intl.DateTimeFormat returns for the current locale', async () => { const defaultFormat = 'MM/dd/yyyy'; - expect(picker.locale).to.equal('en-US'); + expect(picker.locale).to.equal('en'); expect(picker.inputFormat).to.equal(defaultFormat); picker.locale = 'fr'; @@ -579,7 +581,7 @@ describe('Date picker', () => { }); it('should use the value of locale format for displayFormat, if it is not defined', async () => { - expect(picker.locale).to.equal('en-US'); + expect(picker.locale).to.equal('en'); expect(picker.getAttribute('display-format')).to.be.null; expect(picker.displayFormat).to.equal('M/d/yyyy'); @@ -624,27 +626,27 @@ describe('Date picker', () => { }); it('should open/close the picker on invoking show/hide/toggle and not emit events', async () => { - const eventSpy = spy(picker, 'emitEvent'); + const spy = vi.spyOn(picker, 'emitEvent'); expect(picker.open).to.be.false; await picker.show(); - expect(eventSpy).not.called; + expect(spy).not.toHaveBeenCalled(); expect(picker.open).to.be.true; await picker.hide(); - expect(eventSpy).not.called; + expect(spy).not.toHaveBeenCalled(); expect(picker.open).to.be.false; await picker.toggle(); - expect(eventSpy).not.called; + expect(spy).not.toHaveBeenCalled(); expect(picker.open).to.be.true; await picker.toggle(); - expect(eventSpy).not.called; + expect(spy).not.toHaveBeenCalled(); expect(picker.open).to.be.false; }); @@ -661,22 +663,22 @@ describe('Date picker', () => { }); it('should delegate stepUp and stepDown to the igc-date-time-input', async () => { - const stepUpSpy = spy(dateTimeInput, 'stepUp'); - const stepDownSpy = spy(dateTimeInput, 'stepDown'); + const stepUpSpy = vi.spyOn(dateTimeInput, 'stepUp'); + const stepDownSpy = vi.spyOn(dateTimeInput, 'stepDown'); picker.stepUp(); await elementUpdated(picker); - expect(stepUpSpy).called; + expect(stepUpSpy).toHaveBeenCalled(); picker.stepDown(); await elementUpdated(picker); - expect(stepDownSpy).called; + expect(stepDownSpy).toHaveBeenCalled(); }); it('should select the text in the input with the select method', async () => { - const selectSpy = spy(dateTimeInput, 'select'); + const selectSpy = vi.spyOn(dateTimeInput, 'select'); picker.value = new Date(); await elementUpdated(picker); @@ -685,13 +687,13 @@ describe('Date picker', () => { picker.select(); await elementUpdated(picker); - expect(selectSpy).to.be.called; + expect(selectSpy).toHaveBeenCalled(); expect(input.selectionStart).to.eq(0); expect(input.selectionEnd).to.eq(input.value.length); }); it('should set the text selection range in the input with setSelectionRange()', async () => { - const selectionRangeSpy = spy(dateTimeInput, 'setSelectionRange'); + const selectionRangeSpy = vi.spyOn(dateTimeInput, 'setSelectionRange'); picker.value = new Date(); await elementUpdated(picker); @@ -700,13 +702,13 @@ describe('Date picker', () => { picker.setSelectionRange(0, 2); await elementUpdated(picker); - expect(selectionRangeSpy).to.be.called; + expect(selectionRangeSpy).toHaveBeenCalled(); expect(input.selectionStart).to.eq(0); expect(input.selectionEnd).to.eq(2); }); it('should replace the selected text in the input and re-apply the mask with setRangeText()', async () => { - const setRangeTextSpy = spy(dateTimeInput, 'setRangeText'); + const setRangeTextSpy = vi.spyOn(dateTimeInput, 'setRangeText'); picker.value = new Date(2024, 2, 21); const expectedValue = new Date(2023, 2, 21); await elementUpdated(picker); @@ -715,7 +717,7 @@ describe('Date picker', () => { picker.setRangeText('2023', 6, 10); await elementUpdated(picker); - expect(setRangeTextSpy).to.be.called; + expect(setRangeTextSpy).toHaveBeenCalled(); checkDatesEqual(new Date(input.value), expectedValue); checkDatesEqual(picker.value!, expectedValue); @@ -725,22 +727,22 @@ describe('Date picker', () => { describe('Interactions', () => { it('should close the picker when in open state on pressing Escape', async () => { - const eventSpy = spy(picker, 'emitEvent'); + const eventSpy = vi.spyOn(picker, 'emitEvent'); picker.focus(); simulateKeyboard(picker, escapeKey); await elementUpdated(picker); - expect(eventSpy).not.called; + expect(eventSpy).not.toHaveBeenCalled(); await picker.show(); simulateKeyboard(picker, escapeKey); await elementUpdated(picker); - expect(eventSpy).calledTwice; - expect(eventSpy).calledWith('igcClosing'); - expect(eventSpy).calledWith('igcClosed'); - eventSpy.resetHistory(); + expect(eventSpy).toHaveBeenCalledTimes(2); + expectCalledWith(eventSpy, 'igcClosing'); + expectCalledWith(eventSpy, 'igcClosed'); + eventSpy.mockClear(); // dialog mode picker.mode = 'dialog'; @@ -749,13 +751,13 @@ describe('Date picker', () => { simulateKeyboard(picker, escapeKey); await elementUpdated(picker); - expect(eventSpy).calledTwice; - expect(eventSpy).calledWith('igcClosing'); - expect(eventSpy).calledWith('igcClosed'); + expect(eventSpy).toHaveBeenCalledTimes(2); + expectCalledWith(eventSpy, 'igcClosing'); + expectCalledWith(eventSpy, 'igcClosed'); }); it('should open the calendar picker on Alt + ArrowDown and close it on Alt + ArrowUp - dropdown mode', async () => { - const eventSpy = spy(picker, 'emitEvent'); + const eventSpy = vi.spyOn(picker, 'emitEvent'); expect(picker.open).to.be.false; picker.focus(); simulateKeyboard(picker, [altKey, arrowDown]); @@ -763,22 +765,21 @@ describe('Date picker', () => { expect(picker.open).to.be.true; - expect(eventSpy).calledWith('igcOpening'); - expect(eventSpy).calledWith('igcOpened'); - - eventSpy.resetHistory(); + expect(eventSpy).toHaveBeenCalledWith('igcOpening', { cancelable: true }); + expect(eventSpy).toHaveBeenCalledWith('igcOpened'); + eventSpy.mockClear(); simulateKeyboard(picker, [altKey, arrowUp]); await elementUpdated(picker); expect(picker.open).to.be.false; - expect(eventSpy).calledWith('igcClosing'); - expect(eventSpy).calledWith('igcClosed'); - eventSpy.resetHistory(); + expect(eventSpy).toHaveBeenCalledWith('igcClosing', { cancelable: true }); + expect(eventSpy).toHaveBeenCalledWith('igcClosed'); + eventSpy.mockClear(); }); it('should open the calendar picker on Alt + ArrowDown and close it on Alt + ArrowUp - dialog mode', async () => { - const eventSpy = spy(picker, 'emitEvent'); + const eventSpy = vi.spyOn(picker, 'emitEvent'); expect(picker.open).to.be.false; picker.focus(); picker.mode = 'dialog'; @@ -791,28 +792,28 @@ describe('Date picker', () => { expect(picker.open).to.be.true; expect(dialog).not.to.be.undefined; expect(dialog?.open).to.be.true; - expect(eventSpy).calledWith('igcOpening'); - expect(eventSpy).calledWith('igcOpened'); - eventSpy.resetHistory(); + expect(eventSpy).toHaveBeenCalledWith('igcOpening', { cancelable: true }); + expect(eventSpy).toHaveBeenCalledWith('igcOpened'); + eventSpy.mockClear(); simulateKeyboard(picker, [altKey, arrowUp]); await elementUpdated(picker); expect(picker.open).to.be.false; - expect(eventSpy).calledWith('igcClosing'); - expect(eventSpy).calledWith('igcClosed'); + expect(eventSpy).toHaveBeenCalledWith('igcClosing', { cancelable: true }); + expect(eventSpy).toHaveBeenCalledWith('igcClosed'); }); it('should emit or not igcInput according to nonEditable property', async () => { const expectedValue = new Date(); - const eventSpy = spy(picker, 'emitEvent'); + const eventSpy = vi.spyOn(picker, 'emitEvent'); dateTimeInput.focus(); simulateKeyboard(dateTimeInput, arrowUp); await elementUpdated(picker); - expect(eventSpy).calledOnceWith('igcInput'); - eventSpy.resetHistory(); + expectCalledWith(eventSpy, 'igcInput'); + eventSpy.mockClear(); checkDatesEqual(picker.value as Date, expectedValue); picker.value = null; @@ -823,7 +824,7 @@ describe('Date picker', () => { simulateKeyboard(dateTimeInput, arrowUp); await elementUpdated(picker); - expect(eventSpy).not.called; + expect(eventSpy).not.toHaveBeenCalled(); expect(picker.value).to.be.null; dateTimeInput.dispatchEvent( @@ -831,7 +832,7 @@ describe('Date picker', () => { ); await elementUpdated(picker); - expect(eventSpy).not.called; + expect(eventSpy).not.toHaveBeenCalled(); expect(picker.value).to.be.null; }); @@ -951,7 +952,7 @@ describe('Date picker', () => { }); it('should update the calendar view when typing, i.e. switch to other month', async () => { - const eventSpy = spy(picker, 'emitEvent'); + const eventSpy = vi.spyOn(picker, 'emitEvent'); const date = new CalendarDay({ year: 2025, month: 1, date: 1 }); picker.value = date.native; picker.open = true; @@ -967,8 +968,8 @@ describe('Date picker', () => { await elementUpdated(picker); - expect(eventSpy).calledWith('igcInput'); - expect(eventSpy).not.calledWith('igcChange'); + expectCalledWith(eventSpy, 'igcInput'); + expectNotCalledWith(eventSpy, 'igcChange'); const expectedValue = new CalendarDay({ year: 2025, month: 3, date: 1 }) .native; @@ -976,7 +977,7 @@ describe('Date picker', () => { }); it('issue 1884 - should emit igcChange event in dialog mode after clearing the value and losing focus', async () => { - const eventSpy = spy(picker, 'emitEvent'); + const eventSpy = vi.spyOn(picker, 'emitEvent'); // Dropdown mode @@ -994,11 +995,11 @@ describe('Date picker', () => { picker.blur(); expect(isFocused(dateTimeInput)).to.be.false; - expect(eventSpy).to.be.calledWith('igcChange', { + expect(eventSpy).toHaveBeenCalledWith('igcChange', { detail: null, }); - eventSpy.resetHistory(); + eventSpy.mockClear(); // Dialog mode picker.mode = 'dialog'; @@ -1016,7 +1017,7 @@ describe('Date picker', () => { picker.blur(); expect(isFocused(dateTimeInput)).to.be.false; - expect(eventSpy).to.be.calledWith('igcChange', { + expect(eventSpy).toHaveBeenCalledWith('igcChange', { detail: null, }); }); diff --git a/src/components/date-range-picker/date-range-picker-single.form.spec.ts b/src/components/date-range-picker/date-range-picker-single.form.spec.ts index 77d592d4a..316f2e00b 100644 --- a/src/components/date-range-picker/date-range-picker-single.form.spec.ts +++ b/src/components/date-range-picker/date-range-picker-single.form.spec.ts @@ -1,7 +1,8 @@ -import { elementUpdated, expect, fixture, html } from '@open-wc/testing'; +import { beforeAll, beforeEach, describe, expect, it } from 'vitest'; import { CalendarDay } from '../calendar/model.js'; import { type DateRangeDescriptor, DateRangeType } from '../calendar/types.js'; import { defineComponents } from '../common/definitions/defineComponents.js'; +import { elementUpdated, fixture, html } from '../common/helpers.spec.js'; import { createFormAssociatedTestBed, simulateClick, @@ -18,7 +19,7 @@ import IgcDateRangePickerComponent, { import { checkSelectedRange, getIcon } from './date-range-picker.utils.spec.js'; describe('Date Range Picker Single Input - Form integration', () => { - before(() => defineComponents(IgcDateRangePickerComponent)); + beforeAll(() => defineComponents(IgcDateRangePickerComponent)); let picker: IgcDateRangePickerComponent; let input: IgcInputComponent; diff --git a/src/components/date-range-picker/date-range-picker-single.spec.ts b/src/components/date-range-picker/date-range-picker-single.spec.ts index 8d79c7dd8..9c8030811 100644 --- a/src/components/date-range-picker/date-range-picker-single.spec.ts +++ b/src/components/date-range-picker/date-range-picker-single.spec.ts @@ -1,11 +1,4 @@ -import { - elementUpdated, - expect, - fixture, - html, - waitUntil, -} from '@open-wc/testing'; -import { spy } from 'sinon'; +import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; import IgcCalendarComponent from '../calendar/calendar.js'; import { CalendarDay } from '../calendar/model.js'; import { @@ -16,6 +9,15 @@ import { } from '../common/controllers/key-bindings.js'; import { defineComponents } from '../common/definitions/defineComponents.js'; import { + elementUpdated, + fixture, + html, + waitUntil, +} from '../common/helpers.spec.js'; +import { + eventMatch, + expectCalledWith, + expectNotCalledWith, isFocused, simulateClick, simulateKeyboard, @@ -30,7 +32,7 @@ import { } from './date-range-picker.utils.spec.js'; describe('Date range picker - single input', () => { - before(() => defineComponents(IgcDateRangePickerComponent)); + beforeAll(() => defineComponents(IgcDateRangePickerComponent)); let picker: IgcDateRangePickerComponent; let input: IgcInputComponent; @@ -132,7 +134,7 @@ describe('Date range picker - single input', () => { }); it('should modify value only through calendar selection and not input', async () => { - const eventSpy = spy(picker, 'emitEvent'); + const spy = vi.spyOn(picker, 'emitEvent'); // current implementation of DRP single input is not editable; // to refactor when the input is made editable //picker.nonEditable = true; @@ -146,7 +148,7 @@ describe('Date range picker - single input', () => { expect(input.value).to.equal(''); expect(picker.value).to.deep.equal({ start: null, end: null }); expect(calendar.values).to.deep.equal([]); - expect(eventSpy).not.to.be.called; + expect(spy).not.toHaveBeenCalled(); await picker.show(); await selectDates(today, tomorrow, calendar); @@ -155,7 +157,7 @@ describe('Date range picker - single input', () => { { start: today.native, end: tomorrow.native }, false ); - expect(eventSpy).calledWith('igcChange'); + expectCalledWith(spy, 'igcChange'); }); it('should set properties of the input correctly', async () => { @@ -257,7 +259,7 @@ describe('Date range picker - single input', () => { }); describe('Methods', () => { it('should clear the input on invoking clear()', async () => { - const eventSpy = spy(picker, 'emitEvent'); + const spy = vi.spyOn(picker, 'emitEvent'); picker.value = { start: CalendarDay.from(new Date(2025, 3, 9)).native, end: CalendarDay.from(new Date(2025, 3, 10)).native, @@ -269,12 +271,12 @@ describe('Date range picker - single input', () => { picker.clear(); await elementUpdated(picker); - expect(eventSpy).not.called; + expect(spy).not.toHaveBeenCalled(); expect(picker.value).to.deep.equal(null); expect(input.value).to.equal(''); }); it('should select a date range on invoking select', async () => { - const eventSpy = spy(picker, 'emitEvent'); + const spy = vi.spyOn(picker, 'emitEvent'); expect(picker.value).to.deep.equal({ start: null, end: null }); const start = CalendarDay.from(new Date(2025, 3, 9)).native; const end = CalendarDay.from(new Date(2025, 3, 10)).native; @@ -289,18 +291,18 @@ describe('Date range picker - single input', () => { end, }); expect(input.value).to.equal('4/9/2025 - 4/10/2025'); - expect(eventSpy).not.called; + expect(spy).not.toHaveBeenCalled(); }); }); describe('Interactions', () => { describe('Selection via the calendar', () => { it('should select a single date in dropdown mode and emit igcChange', async () => { - const eventSpy = spy(picker, 'emitEvent'); + const spy = vi.spyOn(picker, 'emitEvent'); picker.open = true; await elementUpdated(picker); await selectDates(today, null, calendar); - expect(eventSpy).calledWith('igcChange'); + expectCalledWith(spy, 'igcChange'); checkSelectedRange( picker, { start: today.native, end: today.native }, @@ -313,13 +315,13 @@ describe('Date range picker - single input', () => { }); it('should select a range of dates in dropdown mode and emit igcChange', async () => { - const eventSpy = spy(picker, 'emitEvent'); + const spy = vi.spyOn(picker, 'emitEvent'); picker.open = true; await elementUpdated(picker); await selectDates(today, tomorrow, calendar); - expect(eventSpy).calledWith('igcChange'); + expectCalledWith(spy, 'igcChange'); checkSelectedRange( picker, { @@ -335,7 +337,7 @@ describe('Date range picker - single input', () => { }); it('should select a range of dates in dialog mode and emit igcChange when done is clicked', async () => { - const eventSpy = spy(picker, 'emitEvent'); + const spy = vi.spyOn(picker, 'emitEvent'); picker.mode = 'dialog'; await elementUpdated(picker); @@ -344,7 +346,7 @@ describe('Date range picker - single input', () => { await elementUpdated(picker); await selectDates(today, tomorrow, calendar); - expect(eventSpy).not.to.be.calledWith('igcChange'); + expectNotCalledWith(spy, 'igcChange'); let dialog = picker.renderRoot.querySelector('igc-dialog'); expect(dialog?.hasAttribute('open')).to.equal(true); @@ -353,7 +355,7 @@ describe('Date range picker - single input', () => { ) as HTMLButtonElement; doneBtn?.click(); await elementUpdated(picker); - expect(eventSpy).calledWith('igcChange'); + expectCalledWith(spy, 'igcChange'); checkSelectedRange( picker, { @@ -368,7 +370,7 @@ describe('Date range picker - single input', () => { }); it('should select a range of dates in dialog mode and emit igcChange when clicked outside of dialog', async () => { - const eventSpy = spy(picker, 'emitEvent'); + const spy = vi.spyOn(picker, 'emitEvent'); picker.mode = 'dialog'; await elementUpdated(picker); @@ -377,7 +379,7 @@ describe('Date range picker - single input', () => { await elementUpdated(picker); await selectDates(today, tomorrow, calendar); - expect(eventSpy).not.to.be.calledWith('igcChange'); + expectNotCalledWith(spy, 'igcChange'); let dialog = picker.renderRoot.querySelector( 'igc-dialog' ) as IgcDialogComponent; @@ -388,10 +390,10 @@ describe('Date range picker - single input', () => { simulateClick(nativeDialog, { clientX: x + 1, clientY: y - 1 }); await elementUpdated(dialog); - await waitUntil(() => eventSpy.calledWith('igcClosed')); + await waitUntil(() => eventMatch(spy, 'igcClosed')); await elementUpdated(picker); - expect(eventSpy).calledWith('igcChange'); + expectCalledWith(spy, 'igcChange'); checkSelectedRange( picker, { @@ -408,7 +410,7 @@ describe('Date range picker - single input', () => { }); it('should not emit igcChange when cancel is clicked and the value should be the initial value', async () => { - const eventSpy = spy(picker, 'emitEvent'); + const spy = vi.spyOn(picker, 'emitEvent'); picker.mode = 'dialog'; const date1 = new CalendarDay({ @@ -428,7 +430,7 @@ describe('Date range picker - single input', () => { await elementUpdated(picker); await selectDates(date1, date2, calendar); - expect(eventSpy).not.to.be.calledWith('igcChange'); + expectNotCalledWith(spy, 'igcChange'); let dialog = picker.renderRoot.querySelector('igc-dialog'); expect(dialog?.hasAttribute('open')).to.equal(true); checkSelectedRange( @@ -442,7 +444,7 @@ describe('Date range picker - single input', () => { ) as HTMLButtonElement; cancelBtn?.click(); await elementUpdated(picker); - expect(eventSpy).not.to.be.calledWith('igcChange'); + expectNotCalledWith(spy, 'igcChange'); dialog = picker.renderRoot.querySelector('igc-dialog'); expect(dialog?.hasAttribute('open')).to.equal(false); checkSelectedRange( @@ -456,7 +458,7 @@ describe('Date range picker - single input', () => { }); it('should revert to no value when cancel is clicked and initial value is null', async () => { - const eventSpy = spy(picker, 'emitEvent'); + const spy = vi.spyOn(picker, 'emitEvent'); picker.mode = 'dialog'; const date1 = new CalendarDay({ @@ -476,7 +478,7 @@ describe('Date range picker - single input', () => { await elementUpdated(picker); await selectDates(date1, date2, calendar); - expect(eventSpy).not.to.be.calledWith('igcChange'); + expectNotCalledWith(spy, 'igcChange'); let dialog = picker.renderRoot.querySelector('igc-dialog'); expect(dialog?.hasAttribute('open')).to.equal(true); checkSelectedRange( @@ -490,7 +492,7 @@ describe('Date range picker - single input', () => { ) as HTMLButtonElement; cancelBtn?.click(); await elementUpdated(picker); - expect(eventSpy).not.to.be.calledWith('igcChange'); + expectNotCalledWith(spy, 'igcChange'); dialog = picker.renderRoot.querySelector('igc-dialog'); expect(dialog?.hasAttribute('open')).to.equal(false); @@ -504,7 +506,7 @@ describe('Date range picker - single input', () => { }); it('should not emit igcChange when escape is pressed and the value should be the initial value', async () => { - const eventSpy = spy(picker, 'emitEvent'); + const spy = vi.spyOn(picker, 'emitEvent'); picker.mode = 'dialog'; const date1 = new CalendarDay({ @@ -524,7 +526,7 @@ describe('Date range picker - single input', () => { await elementUpdated(picker); await selectDates(date1, date2, calendar); - expect(eventSpy).not.to.be.calledWith('igcChange'); + expectNotCalledWith(spy, 'igcChange'); let dialog = picker.renderRoot.querySelector('igc-dialog'); expect(dialog?.hasAttribute('open')).to.equal(true); checkSelectedRange( @@ -536,7 +538,7 @@ describe('Date range picker - single input', () => { simulateKeyboard(picker, escapeKey); await elementUpdated(picker); await elementUpdated(input); - expect(eventSpy).not.to.be.calledWith('igcChange'); + expectNotCalledWith(spy, 'igcChange'); dialog = picker.renderRoot.querySelector('igc-dialog'); expect(dialog?.hasAttribute('open')).to.equal(false); @@ -570,7 +572,7 @@ describe('Date range picker - single input', () => { }); it('should clear the inputs on clicking the clear icon', async () => { - const eventSpy = spy(picker, 'emitEvent'); + const spy = vi.spyOn(picker, 'emitEvent'); picker.useTwoInputs = false; picker.value = { start: today.native, end: tomorrow.native }; await elementUpdated(picker); @@ -586,7 +588,7 @@ describe('Date range picker - single input', () => { await elementUpdated(input); expect(isFocused(input)).to.be.false; - expect(eventSpy).to.be.calledWith('igcChange', { + expect(spy).toHaveBeenCalledWith('igcChange', { detail: null, }); expect(picker.open).to.be.false; @@ -595,7 +597,7 @@ describe('Date range picker - single input', () => { }); it('should toggle the calendar with keyboard combinations and keep focus', async () => { - const eventSpy = spy(picker, 'emitEvent'); + const spy = vi.spyOn(picker, 'emitEvent'); const input = picker.renderRoot!.querySelector( IgcInputComponent.tagName )!; @@ -608,22 +610,26 @@ describe('Date range picker - single input', () => { expect(picker.open).to.be.true; expect(isFocused(input)).to.be.false; - expect(eventSpy.firstCall).calledWith('igcOpening'); - expect(eventSpy.lastCall).calledWith('igcOpened'); - eventSpy.resetHistory(); + expect(spy).toHaveBeenNthCalledWith(1, 'igcOpening', { + cancelable: true, + }); + expect(spy).toHaveBeenNthCalledWith(2, 'igcOpened'); + spy.mockClear(); simulateKeyboard(input, [altKey, arrowUp]); await elementUpdated(picker); expect(picker.open).to.be.false; expect(isFocused(input)).to.be.true; - expect(eventSpy.firstCall).calledWith('igcClosing'); - expect(eventSpy.lastCall).calledWith('igcClosed'); - eventSpy.resetHistory(); + expect(spy).toHaveBeenNthCalledWith(1, 'igcClosing', { + cancelable: true, + }); + expect(spy).toHaveBeenNthCalledWith(2, 'igcClosed'); + spy.mockClear(); simulateKeyboard(input, [altKey, arrowDown]); await elementUpdated(picker); - eventSpy.resetHistory(); + spy.mockClear(); simulateKeyboard(picker, escapeKey); await elementUpdated(picker); @@ -631,8 +637,10 @@ describe('Date range picker - single input', () => { expect(picker.open).to.be.false; expect(isFocused(input)).to.be.true; - expect(eventSpy.firstCall).calledWith('igcClosing'); - expect(eventSpy.lastCall).calledWith('igcClosed'); + expect(spy).toHaveBeenNthCalledWith(1, 'igcClosing', { + cancelable: true, + }); + expect(spy).toHaveBeenNthCalledWith(2, 'igcClosed'); }); }); @@ -644,17 +652,17 @@ describe('Date range picker - single input', () => { await elementUpdated(picker); }); it('should not clear the input(s) via the clear icon when readOnly is true', async () => { - const eventSpy = spy(picker, 'emitEvent'); + const spy = vi.spyOn(picker, 'emitEvent'); simulateClick(getIcon(picker, clearIcon)); await elementUpdated(picker); expect(picker.value).to.deep.equal(testValue); - expect(eventSpy).not.called; + expect(spy).not.toHaveBeenCalled(); checkSelectedRange(picker, testValue, false); }); it('should not open the calendar on clicking the input - dropdown mode', async () => { - const eventSpy = spy(picker, 'emitEvent'); + const spy = vi.spyOn(picker, 'emitEvent'); const igcInput = picker.renderRoot.querySelector( IgcInputComponent.tagName )!; @@ -663,10 +671,10 @@ describe('Date range picker - single input', () => { await elementUpdated(picker); expect(picker.open).to.be.false; - expect(eventSpy).not.called; + expect(spy).not.toHaveBeenCalled(); }); it('should not open the calendar on clicking the input - dialog mode', async () => { - const eventSpy = spy(picker, 'emitEvent'); + const spy = vi.spyOn(picker, 'emitEvent'); picker.mode = 'dialog'; await elementUpdated(picker); const igcInput = picker.renderRoot.querySelector( @@ -678,7 +686,7 @@ describe('Date range picker - single input', () => { await elementUpdated(picker); expect(picker.open).to.be.false; - expect(eventSpy).not.called; + expect(spy).not.toHaveBeenCalled(); }); }); }); diff --git a/src/components/date-range-picker/date-range-picker-two-inputs.form.spec.ts b/src/components/date-range-picker/date-range-picker-two-inputs.form.spec.ts index 259e56d0f..67a814a08 100644 --- a/src/components/date-range-picker/date-range-picker-two-inputs.form.spec.ts +++ b/src/components/date-range-picker/date-range-picker-two-inputs.form.spec.ts @@ -1,7 +1,8 @@ -import { elementUpdated, expect, fixture, html } from '@open-wc/testing'; +import { beforeAll, beforeEach, describe, expect, it } from 'vitest'; import { CalendarDay } from '../calendar/model.js'; import { type DateRangeDescriptor, DateRangeType } from '../calendar/types.js'; import { defineComponents } from '../common/definitions/defineComponents.js'; +import { elementUpdated, fixture, html } from '../common/helpers.spec.js'; import { createFormAssociatedTestBed, simulateClick, @@ -24,7 +25,7 @@ import { } from './date-range-picker.utils.spec.js'; describe('Date Range Picker Two Inputs - Form integration', () => { - before(() => defineComponents(IgcDateRangePickerComponent)); + beforeAll(() => defineComponents(IgcDateRangePickerComponent)); let picker: IgcDateRangePickerComponent; let dateTimeInputs: IgcDateTimeInputComponent[]; diff --git a/src/components/date-range-picker/date-range-picker-two-inputs.spec.ts b/src/components/date-range-picker/date-range-picker-two-inputs.spec.ts index 5a5681b3c..a2f95c20d 100644 --- a/src/components/date-range-picker/date-range-picker-two-inputs.spec.ts +++ b/src/components/date-range-picker/date-range-picker-two-inputs.spec.ts @@ -1,11 +1,4 @@ -import { - elementUpdated, - expect, - fixture, - html, - waitUntil, -} from '@open-wc/testing'; -import { spy } from 'sinon'; +import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; import IgcCalendarComponent from '../calendar/calendar.js'; import { CalendarDay } from '../calendar/model.js'; import { @@ -15,8 +8,17 @@ import { escapeKey, } from '../common/controllers/key-bindings.js'; import { defineComponents } from '../common/definitions/defineComponents.js'; +import { + elementUpdated, + fixture, + html, + waitUntil, +} from '../common/helpers.spec.js'; import { checkDatesEqual, + eventMatch, + expectCalledWith, + expectNotCalledWith, isFocused, simulateClick, simulateInput, @@ -32,7 +34,7 @@ import { } from './date-range-picker.utils.spec.js'; describe('Date range picker - two inputs', () => { - before(() => defineComponents(IgcDateRangePickerComponent)); + beforeAll(() => defineComponents(IgcDateRangePickerComponent)); let picker: IgcDateRangePickerComponent; let dateTimeInputs: Array; @@ -149,7 +151,7 @@ describe('Date range picker - two inputs', () => { }); it('should modify value only through calendar selection and not input when nonEditable is true', async () => { - const eventSpy = spy(picker, 'emitEvent'); + const spy = vi.spyOn(picker, 'emitEvent'); picker.nonEditable = true; await elementUpdated(picker); @@ -161,12 +163,12 @@ describe('Date range picker - two inputs', () => { expect(dateTimeInputs[0].value).to.equal(null); expect(picker.value).to.deep.equal({ start: null, end: null }); expect(calendar.values).to.deep.equal([]); - expect(eventSpy).not.to.be.called; + expect(spy).not.toHaveBeenCalled(); await picker.show(); await selectDates(today, tomorrow, calendar); checkSelectedRange(picker, { start: today.native, end: tomorrow.native }); - expect(eventSpy).calledWith('igcChange'); + expectCalledWith(spy, 'igcChange'); }); it('should set properties of the inputs correctly', async () => { @@ -230,7 +232,7 @@ describe('Date range picker - two inputs', () => { }); describe('Methods', () => { it('should clear the input on invoking clear()', async () => { - const eventSpy = spy(picker, 'emitEvent'); + const spy = vi.spyOn(picker, 'emitEvent'); picker.value = { start: today.native, end: tomorrow.native }; await elementUpdated(picker); @@ -239,13 +241,13 @@ describe('Date range picker - two inputs', () => { picker.clear(); await elementUpdated(picker); - expect(eventSpy).not.called; + expect(spy).not.toHaveBeenCalled(); expect(picker.value).to.deep.equal(null); expect(dateTimeInputs[0].value).to.be.null; expect(dateTimeInputs[1].value).to.be.null; }); it('should select a date range on invoking select', async () => { - const eventSpy = spy(picker, 'emitEvent'); + const spy = vi.spyOn(picker, 'emitEvent'); expect(picker.value).to.deep.equal({ start: null, end: null }); picker.select({ start: today.native, end: tomorrow.native }); @@ -257,19 +259,19 @@ describe('Date range picker - two inputs', () => { }); expect(dateTimeInputs[0].value).to.equal(picker.value?.start); expect(dateTimeInputs[1].value).to.equal(picker.value?.end); - expect(eventSpy).not.called; + expect(spy).not.toHaveBeenCalled(); }); }); describe('Interactions', () => { describe('Selection via the calendar', () => { it('should select a single date in dropdown mode and emit igcChange', async () => { - const eventSpy = spy(picker, 'emitEvent'); + const spy = vi.spyOn(picker, 'emitEvent'); picker.open = true; await elementUpdated(picker); await selectDates(today, null, calendar); - expect(eventSpy).calledWith('igcChange'); + expectCalledWith(spy, 'igcChange'); checkSelectedRange(picker, { start: today.native, end: today.native }); const popover = picker.renderRoot.querySelector('igc-popover'); @@ -278,13 +280,13 @@ describe('Date range picker - two inputs', () => { }); it('should select a range of dates in dropdown mode and emit igcChange', async () => { - const eventSpy = spy(picker, 'emitEvent'); + const spy = vi.spyOn(picker, 'emitEvent'); picker.open = true; await elementUpdated(picker); await selectDates(today, tomorrow, calendar); - expect(eventSpy).calledWith('igcChange'); + expectCalledWith(spy, 'igcChange'); checkSelectedRange(picker, { start: today.native, end: tomorrow.native, @@ -296,7 +298,7 @@ describe('Date range picker - two inputs', () => { }); it('should swap the selected dates after input if the end is earlier than the start', async () => { - const eventSpy = spy(picker, 'emitEvent'); + const spy = vi.spyOn(picker, 'emitEvent'); const aMonthAgo = today.add('month', -1); picker.value = null; await elementUpdated(picker); @@ -308,8 +310,8 @@ describe('Date range picker - two inputs', () => { simulateKeyboard(dateTimeInputs[0], arrowUp); await elementUpdated(picker); - expect(eventSpy).calledWith('igcInput'); - eventSpy.resetHistory(); + expectCalledWith(spy, 'igcInput'); + spy.mockClear(); dateTimeInputs[1].focus(); // first arrow sets today, second sets aMonthAgo @@ -327,7 +329,7 @@ describe('Date range picker - two inputs', () => { dateTimeInputs[1].blur(); await elementUpdated(picker); - expect(eventSpy).calledWith('igcChange'); + expectCalledWith(spy, 'igcChange'); checkSelectedRange(picker, { start: aMonthAgo.native, end: today.native, @@ -335,7 +337,7 @@ describe('Date range picker - two inputs', () => { }); it('should select a range of dates in dialog mode and emit igcChange when done is clicked', async () => { - const eventSpy = spy(picker, 'emitEvent'); + const spy = vi.spyOn(picker, 'emitEvent'); picker.mode = 'dialog'; await elementUpdated(picker); @@ -344,7 +346,7 @@ describe('Date range picker - two inputs', () => { await elementUpdated(picker); await selectDates(today, tomorrow, calendar); - expect(eventSpy).not.to.be.calledWith('igcChange'); + expectNotCalledWith(spy, 'igcChange'); let dialog = picker.renderRoot.querySelector('igc-dialog'); expect(dialog?.hasAttribute('open')).to.equal(true); @@ -353,7 +355,7 @@ describe('Date range picker - two inputs', () => { ) as HTMLButtonElement; doneBtn?.click(); await elementUpdated(picker); - expect(eventSpy).calledWith('igcChange'); + expectCalledWith(spy, 'igcChange'); checkSelectedRange(picker, { start: today.native, end: tomorrow.native, @@ -364,7 +366,7 @@ describe('Date range picker - two inputs', () => { }); it('should select a range of dates in dialog mode and emit igcChange when clicked outside of dialog', async () => { - const eventSpy = spy(picker, 'emitEvent'); + const spy = vi.spyOn(picker, 'emitEvent'); picker.mode = 'dialog'; await elementUpdated(picker); @@ -373,7 +375,7 @@ describe('Date range picker - two inputs', () => { await elementUpdated(picker); await selectDates(today, tomorrow, calendar); - expect(eventSpy).not.to.be.calledWith('igcChange'); + expectNotCalledWith(spy, 'igcChange'); let dialog = picker.renderRoot.querySelector( 'igc-dialog' ) as IgcDialogComponent; @@ -384,10 +386,10 @@ describe('Date range picker - two inputs', () => { simulateClick(nativeDialog, { clientX: x + 1, clientY: y - 1 }); await elementUpdated(dialog); - await waitUntil(() => eventSpy.calledWith('igcClosed')); + await waitUntil(() => eventMatch(spy, 'igcClosed')); await elementUpdated(picker); - expect(eventSpy).calledWith('igcChange'); + expectCalledWith(spy, 'igcChange'); checkSelectedRange(picker, { start: today.native, end: tomorrow.native, @@ -400,7 +402,7 @@ describe('Date range picker - two inputs', () => { }); it('should not emit igcChange when cancel is clicked and the value should be the initial value', async () => { - const eventSpy = spy(picker, 'emitEvent'); + const spy = vi.spyOn(picker, 'emitEvent'); picker.mode = 'dialog'; const date1 = new CalendarDay({ @@ -420,7 +422,7 @@ describe('Date range picker - two inputs', () => { await elementUpdated(picker); await selectDates(date1, date2, calendar); - expect(eventSpy).not.to.be.calledWith('igcChange'); + expectNotCalledWith(spy, 'igcChange'); let dialog = picker.renderRoot.querySelector('igc-dialog'); expect(dialog?.hasAttribute('open')).to.equal(true); checkSelectedRange(picker, { start: date1.native, end: date2.native }); @@ -430,7 +432,7 @@ describe('Date range picker - two inputs', () => { ) as HTMLButtonElement; cancelBtn?.click(); await elementUpdated(picker); - expect(eventSpy).not.to.be.calledWith('igcChange'); + expectNotCalledWith(spy, 'igcChange'); dialog = picker.renderRoot.querySelector('igc-dialog'); expect(dialog?.hasAttribute('open')).to.equal(false); checkSelectedRange(picker, { @@ -440,7 +442,7 @@ describe('Date range picker - two inputs', () => { }); it('should revert to no value when cancel is clicked and initial value is null', async () => { - const eventSpy = spy(picker, 'emitEvent'); + const spy = vi.spyOn(picker, 'emitEvent'); picker.mode = 'dialog'; const date1 = new CalendarDay({ @@ -460,7 +462,7 @@ describe('Date range picker - two inputs', () => { await elementUpdated(picker); await selectDates(date1, date2, calendar); - expect(eventSpy).not.to.be.calledWith('igcChange'); + expectNotCalledWith(spy, 'igcChange'); let dialog = picker.renderRoot.querySelector('igc-dialog'); expect(dialog?.hasAttribute('open')).to.equal(true); checkSelectedRange(picker, { start: date1.native, end: date2.native }); @@ -470,7 +472,7 @@ describe('Date range picker - two inputs', () => { ) as HTMLButtonElement; cancelBtn?.click(); await elementUpdated(picker); - expect(eventSpy).not.to.be.calledWith('igcChange'); + expectNotCalledWith(spy, 'igcChange'); dialog = picker.renderRoot.querySelector('igc-dialog'); expect(dialog?.hasAttribute('open')).to.equal(false); @@ -487,7 +489,7 @@ describe('Date range picker - two inputs', () => { }); it('should not emit igcChange when escape is pressed and the value should be the initial value', async () => { - const eventSpy = spy(picker, 'emitEvent'); + const spy = vi.spyOn(picker, 'emitEvent'); picker.mode = 'dialog'; const date1 = new CalendarDay({ @@ -507,7 +509,7 @@ describe('Date range picker - two inputs', () => { await elementUpdated(picker); await selectDates(date1, date2, calendar); - expect(eventSpy).not.to.be.calledWith('igcChange'); + expectNotCalledWith(spy, 'igcChange'); let dialog = picker.renderRoot.querySelector('igc-dialog'); expect(dialog?.hasAttribute('open')).to.equal(true); checkSelectedRange(picker, { start: date1.native, end: date2.native }); @@ -516,7 +518,7 @@ describe('Date range picker - two inputs', () => { await elementUpdated(picker); await elementUpdated(dateTimeInputs[0]); await elementUpdated(dateTimeInputs[1]); - expect(eventSpy).not.to.be.calledWith('igcChange'); + expectNotCalledWith(spy, 'igcChange'); dialog = picker.renderRoot.querySelector('igc-dialog'); expect(dialog?.hasAttribute('open')).to.equal(false); @@ -530,14 +532,14 @@ describe('Date range picker - two inputs', () => { describe('Interactions with the inputs and the open and clear buttons', () => { it('should emit or not igcInput according to nonEditable property', async () => { const expectedValue = today.native; - const eventSpy = spy(picker, 'emitEvent'); + const spy = vi.spyOn(picker, 'emitEvent'); dateTimeInputs[0].focus(); simulateKeyboard(dateTimeInputs[0], arrowUp); await elementUpdated(picker); - expect(eventSpy).calledOnceWith('igcInput'); - eventSpy.resetHistory(); + expectCalledWith(spy, 'igcInput'); + spy.mockClear(); expect(dateTimeInputs[0].value).to.not.be.null; checkDatesEqual(dateTimeInputs[0].value!, expectedValue); @@ -549,7 +551,7 @@ describe('Date range picker - two inputs', () => { simulateKeyboard(dateTimeInputs[0], arrowUp); await elementUpdated(picker); - expect(eventSpy).not.called; + expect(spy).not.toHaveBeenCalled(); expect(dateTimeInputs[0].value).to.be.null; dateTimeInputs[0].dispatchEvent( @@ -557,7 +559,7 @@ describe('Date range picker - two inputs', () => { ); await elementUpdated(picker); - expect(eventSpy).not.to.be.called; + expect(spy).not.toHaveBeenCalled(); expect(picker.value).to.deep.equal(null); }); @@ -586,7 +588,7 @@ describe('Date range picker - two inputs', () => { }); it('should clear the inputs on clicking the clear icon', async () => { - const eventSpy = spy(picker, 'emitEvent'); + const spy = vi.spyOn(picker, 'emitEvent'); picker.value = { start: today.native, end: tomorrow.native }; await elementUpdated(picker); @@ -599,7 +601,7 @@ describe('Date range picker - two inputs', () => { await elementUpdated(dateTimeInputs[0]); expect(isFocused(dateTimeInputs[0])).to.be.false; - expect(eventSpy).to.be.calledWith('igcChange', { + expect(spy).toHaveBeenCalledWith('igcChange', { detail: { start: null, end: null }, }); expect(picker.open).to.be.false; @@ -609,7 +611,7 @@ describe('Date range picker - two inputs', () => { }); it('should emit igcInput and igcChange on input value change', async () => { - const eventSpy = spy(picker, 'emitEvent'); + const spy = vi.spyOn(picker, 'emitEvent'); const now = new CalendarDay({ year: 2026, month: 3, date: 1 }); const aMonthAgo = now.add('month', -1); @@ -624,23 +626,22 @@ describe('Date range picker - two inputs', () => { simulateKeyboard(dateTimeInputs[0], arrowDown); await elementUpdated(picker); - expect(eventSpy).calledWith('igcInput', { + expect(spy).toHaveBeenCalledWith('igcInput', { detail: { start: aMonthAgo.native, end: null }, }); - eventSpy.resetHistory(); + spy.mockClear(); dateTimeInputs[0].blur(); - expect(eventSpy).calledWith('igcChange', { + expect(spy).toHaveBeenCalledWith('igcChange', { detail: { start: aMonthAgo.native, end: null }, }); }); it('should set the calendar active date to the altered date of the range while typing', async () => { - const eventSpy = spy(picker, 'emitEvent'); + const spy = vi.spyOn(picker, 'emitEvent'); const now = new CalendarDay({ year: 2026, month: 3, date: 1 }); const aMonthAgo = now.add('month', -1); - // const aMonthAgo = today.add('month', -1); picker.value = null; picker.open = true; await elementUpdated(picker); @@ -656,9 +657,9 @@ describe('Date range picker - two inputs', () => { simulateKeyboard(dateTimeInputs[0], arrowUp); await elementUpdated(picker); - expect(eventSpy).calledWith('igcInput'); - expect(eventSpy).not.calledWith('igcChange'); - eventSpy.resetHistory(); + expectCalledWith(spy, 'igcInput'); + expectNotCalledWith(spy, 'igcChange'); + spy.mockClear(); checkDatesEqual(dateTimeInputs[0].value!, now.native); // typing a single date does not select a range in the calendar checkSelectedRange(picker, { start: now.native, end: null }); @@ -668,10 +669,10 @@ describe('Date range picker - two inputs', () => { expect(isFocused(dateTimeInputs[1])).to.be.true; // emit igcChange on losing focus of the edited input - expect(eventSpy).calledWith('igcChange', { + expect(spy).toHaveBeenCalledWith('igcChange', { detail: { start: now.native, end: null }, }); - eventSpy.resetHistory(); + spy.mockClear(); dateTimeInputs[1].value = now.native; await elementUpdated(picker); @@ -681,8 +682,8 @@ describe('Date range picker - two inputs', () => { simulateKeyboard(dateTimeInputs[1], arrowUp); await elementUpdated(picker); - expect(eventSpy).calledWith('igcInput'); - eventSpy.resetHistory(); + expectCalledWith(spy, 'igcInput'); + spy.mockClear(); checkDatesEqual(dateTimeInputs[1].value!, now.native); // typing the end date as well results in a selected range of a single date checkSelectedRange(picker, { start: now.native, end: now.native }); @@ -691,8 +692,8 @@ describe('Date range picker - two inputs', () => { simulateKeyboard(dateTimeInputs[1], arrowDown); await elementUpdated(picker); - expect(eventSpy).calledWith('igcInput'); - eventSpy.resetHistory(); + expectCalledWith(spy, 'igcInput'); + spy.mockClear(); checkDatesEqual(dateTimeInputs[1].value!, aMonthAgo.native); // changing the end date while typing alters the selected range // the active date is set to the typed date, in this case the end one @@ -707,13 +708,13 @@ describe('Date range picker - two inputs', () => { expect(isFocused(dateTimeInputs[0])).to.be.true; await elementUpdated(picker); - expect(eventSpy).calledWith('igcChange', { + expect(spy).toHaveBeenCalledWith('igcChange', { detail: { start: aMonthAgo.native, end: now.native }, }); }); it('should set the calendar active date to the typed date and reflect selection in calendar', async () => { - const eventSpy = spy(picker, 'emitEvent'); + const spy = vi.spyOn(picker, 'emitEvent'); picker.value = null; picker.open = true; await elementUpdated(picker); @@ -734,7 +735,7 @@ describe('Date range picker - two inputs', () => { checkSelectedRange(picker, { start: expectedDate, end: null }); checkDatesEqual(calendar.activeDate, expectedDate); - expect(eventSpy).calledWith('igcInput', { + expect(spy).toHaveBeenCalledWith('igcInput', { detail: { start: expectedDate, end: null }, }); @@ -754,13 +755,13 @@ describe('Date range picker - two inputs', () => { checkSelectedRange(picker, { start: expectedDate, end: expectedDate2 }); checkDatesEqual(calendar.activeDate, expectedDate2); - expect(eventSpy).calledWith('igcInput', { + expect(spy).toHaveBeenCalledWith('igcInput', { detail: { start: expectedDate, end: expectedDate2 }, }); }); it('should set the calendar active date to any of the defined dates start/end', async () => { - const eventSpy = spy(picker, 'emitEvent'); + const spy = vi.spyOn(picker, 'emitEvent'); const march1st2025 = today.set({ month: 2, date: 1, @@ -783,7 +784,7 @@ describe('Date range picker - two inputs', () => { simulateInput(input1, { inputType: 'deleteContentBackward' }); await elementUpdated(picker); - expect(eventSpy).calledWith('igcInput'); + expectCalledWith(spy, 'igcInput'); // the active date is set to the end date as it is the first defined in the range checkDatesEqual(calendar.activeDate, june3rd2025); @@ -796,7 +797,7 @@ describe('Date range picker - two inputs', () => { checkDatesEqual(calendar.activeDate, june3rd2025); }); it('should toggle the calendar with keyboard combinations and keep focus', async () => { - const eventSpy = spy(picker, 'emitEvent'); + const spy = vi.spyOn(picker, 'emitEvent'); dateTimeInputs[0].focus(); expect(isFocused(dateTimeInputs[0])).to.be.true; @@ -806,21 +807,25 @@ describe('Date range picker - two inputs', () => { expect(picker.open).to.be.true; expect(isFocused(dateTimeInputs[0])).to.be.false; - expect(eventSpy.firstCall).calledWith('igcOpening'); - expect(eventSpy.lastCall).calledWith('igcOpened'); - eventSpy.resetHistory(); + expect(spy).toHaveBeenNthCalledWith(1, 'igcOpening', { + cancelable: true, + }); + expect(spy).toHaveBeenNthCalledWith(2, 'igcOpened'); + spy.mockClear(); simulateKeyboard(dateTimeInputs[0], [altKey, arrowUp]); await elementUpdated(picker); expect(picker.open).to.be.false; expect(isFocused(dateTimeInputs[0])).to.be.true; - expect(eventSpy.firstCall).calledWith('igcClosing'); - expect(eventSpy.lastCall).calledWith('igcClosed'); + expect(spy).toHaveBeenNthCalledWith(1, 'igcClosing', { + cancelable: true, + }); + expect(spy).toHaveBeenNthCalledWith(2, 'igcClosed'); simulateKeyboard(dateTimeInputs[0], [altKey, arrowDown]); await elementUpdated(picker); - eventSpy.resetHistory(); + spy.mockClear(); simulateKeyboard(dateTimeInputs[0], escapeKey); await elementUpdated(picker); @@ -828,8 +833,10 @@ describe('Date range picker - two inputs', () => { expect(picker.open).to.be.false; expect(isFocused(dateTimeInputs[0])).to.be.true; - expect(eventSpy.firstCall).calledWith('igcClosing'); - expect(eventSpy.lastCall).calledWith('igcClosed'); + expect(spy).toHaveBeenNthCalledWith(1, 'igcClosing', { + cancelable: true, + }); + expect(spy).toHaveBeenNthCalledWith(2, 'igcClosed'); }); }); describe('Readonly state', () => { @@ -838,7 +845,7 @@ describe('Date range picker - two inputs', () => { await elementUpdated(picker); }); it('should not modify value through selection or typing when readOnly is true', async () => { - const eventSpy = spy(picker, 'emitEvent'); + const spy = vi.spyOn(picker, 'emitEvent'); expect(picker.value).to.deep.equal({ start: null, end: null }); await picker.show(); @@ -846,7 +853,7 @@ describe('Date range picker - two inputs', () => { expect(picker.value).to.deep.equal({ start: null, end: null }); expect(calendar.values).to.deep.equal([]); - expect(eventSpy).not.to.be.called; + expect(spy).not.toHaveBeenCalled(); await picker.hide(); @@ -857,10 +864,10 @@ describe('Date range picker - two inputs', () => { expect(dateTimeInputs[0].value).to.equal(null); expect(picker.value).to.deep.equal({ start: null, end: null }); expect(calendar.values).to.deep.equal([]); - expect(eventSpy).not.to.be.called; + expect(spy).not.toHaveBeenCalled(); }); it('should not clear the inputs via the clear icon when readOnly is true', async () => { - const eventSpy = spy(picker, 'emitEvent'); + const spy = vi.spyOn(picker, 'emitEvent'); const testValue = { start: today.native, end: tomorrow.native }; picker.value = testValue; await elementUpdated(picker); @@ -868,21 +875,21 @@ describe('Date range picker - two inputs', () => { simulateClick(getIcon(picker, clearIcon)); expect(picker.value).to.deep.equal(testValue); - expect(eventSpy).not.called; + expect(spy).not.toHaveBeenCalled(); checkSelectedRange(picker, testValue); }); it('should not open the calendar on clicking the input - dropdown mode', async () => { - const eventSpy = spy(picker, 'emitEvent'); + const spy = vi.spyOn(picker, 'emitEvent'); const nativeInput = dateTimeInputs[0].renderRoot.querySelector('input')!; simulateClick(nativeInput); await elementUpdated(picker); expect(picker.open).to.be.false; - expect(eventSpy).not.called; + expect(spy).not.toHaveBeenCalled(); }); it('should not open the calendar on clicking the input - dialog mode', async () => { - const eventSpy = spy(picker, 'emitEvent'); + const spy = vi.spyOn(picker, 'emitEvent'); picker.mode = 'dialog'; await elementUpdated(picker); @@ -892,7 +899,7 @@ describe('Date range picker - two inputs', () => { await elementUpdated(picker); expect(picker.open).to.be.false; - expect(eventSpy).not.called; + expect(spy).not.toHaveBeenCalled(); }); }); }); diff --git a/src/components/date-range-picker/date-range-picker.common.spec.ts b/src/components/date-range-picker/date-range-picker.common.spec.ts index 0a917f607..e4799ad5c 100644 --- a/src/components/date-range-picker/date-range-picker.common.spec.ts +++ b/src/components/date-range-picker/date-range-picker.common.spec.ts @@ -1,6 +1,13 @@ -import { elementUpdated, expect, fixture, html } from '@open-wc/testing'; -import type Sinon from 'sinon'; -import { spy } from 'sinon'; +import { + afterEach, + beforeAll, + beforeEach, + describe, + expect, + it, + type MockInstance, + vi, +} from 'vitest'; import type IgcButtonComponent from '../button/button.js'; import IgcCalendarComponent from '../calendar/calendar.js'; import { CalendarDay } from '../calendar/model.js'; @@ -13,9 +20,13 @@ import { escapeKey, } from '../common/controllers/key-bindings.js'; import { defineComponents } from '../common/definitions/defineComponents.js'; +import { elementUpdated, fixture, html } from '../common/helpers.spec.js'; import type { IgcDateRangePickerResourceStrings } from '../common/i18n/EN/date-range-picker.resources.js'; +import { first, last } from '../common/util.js'; import { checkDatesEqual, + expectCalledWith, + expectNotCalledWith, simulateClick, simulateKeyboard, } from '../common/utils.spec.js'; @@ -33,7 +44,7 @@ import { import IgcPredefinedRangesAreaComponent from './predefined-ranges-area.js'; describe('Date range picker - common tests for single and two inputs mode', () => { - before(() => defineComponents(IgcDateRangePickerComponent)); + beforeAll(() => defineComponents(IgcDateRangePickerComponent)); let picker: IgcDateRangePickerComponent; let calendar: IgcCalendarComponent; @@ -225,12 +236,12 @@ describe('Date range picker - common tests for single and two inputs mode', () = }); it('should keep the picker open when keepOpenOnSelect is enabled and a selection is made in the calendar picker', async () => { - const eventSpy = spy(picker, 'emitEvent'); + const spy = vi.spyOn(picker, 'emitEvent'); picker.keepOpenOnSelect = true; await elementUpdated(picker); await picker.show(); await selectDates(today, tomorrow, calendar); - expect(eventSpy).calledWith('igcChange'); + expectCalledWith(spy, 'igcChange'); checkSelectedRange( picker, { start: today.native, end: tomorrow.native }, @@ -307,7 +318,7 @@ describe('Date range picker - common tests for single and two inputs mode', () = it('should default inputFormat to whatever Intl.DateTimeFormat returns for the current locale', async () => { const defaultFormat = 'MM/dd/yyyy'; - expect(picker.locale).to.equal('en-US'); + expect(picker.locale).to.equal('en'); expect(picker.inputFormat).to.equal(defaultFormat); picker.locale = 'fr'; @@ -317,7 +328,7 @@ describe('Date range picker - common tests for single and two inputs mode', () = }); it('should use the value of locale format for displayFormat, if it is not defined', async () => { - expect(picker.locale).to.equal('en-US'); + expect(picker.locale).to.equal('en'); expect(picker.getAttribute('display-format')).to.be.null; expect(picker.displayFormat).to.equal('M/d/yyyy'); @@ -375,6 +386,7 @@ describe('Date range picker - common tests for single and two inputs mode', () = for (const test of tests) { picker.resourceStrings[test.key] = test.value; } + picker.resourceStrings = { ...picker.resourceStrings }; picker.usePredefinedRanges = true; await elementUpdated(picker); @@ -440,34 +452,34 @@ describe('Date range picker - common tests for single and two inputs mode', () = describe('Methods', () => { it('should open/close the picker on invoking show/hide/toggle and not emit events', async () => { - const eventSpy = spy(picker, 'emitEvent'); + const spy = vi.spyOn(picker, 'emitEvent'); expect(picker.open).to.be.false; await picker.show(); - expect(eventSpy).not.called; + expect(spy).not.toHaveBeenCalled(); expect(picker.open).to.be.true; await picker.hide(); - expect(eventSpy).not.called; + expect(spy).not.toHaveBeenCalled(); expect(picker.open).to.be.false; await picker.toggle(); - expect(eventSpy).not.called; + expect(spy).not.toHaveBeenCalled(); expect(picker.open).to.be.true; await picker.toggle(); - expect(eventSpy).not.called; + expect(spy).not.toHaveBeenCalled(); expect(picker.open).to.be.false; }); }); describe('Interactions', () => { describe('Selection via the calendar', () => { it('should not emit igcChange when value is unchanged and done is clicked (dialog)', async () => { - const eventSpy = spy(picker, 'emitEvent'); + const spy = vi.spyOn(picker, 'emitEvent'); picker.value = { start: today.native, end: tomorrow.native }; picker.mode = 'dialog'; await elementUpdated(picker); @@ -475,7 +487,7 @@ describe('Date range picker - common tests for single and two inputs mode', () = picker.open = true; await elementUpdated(picker); - expect(eventSpy).not.to.be.calledWith('igcChange'); + expectNotCalledWith(spy, 'igcChange'); const dialog = picker.renderRoot.querySelector( IgcDialogComponent.tagName ); @@ -486,25 +498,25 @@ describe('Date range picker - common tests for single and two inputs mode', () = ) as HTMLButtonElement; doneBtn?.click(); await elementUpdated(picker); - expect(eventSpy).not.calledWith('igcChange'); + expectNotCalledWith(spy, 'igcChange'); }); }); describe('Keyboard navigation', () => { it('should close the picker when in open state on pressing Escape', async () => { - const eventSpy = spy(picker, 'emitEvent'); + const spy = vi.spyOn(picker, 'emitEvent'); picker.focus(); simulateKeyboard(picker, escapeKey); await elementUpdated(picker); - expect(eventSpy).not.called; + expect(spy).not.toHaveBeenCalled(); await picker.show(); simulateKeyboard(picker, escapeKey); await elementUpdated(picker); - expect(eventSpy).calledTwice; - expect(eventSpy).calledWith('igcClosing'); - expect(eventSpy).calledWith('igcClosed'); - eventSpy.resetHistory(); + expect(spy.mock.calls).lengthOf(2); + expectCalledWith(spy, 'igcClosing'); + expectCalledWith(spy, 'igcClosed'); + spy.mockClear(); // dialog mode picker.mode = 'dialog'; @@ -515,35 +527,34 @@ describe('Date range picker - common tests for single and two inputs mode', () = simulateKeyboard(picker, escapeKey); await elementUpdated(picker); - expect(eventSpy).calledTwice; - expect(eventSpy).calledWith('igcClosing'); - expect(eventSpy).calledWith('igcClosed'); + expect(spy.mock.calls).lengthOf(2); + expectCalledWith(spy, 'igcClosing'); + expectCalledWith(spy, 'igcClosed'); }); it('should open the calendar picker on Alt + ArrowDown and close it on Alt + ArrowUp - dropdown mode', async () => { - const eventSpy = spy(picker, 'emitEvent'); + const spy = vi.spyOn(picker, 'emitEvent'); expect(picker.open).to.be.false; picker.focus(); simulateKeyboard(picker, [altKey, arrowDown]); await elementUpdated(picker); expect(picker.open).to.be.true; - expect(eventSpy).calledWith('igcOpening'); - expect(eventSpy).calledWith('igcOpened'); + expectCalledWith(spy, 'igcOpening'); + expectCalledWith(spy, 'igcOpened'); - eventSpy.resetHistory(); + spy.mockClear(); simulateKeyboard(picker, [altKey, arrowUp]); await elementUpdated(picker); expect(picker.open).to.be.false; - expect(eventSpy).calledWith('igcClosing'); - expect(eventSpy).calledWith('igcClosed'); - eventSpy.resetHistory(); + expectCalledWith(spy, 'igcClosing'); + expectCalledWith(spy, 'igcClosed'); }); it('should open the calendar picker on Alt + ArrowDown and close it on Alt + ArrowUp - dialog mode', async () => { - const eventSpy = spy(picker, 'emitEvent'); + const spy = vi.spyOn(picker, 'emitEvent'); expect(picker.open).to.be.false; picker.focus(); picker.mode = 'dialog'; @@ -557,16 +568,16 @@ describe('Date range picker - common tests for single and two inputs mode', () = ); expect(picker.open).to.be.true; expect(dialog?.open).to.be.true; - expect(eventSpy).calledWith('igcOpening'); - expect(eventSpy).calledWith('igcOpened'); - eventSpy.resetHistory(); + expectCalledWith(spy, 'igcOpening'); + expectCalledWith(spy, 'igcOpened'); + spy.mockClear(); simulateKeyboard(picker, [altKey, arrowUp]); await elementUpdated(picker); expect(picker.open).to.be.false; - expect(eventSpy).calledWith('igcClosing'); - expect(eventSpy).calledWith('igcClosed'); + expectCalledWith(spy, 'igcClosing'); + expectCalledWith(spy, 'igcClosed'); }); describe('Interactions with the show icon', () => { @@ -589,7 +600,7 @@ describe('Date range picker - common tests for single and two inputs mode', () = }); describe('Readonly state', () => { it('should not modify value through selection when readOnly is true', async () => { - const eventSpy = spy(picker, 'emitEvent'); + const spy = vi.spyOn(picker, 'emitEvent'); picker.readOnly = true; await elementUpdated(picker); expect(picker.value).to.deep.equal({ start: null, end: null }); @@ -602,7 +613,7 @@ describe('Date range picker - common tests for single and two inputs mode', () = await selectDates(today, tomorrow, calendar); expect(picker.value).to.deep.equal({ start: null, end: null }); expect(calendar.values).to.deep.equal([]); - expect(eventSpy).not.to.be.called; + expect(spy).not.toHaveBeenCalled(); }); describe('Dropdown mode', () => { beforeEach(async () => { @@ -672,7 +683,7 @@ describe('Date range picker - common tests for single and two inputs mode', () = }); }); describe('Predefined ranges', () => { - let eventSpy: Sinon.SinonSpy; + let spy: MockInstance; const previousThreeDaysStart = CalendarDay.today.add('day', -3).native; const nextThreeDaysEnd = CalendarDay.today.add('day', 3).native; @@ -694,15 +705,15 @@ describe('Date range picker - common tests for single and two inputs mode', () = ]; beforeEach(async () => { - eventSpy = spy(picker, 'emitEvent'); + spy = vi.spyOn(picker, 'emitEvent'); }); afterEach(() => { - eventSpy.resetHistory(); + spy.mockClear(); }); function getEventArgs() { - return eventSpy.firstCall.lastArg.detail as DateRangeValue; + return last(first(spy.mock.calls)).detail as DateRangeValue; } function getPredefinedArea() { @@ -748,7 +759,7 @@ describe('Date range picker - common tests for single and two inputs mode', () = simulateClick(chip); await elementUpdated(chip); - expect(eventSpy).calledWith('igcChange'); + expectCalledWith(spy, 'igcChange'); const range = getEventArgs(); expect(picker.activeDate).to.eql(range.start); @@ -758,7 +769,7 @@ describe('Date range picker - common tests for single and two inputs mode', () = false ); expect(popover?.open).to.be.false; - eventSpy.resetHistory(); + spy.mockClear(); } }); @@ -772,14 +783,17 @@ describe('Date range picker - common tests for single and two inputs mode', () = picker.open = true; await elementUpdated(picker); - const chipSpy = spy(getPredefinedArea()!, '_handleRangeSelect' as any); + const chipSpy = vi.spyOn( + getPredefinedArea()!, + '_handleRangeSelect' as any + ); for (const chip of getRangeChips()) { await picker.show(); simulateClick(chip); await elementUpdated(picker); - const range = chipSpy.firstCall.firstArg as DateRangeValue; + const range = chipSpy.mock.calls[0][0] as DateRangeValue; expect(picker.activeDate).to.eql(range.start); checkSelectedRange( @@ -795,15 +809,15 @@ describe('Date range picker - common tests for single and two inputs mode', () = simulateClick(getDialogDoneButton()); await elementUpdated(picker); - if (eventSpy.args.length > 2) { - expect(eventSpy).calledWith('igcChange'); + if (spy.mock.calls.length > 2) { + expectCalledWith(spy, 'igcChange'); } - expect(eventSpy).calledWith('igcClosing'); - expect(eventSpy).calledWith('igcClosed'); + expectCalledWith(spy, 'igcClosing'); + expectCalledWith(spy, 'igcClosed'); expect(getDialog()?.open).to.be.false; - chipSpy.resetHistory(); - eventSpy.resetHistory(); + chipSpy.mockClear(); + spy.mockClear(); } }); @@ -819,7 +833,7 @@ describe('Date range picker - common tests for single and two inputs mode', () = simulateClick(chip); await elementUpdated(picker); - expect(eventSpy).calledWith('igcChange'); + expectCalledWith(spy, 'igcChange'); const range = getEventArgs(); expect(picker.activeDate).to.eql(range.start); @@ -832,7 +846,7 @@ describe('Date range picker - common tests for single and two inputs mode', () = false ); expect(popover?.open).to.be.false; - eventSpy.resetHistory(); + spy.mockClear(); } }); }); diff --git a/src/components/date-range-picker/date-range-picker.utils.spec.ts b/src/components/date-range-picker/date-range-picker.utils.spec.ts index 7acf36532..457ddd2b1 100644 --- a/src/components/date-range-picker/date-range-picker.utils.spec.ts +++ b/src/components/date-range-picker/date-range-picker.utils.spec.ts @@ -1,7 +1,8 @@ -import { elementUpdated, expect } from '@open-wc/testing'; +import { expect } from 'vitest'; import IgcCalendarComponent from '../calendar/calendar.js'; import { getCalendarDOM, getDOMDate } from '../calendar/helpers.spec.js'; import type { CalendarDay } from '../calendar/model.js'; +import { elementUpdated } from '../common/helpers.spec.js'; import { equal } from '../common/util.js'; import { checkDatesEqual, simulateClick } from '../common/utils.spec.js'; import IgcDateTimeInputComponent from '../date-time-input/date-time-input.js'; diff --git a/src/components/date-range-picker/predefined-ranges-area.spec.ts b/src/components/date-range-picker/predefined-ranges-area.spec.ts index e97a00109..9bf9da0e4 100644 --- a/src/components/date-range-picker/predefined-ranges-area.spec.ts +++ b/src/components/date-range-picker/predefined-ranges-area.spec.ts @@ -1,15 +1,15 @@ -import { elementUpdated, expect, fixture, html } from '@open-wc/testing'; +import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; -import { spy } from 'sinon'; import { CalendarDay } from '../calendar/model.js'; import IgcChipComponent from '../chip/chip.js'; import { defineComponents } from '../common/definitions/defineComponents.js'; +import { elementUpdated, fixture, html } from '../common/helpers.spec.js'; import { simulateClick } from '../common/utils.spec.js'; import type { CustomDateRange } from './date-range-picker.js'; import IgcPredefinedRangesAreaComponent from './predefined-ranges-area.js'; describe('Predefined Area', () => { - before(() => { + beforeAll(() => { defineComponents(IgcPredefinedRangesAreaComponent); }); @@ -121,7 +121,7 @@ describe('Predefined Area', () => { createComponentWithCustomRanges() ); - const eventSpy = spy(); + const eventSpy = vi.fn(); component.addEventListener('igcRangeSelect', eventSpy); const chips = getChips(); @@ -134,8 +134,9 @@ describe('Predefined Area', () => { simulateClick(chip); await elementUpdated(component); - expect(eventSpy.calledWithMatch({ detail: ranges[idx].dateRange })).to - .be.true; + expect(eventSpy).toHaveBeenCalledWith( + expect.objectContaining({ detail: ranges[idx].dateRange }) + ); } }); }); diff --git a/src/components/date-time-input/date-time-input.spec.ts b/src/components/date-time-input/date-time-input.spec.ts index d10f504f3..53f0efe41 100644 --- a/src/components/date-time-input/date-time-input.spec.ts +++ b/src/components/date-time-input/date-time-input.spec.ts @@ -1,5 +1,4 @@ -import { elementUpdated, expect, fixture, html } from '@open-wc/testing'; -import { spy } from 'sinon'; +import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; import { CalendarDay, toCalendarDay } from '../calendar/model.js'; import { altKey, @@ -10,8 +9,10 @@ import { ctrlKey, } from '../common/controllers/key-bindings.js'; import { defineComponents } from '../common/definitions/defineComponents.js'; +import { elementUpdated, fixture, html } from '../common/helpers.spec.js'; import { createFormAssociatedTestBed, + expectCalledWith, isFocused, simulateInput, simulateKeyboard, @@ -27,7 +28,7 @@ import IgcDateTimeInputComponent from './date-time-input.js'; import { DatePart, type DatePartDeltas, DateTimeUtil } from './date-util.js'; describe('Date Time Input component', () => { - before(() => { + beforeAll(() => { defineComponents(IgcDateTimeInputComponent); }); @@ -188,7 +189,7 @@ describe('Date Time Input component', () => { }); it('should emit igcChange on blur after an incomplete mask has been parsed - issue #1695', async () => { - const eventSpy = spy(el, 'emitEvent'); + const spy = vi.spyOn(el, 'emitEvent'); el.focus(); await elementUpdated(el); @@ -201,7 +202,7 @@ describe('Date Time Input component', () => { el.blur(); await elementUpdated(el); - expect(eventSpy).calledWith('igcChange'); + expectCalledWith(spy, 'igcChange'); expect(input.value).to.deep.equal('1/1/2000'); }); @@ -568,17 +569,17 @@ describe('Date Time Input component', () => { el.focus(); await elementUpdated(el); - const eventSpy = spy(el, 'emitEvent'); + const spy = vi.spyOn(el, 'emitEvent'); simulateKeyboard(input, [altKey, arrowUp]); await elementUpdated(el); - expect(eventSpy).not.to.have.been.called; + expect(spy).not.toHaveBeenCalled(); simulateKeyboard(input, [altKey, arrowDown]); await elementUpdated(el); - expect(eventSpy).not.to.have.been.called; + expect(spy).not.toHaveBeenCalled(); }); it('Alt + ArrowUp/Down is a no-op', async () => { @@ -587,22 +588,21 @@ describe('Date Time Input component', () => { el.focus(); await elementUpdated(el); - const eventSpy = spy(el, 'emitEvent'); + const spy = vi.spyOn(el, 'emitEvent'); simulateKeyboard(input, [altKey, arrowUp]); await elementUpdated(el); - expect(eventSpy).not.to.have.been.called; + expect(spy).not.toHaveBeenCalled(); simulateKeyboard(input, [altKey, arrowDown]); await elementUpdated(el); - expect(eventSpy).not.to.have.been.called; + expect(spy).not.toHaveBeenCalled(); }); it('should not emit change event when readonly', async () => { - const eventSpy = spy(el, 'emitEvent'); - + const spy = vi.spyOn(el, 'emitEvent'); el.value = new Date(2023, 5, 1); el.readOnly = true; el.focus(); @@ -611,7 +611,7 @@ describe('Date Time Input component', () => { el.blur(); await elementUpdated(el); - expect(eventSpy.getCalls()).empty; + expect(spy).not.toHaveBeenCalled(); }); it('should not move input selection (caret) from a focused part when stepUp/stepDown are invoked', async () => { @@ -890,7 +890,7 @@ describe('Date Time Input component', () => { }); it('should emit events correctly', async () => { - const eventSpy = spy(el, 'emitEvent'); + const spy = vi.spyOn(el, 'emitEvent'); el.focus(); await elementUpdated(el); @@ -899,24 +899,24 @@ describe('Date Time Input component', () => { simulateKeyboard(input, arrowUp); await elementUpdated(el); - expect(eventSpy).calledWith('igcInput'); - eventSpy.resetHistory(); + expectCalledWith(spy, 'igcInput'); + spy.mockClear(); simulateKeyboard(input, arrowDown); await elementUpdated(el); - expect(eventSpy).calledWith('igcInput'); - eventSpy.resetHistory(); + expectCalledWith(spy, 'igcInput'); + spy.mockClear(); simulateWheel(input, { deltaY: -125 }); await elementUpdated(el); - expect(eventSpy).calledWith('igcInput'); - eventSpy.resetHistory(); + expectCalledWith(spy, 'igcInput'); + spy.mockClear(); el.blur(); await elementUpdated(el); expect(isFocused(el)).to.be.false; - expect(eventSpy).calledWith('igcChange'); + expectCalledWith(spy, 'igcChange'); el.clear(); await elementUpdated(el); @@ -930,7 +930,7 @@ describe('Date Time Input component', () => { el.blur(); await elementUpdated(el); expect(isFocused(el)).to.be.false; - expect(eventSpy).calledWith('igcChange'); + expectCalledWith(spy, 'igcChange'); }); }); diff --git a/src/components/date-time-input/date-util.spec.ts b/src/components/date-time-input/date-util.spec.ts index c630e4b5b..6e2dfd414 100644 --- a/src/components/date-time-input/date-util.spec.ts +++ b/src/components/date-time-input/date-util.spec.ts @@ -1,5 +1,4 @@ -import { expect } from '@open-wc/testing'; - +import { describe, expect, it } from 'vitest'; import { DateParts, DateTimeUtil } from './date-util.js'; describe('Date Util', () => { diff --git a/src/components/dialog/dialog.spec.ts b/src/components/dialog/dialog.spec.ts index 6403ccbc1..f707bfe20 100644 --- a/src/components/dialog/dialog.spec.ts +++ b/src/components/dialog/dialog.spec.ts @@ -1,19 +1,22 @@ +import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; +import IgcButtonComponent from '../button/button.js'; +import { defineComponents } from '../common/definitions/defineComponents.js'; import { elementUpdated, - expect, fixture, html, waitUntil, -} from '@open-wc/testing'; -import { spy } from 'sinon'; - -import IgcButtonComponent from '../button/button.js'; -import { defineComponents } from '../common/definitions/defineComponents.js'; -import { simulateClick } from '../common/utils.spec.js'; +} from '../common/helpers.spec.js'; +import { + eventMatch, + expectCalledWith, + expectNotCalledWith, + simulateClick, +} from '../common/utils.spec.js'; import IgcDialogComponent from './dialog.js'; describe('Dialog', () => { - before(() => { + beforeAll(() => { defineComponents(IgcDialogComponent); }); @@ -159,21 +162,21 @@ describe('Dialog', () => { }); it('does not emit events through API calls', async () => { - const eventSpy = spy(dialog, 'emitEvent'); + const spy = vi.spyOn(dialog, 'emitEvent'); await dialog.show(); expect(dialog.open).to.be.true; - expect(eventSpy.getCalls()).is.empty; + expect(spy.mock.calls).to.be.empty; await dialog.hide(); expect(dialog.open).to.be.false; - expect(eventSpy.getCalls()).is.empty; + expect(spy.mock.calls).to.be.empty; dialog.open = false; await elementUpdated(dialog); expect(dialog.open).to.be.false; - expect(eventSpy.getCalls()).is.empty; + expect(spy.mock.calls).to.be.empty; }); }); @@ -186,20 +189,22 @@ describe('Dialog', () => { }); it('should close the dialog when the user presses Escape', async () => { - const eventSpy = spy(dialog, 'emitEvent'); + const spy = vi.spyOn(dialog, 'emitEvent'); await dialog.show(); nativeDialog.dispatchEvent(new Event('cancel')); await elementUpdated(dialog); await waitUntil(() => !dialog.open); - expect(eventSpy.getCalls()).lengthOf(2); - expect(eventSpy.firstCall).calledWith('igcClosing'); - expect(eventSpy.secondCall).calledWith('igcClosed'); + expect(spy.mock.calls).lengthOf(2); + expect(spy).toHaveBeenNthCalledWith(1, 'igcClosing', { + cancelable: true, + }); + expect(spy).toHaveBeenNthCalledWith(2, 'igcClosed'); }); it('should not close the dialog when the user presses Escape and `keepOpenOnEscape` is set', async () => { - const eventSpy = spy(dialog, 'emitEvent'); + const spy = vi.spyOn(dialog, 'emitEvent'); dialog.keepOpenOnEscape = true; await dialog.show(); @@ -208,11 +213,11 @@ describe('Dialog', () => { await elementUpdated(dialog); expect(dialog.open).to.be.true; - expect(eventSpy.getCalls()).is.empty; + expect(spy.mock.calls).to.be.empty; }); it('default action button emits closing events', async () => { - const eventSpy = spy(dialog, 'emitEvent'); + const spy = vi.spyOn(dialog, 'emitEvent'); await dialog.show(); simulateClick( @@ -221,16 +226,18 @@ describe('Dialog', () => { await elementUpdated(dialog); await waitUntil(() => !dialog.open); - expect(eventSpy.getCalls()).lengthOf(2); - expect(eventSpy.firstCall).calledWith('igcClosing'); - expect(eventSpy.secondCall).calledWith('igcClosed'); + expect(spy.mock.calls).lengthOf(2); + expect(spy).toHaveBeenNthCalledWith(1, 'igcClosing', { + cancelable: true, + }); + expect(spy).toHaveBeenNthCalledWith(2, 'igcClosed'); }); it('can cancel `igcClosing` events when clicking outside the dialog area', async () => { dialog.closeOnOutsideClick = true; await dialog.show(); - const eventSpy = spy(dialog, 'emitEvent'); + const spy = vi.spyOn(dialog, 'emitEvent'); dialog.addEventListener('igcClosing', (e) => e.preventDefault(), { once: true, }); @@ -239,8 +246,8 @@ describe('Dialog', () => { simulateClick(nativeDialog, { clientX: x - 1, clientY: y - 1 }); await elementUpdated(dialog); - expect(eventSpy).calledWith('igcClosing'); - expect(eventSpy).not.calledWith('igcClosed'); + expectCalledWith(spy, 'igcClosing'); + expectNotCalledWith(spy, 'igcClosed'); }); it('does not close the dialog on clicking outside when `closeOnOutsideClick` is not set', async () => { @@ -257,13 +264,13 @@ describe('Dialog', () => { dialog.closeOnOutsideClick = true; await dialog.show(); - const eventSpy = spy(dialog, 'emitEvent'); + const spy = vi.spyOn(dialog, 'emitEvent'); const { x, y } = dialog.getBoundingClientRect(); simulateClick(nativeDialog, { clientX: x + 1, clientY: y - 1 }); await elementUpdated(dialog); - await waitUntil(() => eventSpy.calledWith('igcClosed')); + await waitUntil(() => eventMatch(spy, 'igcClosed')); expect(dialog.open).to.be.false; }); diff --git a/src/components/divider/divider.spec.ts b/src/components/divider/divider.spec.ts index 5bd99dcb5..e648b07c4 100644 --- a/src/components/divider/divider.spec.ts +++ b/src/components/divider/divider.spec.ts @@ -1,14 +1,14 @@ -import { expect, fixture, html } from '@open-wc/testing'; - +import { beforeAll, beforeEach, describe, expect, it } from 'vitest'; import { defineComponents } from '../common/definitions/defineComponents.js'; +import { fixture, html } from '../common/helpers.spec.js'; import IgcDividerComponent from './divider.js'; describe('Divider', () => { - before(() => { + beforeAll(() => { defineComponents(IgcDividerComponent); }); - const createDefaultDivider = () => html` `; + const createDefaultDivider = () => html``; const createVerticalDashedDivider = () => html` diff --git a/src/components/dropdown/dropdown.spec.ts b/src/components/dropdown/dropdown.spec.ts index fb7cbafec..2986be725 100644 --- a/src/components/dropdown/dropdown.spec.ts +++ b/src/components/dropdown/dropdown.spec.ts @@ -1,6 +1,4 @@ -import { elementUpdated, expect, fixture, html } from '@open-wc/testing'; -import { spy } from 'sinon'; - +import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; import IgcButtonComponent from '../button/button.js'; import { arrowDown, @@ -12,6 +10,7 @@ import { tabKey, } from '../common/controllers/key-bindings.js'; import { defineComponents } from '../common/definitions/defineComponents.js'; +import { elementUpdated, fixture, html } from '../common/helpers.spec.js'; import { simulateClick, simulateKeyboard, @@ -28,7 +27,7 @@ type ItemState = { }; describe('Dropdown', () => { - before(() => defineComponents(IgcDropdownComponent, IgcButtonComponent)); + beforeAll(() => defineComponents(IgcDropdownComponent, IgcButtonComponent)); let dropDown: IgcDropdownComponent; const Items = [ @@ -144,15 +143,18 @@ describe('Dropdown', () => { }); it('`close` behavior', async () => { - const eventSpy = spy(dropDown, 'emitEvent'); + const spy = vi.spyOn(dropDown, 'emitEvent'); dropDown.scrollStrategy = 'close'; await openDropdown(); await simulateScroll(container, { top: 200 }); expect(dropDown.open).to.be.false; - expect(eventSpy.firstCall).calledWith('igcClosing'); - expect(eventSpy.lastCall).calledWith('igcClosed'); + expect(spy.mock.calls).lengthOf(2); + expect(spy).toHaveBeenNthCalledWith(1, 'igcClosing', { + cancelable: true, + }); + expect(spy).toHaveBeenNthCalledWith(2, 'igcClosed'); }); it('`block behavior`', async () => { @@ -205,7 +207,7 @@ describe('Dropdown', () => { }); it('relevant events are fired in order', async () => { - const eventSpy = spy(dropDown, 'emitEvent'); + const spy = vi.spyOn(dropDown, 'emitEvent'); // No opening sequence of events since detached dropdowns are opened with API invocation @@ -217,11 +219,13 @@ describe('Dropdown', () => { simulateKeyboard(btn, enterKey); await elementUpdated(dropDown); - expect(eventSpy.firstCall).calledWith('igcChange', { + expect(spy).toHaveBeenNthCalledWith(1, 'igcChange', { detail: dropDown.selectedItem, }); - expect(eventSpy.secondCall).calledWith('igcClosing'); - expect(eventSpy.thirdCall).calledWith('igcClosed'); + expect(spy).toHaveBeenNthCalledWith(2, 'igcClosing', { + cancelable: true, + }); + expect(spy).toHaveBeenNthCalledWith(3, 'igcClosed'); }); it('outside click behavior is enforced', async () => { @@ -676,32 +680,34 @@ describe('Dropdown', () => { }); it('does not emit events on API calls', async () => { - const eventSpy = spy(dropDown, 'emitEvent'); + const spy = vi.spyOn(dropDown, 'emitEvent'); await openDropdown(); - expect(eventSpy).not.to.be.called; + expect(spy).not.toHaveBeenCalled(); await closeDropdown(); - expect(eventSpy).not.to.be.called; + expect(spy).not.toHaveBeenCalled(); dropDown.select('Testing'); await elementUpdated(dropDown); - expect(eventSpy).not.to.be.called; + expect(spy).not.toHaveBeenCalled(); }); it('emits correct order of events on opening', async () => { - const eventSpy = spy(dropDown, 'emitEvent'); + const spy = vi.spyOn(dropDown, 'emitEvent'); simulateClick(getTarget()); await elementUpdated(dropDown); expect(dropDown.open).to.be.true; - expect(eventSpy.firstCall).calledWith('igcOpening'); - expect(eventSpy.secondCall).calledWith('igcOpened'); + expect(spy).toHaveBeenNthCalledWith(1, 'igcOpening', { + cancelable: true, + }); + expect(spy).toHaveBeenNthCalledWith(2, 'igcOpened'); }); it('emits correct order of events on closing', async () => { - const eventSpy = spy(dropDown, 'emitEvent'); + const spy = vi.spyOn(dropDown, 'emitEvent'); await openDropdown(); @@ -709,12 +715,14 @@ describe('Dropdown', () => { await elementUpdated(dropDown); expect(dropDown.open).to.be.false; - expect(eventSpy.firstCall).calledWith('igcClosing'); - expect(eventSpy.secondCall).calledWith('igcClosed'); + expect(spy).toHaveBeenNthCalledWith(1, 'igcClosing', { + cancelable: true, + }); + expect(spy).toHaveBeenNthCalledWith(2, 'igcClosed'); }); it('emits correct order of events on selection', async () => { - const eventSpy = spy(dropDown, 'emitEvent'); + const spy = vi.spyOn(dropDown, 'emitEvent'); let targetItem = dropDown.items[3]; // Selection through click @@ -724,13 +732,15 @@ describe('Dropdown', () => { await elementUpdated(dropDown); expect(dropDown.open).to.be.false; - expect(eventSpy.firstCall).calledWithExactly('igcChange', { + expect(spy).toHaveBeenNthCalledWith(1, 'igcChange', { detail: targetItem, }); - expect(eventSpy.secondCall).calledWith('igcClosing'); - expect(eventSpy.thirdCall).calledWith('igcClosed'); + expect(spy).toHaveBeenNthCalledWith(2, 'igcClosing', { + cancelable: true, + }); + expect(spy).toHaveBeenNthCalledWith(3, 'igcClosed'); - eventSpy.resetHistory(); + spy.mockClear(); // Selection through keyboard targetItem = dropDown.items[2]; @@ -742,15 +752,17 @@ describe('Dropdown', () => { await elementUpdated(dropDown); expect(dropDown.open).to.be.false; - expect(eventSpy.firstCall).calledWithExactly('igcChange', { + expect(spy).toHaveBeenNthCalledWith(1, 'igcChange', { detail: targetItem, }); - expect(eventSpy.secondCall).calledWith('igcClosing'); - expect(eventSpy.thirdCall).calledWith('igcClosed'); + expect(spy).toHaveBeenNthCalledWith(2, 'igcClosing', { + cancelable: true, + }); + expect(spy).toHaveBeenNthCalledWith(3, 'igcClosed'); }); it('can halt opening event sequence', async () => { - const eventSpy = spy(dropDown, 'emitEvent'); + const spy = vi.spyOn(dropDown, 'emitEvent'); dropDown.addEventListener('igcOpening', (e) => e.preventDefault(), { once: true, }); @@ -759,12 +771,14 @@ describe('Dropdown', () => { await elementUpdated(dropDown); expect(dropDown.open).to.be.false; - expect(eventSpy.firstCall).calledWith('igcOpening'); - expect(eventSpy.secondCall).to.be.null; + expect(spy.mock.calls).lengthOf(1); + expect(spy).toHaveBeenNthCalledWith(1, 'igcOpening', { + cancelable: true, + }); }); it('can halt closing event sequence', async () => { - const eventSpy = spy(dropDown, 'emitEvent'); + const spy = vi.spyOn(dropDown, 'emitEvent'); dropDown.addEventListener('igcClosing', (e) => e.preventDefault(), { once: true, }); @@ -776,10 +790,12 @@ describe('Dropdown', () => { await elementUpdated(dropDown); expect(dropDown.open).to.be.true; - expect(eventSpy.firstCall).calledWith('igcClosing'); - expect(eventSpy.secondCall).to.be.null; + expect(spy.mock.calls).lengthOf(1); + expect(spy).toHaveBeenNthCalledWith(1, 'igcClosing', { + cancelable: true, + }); - eventSpy.resetHistory(); + spy.mockClear(); // With selection dropDown.addEventListener('igcClosing', (e) => e.preventDefault(), { @@ -793,13 +809,17 @@ describe('Dropdown', () => { await elementUpdated(dropDown); expect(dropDown.open).to.be.true; - expect(eventSpy.firstCall).calledWith('igcChange'); - expect(eventSpy.secondCall).calledWith('igcClosing'); - expect(eventSpy.thirdCall).to.be.null; + expect(spy.mock.calls).lengthOf(2); + expect(spy).toHaveBeenNthCalledWith(1, 'igcChange', { + detail: dropDown.selectedItem, + }); + expect(spy).toHaveBeenNthCalledWith(2, 'igcClosing', { + cancelable: true, + }); }); it('can halt closing event sequence on outside click', async () => { - const eventSpy = spy(dropDown, 'emitEvent'); + const spy = vi.spyOn(dropDown, 'emitEvent'); await openDropdown(); @@ -811,8 +831,10 @@ describe('Dropdown', () => { await elementUpdated(dropDown); expect(dropDown.open).to.be.true; - expect(eventSpy.firstCall).calledWith('igcClosing'); - expect(eventSpy.secondCall).to.be.null; + expect(spy.mock.calls).lengthOf(1); + expect(spy).toHaveBeenNthCalledWith(1, 'igcClosing', { + cancelable: true, + }); }); }); diff --git a/src/components/expansion-panel/expansion-panel.spec.ts b/src/components/expansion-panel/expansion-panel.spec.ts index 0461392a0..f06ed74c1 100644 --- a/src/components/expansion-panel/expansion-panel.spec.ts +++ b/src/components/expansion-panel/expansion-panel.spec.ts @@ -1,12 +1,12 @@ import { - elementUpdated, + beforeAll, + beforeEach, + describe, expect, - fixture, - html, - waitUntil, -} from '@open-wc/testing'; -import { spy } from 'sinon'; - + it, + type MockInstance, + vi, +} from 'vitest'; import { altKey, arrowDown, @@ -15,7 +15,17 @@ import { spaceBar, } from '../common/controllers/key-bindings.js'; import { defineComponents } from '../common/definitions/defineComponents.js'; -import { simulateClick, simulateKeyboard } from '../common/utils.spec.js'; +import { + elementUpdated, + fixture, + html, + waitUntil, +} from '../common/helpers.spec.js'; +import { + eventMatch, + simulateClick, + simulateKeyboard, +} from '../common/utils.spec.js'; import type IgcIconComponent from '../icon/icon.js'; import IgcExpansionPanelComponent from './expansion-panel.js'; @@ -29,12 +39,12 @@ type ExpansionSlots = type ExpansionParts = 'header' | 'title' | 'subtitle' | 'content' | 'indicator'; describe('Expansion Panel', () => { - before(() => { + beforeAll(() => { defineComponents(IgcExpansionPanelComponent); }); let panel: IgcExpansionPanelComponent; - let eventSpy: ReturnType; + let spy: MockInstance; const getSlot = (name: ExpansionSlots = '') => { return panel.shadowRoot!.querySelector( @@ -58,12 +68,17 @@ describe('Expansion Panel', () => { state: { open: boolean } = { open: false } ) => { expect(panel.open).to.equal(state.open); - expect(eventSpy.callCount).to.equal(2); - expect(eventSpy.firstCall).calledWith( + expect(spy.mock.calls).lengthOf(2); + expect(spy).toHaveBeenNthCalledWith( + 1, state.open ? 'igcOpening' : 'igcClosing', - { cancelable: true, detail: panel } + { + cancelable: true, + detail: panel, + } ); - expect(eventSpy.secondCall).calledWith( + expect(spy).toHaveBeenNthCalledWith( + 2, state.open ? 'igcOpened' : 'igcClosed', { detail: panel, @@ -280,21 +295,21 @@ describe('Expansion Panel', () => { describe('User interactions', () => { beforeEach(async () => { panel = await fixture(createDefaultPanel()); - eventSpy = spy(panel, 'emitEvent'); + spy = vi.spyOn(panel, 'emitEvent'); }); it('should expand/collapse on header click', async () => { const header = getDOMPart('header'); simulateClick(header); - await waitUntil(() => eventSpy.calledWith('igcOpened')); + await waitUntil(() => eventMatch(spy, 'igcOpened')); verifyStateAndEventSequence({ open: true }); - eventSpy.resetHistory(); + spy.mockClear(); simulateClick(header); - await waitUntil(() => eventSpy.calledWith('igcClosed')); + await waitUntil(() => eventMatch(spy, 'igcClosed')); verifyStateAndEventSequence({ open: false }); }); @@ -303,14 +318,14 @@ describe('Expansion Panel', () => { const header = getDOMPart('header'); simulateKeyboard(header, spaceBar); - await waitUntil(() => eventSpy.calledWith('igcOpened')); + await waitUntil(() => eventMatch(spy, 'igcOpened')); verifyStateAndEventSequence({ open: true }); - eventSpy.resetHistory(); + spy.mockClear(); simulateKeyboard(header, enterKey); - await waitUntil(() => eventSpy.calledWith('igcClosed')); + await waitUntil(() => eventMatch(spy, 'igcClosed')); verifyStateAndEventSequence({ open: false }); }); @@ -319,14 +334,14 @@ describe('Expansion Panel', () => { const header = getDOMPart('header'); simulateKeyboard(header, [altKey, arrowDown]); - await waitUntil(() => eventSpy.calledWith('igcOpened')); + await waitUntil(() => eventMatch(spy, 'igcOpened')); verifyStateAndEventSequence({ open: true }); - eventSpy.resetHistory(); + spy.mockClear(); simulateKeyboard(header, [altKey, arrowUp]); - await waitUntil(() => eventSpy.calledWith('igcClosed')); + await waitUntil(() => eventMatch(spy, 'igcClosed')); verifyStateAndEventSequence({ open: false }); }); @@ -341,46 +356,46 @@ describe('Expansion Panel', () => { await elementUpdated(panel); expect(panel.open).to.be.false; - expect(eventSpy.callCount).to.equal(0); + expect(spy.mock.calls).lengthOf(0); simulateClick(header); await elementUpdated(panel); expect(panel.open).to.be.false; - expect(eventSpy.callCount).to.equal(0); + expect(spy.mock.calls).lengthOf(0); }); }); describe('Events', () => { beforeEach(async () => { panel = await fixture(createDefaultPanel()); - eventSpy = spy(panel, 'emitEvent'); + spy = vi.spyOn(panel, 'emitEvent'); }); it('does not emit duplicate events for expanded/collapsed state on Alt + Arrow keys', async () => { const header = getDOMPart('header'); simulateKeyboard(header, [altKey, arrowDown]); - await waitUntil(() => eventSpy.calledWith('igcOpened')); + await waitUntil(() => eventMatch(spy, 'igcOpened')); verifyStateAndEventSequence({ open: true }); simulateKeyboard(header, [altKey, arrowDown]); await elementUpdated(panel); - expect(eventSpy.callCount).to.equal(2); + expect(spy.mock.calls).lengthOf(2); - eventSpy.resetHistory(); + spy.mockClear(); simulateKeyboard(header, [altKey, arrowUp]); - await waitUntil(() => eventSpy.calledWith('igcClosed')); + await waitUntil(() => eventMatch(spy, 'igcClosed')); verifyStateAndEventSequence({ open: false }); simulateKeyboard(header, [altKey, arrowUp]); await elementUpdated(panel); - expect(eventSpy.callCount).to.equal(2); + expect(spy.mock.calls).lengthOf(2); }); it('should be able to cancel -ing events', async () => { @@ -394,12 +409,12 @@ describe('Expansion Panel', () => { await elementUpdated(panel); expect(panel.open).to.be.false; - expect(eventSpy).calledOnceWith('igcOpening', { + expect(spy).toHaveBeenCalledExactlyOnceWith('igcOpening', { cancelable: true, detail: panel, }); - eventSpy.resetHistory(); + spy.mockClear(); panel.open = true; await elementUpdated(panel); @@ -412,7 +427,7 @@ describe('Expansion Panel', () => { await elementUpdated(panel); expect(panel.open).to.be.true; - expect(eventSpy).calledOnceWith('igcClosing', { + expect(spy).toHaveBeenCalledExactlyOnceWith('igcClosing', { cancelable: true, detail: panel, }); diff --git a/src/components/file-input/file-input.spec.ts b/src/components/file-input/file-input.spec.ts index 446ee98bb..536d64891 100644 --- a/src/components/file-input/file-input.spec.ts +++ b/src/components/file-input/file-input.spec.ts @@ -1,7 +1,7 @@ -import { elementUpdated, expect, fixture, html } from '@open-wc/testing'; import type { TemplateResult } from 'lit'; -import { spy } from 'sinon'; +import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; import { defineComponents } from '../common/definitions/defineComponents.js'; +import { elementUpdated, fixture, html } from '../common/helpers.spec.js'; import { first } from '../common/util.js'; import { createFormAssociatedTestBed } from '../common/utils.spec.js'; import { @@ -16,7 +16,7 @@ describe('File Input component', () => { new File(['image data'], 'image.png', { type: 'image/png' }), ]; - before(() => { + beforeAll(() => { defineComponents(IgcFileInputComponent); }); @@ -151,23 +151,23 @@ describe('File Input component', () => { it('emits igcChange', async () => { await createFixture(html``); - const eventSpy = spy(element, 'emitEvent'); + const spy = vi.spyOn(element, 'emitEvent'); simulateFileUpload(input, [first(files)]); await elementUpdated(element); - expect(eventSpy).calledWith('igcChange', { + expect(spy).toHaveBeenCalledWith('igcChange', { detail: input.files, }); }); it('emits igcCancel', async () => { - const eventSpy = spy(element, 'emitEvent'); + const spy = vi.spyOn(element, 'emitEvent'); input.dispatchEvent(new Event('cancel', { bubbles: true })); await elementUpdated(element); - expect(eventSpy).calledOnceWith('igcCancel', { + expect(spy).toHaveBeenCalledWith('igcCancel', { detail: input.files, }); }); diff --git a/src/components/focus-trap/focus-trap.spec.ts b/src/components/focus-trap/focus-trap.spec.ts index fb8abf1fb..9fa742580 100644 --- a/src/components/focus-trap/focus-trap.spec.ts +++ b/src/components/focus-trap/focus-trap.spec.ts @@ -1,14 +1,14 @@ -import { expect, fixture, html } from '@open-wc/testing'; - +import { beforeAll, beforeEach, describe, expect, it } from 'vitest'; import IgcButtonComponent from '../button/button.js'; import IgcCalendarComponent from '../calendar/calendar.js'; import type IgcDaysViewComponent from '../calendar/days-view/days-view.js'; import { defineComponents } from '../common/definitions/defineComponents.js'; +import { fixture, html } from '../common/helpers.spec.js'; import IgcInputComponent from '../input/input.js'; import IgcFocusTrapComponent from './focus-trap.js'; describe('Focus trap', () => { - before(() => + beforeAll(() => defineComponents( IgcFocusTrapComponent, IgcCalendarComponent, diff --git a/src/components/icon/icon.spec.ts b/src/components/icon/icon.spec.ts index 45af71ef1..188bea147 100644 --- a/src/components/icon/icon.spec.ts +++ b/src/components/icon/icon.spec.ts @@ -1,13 +1,19 @@ +import { + afterEach, + beforeAll, + beforeEach, + describe, + expect, + it, + vi, +} from 'vitest'; +import { defineComponents } from '../common/definitions/defineComponents.js'; import { aTimeout, elementUpdated, - expect, fixture, html, -} from '@open-wc/testing'; -import { stub } from 'sinon'; - -import { defineComponents } from '../common/definitions/defineComponents.js'; +} from '../common/helpers.spec.js'; import { first, last } from '../common/util.js'; import IgcIconComponent from './icon.js'; import { @@ -39,14 +45,12 @@ const searchSvgContent = const searchSvg = `${searchSvgContent}`; function mockResponse() { - const response = new globalThis.Response(bugSvg, { + return new globalThis.Response(bugSvg, { status: 200, headers: { 'Content-Type': 'image/svg+xml', }, }); - - return Promise.resolve(response); } describe('Icon registry', () => { @@ -54,8 +58,7 @@ describe('Icon registry', () => { const collection = 'test-collection'; beforeEach(() => { - const mocked = stub(globalThis, 'fetch'); - mocked.onCall(0).returns(mockResponse()); + vi.spyOn(globalThis, 'fetch').mockResolvedValueOnce(mockResponse()); }); it('is registered', async () => { @@ -82,7 +85,7 @@ describe('Icon registry', () => { }); describe('Referential Icons', () => { - before(() => { + beforeAll(() => { defineComponents(IgcIconComponent); }); @@ -127,7 +130,7 @@ describe('Icon registry', () => { }); afterEach(() => { - (globalThis.fetch as any).restore(); + vi.restoreAllMocks(); }); }); @@ -162,7 +165,7 @@ describe('Icon broadcast service', () => { const iconName = 'bug'; registerIconFromText(iconName, bugSvg, collectionName); - await aTimeout(0); + await aTimeout(50); const { actionType, collections } = first(events).data; expect(actionType).to.equal(ActionType.RegisterIcon); @@ -182,7 +185,7 @@ describe('Icon broadcast service', () => { for (const each of icons) { registerIconFromText(each[0], each[1], collectionName); } - await aTimeout(0); + await aTimeout(50); expect(events).lengthOf(icons.length); for (const [idx, event] of events.entries()) { @@ -206,7 +209,7 @@ describe('Icon broadcast service', () => { name: 'reference-test', collection: collectionName, }); - await aTimeout(0); + await aTimeout(50); const { actionType, collections, references } = last(events).data; @@ -226,7 +229,7 @@ describe('Icon broadcast service', () => { meta.name = 'reference-test'; meta.collection = collectionName; setIconRef(refName, refCollectionName, meta); - await aTimeout(0); + await aTimeout(50); const { actionType, collections, references } = last(events).data; @@ -249,7 +252,7 @@ describe('Icon broadcast service', () => { }, overwrite: true, }); - await aTimeout(0); + await aTimeout(50); expect(events.length).to.equal(0); }); @@ -264,7 +267,7 @@ describe('Icon broadcast service', () => { // a peer is requesting a state sync channel.postMessage({ actionType: ActionType.SyncState }); - await aTimeout(20); + await aTimeout(200); // all icon broadcasts must respond with their state // 2 from broadcast service + 1 from global. @@ -285,7 +288,7 @@ describe('Icon broadcast service', () => { // a peer is requesting a state sync channel.postMessage({ actionType: ActionType.SyncState }); - await aTimeout(0); + await aTimeout(50); expect(events).lengthOf(2); // [ActionType.RegisterIcon, ActionType.SyncState] @@ -310,7 +313,7 @@ describe('Icon broadcast service', () => { // a peer is requesting a state sync channel.postMessage({ actionType: ActionType.SyncState }); - await aTimeout(0); + await aTimeout(50); expect(events).lengthOf(1); // [ActionType.SyncState] @@ -322,7 +325,7 @@ describe('Icon broadcast service', () => { }); describe('Icon component', () => { - before(() => { + beforeAll(() => { defineComponents(IgcIconComponent); }); diff --git a/src/components/input/input.spec.ts b/src/components/input/input.spec.ts index 9f79262ff..862919e2d 100644 --- a/src/components/input/input.spec.ts +++ b/src/components/input/input.spec.ts @@ -1,14 +1,13 @@ +import type { TemplateResult } from 'lit'; +import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; +import { configureTheme } from '../../theming/config.js'; +import { defineComponents } from '../common/definitions/defineComponents.js'; import { elementUpdated, - expect, fixture, html, nextFrame, -} from '@open-wc/testing'; -import type { TemplateResult } from 'lit'; -import { spy } from 'sinon'; -import { configureTheme } from '../../theming/config.js'; -import { defineComponents } from '../common/definitions/defineComponents.js'; +} from '../common/helpers.spec.js'; import { createFormAssociatedTestBed, isFocused, @@ -21,7 +20,7 @@ import { import IgcInputComponent from './input.js'; describe('Input component', () => { - before(() => { + beforeAll(() => { defineComponents(IgcInputComponent); }); @@ -280,22 +279,26 @@ describe('Input component', () => { }); it('emits igcInput', async () => { - const eventSpy = spy(element, 'emitEvent'); + const spy = vi.spyOn(element, 'emitEvent'); simulateInput(input, { value: '123' }); await elementUpdated(element); - expect(eventSpy).calledOnceWithExactly('igcInput', { detail: '123' }); + expect(spy).toHaveBeenCalledExactlyOnceWith('igcInput', { + detail: '123', + }); }); it('emits igcChange', async () => { simulateInput(input, { value: '123' }); await elementUpdated(element); - const eventSpy = spy(element, 'emitEvent'); + const spy = vi.spyOn(element, 'emitEvent'); input.dispatchEvent(new Event('change')); - expect(eventSpy).calledOnceWithExactly('igcChange', { detail: '123' }); + expect(spy).toHaveBeenCalledExactlyOnceWith('igcChange', { + detail: '123', + }); }); }); }); diff --git a/src/components/list/list.spec.ts b/src/components/list/list.spec.ts index 537df6706..25311ef9a 100644 --- a/src/components/list/list.spec.ts +++ b/src/components/list/list.spec.ts @@ -1,10 +1,10 @@ -import { expect, fixture, html } from '@open-wc/testing'; - +import { beforeAll, beforeEach, describe, expect, it } from 'vitest'; import { defineComponents } from '../common/definitions/defineComponents.js'; +import { fixture, html } from '../common/helpers.spec.js'; import IgcListComponent from './list.js'; describe('List', () => { - before(() => { + beforeAll(() => { defineComponents(IgcListComponent); }); diff --git a/src/components/mask-input/mask-input.spec.ts b/src/components/mask-input/mask-input.spec.ts index c5a87e866..91edbba0d 100644 --- a/src/components/mask-input/mask-input.spec.ts +++ b/src/components/mask-input/mask-input.spec.ts @@ -1,7 +1,6 @@ -import { elementUpdated, expect, fixture } from '@open-wc/testing'; -import { html } from 'lit'; -import { spy } from 'sinon'; +import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; import { defineComponents } from '../common/definitions/defineComponents.js'; +import { elementUpdated, fixture, html } from '../common/helpers.spec.js'; import { createFormAssociatedTestBed, simulateInput, @@ -16,7 +15,7 @@ import IgcMaskInputComponent from './mask-input.js'; import { MaskParser } from './mask-parser.js'; describe('Masked input', () => { - before(() => defineComponents(IgcMaskInputComponent)); + beforeAll(() => defineComponents(IgcMaskInputComponent)); const parser = new MaskParser(); const defaultPrompt = '_'; @@ -269,24 +268,26 @@ describe('Masked input', () => { it('igcChange event', async () => { syncParser(); - const eventSpy = spy(element, 'emitEvent'); + const spy = vi.spyOn(element, 'emitEvent'); element.value = 'abc'; await elementUpdated(element); input.dispatchEvent(new Event('change')); - expect(eventSpy).calledWith('igcChange', { detail: 'abc' }); + expect(spy).toHaveBeenCalledExactlyOnceWith('igcChange', { + detail: 'abc', + }); }); it('igcChange event with literals', async () => { syncParser(); - const eventSpy = spy(element, 'emitEvent'); + const spy = vi.spyOn(element, 'emitEvent'); element.value = 'abc'; element.valueMode = 'withFormatting'; await elementUpdated(element); input.dispatchEvent(new Event('change')); - expect(eventSpy).calledWith('igcChange', { + expect(spy).toHaveBeenCalledExactlyOnceWith('igcChange', { detail: parser.apply(element.value), }); }); @@ -296,17 +297,18 @@ describe('Masked input', () => { await elementUpdated(element); syncParser(); - const eventSpy = spy(element, 'emitEvent'); + const spy = vi.spyOn(element, 'emitEvent'); element.value = '111'; element.setSelectionRange(2, 3); await elementUpdated(element); - // fireInputEvent(input, 'insertText'); simulateInput(input, { inputType: 'insertText', skipValueProperty: true, }); - expect(eventSpy).calledWith('igcInput', { detail: '111' }); + expect(spy).toHaveBeenCalledExactlyOnceWith('igcInput', { + detail: '111', + }); }); it('igInput event (end of pattern)', async () => { @@ -314,7 +316,7 @@ describe('Masked input', () => { await elementUpdated(element); syncParser(); - const eventSpy = spy(element, 'emitEvent'); + const spy = vi.spyOn(element, 'emitEvent'); element.value = '111'; element.setSelectionRange(3, 3); await elementUpdated(element); @@ -323,7 +325,7 @@ describe('Masked input', () => { inputType: 'insertText', skipValueProperty: true, }); - expect(eventSpy).not.calledWith('igcInput', { detail: '111' }); + expect(spy).not.toHaveBeenCalledWith('igcInput', { detail: '111' }); }); it('is accessible', async () => { diff --git a/src/components/mask-input/mask-parser.spec.ts b/src/components/mask-input/mask-parser.spec.ts index ac20b2309..d3634dfb7 100644 --- a/src/components/mask-input/mask-parser.spec.ts +++ b/src/components/mask-input/mask-parser.spec.ts @@ -1,5 +1,4 @@ -import { expect } from '@open-wc/testing'; - +import { beforeEach, describe, expect, it } from 'vitest'; import { MaskParser } from './mask-parser.js'; describe('Mask parser', () => { diff --git a/src/components/nav-drawer/nav-drawer.spec.ts b/src/components/nav-drawer/nav-drawer.spec.ts index 9247367df..ac0f41ba0 100644 --- a/src/components/nav-drawer/nav-drawer.spec.ts +++ b/src/components/nav-drawer/nav-drawer.spec.ts @@ -1,12 +1,12 @@ -import { expect, fixture, html } from '@open-wc/testing'; import type { TemplateResult } from 'lit'; - +import { beforeAll, beforeEach, describe, expect, it } from 'vitest'; import { defineComponents } from '../common/definitions/defineComponents.js'; +import { fixture, html } from '../common/helpers.spec.js'; import IgcIconComponent from '../icon/icon.js'; import IgcNavDrawerComponent from './nav-drawer.js'; describe('Navigation Drawer', () => { - before(() => { + beforeAll(() => { defineComponents(IgcNavDrawerComponent, IgcIconComponent); }); diff --git a/src/components/navbar/navbar.spec.ts b/src/components/navbar/navbar.spec.ts index 662c10301..030bfcea5 100644 --- a/src/components/navbar/navbar.spec.ts +++ b/src/components/navbar/navbar.spec.ts @@ -1,9 +1,10 @@ -import { expect, fixture, html } from '@open-wc/testing'; - -import { defineComponents, IgcNavbarComponent } from '../../index.js'; +import { beforeAll, describe, expect, it } from 'vitest'; +import { defineComponents } from '../common/definitions/defineComponents.js'; +import { fixture, html } from '../common/helpers.spec.js'; +import IgcNavbarComponent from './navbar.js'; describe('Navbar component', () => { - before(() => { + beforeAll(() => { defineComponents(IgcNavbarComponent); }); diff --git a/src/components/popover/popover.spec.ts b/src/components/popover/popover.spec.ts index a4c534826..5e1ce5ab5 100644 --- a/src/components/popover/popover.spec.ts +++ b/src/components/popover/popover.spec.ts @@ -1,12 +1,11 @@ +import { beforeAll, beforeEach, describe, expect, it } from 'vitest'; +import { defineComponents } from '../common/definitions/defineComponents.js'; import { elementUpdated, - expect, fixture, html, nextFrame, -} from '@open-wc/testing'; - -import { defineComponents } from '../common/definitions/defineComponents.js'; +} from '../common/helpers.spec.js'; import IgcPopoverComponent from './popover.js'; async function waitForPaint(popover: IgcPopoverComponent) { @@ -52,7 +51,7 @@ function createNonSlottedPopover(isOpen = false) { } describe('Popover', () => { - before(() => { + beforeAll(() => { defineComponents(IgcPopoverComponent); }); diff --git a/src/components/progress/circular-progress.spec.ts b/src/components/progress/circular-progress.spec.ts index 1e8caf19f..8ec052b7a 100644 --- a/src/components/progress/circular-progress.spec.ts +++ b/src/components/progress/circular-progress.spec.ts @@ -1,11 +1,11 @@ +import { beforeAll, beforeEach, describe, expect, it } from 'vitest'; +import { defineComponents } from '../common/definitions/defineComponents.js'; import { elementUpdated, - expect, fixture, html, nextFrame, -} from '@open-wc/testing'; -import { defineComponents } from '../common/definitions/defineComponents.js'; +} from '../common/helpers.spec.js'; import { first } from '../common/util.js'; import IgcCircularGradientComponent from './circular-gradient.js'; import IgcCircularProgressComponent from './circular-progress.js'; @@ -24,7 +24,7 @@ describe('Circular progress component', () => { await nextFrame(); }; - before(() => { + beforeAll(() => { defineComponents(IgcCircularProgressComponent); }); diff --git a/src/components/progress/linear-progress.spec.ts b/src/components/progress/linear-progress.spec.ts index 242668aea..884c7270f 100644 --- a/src/components/progress/linear-progress.spec.ts +++ b/src/components/progress/linear-progress.spec.ts @@ -1,11 +1,11 @@ +import { beforeAll, beforeEach, describe, expect, it } from 'vitest'; +import { defineComponents } from '../common/definitions/defineComponents.js'; import { elementUpdated, - expect, fixture, html, nextFrame, -} from '@open-wc/testing'; -import { defineComponents } from '../common/definitions/defineComponents.js'; +} from '../common/helpers.spec.js'; import { first } from '../common/util.js'; import IgcLinearProgressComponent from './linear-progress.js'; @@ -23,7 +23,7 @@ describe('Linear progress component', () => { await nextFrame(); }; - before(() => { + beforeAll(() => { defineComponents(IgcLinearProgressComponent); }); diff --git a/src/components/radio-group/radio-group.spec.ts b/src/components/radio-group/radio-group.spec.ts index 2da7f1a2b..9d7e6ad55 100644 --- a/src/components/radio-group/radio-group.spec.ts +++ b/src/components/radio-group/radio-group.spec.ts @@ -1,6 +1,12 @@ -import { elementUpdated, expect, fixture, html } from '@open-wc/testing'; -import { spy } from 'sinon'; - +import { + beforeAll, + beforeEach, + describe, + expect, + it, + type MockInstance, + vi, +} from 'vitest'; import { arrowDown, arrowLeft, @@ -8,13 +14,18 @@ import { arrowUp, } from '../common/controllers/key-bindings.js'; import { defineComponents } from '../common/definitions/defineComponents.js'; +import { elementUpdated, fixture, html } from '../common/helpers.spec.js'; import { first, last } from '../common/util.js'; -import { isFocused, simulateKeyboard } from '../common/utils.spec.js'; +import { + expectCalledWith, + isFocused, + simulateKeyboard, +} from '../common/utils.spec.js'; import IgcRadioComponent from '../radio/radio.js'; import IgcRadioGroupComponent from './radio-group.js'; describe('Radio Group Component', () => { - before(() => { + beforeAll(() => { defineComponents(IgcRadioGroupComponent); }); @@ -94,7 +105,7 @@ describe('Radio Group Component', () => { }); describe('Keyboard navigation', () => { - let spies: unknown[]; + let spies: MockInstance[]; async function waitForUpdate() { await Promise.all(radios.map((radio) => elementUpdated(radio))); @@ -109,7 +120,7 @@ describe('Radio Group Component', () => { radios = Array.from( group.querySelectorAll(IgcRadioComponent.tagName) ); - spies = radios.map((radio) => spy(radio, 'emitEvent')); + spies = radios.map((radio) => vi.spyOn(radio, 'emitEvent')); }); it('should be able to navigate through radios using arrow keys', async () => { @@ -121,7 +132,7 @@ describe('Radio Group Component', () => { validateGroupSelected(first); expect(isFocused(first)).to.be.true; - expect(firstSpy).calledWith('igcChange'); + expectCalledWith(firstSpy, 'igcChange'); simulateKeyboard(first, arrowDown); await waitForUpdate(); @@ -129,7 +140,7 @@ describe('Radio Group Component', () => { validateGroupSelected(second); expect(isFocused(first)).to.be.false; expect(isFocused(second)).to.be.true; - expect(secondSpy).calledWith('igcChange'); + expectCalledWith(secondSpy, 'igcChange'); simulateKeyboard(second, arrowUp); await waitForUpdate(); @@ -137,7 +148,7 @@ describe('Radio Group Component', () => { validateGroupSelected(first); expect(isFocused(second)).to.be.false; expect(isFocused(first)).to.be.true; - expect(firstSpy).to.be.calledWith('igcChange'); + expectCalledWith(firstSpy, 'igcChange'); simulateKeyboard(first, arrowRight); await waitForUpdate(); @@ -145,7 +156,7 @@ describe('Radio Group Component', () => { validateGroupSelected(second); expect(isFocused(first)).to.be.false; expect(isFocused(second)).to.be.true; - expect(secondSpy).to.be.calledWith('igcChange'); + expectCalledWith(secondSpy, 'igcChange'); simulateKeyboard(second, arrowLeft); await waitForUpdate(); @@ -153,7 +164,7 @@ describe('Radio Group Component', () => { validateGroupSelected(first); expect(isFocused(second)).to.be.false; expect(isFocused(first)).to.be.true; - expect(firstSpy).to.be.calledWith('igcChange'); + expectCalledWith(firstSpy, 'igcChange'); simulateKeyboard(first, arrowLeft); await waitForUpdate(); @@ -161,7 +172,7 @@ describe('Radio Group Component', () => { validateGroupSelected(third); expect(isFocused(first)).to.be.false; expect(isFocused(third)).to.be.true; - expect(thirdSpy).to.be.calledWith('igcChange'); + expectCalledWith(thirdSpy, 'igcChange'); simulateKeyboard(third, arrowDown); await waitForUpdate(); @@ -169,7 +180,7 @@ describe('Radio Group Component', () => { validateGroupSelected(first); expect(isFocused(third)).to.be.false; expect(isFocused(first)).to.be.true; - expect(firstSpy).to.be.calledWith('igcChange'); + expectCalledWith(firstSpy, 'igcChange'); }); it('should skip disabled radios when navigating', async () => { @@ -190,8 +201,8 @@ describe('Radio Group Component', () => { validateGroupSelected(third); expect(isFocused(first)).to.be.false; expect(isFocused(third)).to.be.true; - expect(secondSpy).to.not.be.called; - expect(thirdSpy).calledWith('igcChange'); + expect(secondSpy).not.toHaveBeenCalled(); + expectCalledWith(thirdSpy, 'igcChange'); }); }); }); diff --git a/src/components/radio/radio.spec.ts b/src/components/radio/radio.spec.ts index b0121d3ea..8d3306b58 100644 --- a/src/components/radio/radio.spec.ts +++ b/src/components/radio/radio.spec.ts @@ -1,6 +1,6 @@ -import { elementUpdated, expect, fixture, html } from '@open-wc/testing'; -import { spy } from 'sinon'; +import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; import { defineComponents } from '../common/definitions/defineComponents.js'; +import { elementUpdated, fixture, html } from '../common/helpers.spec.js'; import { first, last } from '../common/util.js'; import { createFormAssociatedTestBed, @@ -14,7 +14,7 @@ import { import IgcRadioComponent from './radio.js'; describe('Radio Component', () => { - before(() => { + beforeAll(() => { defineComponents(IgcRadioComponent); }); @@ -144,11 +144,11 @@ describe('Radio Component', () => { }); it('should emit igcChange event when radio is checked', async () => { - const eventSpy = spy(radio, 'emitEvent'); + const spy = vi.spyOn(radio, 'emitEvent'); radio.click(); await elementUpdated(radio); - expect(eventSpy).calledWithExactly('igcChange', { + expect(spy).toHaveBeenCalledWith('igcChange', { detail: { checked: true, value: undefined, @@ -157,15 +157,16 @@ describe('Radio Component', () => { }); it('it should not emit igcChange event on already checked radio', async () => { - const eventSpy = spy(radio, 'emitEvent'); + const spy = vi.spyOn(radio, 'emitEvent'); radio.click(); await elementUpdated(radio); - expect(eventSpy.getCalls()).lengthOf(1); + + expect(spy).toHaveBeenCalledTimes(1); radio.click(); await elementUpdated(radio); - expect(eventSpy.getCalls()).lengthOf(1); + expect(spy).toHaveBeenCalledTimes(1); }); it('should be able to use external elements as label', async () => { @@ -181,13 +182,13 @@ describe('Radio Component', () => { }); it('should emit click event only once', async () => { - const eventSpy = spy(radio, 'click'); + const spy = vi.spyOn(radio, 'click'); - radio.addEventListener('click', eventSpy); + radio.addEventListener('click', spy); simulateClick(radio); await elementUpdated(radio); - expect(eventSpy.callCount).to.equal(1); + expect(spy).toHaveBeenCalledTimes(1); }); it('should update the validity state of the group when a single radio is validated', async () => { diff --git a/src/components/rating/rating.spec.ts b/src/components/rating/rating.spec.ts index 0639d01f3..53f44f065 100644 --- a/src/components/rating/rating.spec.ts +++ b/src/components/rating/rating.spec.ts @@ -1,13 +1,5 @@ -import { - elementUpdated, - expect, - fixture, - fixtureCleanup, - html, -} from '@open-wc/testing'; -import { nothing } from 'lit'; -import { spy } from 'sinon'; - +import { html, nothing } from 'lit'; +import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; import { arrowDown, arrowLeft, @@ -17,6 +9,7 @@ import { homeKey, } from '../common/controllers/key-bindings.js'; import { defineComponents } from '../common/definitions/defineComponents.js'; +import { cleanup, elementUpdated, fixture } from '../common/helpers.spec.js'; import { createFormAssociatedTestBed, simulateClick, @@ -27,7 +20,7 @@ import IgcRatingComponent from './rating.js'; import IgcRatingSymbolComponent from './rating-symbol.js'; describe('Rating component', () => { - before(() => { + beforeAll(() => { defineComponents(IgcRatingComponent); }); @@ -219,7 +212,7 @@ describe('Rating component', () => { expect(symbol.getClientRects()).to.not.be.empty; }); - fixtureCleanup(); + cleanup(); rating = await fixture(renderRating(true)); expect(getProjectedSymbols(rating)).to.have.lengthOf(3); @@ -284,17 +277,17 @@ describe('Rating component', () => { }); it('correctly updates value on click', async () => { - const eventSpy = spy(el, 'emitEvent'); + const spy = vi.spyOn(el, 'emitEvent'); const symbol = getRatingSymbols(el).item(2); const { x, width } = getBoundingRect(symbol); simulateClick(symbol, { clientX: x + width / 2 }); - expect(eventSpy).calledOnceWithExactly('igcChange', { detail: 3 }); + expect(spy).toHaveBeenCalledWith('igcChange', { detail: 3 }); expect(el.value).to.equal(3); }); it('correctly updates value on click [precision != 1]', async () => { - const eventSpy = spy(el, 'emitEvent'); + const spy = vi.spyOn(el, 'emitEvent'); el.step = 0.5; await elementUpdated(el); @@ -302,12 +295,12 @@ describe('Rating component', () => { const { x, width } = getBoundingRect(symbol); simulateClick(symbol, { clientX: x + width / 4 }); - expect(eventSpy).calledOnceWithExactly('igcChange', { detail: 2.5 }); + expect(spy).toHaveBeenCalledWith('igcChange', { detail: 2.5 }); expect(el.value).to.equal(2.5); }); it('rounds correctly to the next step, when the step is different than 1', async () => { - const eventSpy = spy(el, 'emitEvent'); + const spy = vi.spyOn(el, 'emitEvent'); el.step = 0.4; await elementUpdated(el); @@ -315,24 +308,24 @@ describe('Rating component', () => { const { x, width } = getBoundingRect(symbol); simulateClick(symbol, { clientX: x + width * 0.55 }); // Click 55% across the width of the symbol - expect(eventSpy).calledOnceWithExactly('igcChange', { detail: 0.8 }); + expect(spy).toHaveBeenCalledWith('igcChange', { detail: 0.8 }); expect(el.value).to.equal(0.8); }); it('issue-1548 - Inaccurate value calculation when precision == 1', async () => { - const eventSpy = spy(el, 'emitEvent'); + const spy = vi.spyOn(el, 'emitEvent'); const symbol = getRatingSymbols(el).item(2); const { x, width } = getBoundingRect(symbol); // Simulate offset click when precision == 1 simulateClick(symbol, { clientX: x + width / 4 }); - expect(eventSpy).calledOnceWithExactly('igcChange', { detail: 3 }); + expect(spy).toHaveBeenCalledWith('igcChange', { detail: 3 }); expect(el.value).to.equal(3); }); it('correctly reflects hover state', async () => { - const eventSpy = spy(el, 'emitEvent'); + const spy = vi.spyOn(el, 'emitEvent'); el.value = 2; el.hoverPreview = true; await elementUpdated(el); @@ -340,12 +333,12 @@ describe('Rating component', () => { const { x, width } = getBoundingRect(symbol); simulatePointerMove(symbol, { clientX: x + width / 2 }); - expect(eventSpy).calledOnceWithExactly('igcHover', { detail: 3 }); + expect(spy).toHaveBeenCalledWith('igcHover', { detail: 3 }); expect(el.value).to.equal(2); }); it('does not reset value if the same rating value is clicked with allow-reset = false', async () => { - const eventSpy = spy(el, 'emitEvent'); + const spy = vi.spyOn(el, 'emitEvent'); const symbol = getRatingSymbols(el).item(4); const { x, width } = getBoundingRect(symbol); @@ -354,11 +347,11 @@ describe('Rating component', () => { simulateClick(symbol, { clientX: x + width / 2 }); expect(el.value).to.equal(5); - expect(eventSpy).not.to.be.called; + expect(spy).not.toHaveBeenCalled(); }); it('correctly resets value if the same rating value is clicked with allow-reset = true', async () => { - const eventSpy = spy(el, 'emitEvent'); + const spy = vi.spyOn(el, 'emitEvent'); const symbol = getRatingSymbols(el).item(4); const { x, width } = getBoundingRect(symbol); @@ -369,40 +362,40 @@ describe('Rating component', () => { simulateClick(symbol, { clientX: x + width / 2 }); expect(el.value).to.equal(0); - expect(eventSpy).to.have.been.calledOnceWith('igcChange', { detail: 0 }); + expect(spy).toHaveBeenCalledWith('igcChange', { detail: 0 }); }); it('does nothing on click if disabled', async () => { - const eventSpy = spy(el, 'emitEvent'); + const spy = vi.spyOn(el, 'emitEvent'); el.disabled = true; await elementUpdated(el); getRatingSymbols(el).item(3).click(); - expect(eventSpy).to.not.called; + expect(spy).not.toHaveBeenCalled(); expect(el.value).to.equal(0); }); it('does nothing on click if readonly', async () => { - const eventSpy = spy(el, 'emitEvent'); + const spy = vi.spyOn(el, 'emitEvent'); el.readOnly = true; await elementUpdated(el); getRatingSymbols(el).item(3).click(); await elementUpdated(el); - expect(eventSpy).to.not.be.called; + expect(spy).not.toHaveBeenCalled(); expect(el.value).to.equal(0); }); it('does nothing on keyboard interaction if readonly', async () => { - const eventSpy = spy(el, 'emitEvent'); + const spy = vi.spyOn(el, 'emitEvent'); el.readOnly = true; await elementUpdated(el); simulateKeyboard(el, arrowRight); await elementUpdated(el); - expect(eventSpy).to.not.be.called; + expect(spy).not.toHaveBeenCalled(); expect(el.value).to.equal(0); }); @@ -477,13 +470,13 @@ describe('Rating component', () => { }); it('should not emit a change event if the value is unchanged', async () => { - const eventSpy = spy(el, 'emitEvent'); + const spy = vi.spyOn(el, 'emitEvent'); el.value = 5; await elementUpdated(el); simulateKeyboard(el, arrowRight); - expect(eventSpy).to.not.be.calledOnce; + expect(spy).not.toHaveBeenCalled(); }); it('sets step to 1 if in single selection mode', async () => { diff --git a/src/components/resize-container/resize-container.spec.ts b/src/components/resize-container/resize-container.spec.ts index 29a033b7e..596f9c834 100644 --- a/src/components/resize-container/resize-container.spec.ts +++ b/src/components/resize-container/resize-container.spec.ts @@ -1,10 +1,18 @@ -import { elementUpdated, expect, fixture, html } from '@open-wc/testing'; -import type Sinon from 'sinon'; -import { spy } from 'sinon'; - +import { + beforeAll, + beforeEach, + describe, + expect, + it, + type MockInstance, + vi, +} from 'vitest'; import { escapeKey } from '../common/controllers/key-bindings.js'; import { defineComponents } from '../common/definitions/defineComponents.js'; +import { elementUpdated, fixture, html } from '../common/helpers.spec.js'; import { + expectCalledWith, + expectNotCalledWith, simulateKeyboard, simulateLostPointerCapture, simulatePointerDown, @@ -12,15 +20,13 @@ import { simulatePointerLeave, simulatePointerMove, } from '../common/utils.spec.js'; -import IgcResizeContainerComponent, { - type IgcResizeContainerComponentEventMap, -} from './resize-container.js'; +import IgcResizeContainerComponent from './resize-container.js'; import type { ResizeCallbackParams } from './types.js'; describe('Resize container', () => { let element: IgcResizeContainerComponent; - before(() => { + beforeAll(() => { defineComponents(IgcResizeContainerComponent); }); @@ -113,67 +119,67 @@ describe('Resize container', () => { it('resize behavior should only start when interacting with the trigger element', async () => { await setResizeActiveState(element); - const eventSpy = spy(element, 'emitEvent'); + const spy = vi.spyOn(element, 'emitEvent'); simulatePointerDown(element); await elementUpdated(element); - expect(eventSpy).not.calledWith('igcResizeStart'); + expectNotCalledWith(spy, 'igcResizeStart'); }); it('should not start resize behavior with non-primary "button"', async () => { await setResizeActiveState(element); - const eventSpy = spy(element, 'emitEvent'); + const spy = vi.spyOn(element, 'emitEvent'); const DOM = getDOM(element); simulatePointerDown(DOM.adorners.corner, { button: 1 }); await elementUpdated(element); - expect(eventSpy).not.calledWith('igcResizeStart'); + expectNotCalledWith(spy, 'igcResizeStart'); }); it('should not have a ghost element when in immediate mode', async () => { await setResizeActiveState(element); - const eventSpy = spy(element, 'emitEvent'); + const spy = vi.spyOn(element, 'emitEvent'); const { adorners } = getDOM(element); simulatePointerDown(adorners.corner); await elementUpdated(element); - const { ghost } = getResizeEventState(eventSpy); + const { ghost } = getResizeEventState(spy); expect(ghost).is.null; }); it('should fire `resizeStart` on pointer interaction', async () => { await setResizeActiveState(element); - const eventSpy = spy(element, 'emitEvent'); + const spy = vi.spyOn(element, 'emitEvent'); const { adorners } = getDOM(element); simulatePointerDown(adorners.corner); await elementUpdated(element); - expect(eventSpy).calledWith('igcResizeStart'); + expectCalledWith(spy, 'igcResizeStart'); }); it('should not fire `resize` unless `resizeStart` is triggered', async () => { await setResizeActiveState(element); - const eventSpy = spy(element, 'emitEvent'); + const spy = vi.spyOn(element, 'emitEvent'); const { adorners } = getDOM(element); simulatePointerMove(adorners.corner); await elementUpdated(element); - expect(eventSpy).not.calledWith('igcResizeStart'); + expectNotCalledWith(spy, 'igcResizeStart'); }); it('should fire `resize` when resizing behavior is triggered', async () => { await setResizeActiveState(element); - const eventSpy = spy(element, 'emitEvent'); + const spy = vi.spyOn(element, 'emitEvent'); const { adorners } = getDOM(element); simulatePointerDown(adorners.corner); @@ -182,7 +188,7 @@ describe('Resize container', () => { simulatePointerMove(adorners.corner); await elementUpdated(element); - expect(eventSpy).calledWith('igcResize'); + expectCalledWith(spy, 'igcResize'); }); it('should fire `resizeEnd` when resizing is finished', async () => { @@ -193,22 +199,21 @@ describe('Resize container', () => { simulatePointerDown(adorners.corner); await elementUpdated(element); - const eventSpy = spy(element, 'emitEvent'); + const spy = vi.spyOn(element, 'emitEvent'); simulateLostPointerCapture(adorners.corner); await elementUpdated(element); - expect(eventSpy).calledWith('igcResizeEnd'); + expectCalledWith(spy, 'igcResizeEnd'); }); it('should not fire `resizeCancel` when escape key is pressed without active resizing', async () => { - const eventSpy = spy(element, 'emitEvent'); - + const spy = vi.spyOn(element, 'emitEvent'); // Default state simulateKeyboard(element, escapeKey); await elementUpdated(element); - expect(eventSpy).not.calledWith('igcResizeCancel'); + expectNotCalledWith(spy, 'igcResizeCancel'); // While in "active" state but not in resize mode await setResizeActiveState(element); @@ -216,8 +221,7 @@ describe('Resize container', () => { simulateKeyboard(element, escapeKey); await elementUpdated(element); - expect(eventSpy).not.calledWith('igcResizeCancel'); - expect(eventSpy.getCalls()).to.be.empty; + expectNotCalledWith(spy, 'igcResizeCancel'); }); it('should fire `resizeCancel` when escape key is pressed during resizing', async () => { @@ -228,12 +232,12 @@ describe('Resize container', () => { simulatePointerDown(adorners.corner); await elementUpdated(element); - const eventSpy = spy(element, 'emitEvent'); + const spy = vi.spyOn(element, 'emitEvent'); simulateKeyboard(element, escapeKey); await elementUpdated(element); - expect(eventSpy).calledWith('igcResizeCancel'); + expectCalledWith(spy, 'igcResizeCancel'); }); }); @@ -241,18 +245,16 @@ describe('Resize container', () => { it('`resizeStart` event parameters match initial state', async () => { await setResizeActiveState(element); - const eventSpy = spy(element, 'emitEvent'); + const spy = vi.spyOn(element, 'emitEvent'); const DOM = getDOM(element); simulatePointerDown(DOM.adorners.corner); await elementUpdated(element); - expect(eventSpy).calledWith('igcResizeStart'); + expectCalledWith(spy, 'igcResizeStart'); const targetRect = DOM.container.getBoundingClientRect(); - const { initial, current, deltaX, deltaY } = - getResizeEventState(eventSpy); - + const { initial, current, deltaX, deltaY } = getResizeEventState(spy); // Assert the resize container DOM rect matches the event parameters state expect(initial).to.eql(targetRect); expect(current).to.eql(targetRect); @@ -264,7 +266,7 @@ describe('Resize container', () => { it('`resize` event parameters match resize target state', async () => { await setResizeActiveState(element); - const eventSpy = spy(element, 'emitEvent'); + const spy = vi.spyOn(element, 'emitEvent'); const DOM = getDOM(element); simulatePointerDown(DOM.adorners.corner); @@ -281,7 +283,7 @@ describe('Resize container', () => { await elementUpdated(element); rect = DOM.container.getBoundingClientRect(); - current = getResizeEventState(eventSpy); + current = getResizeEventState(spy); // Correct deltas expect([current.deltaX, current.deltaY]).to.eql([ @@ -300,13 +302,13 @@ describe('Resize container', () => { it('`resizeEnd` event parameters match resize target end state', async () => { await setResizeActiveState(element); - const eventSpy = spy(element, 'emitEvent'); + const spy = vi.spyOn(element, 'emitEvent'); const DOM = getDOM(element); simulatePointerDown(DOM.adorners.corner); await elementUpdated(element); - let state = getResizeEventState(eventSpy); + let state = getResizeEventState(spy); simulatePointerMove( DOM.adorners.corner, @@ -319,7 +321,7 @@ describe('Resize container', () => { simulateLostPointerCapture(DOM.adorners.corner); await elementUpdated(element); - state = getResizeEventState(eventSpy); + state = getResizeEventState(spy); const rect = DOM.container.getBoundingClientRect(); expect([ @@ -336,14 +338,14 @@ describe('Resize container', () => { it('`resizeCancel` correctly restores initial state', async () => { await setResizeActiveState(element); - const eventSpy = spy(element, 'emitEvent'); + const spy = vi.spyOn(element, 'emitEvent'); const DOM = getDOM(element); simulatePointerDown(DOM.adorners.corner); await elementUpdated(element); let rect: DOMRect; - let state = getResizeEventState(eventSpy); + let state = getResizeEventState(spy); simulatePointerMove( DOM.adorners.corner, @@ -355,7 +357,7 @@ describe('Resize container', () => { ); await elementUpdated(element); - state = getResizeEventState(eventSpy); + state = getResizeEventState(spy); rect = DOM.container.getBoundingClientRect(); expect([rect.width, rect.height]).to.eql([ @@ -385,15 +387,15 @@ describe('Resize container', () => { await setResizeActiveState(element); const DOM = getDOM(element); - const eventSpy = spy(element, 'emitEvent'); + const spy = vi.spyOn(element, 'emitEvent'); const rect = DOM.container.getBoundingClientRect(); simulatePointerDown(DOM.adorners.side); await elementUpdated(element); - let state = getResizeEventState(eventSpy); + let state = getResizeEventState(spy); - expect(eventSpy).calledWith('igcResizeStart'); + expectCalledWith(spy, 'igcResizeStart'); expect(state.trigger).to.equal(DOM.adorners.side); simulatePointerMove(DOM.adorners.side, { @@ -402,18 +404,18 @@ describe('Resize container', () => { }); await elementUpdated(element); - state = getResizeEventState(eventSpy); + state = getResizeEventState(spy); - expect(eventSpy).calledWith('igcResize'); + expectCalledWith(spy, 'igcResize'); expect(state.current.height).to.equal(rect.height); expect(state.current.width).to.equal(rect.width + 500); simulateLostPointerCapture(DOM.adorners.side); await elementUpdated(element); - state = getResizeEventState(eventSpy); + state = getResizeEventState(spy); - expect(eventSpy).calledWith('igcResizeEnd'); + expectCalledWith(spy, 'igcResizeEnd'); expect(state.current).to.eql(DOM.container.getBoundingClientRect()); }); @@ -421,15 +423,15 @@ describe('Resize container', () => { await setResizeActiveState(element); const DOM = getDOM(element); - const eventSpy = spy(element, 'emitEvent'); + const spy = vi.spyOn(element, 'emitEvent'); const rect = DOM.container.getBoundingClientRect(); simulatePointerDown(DOM.adorners.bottom); await elementUpdated(element); - let state = getResizeEventState(eventSpy); + let state = getResizeEventState(spy); - expect(eventSpy).calledWith('igcResizeStart'); + expectCalledWith(spy, 'igcResizeStart'); expect(state.trigger).to.equal(DOM.adorners.bottom); simulatePointerMove(DOM.adorners.bottom, { @@ -438,18 +440,18 @@ describe('Resize container', () => { }); await elementUpdated(element); - state = getResizeEventState(eventSpy); + state = getResizeEventState(spy); - expect(eventSpy).calledWith('igcResize'); + expectCalledWith(spy, 'igcResize'); expect(state.current.width).to.equal(rect.width); expect(state.current.height).to.equal(rect.height + 500); simulateLostPointerCapture(DOM.adorners.bottom); await elementUpdated(element); - state = getResizeEventState(eventSpy); + state = getResizeEventState(spy); - expect(eventSpy).calledWith('igcResizeEnd'); + expectCalledWith(spy, 'igcResizeEnd'); expect(state.current).to.eql(DOM.container.getBoundingClientRect()); }); }); @@ -520,24 +522,24 @@ describe('Resize container', () => { it('resize behavior should only start when interacting with the trigger element', async () => { await setResizeActiveState(element); - const eventSpy = spy(element, 'emitEvent'); + const spy = vi.spyOn(element, 'emitEvent'); simulatePointerDown(element); await elementUpdated(element); - expect(eventSpy).not.calledWith('igcResizeStart'); + expectNotCalledWith(spy, 'igcResizeStart'); }); it('should not start resize behavior with non-primary "button"', async () => { await setResizeActiveState(element); - const eventSpy = spy(element, 'emitEvent'); + const spy = vi.spyOn(element, 'emitEvent'); const DOM = getDOM(element); simulatePointerDown(DOM.adorners.corner, { button: 1 }); await elementUpdated(element); - expect(eventSpy).not.calledWith('igcResizeStart'); + expectNotCalledWith(spy, 'igcResizeStart'); }); it('should initialize the default ghost on pointerdown', async () => { @@ -574,25 +576,25 @@ describe('Resize container', () => { it('should fire `resizeStart` on pointer interaction', async () => { await setResizeActiveState(element); - const eventSpy = spy(element, 'emitEvent'); + const spy = vi.spyOn(element, 'emitEvent'); const { adorners } = getDOM(element); simulatePointerDown(adorners.corner); await elementUpdated(element); - expect(eventSpy).calledWith('igcResizeStart'); + expectCalledWith(spy, 'igcResizeStart'); }); it('should not fire `resize` unless `resizeStart` is triggered', async () => { await setResizeActiveState(element); - const eventSpy = spy(element, 'emitEvent'); + const spy = vi.spyOn(element, 'emitEvent'); const { adorners } = getDOM(element); simulatePointerMove(adorners.corner); await elementUpdated(element); - expect(eventSpy).not.calledWith('igcResizeStart'); + expectNotCalledWith(spy, 'igcResizeStart'); }); it('should fire `resize` event', async () => { @@ -603,12 +605,12 @@ describe('Resize container', () => { simulatePointerDown(adorners.corner); await elementUpdated(element); - const eventSpy = spy(element, 'emitEvent'); + const spy = vi.spyOn(element, 'emitEvent'); simulatePointerMove(adorners.corner); await elementUpdated(element); - expect(eventSpy).calledWith('igcResize'); + expectCalledWith(spy, 'igcResize'); }); it('should fire `resizeEnd` when resizing is finished', async () => { @@ -619,12 +621,12 @@ describe('Resize container', () => { simulatePointerDown(adorners.corner); await elementUpdated(element); - const eventSpy = spy(element, 'emitEvent'); + const spy = vi.spyOn(element, 'emitEvent'); simulateLostPointerCapture(adorners.corner); await elementUpdated(element); - expect(eventSpy).calledWith('igcResizeEnd'); + expectCalledWith(spy, 'igcResizeEnd'); }); it('should remove ghost element when resizing is done', async () => { @@ -635,23 +637,23 @@ describe('Resize container', () => { simulatePointerDown(DOM.adorners.corner); await elementUpdated(element); - const eventSpy = spy(element, 'emitEvent'); + const spy = vi.spyOn(element, 'emitEvent'); simulateLostPointerCapture(DOM.adorners.corner); await elementUpdated(element); - expect(eventSpy).calledWith('igcResizeEnd'); + expectCalledWith(spy, 'igcResizeEnd'); expect(DOM.ghostElement).is.null; }); it('should not fire `resizeCancel` when escape key is pressed without active resizing', async () => { - const eventSpy = spy(element, 'emitEvent'); + const spy = vi.spyOn(element, 'emitEvent'); // Default state simulateKeyboard(element, escapeKey); await elementUpdated(element); - expect(eventSpy).not.calledWith('igcResizeCancel'); + expectNotCalledWith(spy, 'igcResizeCancel'); // While in "active" state but not in resize mode await setResizeActiveState(element); @@ -659,8 +661,8 @@ describe('Resize container', () => { simulateKeyboard(element, escapeKey); await elementUpdated(element); - expect(eventSpy).not.calledWith('igcResizeCancel'); - expect(eventSpy.getCalls()).to.be.empty; + expectNotCalledWith(spy, 'igcResizeCancel'); + expect(spy.mock.calls).to.be.empty; }); it('should fire `resizeCancel` when escape key is pressed during resizing', async () => { @@ -671,12 +673,12 @@ describe('Resize container', () => { simulatePointerDown(adorners.corner); await elementUpdated(element); - const eventSpy = spy(element, 'emitEvent'); + const spy = vi.spyOn(element, 'emitEvent'); simulateKeyboard(element, escapeKey); await elementUpdated(element); - expect(eventSpy).calledWith('igcResizeCancel'); + expectCalledWith(spy, 'igcResizeCancel'); }); it('should remove ghost element on `resizeCancel`', async () => { @@ -687,12 +689,12 @@ describe('Resize container', () => { simulatePointerDown(DOM.adorners.corner); await elementUpdated(element); - const eventSpy = spy(element, 'emitEvent'); + const spy = vi.spyOn(element, 'emitEvent'); simulateKeyboard(element, escapeKey); await elementUpdated(element); - expect(eventSpy).calledWith('igcResizeCancel'); + expectCalledWith(spy, 'igcResizeCancel'); expect(DOM.ghostElement).is.null; }); }); @@ -701,18 +703,16 @@ describe('Resize container', () => { it('`resizeStart` event parameters match initial state', async () => { await setResizeActiveState(element); - const eventSpy = spy(element, 'emitEvent'); + const spy = vi.spyOn(element, 'emitEvent'); const DOM = getDOM(element); simulatePointerDown(DOM.adorners.corner); await elementUpdated(element); - expect(eventSpy).calledWith('igcResizeStart'); + expectCalledWith(spy, 'igcResizeStart'); const targetRect = DOM.ghostElement.getBoundingClientRect(); - const { initial, current, deltaX, deltaY } = - getResizeEventState(eventSpy); - + const { initial, current, deltaX, deltaY } = getResizeEventState(spy); // Assert the resize container DOM rect matches the event parameters state expect(initial).to.eql(targetRect); expect(current).to.eql(targetRect); @@ -724,7 +724,7 @@ describe('Resize container', () => { it('`resize` event parameters match resize target state', async () => { await setResizeActiveState(element); - const eventSpy = spy(element, 'emitEvent'); + const spy = vi.spyOn(element, 'emitEvent'); const DOM = getDOM(element); simulatePointerDown(DOM.adorners.corner); @@ -741,7 +741,7 @@ describe('Resize container', () => { await elementUpdated(element); rect = DOM.ghostElement.getBoundingClientRect(); - current = getResizeEventState(eventSpy); + current = getResizeEventState(spy); // Correct deltas expect([current.deltaX, current.deltaY]).to.eql([ @@ -760,13 +760,13 @@ describe('Resize container', () => { it('`resizeEnd` event parameters match resize target end state', async () => { await setResizeActiveState(element); - const eventSpy = spy(element, 'emitEvent'); + const spy = vi.spyOn(element, 'emitEvent'); const DOM = getDOM(element); simulatePointerDown(DOM.adorners.corner); await elementUpdated(element); - let state = getResizeEventState(eventSpy); + let state = getResizeEventState(spy); simulatePointerMove( DOM.adorners.corner, @@ -779,7 +779,7 @@ describe('Resize container', () => { simulateLostPointerCapture(DOM.adorners.corner); await elementUpdated(element); - state = getResizeEventState(eventSpy); + state = getResizeEventState(spy); const rect = DOM.container.getBoundingClientRect(); expect([ @@ -796,14 +796,14 @@ describe('Resize container', () => { it('`resizeCancel` correctly restores initial state', async () => { await setResizeActiveState(element); - const eventSpy = spy(element, 'emitEvent'); + const spy = vi.spyOn(element, 'emitEvent'); const DOM = getDOM(element); simulatePointerDown(DOM.adorners.corner); await elementUpdated(element); let rect: DOMRect; - let state = getResizeEventState(eventSpy); + let state = getResizeEventState(spy); simulatePointerMove( DOM.adorners.corner, @@ -815,7 +815,7 @@ describe('Resize container', () => { ); await elementUpdated(element); - state = getResizeEventState(eventSpy); + state = getResizeEventState(spy); rect = DOM.ghostElement.getBoundingClientRect(); expect([rect.width, rect.height]).to.eql([ @@ -845,16 +845,16 @@ describe('Resize container', () => { await setResizeActiveState(element); const DOM = getDOM(element); - const eventSpy = spy(element, 'emitEvent'); + const spy = vi.spyOn(element, 'emitEvent'); const rect = DOM.container.getBoundingClientRect(); simulatePointerDown(DOM.adorners.side); await elementUpdated(element); const ghostRect = DOM.ghostElement.getBoundingClientRect(); - let state = getResizeEventState(eventSpy); + let state = getResizeEventState(spy); - expect(eventSpy).calledWith('igcResizeStart'); + expectCalledWith(spy, 'igcResizeStart'); expect(state.trigger).to.equal(DOM.adorners.side); simulatePointerMove(DOM.adorners.side, { @@ -863,18 +863,18 @@ describe('Resize container', () => { }); await elementUpdated(element); - state = getResizeEventState(eventSpy); + state = getResizeEventState(spy); - expect(eventSpy).calledWith('igcResize'); + expectCalledWith(spy, 'igcResize'); expect(state.current.height).to.equal(ghostRect.height); expect(state.current.width).to.equal(ghostRect.width + 500); simulateLostPointerCapture(DOM.adorners.side); await elementUpdated(element); - state = getResizeEventState(eventSpy); + state = getResizeEventState(spy); - expect(eventSpy).calledWith('igcResizeEnd'); + expectCalledWith(spy, 'igcResizeEnd'); expect(state.current).to.eql(DOM.container.getBoundingClientRect()); }); @@ -882,16 +882,16 @@ describe('Resize container', () => { await setResizeActiveState(element); const DOM = getDOM(element); - const eventSpy = spy(element, 'emitEvent'); + const spy = vi.spyOn(element, 'emitEvent'); const rect = DOM.container.getBoundingClientRect(); simulatePointerDown(DOM.adorners.bottom); await elementUpdated(element); const ghostRect = DOM.ghostElement.getBoundingClientRect(); - let state = getResizeEventState(eventSpy); + let state = getResizeEventState(spy); - expect(eventSpy).calledWith('igcResizeStart'); + expectCalledWith(spy, 'igcResizeStart'); expect(state.trigger).to.equal(DOM.adorners.bottom); simulatePointerMove(DOM.adorners.bottom, { @@ -900,18 +900,18 @@ describe('Resize container', () => { }); await elementUpdated(element); - state = getResizeEventState(eventSpy); + state = getResizeEventState(spy); - expect(eventSpy).calledWith('igcResize'); + expectCalledWith(spy, 'igcResize'); expect(state.current.width).to.equal(ghostRect.width); expect(state.current.height).to.equal(ghostRect.height + 500); simulateLostPointerCapture(DOM.adorners.bottom); await elementUpdated(element); - state = getResizeEventState(eventSpy); + state = getResizeEventState(spy); - expect(eventSpy).calledWith('igcResizeEnd'); + expectCalledWith(spy, 'igcResizeEnd'); expect(state.current).to.eql(DOM.container.getBoundingClientRect()); }); }); @@ -953,14 +953,6 @@ async function setResizeActiveState( await elementUpdated(resizeElement); } -function getResizeEventState( - eventSpy: Sinon.SinonSpy< - [ - type: keyof IgcResizeContainerComponentEventMap, - eventInitDict?: CustomEventInit | undefined, - ], - boolean - > -): ResizeCallbackParams['state'] { - return eventSpy.lastCall.lastArg.detail.state; +function getResizeEventState(spy: MockInstance): ResizeCallbackParams['state'] { + return spy.mock.lastCall![1].detail.state; } diff --git a/src/components/ripple/ripple.spec.ts b/src/components/ripple/ripple.spec.ts index 9cd5540d0..a865c2add 100644 --- a/src/components/ripple/ripple.spec.ts +++ b/src/components/ripple/ripple.spec.ts @@ -1,6 +1,7 @@ -import { elementUpdated, expect, fixture, html } from '@open-wc/testing'; +import { beforeAll, beforeEach, describe, expect, it } from 'vitest'; import IgcButtonComponent from '../button/button.js'; import { defineComponents } from '../common/definitions/defineComponents.js'; +import { elementUpdated, fixture, html } from '../common/helpers.spec.js'; import { simulatePointerDown } from '../common/utils.spec.js'; import IgcRippleComponent from './ripple.js'; @@ -8,7 +9,7 @@ describe('Ripple', () => { let ripple: IgcRippleComponent; let button: IgcButtonComponent; - before(() => { + beforeAll(() => { defineComponents(IgcRippleComponent, IgcButtonComponent); }); diff --git a/src/components/select/select.spec.ts b/src/components/select/select.spec.ts index 5a7a7fd54..171fdc39a 100644 --- a/src/components/select/select.spec.ts +++ b/src/components/select/select.spec.ts @@ -1,5 +1,4 @@ -import { elementUpdated, expect, fixture, html } from '@open-wc/testing'; -import { spy, useFakeTimers } from 'sinon'; +import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; import { altKey, arrowDown, @@ -12,8 +11,11 @@ import { tabKey, } from '../common/controllers/key-bindings.js'; import { defineComponents } from '../common/definitions/defineComponents.js'; +import { elementUpdated, fixture, html } from '../common/helpers.spec.js'; import { createFormAssociatedTestBed, + expectCalledWith, + expectNotCalledWith, isFocused, simulateClick, simulateKeyboard, @@ -46,7 +48,7 @@ type SelectSlots = | 'toggle-icon-expanded'; describe('Select', () => { - before(() => defineComponents(IgcSelectComponent)); + beforeAll(() => defineComponents(IgcSelectComponent)); let select: IgcSelectComponent; @@ -322,15 +324,17 @@ describe('Select', () => { }); it('`close` behavior', async () => { - const eventSpy = spy(select, 'emitEvent'); + const spy = vi.spyOn(select, 'emitEvent'); select.scrollStrategy = 'close'; await openSelect(); await simulateScroll(container, { top: 200 }); expect(select.open).to.be.false; - expect(eventSpy.firstCall).calledWith('igcClosing'); - expect(eventSpy.lastCall).calledWith('igcClosed'); + expect(spy).toHaveBeenNthCalledWith(1, 'igcClosing', { + cancelable: true, + }); + expect(spy).toHaveBeenNthCalledWith(2, 'igcClosed'); }); it('`block behavior`', async () => { @@ -616,13 +620,13 @@ describe('Select', () => { }); it('keyboard selection works (closed)', async () => { - const eventSpy = spy(select, 'emitEvent'); + const spy = vi.spyOn(select, 'emitEvent'); for (const item of select.items) { simulateKeyboard(select, arrowDown); await elementUpdated(select); - expect(eventSpy).calledWith('igcChange', { detail: item }); + expect(spy).toHaveBeenCalledWith('igcChange', { detail: item }); checkItemState(item, { selected: true }); } }); @@ -834,13 +838,13 @@ describe('Select', () => { // Search selection it('does not select disabled items when searching (closed state)', async () => { - const eventSpy = spy(select, 'emitEvent'); + const spy = vi.spyOn(select, 'emitEvent'); simulateKeyboard(select, 'tes'.split('')); await elementUpdated(select); const item = select.items[2]; - expect(eventSpy).not.calledWith('igcChange', { detail: item }); + expect(spy).not.toHaveBeenCalledWith('igcChange', { detail: item }); checkItemState(item, { active: false, selected: false }); expect(select.value).to.be.undefined; @@ -848,14 +852,14 @@ describe('Select', () => { }); it('does not activates disabled items when searching (open state)', async () => { - const eventSpy = spy(select, 'emitEvent'); + const spy = vi.spyOn(select, 'emitEvent'); await openSelect(); simulateKeyboard(select, 'tes'.split('')); await elementUpdated(select); const item = select.items[2]; - expect(eventSpy).not.calledWith('igcChange', { detail: item }); + expect(spy).not.toHaveBeenCalledWith('igcChange', { detail: item }); checkItemState(item, { active: false, selected: false }); expect(select.value).to.be.undefined; @@ -863,31 +867,31 @@ describe('Select', () => { }); it('resumes search after default timeout', async () => { - const clock = useFakeTimers({ now: 0, toFake: ['Date'] }); + vi.useFakeTimers({ now: 0, toFake: ['Date'] }); simulateKeyboard(select, 'null'.split('')); await elementUpdated(select); expect(select.selectedItem).to.be.null; - await clock.tickAsync(501); + await vi.advanceTimersByTimeAsync(501); simulateKeyboard(select, 'impl'.split('')); await elementUpdated(select); expect(select.selectedItem?.value).to.equal('implementation'); - clock.restore(); + vi.useRealTimers(); }); it('activates the correct item when searching with character keys (open state)', async () => { - const eventSpy = spy(select, 'emitEvent'); + const spy = vi.spyOn(select, 'emitEvent'); await openSelect(); simulateKeyboard(select, 'doc'.split('')); await elementUpdated(select); const item = select.items.at(-2)!; - expect(eventSpy).not.calledWith('igcChange', { detail: item }); + expect(spy).not.toHaveBeenCalledWith('igcChange', { detail: item }); checkItemState(item, { active: true, selected: false }); expect(select.value).to.be.undefined; @@ -896,13 +900,13 @@ describe('Select', () => { }); it('selects the correct item when searching (closed state)', async () => { - const eventSpy = spy(select, 'emitEvent'); + const spy = vi.spyOn(select, 'emitEvent'); simulateKeyboard(select, 'doc'.split('')); await elementUpdated(select); const item = select.items.at(-2)!; - expect(eventSpy).calledWith('igcChange', { detail: item }); + expect(spy).toHaveBeenCalledWith('igcChange', { detail: item }); checkItemState(item, { active: true, selected: true }); expect(select.value).to.equal(item.value); @@ -929,13 +933,15 @@ describe('Select', () => { }); it('Home key (closed state)', async () => { - const eventSpy = spy(select, 'emitEvent'); + const spy = vi.spyOn(select, 'emitEvent'); simulateKeyboard(select, homeKey); await elementUpdated(select); const item = select.items[0]; - expect(eventSpy).calledOnceWith('igcChange', { detail: item }); + expect(spy).toHaveBeenCalledExactlyOnceWith('igcChange', { + detail: item, + }); checkItemState(item, { active: true, selected: true }); expect(select.value).to.equal(item.value); @@ -944,14 +950,14 @@ describe('Select', () => { }); it('Home key (open state)', async () => { - const eventSpy = spy(select, 'emitEvent'); + const spy = vi.spyOn(select, 'emitEvent'); await openSelect(); simulateKeyboard(select, homeKey); await elementUpdated(select); const item = select.items[0]; - expect(eventSpy).not.calledWith('igcChange', { detail: item }); + expect(spy).not.toHaveBeenCalledWith('igcChange', { detail: item }); checkItemState(item, { active: true, selected: false }); expect(select.value).to.be.undefined; @@ -960,13 +966,15 @@ describe('Select', () => { }); it('End key (closed state)', async () => { - const eventSpy = spy(select, 'emitEvent'); + const spy = vi.spyOn(select, 'emitEvent'); simulateKeyboard(select, endKey); await elementUpdated(select); const item = select.items.at(-2)!; - expect(eventSpy).calledOnceWith('igcChange', { detail: item }); + expect(spy).toHaveBeenCalledExactlyOnceWith('igcChange', { + detail: item, + }); checkItemState(item, { active: true, selected: true }); expect(select.value).to.equal(item.value); @@ -975,14 +983,14 @@ describe('Select', () => { }); it('End key (open state)', async () => { - const eventSpy = spy(select, 'emitEvent'); + const spy = vi.spyOn(select, 'emitEvent'); await openSelect(); simulateKeyboard(select, endKey); await elementUpdated(select); const item = select.items.at(-2)!; - expect(eventSpy).not.calledWith('igcChange', { detail: item }); + expect(spy).not.toHaveBeenCalledWith('igcChange', { detail: item }); checkItemState(item, { active: true, selected: false }); expect(select.value).to.be.undefined; @@ -991,7 +999,7 @@ describe('Select', () => { }); it('ArrowDown (closed state)', async () => { - const eventSpy = spy(select, 'emitEvent'); + const spy = vi.spyOn(select, 'emitEvent'); const activeItems = Items.filter((item) => !item.disabled); const { value: lastValue } = activeItems.at(-1)!; @@ -1000,7 +1008,9 @@ describe('Select', () => { simulateKeyboard(select, arrowDown); await elementUpdated(select); - expect(eventSpy).calledWith('igcChange', { detail: getActiveItem() }); + expect(spy).toHaveBeenCalledWith('igcChange', { + detail: getActiveItem(), + }); expect(getActiveItem()?.value).to.equal(value); expect(select.value).to.equal(value); expect(select.selectedItem).to.equal(getActiveItem()); @@ -1010,14 +1020,14 @@ describe('Select', () => { simulateKeyboard(select, arrowDown); await elementUpdated(select); - expect(eventSpy.callCount).to.equal(activeItems.length); + expect(spy).toHaveBeenCalledTimes(activeItems.length); expect(getActiveItem()?.value).to.equal(lastValue); expect(select.value).to.equal(lastValue); expect(select.selectedItem?.value).to.equal(lastValue); }); it('ArrowDown (open state)', async () => { - const eventSpy = spy(select, 'emitEvent'); + const spy = vi.spyOn(select, 'emitEvent'); const activeItems = Items.filter((item) => !item.disabled); await openSelect(); @@ -1026,7 +1036,7 @@ describe('Select', () => { simulateKeyboard(select, arrowDown); await elementUpdated(select); - expect(eventSpy).not.calledWith('igChange'); + expectNotCalledWith(spy, 'igcChange'); expect(getActiveItem()?.value).to.equal(value); expect(select.value).to.be.undefined; expect(select.selectedItem).to.be.null; @@ -1036,14 +1046,14 @@ describe('Select', () => { simulateKeyboard(select, arrowDown); await elementUpdated(select); - expect(eventSpy).not.calledWith('igChange'); + expectNotCalledWith(spy, 'igcChange'); expect(getActiveItem()?.value).to.equal(activeItems.at(-1)?.value); expect(select.value).to.be.undefined; expect(select.selectedItem).to.be.null; }); it('ArrowUp (closed state)', async () => { - const eventSpy = spy(select, 'emitEvent'); + const spy = vi.spyOn(select, 'emitEvent'); const activeItems = Items.filter((item) => !item.disabled).reverse(); const { value: lastValue } = activeItems.at(-1)!; @@ -1054,7 +1064,9 @@ describe('Select', () => { simulateKeyboard(select, arrowUp); await elementUpdated(select); - expect(eventSpy).calledWith('igcChange', { detail: getActiveItem() }); + expect(spy).toHaveBeenCalledWith('igcChange', { + detail: getActiveItem(), + }); expect(getActiveItem()?.value).to.equal(value); expect(select.value).to.equal(value); expect(select.selectedItem).to.equal(getActiveItem()); @@ -1064,14 +1076,14 @@ describe('Select', () => { simulateKeyboard(select, arrowUp); await elementUpdated(select); - expect(eventSpy.callCount).to.equal(activeItems.length); + expect(spy).toHaveBeenCalledTimes(activeItems.length); expect(getActiveItem()?.value).to.equal(lastValue); expect(select.value).to.equal(lastValue); expect(select.selectedItem?.value).to.equal(lastValue); }); it('ArrowUp (open state)', async () => { - const eventSpy = spy(select, 'emitEvent'); + const spy = vi.spyOn(select, 'emitEvent'); const activeItems = Items.filter((item) => !item.disabled).reverse(); await openSelect(); @@ -1082,7 +1094,7 @@ describe('Select', () => { simulateKeyboard(select, arrowUp); await elementUpdated(select); - expect(eventSpy).not.calledWith('igChange'); + expectNotCalledWith(spy, 'igcChange'); expect(getActiveItem()?.value).to.equal(value); expect(select.value).to.be.undefined; expect(select.selectedItem).to.be.null; @@ -1092,7 +1104,7 @@ describe('Select', () => { simulateKeyboard(select, arrowUp); await elementUpdated(select); - expect(eventSpy).not.calledWith('igChange'); + expectNotCalledWith(spy, 'igcChange'); expect(getActiveItem()?.value).to.equal(activeItems.at(-1)?.value); expect(select.value).to.be.undefined; expect(select.selectedItem).to.be.null; @@ -1105,41 +1117,44 @@ describe('Select', () => { }); it('correct sequence of events', async () => { - const eventSpy = spy(select, 'emitEvent'); + const spy = vi.spyOn(select, 'emitEvent'); simulateClick(getInput()); await elementUpdated(select); - expect(eventSpy.firstCall).calledWith('igcOpening'); - expect(eventSpy.secondCall).calledWith('igcOpened'); - - eventSpy.resetHistory(); + expect(spy).toHaveBeenNthCalledWith(1, 'igcOpening', { + cancelable: true, + }); + expect(spy).toHaveBeenNthCalledWith(2, 'igcOpened'); + spy.mockClear(); simulateClick(select.items[0]); await elementUpdated(select); - expect(eventSpy.firstCall).calledWith('igcChange', { + expect(spy).toHaveBeenNthCalledWith(1, 'igcChange', { detail: select.items[0], }); - expect(eventSpy.secondCall).calledWith('igcClosing'); - expect(eventSpy.thirdCall).calledWith('igcClosed'); + expect(spy).toHaveBeenNthCalledWith(2, 'igcClosing', { + cancelable: true, + }); + expect(spy).toHaveBeenNthCalledWith(3, 'igcClosed'); }); it('does not emit events on API calls', async () => { - const eventSpy = spy(select, 'emitEvent'); + const spy = vi.spyOn(select, 'emitEvent'); await openSelect(); - expect(eventSpy).not.to.be.called; + expect(spy).not.toHaveBeenCalled(); await select.hide(); - expect(eventSpy).not.to.be.called; + expect(spy).not.toHaveBeenCalled(); select.select('testing'); - expect(eventSpy).not.to.be.called; + expect(spy).not.toHaveBeenCalled(); }); it('can halt opening event sequence', async () => { - const eventSpy = spy(select, 'emitEvent'); + const spy = vi.spyOn(select, 'emitEvent'); select.addEventListener('igcOpening', (e) => e.preventDefault(), { once: true, }); @@ -1148,12 +1163,12 @@ describe('Select', () => { await elementUpdated(select); expect(select.open).to.be.false; - expect(eventSpy.firstCall).calledWith('igcOpening'); - expect(eventSpy.secondCall).to.be.null; + expectCalledWith(spy, 'igcOpening'); + expectNotCalledWith(spy, 'igcOpened'); }); it('can halt closing event sequence', async () => { - const eventSpy = spy(select, 'emitEvent'); + const spy = vi.spyOn(select, 'emitEvent'); select.addEventListener('igcClosing', (e) => e.preventDefault(), { once: true, }); @@ -1165,10 +1180,9 @@ describe('Select', () => { await elementUpdated(select); expect(select.open).to.be.true; - expect(eventSpy.firstCall).calledWith('igcClosing'); - expect(eventSpy.secondCall).to.be.null; - - eventSpy.resetHistory(); + expectCalledWith(spy, 'igcClosing'); + expectNotCalledWith(spy, 'igcClosed'); + spy.mockClear(); // With selection select.addEventListener('igcClosing', (e) => e.preventDefault(), { @@ -1182,13 +1196,12 @@ describe('Select', () => { await elementUpdated(select); expect(select.open).to.be.true; - expect(eventSpy.firstCall).calledWith('igcChange'); - expect(eventSpy.secondCall).calledWith('igcClosing'); - expect(eventSpy.thirdCall).to.be.null; + expectCalledWith(spy, 'igcChange'); + expectCalledWith(spy, 'igcClosing'); }); it('can halt closing event sequence on outside click', async () => { - const eventSpy = spy(select, 'emitEvent'); + const spy = vi.spyOn(select, 'emitEvent'); select.addEventListener('igcClosing', (e) => e.preventDefault(), { once: true, }); @@ -1199,8 +1212,8 @@ describe('Select', () => { await elementUpdated(select); expect(select.open).to.be.true; - expect(eventSpy.firstCall).calledWith('igcClosing'); - expect(eventSpy.secondCall).to.be.null; + expectCalledWith(spy, 'igcClosing'); + expectNotCalledWith(spy, 'igcClosed'); }); }); diff --git a/src/components/slider/slider.spec.ts b/src/components/slider/slider.spec.ts index 1d1ae5096..5ccce3704 100644 --- a/src/components/slider/slider.spec.ts +++ b/src/components/slider/slider.spec.ts @@ -1,12 +1,4 @@ -import { - aTimeout, - elementUpdated, - expect, - fixture, - html, -} from '@open-wc/testing'; -import { spy } from 'sinon'; - +import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; import { arrowDown, arrowLeft, @@ -18,6 +10,12 @@ import { pageUpKey, } from '../common/controllers/key-bindings.js'; import { defineComponents } from '../common/definitions/defineComponents.js'; +import { + aTimeout, + elementUpdated, + fixture, + html, +} from '../common/helpers.spec.js'; import { asPercent } from '../common/util.js'; import { createFormAssociatedTestBed, @@ -34,7 +32,7 @@ describe('Slider component', () => { describe('Regular', () => { let slider: IgcSliderComponent; - before(() => { + beforeAll(() => { defineComponents(IgcSliderComponent); }); @@ -122,33 +120,33 @@ describe('Slider component', () => { }); it('value should be changed when clicking and dragging the slider and corresponding events are fired', async () => { - const eventSpy = spy(slider, 'emitEvent'); + const spy = vi.spyOn(slider, 'emitEvent'); const { x, width } = slider.getBoundingClientRect(); simulatePointerDown(slider, { clientX: x + width / 2 }); await elementUpdated(slider); expect(slider.value).to.eq(50); - expect(eventSpy).calledOnceWithExactly('igcInput', { detail: 50 }); + expect(spy).toHaveBeenCalledExactlyOnceWith('igcInput', { detail: 50 }); - eventSpy.resetHistory(); + spy.mockClear(); simulatePointerMove(slider, { clientX: x + width * 0.7 }); await elementUpdated(slider); expect(slider.value).to.eq(70); - expect(eventSpy).calledOnceWithExactly('igcInput', { detail: 70 }); + expect(spy).toHaveBeenCalledExactlyOnceWith('igcInput', { detail: 70 }); - eventSpy.resetHistory(); + spy.mockClear(); simulateLostPointerCapture(slider); await elementUpdated(slider); expect(slider.value).to.eq(70); - expect(eventSpy).calledOnceWithExactly('igcChange', { detail: 70 }); + expect(spy).toHaveBeenCalledExactlyOnceWith('igcChange', { detail: 70 }); }); it('events should not be emitted once thumbs reaches end boundary even if pointer events are still fired', async () => { - const eventSpy = spy(slider, 'emitEvent'); + const spy = vi.spyOn(slider, 'emitEvent'); const { x, width } = slider.getBoundingClientRect(); const sliderCenterX = { @@ -161,7 +159,7 @@ describe('Slider component', () => { await elementUpdated(slider); expect(slider.value).to.eq(50); - expect(eventSpy).calledOnceWithExactly('igcInput', { detail: 50 }); + expect(spy).toHaveBeenCalledExactlyOnceWith('igcInput', { detail: 50 }); // Simulate 10 pointer moves with stacking delta = 1/4 of the slider's width simulatePointerMove(slider, sliderCenterX, deltaX, 10); @@ -170,18 +168,18 @@ describe('Slider component', () => { expect(slider.value).to.equal(100); // 1 igcInput for pointerDown + 2 more for each pointermove till the end - expect(eventSpy.callCount).to.equal(3); - expect(eventSpy.lastCall).calledWithExactly('igcInput', { + expect(spy).toHaveBeenCalledTimes(3); + expect(spy).toHaveBeenLastCalledWith('igcInput', { detail: 100, }); - eventSpy.resetHistory(); + spy.mockClear(); simulateLostPointerCapture(slider); await elementUpdated(slider); expect(slider.value).to.eq(100); - expect(eventSpy).calledOnceWithExactly('igcChange', { detail: 100 }); + expect(spy).toHaveBeenCalledExactlyOnceWith('igcChange', { detail: 100 }); }); it('track fill and thumb should be positioned correctly according to the current value', async () => { @@ -319,7 +317,7 @@ describe('Slider component', () => { simulatePointerDown(slider, { clientX: x + width * 0.54321 }); await elementUpdated(slider); - expect(slider.value).to.eq(54.321); + expect(slider.value).approximately(54.321, 0.0001); }); it('primary tick marks should be displayed when primaryTickMarks is greater than 0', async () => { @@ -622,7 +620,7 @@ describe('Slider component', () => { }); it('value should be increased or decreased with 1 step when pressing right/top or down/left arrow keys', async () => { - const eventSpy = spy(slider, 'emitEvent'); + const spy = vi.spyOn(slider, 'emitEvent'); slider.step = 2; slider.value = 50; await elementUpdated(slider); @@ -630,34 +628,34 @@ describe('Slider component', () => { simulateKeyboard(slider, arrowRight); await elementUpdated(slider); expect(slider.value).to.eq(52); - expect(eventSpy).to.be.calledTwice; - expect(eventSpy).calledWith('igcInput', { detail: 52 }); - expect(eventSpy).calledWith('igcChange', { detail: 52 }); + expect(spy).toHaveBeenCalledTimes(2); + expect(spy).toHaveBeenCalledWith('igcInput', { detail: 52 }); + expect(spy).toHaveBeenCalledWith('igcChange', { detail: 52 }); - eventSpy.resetHistory(); + spy.mockClear(); simulateKeyboard(slider, arrowLeft); await elementUpdated(slider); expect(slider.value).to.eq(50); - expect(eventSpy).calledWith('igcInput', { detail: 50 }); - expect(eventSpy).calledWith('igcChange', { detail: 50 }); + expect(spy).toHaveBeenCalledWith('igcInput', { detail: 50 }); + expect(spy).toHaveBeenCalledWith('igcChange', { detail: 50 }); - eventSpy.resetHistory(); + spy.mockClear(); simulateKeyboard(slider, arrowDown); await elementUpdated(slider); expect(slider.value).to.eq(48); - expect(eventSpy).calledWith('igcInput', { detail: 48 }); - expect(eventSpy).calledWith('igcChange', { detail: 48 }); + expect(spy).toHaveBeenCalledWith('igcInput', { detail: 48 }); + expect(spy).toHaveBeenCalledWith('igcChange', { detail: 48 }); - eventSpy.resetHistory(); + spy.mockClear(); simulateKeyboard(slider, arrowUp); await elementUpdated(slider); expect(slider.value).to.eq(50); - expect(eventSpy).calledWith('igcInput', { detail: 50 }); - expect(eventSpy).calledWith('igcChange', { detail: 50 }); + expect(spy).toHaveBeenCalledWith('igcInput', { detail: 50 }); + expect(spy).toHaveBeenCalledWith('igcChange', { detail: 50 }); }); it('fractional step', async () => { - const eventSpy = spy(slider, 'emitEvent'); + const spy = vi.spyOn(slider, 'emitEvent'); const step = 0.25; const lower = 50 - step; @@ -671,22 +669,22 @@ describe('Slider component', () => { await elementUpdated(slider); expect(slider.value).to.equal(lower); - expect(eventSpy).calledWith('igcInput', { detail: lower }); - expect(eventSpy).calledWith('igcChange', { detail: lower }); + expect(spy).toHaveBeenCalledWith('igcInput', { detail: lower }); + expect(spy).toHaveBeenCalledWith('igcChange', { detail: lower }); - eventSpy.resetHistory(); + spy.mockClear(); slider.value = 50; simulateKeyboard(slider, arrowRight); await elementUpdated(slider); expect(slider.value).to.equal(higher); - expect(eventSpy).calledWith('igcInput', { detail: higher }); - expect(eventSpy).calledWith('igcChange', { detail: higher }); + expect(spy).toHaveBeenCalledWith('igcInput', { detail: higher }); + expect(spy).toHaveBeenCalledWith('igcChange', { detail: higher }); }); it('if step is set to 0 it should default to 1 for keyboard selection', async () => { - const eventSpy = spy(slider, 'emitEvent'); + const spy = vi.spyOn(slider, 'emitEvent'); const value = Math.PI; slider.step = 0; @@ -696,16 +694,16 @@ describe('Slider component', () => { await elementUpdated(slider); expect(slider.value).to.equal(value - 1); - expect(eventSpy).calledWith('igcInput', { detail: value - 1 }); - expect(eventSpy).calledWith('igcChange', { detail: value - 1 }); + expect(spy).toHaveBeenCalledWith('igcInput', { detail: value - 1 }); + expect(spy).toHaveBeenCalledWith('igcChange', { detail: value - 1 }); - eventSpy.resetHistory(); + spy.mockClear(); slider.value = value; simulateKeyboard(slider, arrowRight); expect(slider.value).to.equal(value + 1); - expect(eventSpy).calledWith('igcInput', { detail: value + 1 }); - expect(eventSpy).calledWith('igcChange', { detail: value + 1 }); + expect(spy).toHaveBeenCalledWith('igcInput', { detail: value + 1 }); + expect(spy).toHaveBeenCalledWith('igcChange', { detail: value + 1 }); }); it('value should be increased/decreased with 1/10th of the slider range when pressing page up/down keys', async () => { @@ -723,7 +721,7 @@ describe('Slider component', () => { }); it('value should be set to minimum when pressing home key', async () => { - const eventSpy = spy(slider, 'emitEvent'); + const spy = vi.spyOn(slider, 'emitEvent'); slider.min = 10; slider.value = 50; @@ -736,11 +734,11 @@ describe('Slider component', () => { expect(slider.value).to.eq(10); // Only one igcInput and one igcChange events should be fired - expect(eventSpy.callCount).to.equal(2); + expect(spy).toHaveBeenCalledTimes(2); }); it('value should be set to maximum when pressing end key', async () => { - const eventSpy = spy(slider, 'emitEvent'); + const spy = vi.spyOn(slider, 'emitEvent'); slider.max = 90; slider.value = 50; @@ -753,14 +751,14 @@ describe('Slider component', () => { expect(slider.value).to.eq(90); // Only one igcInput and one igcChange events should be fired - expect(eventSpy.callCount).to.equal(2); + expect(spy).toHaveBeenCalledTimes(2); }); }); describe('Range', () => { let slider: IgcRangeSliderComponent; - before(() => { + beforeAll(() => { defineComponents(IgcRangeSliderComponent); }); @@ -913,68 +911,68 @@ describe('Slider component', () => { }); it('closest thumb value should be changed when clicking and dragging the slider and corresponding events are fired', async () => { - const eventSpy = spy(slider, 'emitEvent'); + const spy = vi.spyOn(slider, 'emitEvent'); const { x, width } = slider.getBoundingClientRect(); simulatePointerDown(slider, { clientX: x + width * 0.5 }); await elementUpdated(slider); expect(slider.upper).to.eq(50); - expect(eventSpy).calledOnceWithExactly('igcInput', { + expect(spy).toHaveBeenCalledExactlyOnceWith('igcInput', { detail: { lower: 0, upper: 50 }, }); - eventSpy.resetHistory(); + spy.mockClear(); simulatePointerMove(slider, { clientX: x + width * 0.7 }); await elementUpdated(slider); expect(slider.upper).to.eq(70); - expect(eventSpy).calledOnceWithExactly('igcInput', { + expect(spy).toHaveBeenCalledExactlyOnceWith('igcInput', { detail: { lower: 0, upper: 70 }, }); - eventSpy.resetHistory(); + spy.mockClear(); simulateLostPointerCapture(slider); await elementUpdated(slider); expect(slider.upper).to.eq(70); - expect(eventSpy).calledOnceWithExactly('igcChange', { + expect(spy).toHaveBeenCalledExactlyOnceWith('igcChange', { detail: { lower: 0, upper: 70 }, }); - eventSpy.resetHistory(); + spy.mockClear(); simulatePointerDown(slider, { clientX: x + width * 0.2 }); await elementUpdated(slider); expect(slider.lower).to.eq(20); expect(slider.upper).to.eq(70); - expect(eventSpy).calledOnceWithExactly('igcInput', { + expect(spy).toHaveBeenCalledExactlyOnceWith('igcInput', { detail: { lower: 20, upper: 70 }, }); - eventSpy.resetHistory(); + spy.mockClear(); simulatePointerMove(slider, { clientX: x + width * 0.4 }); await elementUpdated(slider); expect(slider.upper).to.eq(70); expect(slider.lower).to.eq(40); - expect(eventSpy).calledOnceWithExactly('igcInput', { + expect(spy).toHaveBeenCalledExactlyOnceWith('igcInput', { detail: { lower: 40, upper: 70 }, }); - eventSpy.resetHistory(); + spy.mockClear(); simulateLostPointerCapture(slider); await elementUpdated(slider); expect(slider.upper).to.eq(70); expect(slider.lower).to.eq(40); - expect(eventSpy).calledOnceWithExactly('igcChange', { + expect(spy).toHaveBeenCalledExactlyOnceWith('igcChange', { detail: { lower: 40, upper: 70 }, }); }); it('when the lower thumb is dragged beyond the upper thumb, the upper thumb should be focused and its dragging should continue.', async () => { - const eventSpy = spy(slider, 'emitEvent'); + const spy = vi.spyOn(slider, 'emitEvent'); const { x, width } = slider.getBoundingClientRect(); const { thumbs } = getDOM(slider); @@ -987,31 +985,31 @@ describe('Slider component', () => { expect(slider.lower).to.eq(25); expect(slider.upper).to.eq(50); - expect(eventSpy).calledOnceWithExactly('igcInput', { + expect(spy).toHaveBeenCalledExactlyOnceWith('igcInput', { detail: { lower: 25, upper: 50 }, }); expect(slider).to.eq(document.activeElement); expect(thumbs.lower).to.eq(slider.shadowRoot?.activeElement); - eventSpy.resetHistory(); + spy.mockClear(); simulatePointerMove(slider, { clientX: x + width * 0.7 }); await elementUpdated(slider); expect(slider.lower).to.eq(50); expect(slider.upper).to.eq(70); - expect(eventSpy).calledOnceWithExactly('igcInput', { + expect(spy).toHaveBeenCalledExactlyOnceWith('igcInput', { detail: { lower: 50, upper: 70 }, }); expect(slider).to.eq(document.activeElement); expect(thumbs.upper).to.eq(slider.shadowRoot?.activeElement); - eventSpy.resetHistory(); + spy.mockClear(); simulateLostPointerCapture(slider); await elementUpdated(slider); expect(slider.lower).to.eq(50); expect(slider.upper).to.eq(70); - expect(eventSpy).calledOnceWithExactly('igcChange', { + expect(spy).toHaveBeenCalledExactlyOnceWith('igcChange', { detail: { lower: 50, upper: 70 }, }); }); @@ -1062,7 +1060,7 @@ describe('Slider component', () => { describe('Initial rendering race condition', () => { let slider: IgcSliderComponent; - before(() => defineComponents(IgcSliderComponent)); + beforeAll(() => defineComponents(IgcSliderComponent)); beforeEach(async () => { slider = await fixture( diff --git a/src/components/snackbar/snackbar.spec.ts b/src/components/snackbar/snackbar.spec.ts index 898563b73..6bbeeb8f1 100644 --- a/src/components/snackbar/snackbar.spec.ts +++ b/src/components/snackbar/snackbar.spec.ts @@ -1,19 +1,17 @@ +import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; +import IgcButtonComponent from '../button/button.js'; +import { defineComponents } from '../common/definitions/defineComponents.js'; import { elementUpdated, - expect, fixture, html, nextFrame, -} from '@open-wc/testing'; -import { type SinonFakeTimers, spy, useFakeTimers } from 'sinon'; - -import IgcButtonComponent from '../button/button.js'; -import { defineComponents } from '../common/definitions/defineComponents.js'; +} from '../common/helpers.spec.js'; import { finishAnimationsFor } from '../common/utils.spec.js'; import IgcSnackbarComponent from './snackbar.js'; describe('Snackbar', () => { - before(() => { + beforeAll(() => { defineComponents(IgcSnackbarComponent); }); @@ -21,7 +19,6 @@ describe('Snackbar', () => { const defaultContent = 'Hello world'; let snackbar: IgcSnackbarComponent; - let clock: SinonFakeTimers; describe('DOM', () => { beforeEach(async () => { @@ -110,16 +107,11 @@ describe('Snackbar', () => { }; beforeEach(async () => { - clock = useFakeTimers({ toFake: ['setTimeout'] }); snackbar = await fixture( html`${defaultContent}` ); }); - afterEach(() => { - clock.restore(); - }); - it('`open` property', async () => { checkOpenState(false); @@ -133,16 +125,17 @@ describe('Snackbar', () => { }); it('`displayTime` property', async () => { + vi.useFakeTimers({ toFake: ['setTimeout'] }); snackbar.displayTime = 400; await snackbar.show(); checkOpenState(true); - await clock.tickAsync(399); + await vi.advanceTimersByTimeAsync(399); checkOpenState(true); expect(snackbar.open).to.be.true; // hide timer triggers after this tick - await clock.tickAsync(1); + await vi.advanceTimersByTimeAsync(1); // Stop running animations and repaint finishAnimationsFor(snackbar.shadowRoot!); @@ -150,18 +143,23 @@ describe('Snackbar', () => { expect(snackbar.open).to.be.false; checkOpenState(false); + + vi.useRealTimers(); }); it('`keepOpen` overrides `displayTime`', async () => { + vi.useFakeTimers({ toFake: ['setTimeout'] }); snackbar.displayTime = 200; snackbar.keepOpen = true; await snackbar.show(); checkOpenState(true); - await clock.tickAsync(400); + await vi.advanceTimersByTimeAsync(400); expect(snackbar.open).to.be.true; checkOpenState(true); + + vi.useRealTimers(); }); it('`show()` and `hide()`', async () => { @@ -209,10 +207,10 @@ describe('Snackbar', () => { snackbar.actionText = defaultActionText; await elementUpdated(snackbar); - const eventSpy = spy(snackbar, 'emitEvent'); + const spy = vi.spyOn(snackbar, 'emitEvent'); getDefaultActionButton().click(); - expect(eventSpy).calledOnceWithExactly('igcAction'); + expect(spy).toHaveBeenCalledExactlyOnceWith('igcAction'); }); it('emit `igcAction` with slotted content', async () => { @@ -224,10 +222,10 @@ describe('Snackbar', () => { snackbar.appendChild(button); await elementUpdated(snackbar); - const eventSpy = spy(snackbar, 'emitEvent'); + const spy = vi.spyOn(snackbar, 'emitEvent'); button.click(); - expect(eventSpy).calledOnceWithExactly('igcAction'); + expect(spy).toHaveBeenCalledExactlyOnceWith('igcAction'); }); }); }); diff --git a/src/components/stepper/stepper-utils.spec.ts b/src/components/stepper/stepper-utils.spec.ts index c00b0ec28..2c07f7553 100644 --- a/src/components/stepper/stepper-utils.spec.ts +++ b/src/components/stepper/stepper-utils.spec.ts @@ -1,5 +1,4 @@ -import { fixture, html, unsafeStatic } from '@open-wc/testing'; - +import { fixture, html, unsafeStatic } from '../common/helpers.spec.js'; import type IgcStepComponent from './step.js'; import type IgcStepperComponent from './stepper.js'; diff --git a/src/components/stepper/stepper.spec.ts b/src/components/stepper/stepper.spec.ts index a6cfb6314..3bb872e61 100644 --- a/src/components/stepper/stepper.spec.ts +++ b/src/components/stepper/stepper.spec.ts @@ -1,7 +1,14 @@ -import { elementUpdated, expect } from '@open-wc/testing'; -import { spy } from 'sinon'; - -import { defineComponents } from '../../index.js'; +import { + beforeAll, + beforeEach, + describe, + expect, + it, + type MockInstance, + vi, +} from 'vitest'; +import { defineComponents } from '../common/definitions/defineComponents.js'; +import { elementUpdated } from '../common/helpers.spec.js'; import IgcStepComponent from './step.js'; import IgcStepperComponent from './stepper.js'; import { @@ -14,12 +21,12 @@ import { } from './stepper-utils.spec.js'; describe('Stepper', () => { - before(() => { + beforeAll(() => { defineComponents(IgcStepperComponent); }); let stepper: IgcStepperComponent; - let eventSpy: any; + let spy: MockInstance; describe('Basic', async () => { beforeEach(async () => { @@ -185,7 +192,7 @@ describe('Stepper', () => { }); it('Should emit ing and ed events when a step is activated through UI', async () => { - eventSpy = spy(stepper, 'emitEvent'); + spy = vi.spyOn(stepper, 'emitEvent'); await elementUpdated(stepper); const argsIng = { @@ -210,13 +217,13 @@ describe('Stepper', () => { stepHeader?.dispatchEvent(new MouseEvent('click')); await elementUpdated(stepper); - expect(eventSpy.callCount).to.equal(2); - expect(eventSpy.firstCall).calledWith('igcActiveStepChanging', argsIng); - expect(eventSpy.secondCall).calledWith('igcActiveStepChanged', argsEd); + expect(spy).toHaveBeenCalledTimes(2); + expect(spy).toHaveBeenNthCalledWith(1, 'igcActiveStepChanging', argsIng); + expect(spy).toHaveBeenNthCalledWith(2, 'igcActiveStepChanged', argsEd); }); it('Should not emit events when a step is activated through API', async () => { - eventSpy = spy(stepper, 'emitEvent'); + spy = vi.spyOn(stepper, 'emitEvent'); await elementUpdated(stepper); expect(stepper.steps[0].active).to.be.true; @@ -225,19 +232,19 @@ describe('Stepper', () => { await elementUpdated(stepper); expect(stepper.steps[1].active).to.be.true; - expect(eventSpy.callCount).to.equal(0); + expect(spy).toHaveBeenCalledTimes(0); stepper.prev(); await elementUpdated(stepper); expect(stepper.steps[0].active).to.be.true; - expect(eventSpy.callCount).to.equal(0); + expect(spy).toHaveBeenCalledTimes(0); stepper.navigateTo(2); await elementUpdated(stepper); expect(stepper.steps[2].active).to.be.true; - expect(eventSpy.callCount).to.equal(0); + expect(spy).toHaveBeenCalledTimes(0); }); it('Should be able to cancel the igcActiveStepChanging event', async () => { @@ -1028,7 +1035,7 @@ describe('Stepper', () => { describe('Keyboard navigation', async () => { beforeEach(async () => { stepper = await StepperTestFunctions.createStepperElement(simpleStepper); - eventSpy = spy(stepper, 'emitEvent'); + spy = vi.spyOn(stepper, 'emitEvent'); }); it('Should navigate to the first/last step on Home/End key press', async () => { @@ -1404,7 +1411,7 @@ describe('Stepper', () => { describe('Aria', async () => { beforeEach(async () => { stepper = await StepperTestFunctions.createStepperElement(simpleStepper); - eventSpy = spy(stepper, 'emitEvent'); + spy = vi.spyOn(stepper, 'emitEvent'); }); it('Should render proper role and orientation attributes for the stepper', async () => { diff --git a/src/components/tabs/tabs.spec.ts b/src/components/tabs/tabs.spec.ts index 34acf44a5..af2e8fbb6 100644 --- a/src/components/tabs/tabs.spec.ts +++ b/src/components/tabs/tabs.spec.ts @@ -1,12 +1,5 @@ -import { - elementUpdated, - expect, - fixture, - html, - waitUntil, -} from '@open-wc/testing'; import { range } from 'lit/directives/range.js'; -import { spy } from 'sinon'; +import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; import type IgcIconButtonComponent from '../button/icon-button.js'; import { arrowLeft, @@ -17,6 +10,12 @@ import { spaceBar, } from '../common/controllers/key-bindings.js'; import { defineComponents } from '../common/definitions/defineComponents.js'; +import { + elementUpdated, + fixture, + html, + waitUntil, +} from '../common/helpers.spec.js'; import { first, last } from '../common/util.js'; import { simulateClick, simulateKeyboard } from '../common/utils.spec.js'; import IgcTabComponent from './tab.js'; @@ -33,7 +32,7 @@ describe('Tabs component', () => { expect(getComputedStyle(getTabDOM(tab).body).display).to.equal('block'); } - before(() => { + beforeAll(() => { defineComponents(IgcTabComponent, IgcTabsComponent); }); @@ -288,55 +287,55 @@ describe('Tabs component', () => { activeTabWidth = activeTabHeader.getBoundingClientRect().width; expect(indicator.style.transform).to.eq( - `translateX(${activeTabOffsetLeft - scrollContainerWidth + activeTabWidth}px)` + `translateX(${Math.round(activeTabOffsetLeft - scrollContainerWidth + activeTabWidth)}px)` ); - expect(indicator.style.width).to.eq(`${activeTabWidth}px`); + expect(indicator.style.width).to.eq(`${Math.round(activeTabWidth)}px`); }); it('emits `igcChange` when selecting item via mouse click', async () => { - const eventSpy = spy(element, 'emitEvent'); + const spy = vi.spyOn(element, 'emitEvent'); simulateClick(getTabDOM(element.tabs[3]).header); await elementUpdated(element); - expect(eventSpy).calledWithExactly('igcChange', { + expect(spy).toHaveBeenCalledWith('igcChange', { detail: first(getTabsDOM(element).selected), }); }); it('emits `igcChange` when selecting item via arrow key press', async () => { - const eventSpy = spy(element, 'emitEvent'); + const spy = vi.spyOn(element, 'emitEvent'); simulateKeyboard(getTabDOM(element.tabs[1]).header, arrowLeft); await elementUpdated(element); - expect(eventSpy).calledWithExactly('igcChange', { + expect(spy).toHaveBeenCalledWith('igcChange', { detail: first(getTabsDOM(element).selected), }); }); it('does not change active tab when clicking inside the tab content', async () => { - const eventSpy = spy(element, 'emitEvent'); + const spy = vi.spyOn(element, 'emitEvent'); const input = document.createElement('input'); element.tabs[1].append(input); simulateClick(input); await elementUpdated(element); - expect(eventSpy.callCount).to.equal(0); + expect(spy).toHaveBeenCalledTimes(0); verifySelection(element, element.tabs[1]); }); it('does not change active tab with keyboard interaction inside the tab content', async () => { - const eventSpy = spy(element, 'emitEvent'); + const spy = vi.spyOn(element, 'emitEvent'); const input = document.createElement('input'); element.tabs[1].append(input); simulateKeyboard(input, arrowLeft); await elementUpdated(element); - expect(eventSpy.callCount).to.equal(0); + expect(spy).toHaveBeenCalledTimes(0); verifySelection(element, element.tabs[1]); }); @@ -352,7 +351,7 @@ describe('Tabs component', () => { .map((tab) => getTabDOM(tab).header.offsetWidth - expectedWidth) .reduce((a, b) => a - b, 0); - expect(diff).to.equal(0); + expect(diff).to.approximately(0, 2); }); it('aligns tab headers properly when `alignment` is set to start', async () => { diff --git a/src/components/textarea/textarea.spec.ts b/src/components/textarea/textarea.spec.ts index 983575a0c..d866622dd 100644 --- a/src/components/textarea/textarea.spec.ts +++ b/src/components/textarea/textarea.spec.ts @@ -1,14 +1,13 @@ +import type { TemplateResult } from 'lit'; +import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; +import { configureTheme } from '../../theming/config.js'; +import { defineComponents } from '../common/definitions/defineComponents.js'; import { elementUpdated, - expect, fixture, html, nextFrame, -} from '@open-wc/testing'; -import type { TemplateResult } from 'lit'; -import { spy } from 'sinon'; -import { configureTheme } from '../../theming/config.js'; -import { defineComponents } from '../common/definitions/defineComponents.js'; +} from '../common/helpers.spec.js'; import { createFormAssociatedTestBed, isFocused, @@ -21,7 +20,7 @@ import { import IgcTextareaComponent from './textarea.js'; describe('Textarea component', () => { - before(() => { + beforeAll(() => { defineComponents(IgcTextareaComponent); }); @@ -172,22 +171,24 @@ describe('Textarea component', () => { }); it('igcInput', async () => { - const eventSpy = spy(element, 'emitEvent'); + const spy = vi.spyOn(element, 'emitEvent'); simulateInput(textArea, { value: '123' }); await elementUpdated(element); - expect(eventSpy).calledOnceWithExactly('igcInput', { detail: '123' }); + expect(spy).toHaveBeenCalledExactlyOnceWith('igcInput', { + detail: '123', + }); expect(element.value).to.equal(textArea.value); }); it('igcChange', async () => { - const eventSpy = spy(element, 'emitEvent'); + const spy = vi.spyOn(element, 'emitEvent'); textArea.value = '20230317'; textArea.dispatchEvent(new Event('change')); - expect(eventSpy).calledOnceWithExactly('igcChange', { + expect(spy).toHaveBeenCalledExactlyOnceWith('igcChange', { detail: '20230317', }); expect(element.value).to.equal(textArea.value); diff --git a/src/components/tile-manager/tile-dnd.spec.ts b/src/components/tile-manager/tile-dnd.spec.ts index 5adfb42eb..b2d43ae5a 100644 --- a/src/components/tile-manager/tile-dnd.spec.ts +++ b/src/components/tile-manager/tile-dnd.spec.ts @@ -1,15 +1,14 @@ +import { range } from 'lit/directives/range.js'; +import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; +import IgcIconButtonComponent from '../button/icon-button.js'; +import { escapeKey } from '../common/controllers/key-bindings.js'; +import { defineComponents } from '../common/definitions/defineComponents.js'; import { elementUpdated, - expect, fixture, html, nextFrame, -} from '@open-wc/testing'; -import { range } from 'lit/directives/range.js'; -import { restore, spy, stub } from 'sinon'; -import IgcIconButtonComponent from '../button/icon-button.js'; -import { escapeKey } from '../common/controllers/key-bindings.js'; -import { defineComponents } from '../common/definitions/defineComponents.js'; +} from '../common/helpers.spec.js'; import { getCenterPoint } from '../common/util.js'; import { simulateClick, @@ -23,7 +22,7 @@ import IgcTileComponent from './tile.js'; import IgcTileManagerComponent from './tile-manager.js'; describe('Tile drag and drop', () => { - before(() => { + beforeAll(() => { defineComponents(IgcTileManagerComponent); }); @@ -79,6 +78,10 @@ describe('Tile drag and drop', () => { } async function dragAndDrop(tile: IgcTileComponent, target: IgcTileComponent) { + // Ensure target is visible in viewport for elementsFromPoint to work + target.scrollIntoView({ block: 'center' }); + await nextFrame(); + const { x, y } = getCenterPoint(target); simulatePointerDown(tile); @@ -116,11 +119,11 @@ describe('Tile drag and drop', () => { const draggedTile = getTile(0); const dropTarget = getTile(1); - const eventSpy = spy(draggedTile, 'emitEvent'); + const spy = vi.spyOn(draggedTile, 'emitEvent'); await dragAndDrop(draggedTile, dropTarget); - expect(eventSpy).not.called; + expect(spy).not.toHaveBeenCalled(); expect(draggedTile.position).to.equal(0); expect(dropTarget.position).to.equal(1); }); @@ -158,12 +161,12 @@ describe('Tile drag and drop', () => { it('should correctly fire `igcTileDragStart` event', async () => { const tile = getTile(0); - const eventSpy = spy(tile, 'emitEvent'); + const spy = vi.spyOn(tile, 'emitEvent'); simulatePointerDown(tile); await elementUpdated(tile); - expect(eventSpy).calledOnceWithExactly('igcTileDragStart', { + expect(spy).toHaveBeenCalledWith('igcTileDragStart', { detail: tile, cancelable: true, }); @@ -171,7 +174,7 @@ describe('Tile drag and drop', () => { it('should stop drag operation when `igcTileDragStart` is prevented', async () => { const [tile, target] = [getTile(0), getTile(4)]; - const eventSpy = spy(tile, 'emitEvent'); + const spy = vi.spyOn(tile, 'emitEvent'); tile.addEventListener('igcTileDragStart', (event) => { event.preventDefault(); @@ -179,19 +182,22 @@ describe('Tile drag and drop', () => { await dragAndDrop(tile, target); - expect(eventSpy).calledOnceWith('igcTileDragStart'); - expect(eventSpy).not.calledWith('igcTileDragEnd'); - expect(eventSpy.callCount).to.equal(1); + expect(spy).toHaveBeenCalledWith('igcTileDragStart', { + detail: tile, + cancelable: true, + }); + expect(spy).not.toHaveBeenCalledWith('igcTileDragEnd'); + expect(spy).toHaveBeenCalledTimes(1); }); it('should correctly fire `igcTileDragEnd` event', async () => { const [tile, target] = [getTile(0), getTile(4)]; - const eventSpy = spy(tile, 'emitEvent'); + const spy = vi.spyOn(tile, 'emitEvent'); await dragAndDrop(tile, target); - expect(eventSpy).calledTwice; - expect(eventSpy).calledWith('igcTileDragEnd', { + expect(spy).toHaveBeenCalledTimes(2); + expect(spy).toHaveBeenCalledWith('igcTileDragEnd', { detail: tile, }); }); @@ -199,23 +205,34 @@ describe('Tile drag and drop', () => { it('should cancel dragging with Escape', async () => { const draggedTile = getTile(0); const dropTarget = getTile(4); - const eventSpy = spy(draggedTile, 'emitEvent'); + const spy = vi.spyOn(draggedTile, 'emitEvent'); + + // Scroll the target into view before getting coordinates + dropTarget.scrollIntoView({ block: 'center' }); + await nextFrame(); + const { x, y } = getCenterPoint(dropTarget); expect(draggedTile.position).to.equal(0); expect(dropTarget.position).to.equal(4); simulatePointerDown(draggedTile); - simulateTileDragOver(draggedTile, { x, y }); + await elementUpdated(draggedTile); + simulateTileDragOver(draggedTile, { x, y }); + await elementUpdated(tileManager); await viewTransitionComplete(); + expect(draggedTile.position).to.equal(4); expect(dropTarget.position).to.equal(0); simulateKeyboard(tileManager, escapeKey); + await elementUpdated(tileManager); await viewTransitionComplete(); - expect(eventSpy).calledWith('igcTileDragCancel'); + expect(spy).toHaveBeenCalledWith('igcTileDragCancel', { + detail: draggedTile, + }); expect(draggedTile.position).to.equal(0); expect(dropTarget.position).to.equal(4); @@ -273,6 +290,10 @@ describe('Tile drag and drop', () => { dropTarget.colSpan = 3; await elementUpdated(tileManager); + // Ensure target is visible in viewport for elementsFromPoint to work + dropTarget.scrollIntoView({ block: 'center' }); + await nextFrame(); + const { x, y } = getCenterPoint(dropTarget); simulatePointerDown(draggedTile); @@ -350,6 +371,9 @@ describe('Tile drag and drop', () => { const draggedTile = getTile(0); const dropTarget = getTile(1); + // Ensure enough columns for the explicit positioning + tileManager.columnCount = 4; + draggedTile.colStart = 2; draggedTile.rowStart = 2; @@ -399,7 +423,7 @@ describe('Tile drag and drop', () => { it('should rearrange tiles when the tile is dropped', async () => { const draggedTile = getTile(3); const dropTarget = getTile(1); - const eventSpy = spy(draggedTile, 'emitEvent'); + const spy = vi.spyOn(draggedTile, 'emitEvent'); await dragAndDropFromHeader(draggedTile, dropTarget); @@ -411,7 +435,7 @@ describe('Tile drag and drop', () => { 'tile4', ]; - expect(eventSpy).calledTwice; + expect(spy).toHaveBeenCalledTimes(2); tileManager.tiles.forEach((tile, index) => { expect(tile.id).to.equal(expectedIdsAfterDrag[index]); }); @@ -422,7 +446,7 @@ describe('Tile drag and drop', () => { it('should not start dragging if pointer is not over the header', async () => { const draggedTile = getTile(0); const dropTarget = getTile(1); - const eventSpy = spy(draggedTile, 'emitEvent'); + const spy = vi.spyOn(draggedTile, 'emitEvent'); const contentContainer = getTileContentContainer(draggedTile); const { x, y } = getCenterPoint(dropTarget); @@ -435,7 +459,7 @@ describe('Tile drag and drop', () => { simulateLostPointerCapture(draggedTile); await elementUpdated(tileManager); - expect(eventSpy).not.called; + expect(spy).not.toHaveBeenCalled(); expect(draggedTile.position).to.equal(0); expect(dropTarget.position).to.equal(1); }); @@ -462,14 +486,14 @@ describe('Tile drag and drop', () => { it('should disable drag and drop when tile is maximized', async () => { const draggedTile = getTile(0); const dropTarget = getTile(1); - const eventSpy = spy(draggedTile, 'emitEvent'); + const spy = vi.spyOn(draggedTile, 'emitEvent'); draggedTile.maximized = true; await elementUpdated(tileManager); await dragAndDrop(draggedTile, dropTarget); - expect(eventSpy).not.called; + expect(spy).not.toHaveBeenCalled(); expect(draggedTile.position).to.equal(0); expect(dropTarget.position).to.equal(1); }); @@ -477,13 +501,13 @@ describe('Tile drag and drop', () => { it('should disable drag and drop when tile is in fullscreen mode', async () => { const draggedTile = getTile(0); const dropTarget = getTile(1); - const eventSpy = spy(draggedTile, 'emitEvent'); + const spy = vi.spyOn(draggedTile, 'emitEvent'); const buttonFullscreen = draggedTile.renderRoot.querySelector( '[name="fullscreen"]' )!; - draggedTile.requestFullscreen = stub().callsFake(() => { + draggedTile.requestFullscreen = vi.fn().mockImplementation(() => { Object.defineProperty(document, 'fullscreenElement', { value: draggedTile, configurable: true, @@ -498,8 +522,8 @@ describe('Tile drag and drop', () => { await dragAndDrop(draggedTile, dropTarget); - expect(eventSpy).not.calledWith('igcTileDragStart'); - expect(eventSpy).not.calledWith('igcTileDragEnd'); + expect(spy).not.toHaveBeenCalledWith('igcTileDragStart'); + expect(spy).not.toHaveBeenCalledWith('igcTileDragEnd'); const expectedIdsAfterDrag = [ 'tile0', @@ -519,21 +543,21 @@ describe('Tile drag and drop', () => { configurable: true, }); - restore(); + vi.restoreAllMocks(); }); it('should not start a drag operation when interacting with the default tile actions in `tile-header` drag mode', async () => { tileManager.dragMode = 'tile-header'; const tile = getTile(0); const [maximize, _] = getActionButtons(tile); - const eventSpy = spy(tile, 'emitEvent'); + const spy = vi.spyOn(tile, 'emitEvent'); // Wait for maximized transition trigger from UI maximize.click(); await viewTransitionComplete(); expect(tile.maximized).to.be.true; - expect(eventSpy).not.calledWith('igcTileDragStart'); + expect(spy).not.toHaveBeenCalledWith('igcTileDragStart'); // Wait for maximized transition trigger from UI maximize.click(); @@ -546,14 +570,14 @@ describe('Tile drag and drop', () => { tileManager.dragMode = 'tile'; const tile = getTile(0); const [maximize, _] = getActionButtons(tile); - const eventSpy = spy(tile, 'emitEvent'); + const spy = vi.spyOn(tile, 'emitEvent'); // Wait for maximized transition trigger from UI maximize.click(); await viewTransitionComplete(); expect(tile.maximized).to.be.true; - expect(eventSpy).not.calledWith('igcTileDragStart'); + expect(spy).not.toHaveBeenCalledWith('igcTileDragStart'); // Wait for maximized transition trigger from UI maximize.click(); @@ -564,7 +588,7 @@ describe('Tile drag and drop', () => { it('should not start a drag operation when interacting with the slotted tile actions in `tile-header` drag mode', async () => { const { tile, button } = createSlottedActionTile(); - const eventSpy = spy(tile, 'emitEvent'); + const spy = vi.spyOn(tile, 'emitEvent'); tileManager.dragMode = 'tile-header'; tileManager.append(tile); @@ -573,12 +597,12 @@ describe('Tile drag and drop', () => { button.click(); await elementUpdated(tile); - expect(eventSpy).not.calledWith('igcTileDragStart'); + expect(spy).not.toHaveBeenCalledWith('igcTileDragStart'); }); it('should not start a drag operation when interacting with the slotted tile actions in `tile` drag mode', async () => { const { tile, button } = createSlottedActionTile(); - const eventSpy = spy(tile, 'emitEvent'); + const spy = vi.spyOn(tile, 'emitEvent'); tileManager.dragMode = 'tile'; tileManager.append(tile); @@ -587,7 +611,7 @@ describe('Tile drag and drop', () => { button.click(); await elementUpdated(tile); - expect(eventSpy).not.calledWith('igcTileDragStart'); + expect(spy).not.toHaveBeenCalledWith('igcTileDragStart'); }); }); }); diff --git a/src/components/tile-manager/tile-manager.spec.ts b/src/components/tile-manager/tile-manager.spec.ts index e9ac95cdc..2612ed667 100644 --- a/src/components/tile-manager/tile-manager.spec.ts +++ b/src/components/tile-manager/tile-manager.spec.ts @@ -1,21 +1,28 @@ +import { range } from 'lit/directives/range.js'; import { - elementUpdated, + afterEach, + beforeAll, + beforeEach, + describe, expect, + it, + vi, +} from 'vitest'; +import IgcIconButtonComponent from '../button/icon-button.js'; +import { defineComponents } from '../common/definitions/defineComponents.js'; +import { + elementUpdated, fixture, html, nextFrame, -} from '@open-wc/testing'; -import { range } from 'lit/directives/range.js'; -import { match, restore, spy, stub } from 'sinon'; -import IgcIconButtonComponent from '../button/icon-button.js'; -import { defineComponents } from '../common/definitions/defineComponents.js'; +} from '../common/helpers.spec.js'; import { first } from '../common/util.js'; import { simulateClick } from '../common/utils.spec.js'; import IgcTileComponent from './tile.js'; import IgcTileManagerComponent from './tile-manager.js'; describe('Tile Manager component', () => { - before(() => { + beforeAll(() => { defineComponents(IgcTileManagerComponent); }); @@ -213,9 +220,7 @@ describe('Tile Manager component', () => { it("should check tile manager's row and column template style props", async () => { const style = getComputedStyle(getTileManagerBase()); - expect(style.gridTemplateColumns).to.equal( - '234.656px 234.656px 234.656px 0px 0px' - ); + expect(style.gridTemplateColumns).to.equal('334px 0px 0px 0px 0px'); tileManager.columnCount = 15; await elementUpdated(tileManager); @@ -497,13 +502,14 @@ describe('Tile Manager component', () => { describe('Tile state change behavior', () => { let tile: any; + let exitFullscreenMock: ReturnType; beforeEach(async () => { tileManager = await fixture(createTileManager()); tile = first(tileManager.tiles); // Mock `requestFullscreen` - tile.requestFullscreen = stub().callsFake(() => { + tile.requestFullscreen = vi.fn().mockImplementation(() => { Object.defineProperty(document, 'fullscreenElement', { value: tile, configurable: true, @@ -512,14 +518,15 @@ describe('Tile Manager component', () => { }); // Mock `exitFullscreen` + exitFullscreenMock = vi.fn().mockImplementation(() => { + Object.defineProperty(document, 'fullscreenElement', { + value: null, + configurable: true, + }); + return Promise.resolve(); + }); Object.defineProperty(document, 'exitFullscreen', { - value: stub().callsFake(() => { - Object.defineProperty(document, 'fullscreenElement', { - value: null, - configurable: true, - }); - return Promise.resolve(); - }), + value: exitFullscreenMock, configurable: true, }); }); @@ -530,7 +537,7 @@ describe('Tile Manager component', () => { configurable: true, }); - restore(); + vi.restoreAllMocks(); }); it('should correctly change fullscreen state on button click', async () => { @@ -538,25 +545,25 @@ describe('Tile Manager component', () => { simulateClick(btnFullscreen); await elementUpdated(tileManager); - expect(tile.requestFullscreen).to.have.been.calledOnce; - expect(document.exitFullscreen).to.not.have.been.called; + expect(tile.requestFullscreen).toHaveBeenCalledOnce(); + expect(exitFullscreenMock).not.toHaveBeenCalled(); expect(tile.fullscreen).to.be.true; simulateClick(btnFullscreen); await elementUpdated(tileManager); - expect(document.exitFullscreen).to.have.been.calledOnce; + expect(exitFullscreenMock).toHaveBeenCalledOnce(); expect(tile.fullscreen).to.be.false; }); it('should correctly fire `igcTileFullscreen` event', async () => { - const eventSpy = spy(tile, 'emitEvent'); + const eventSpy = vi.spyOn(tile, 'emitEvent'); const fullscreenButton = getActionButtons(tile)[1]; simulateClick(fullscreenButton!); await elementUpdated(tileManager); - expect(eventSpy).calledWith('igcTileFullscreen', { + expect(eventSpy).toHaveBeenCalledWith('igcTileFullscreen', { detail: { tile: tile, state: true }, cancelable: true, }); @@ -565,7 +572,7 @@ describe('Tile Manager component', () => { simulateClick(fullscreenButton!); await elementUpdated(tileManager); - expect(eventSpy).calledWith('igcTileFullscreen', { + expect(eventSpy).toHaveBeenCalledWith('igcTileFullscreen', { detail: { tile: tile, state: false }, cancelable: true, }); @@ -573,7 +580,7 @@ describe('Tile Manager component', () => { }); it('can cancel `igcTileFullscreen` event', async () => { - const eventSpy = spy(tile, 'emitEvent'); + const eventSpy = vi.spyOn(tile, 'emitEvent'); const fullscreenButton = getActionButtons(tile)[1]; tile.addEventListener('igcTileFullscreen', (ev: CustomEvent) => { @@ -583,15 +590,15 @@ describe('Tile Manager component', () => { simulateClick(fullscreenButton!); await elementUpdated(tileManager); - expect(eventSpy).calledWith( + expect(eventSpy).toHaveBeenCalledWith( 'igcTileFullscreen', - match({ + expect.objectContaining({ detail: { tile: tile, state: true }, cancelable: true, }) ); expect(tile.fullscreen).to.be.false; - expect(tile.requestFullscreen).not.to.have.been.called; + expect(tile.requestFullscreen).not.toHaveBeenCalled(); }); it('should update fullscreen property on fullscreenchange (e.g. Esc key is pressed)', async () => { @@ -643,14 +650,14 @@ describe('Tile Manager component', () => { }); it('should correctly fire `igcTileMaximize` event on clicking Maximize button', async () => { - const eventSpy = spy(tile, 'emitEvent'); + const eventSpy = vi.spyOn(tile, 'emitEvent'); const btnMaximize = getActionButtons(tile)[0]; // Wait for maximized transition trigger from UI simulateClick(btnMaximize); await viewTransitionComplete(); - expect(eventSpy).calledWith('igcTileMaximize', { + expect(eventSpy).toHaveBeenCalledWith('igcTileMaximize', { detail: { tile: tile, state: true }, cancelable: true, }); @@ -660,8 +667,8 @@ describe('Tile Manager component', () => { simulateClick(btnMaximize); await viewTransitionComplete(); - expect(eventSpy).to.have.been.calledTwice; - expect(eventSpy).calledWith('igcTileMaximize', { + expect(eventSpy).toHaveBeenCalledTimes(2); + expect(eventSpy).toHaveBeenCalledWith('igcTileMaximize', { detail: { tile: tile, state: false }, cancelable: true, }); @@ -669,7 +676,7 @@ describe('Tile Manager component', () => { }); it('can cancel `igcTileMaximize` event', async () => { - const eventSpy = spy(tile, 'emitEvent'); + const eventSpy = vi.spyOn(tile, 'emitEvent'); tile.addEventListener('igcTileMaximize', (event: Event) => { event.preventDefault(); @@ -679,7 +686,8 @@ describe('Tile Manager component', () => { simulateClick(btnMaximize); await elementUpdated(tileManager); - expect(eventSpy).calledOnceWithExactly('igcTileMaximize', { + expect(eventSpy).toHaveBeenCalledOnce(); + expect(eventSpy).toHaveBeenCalledWith('igcTileMaximize', { detail: { tile: tile, state: true }, cancelable: true, }); diff --git a/src/components/tile-manager/tile-resize.spec.ts b/src/components/tile-manager/tile-resize.spec.ts index eaea3fb24..7e6905d77 100644 --- a/src/components/tile-manager/tile-resize.spec.ts +++ b/src/components/tile-manager/tile-resize.spec.ts @@ -1,31 +1,29 @@ +import { range } from 'lit/directives/range.js'; +import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; +import { escapeKey } from '../common/controllers/key-bindings.js'; +import { defineComponents } from '../common/definitions/defineComponents.js'; import { elementUpdated, - expect, fixture, html, nextFrame, -} from '@open-wc/testing'; -import { range } from 'lit/directives/range.js'; -import type Sinon from 'sinon'; -import { spy } from 'sinon'; -import { escapeKey } from '../common/controllers/key-bindings.js'; -import { defineComponents } from '../common/definitions/defineComponents.js'; +} from '../common/helpers.spec.js'; import { first, last } from '../common/util.js'; import { + expectCalledWith, + expectNotCalledWith, simulateKeyboard, simulateLostPointerCapture, simulatePointerDown, simulatePointerMove, } from '../common/utils.spec.js'; -import IgcResizeContainerComponent, { - type IgcResizeContainerComponentEventMap, -} from '../resize-container/resize-container.js'; +import IgcResizeContainerComponent from '../resize-container/resize-container.js'; import type { ResizeCallbackParams } from '../resize-container/types.js'; import IgcTileComponent from './tile.js'; import IgcTileManagerComponent from './tile-manager.js'; describe('Tile resize', () => { - before(() => { + beforeAll(() => { defineComponents(IgcTileManagerComponent); }); @@ -145,7 +143,7 @@ describe('Tile resize', () => { it('should create a ghost element on resize start', async () => { const DOM = getResizeContainerDOM(firstTile); - const eventSpy = spy(DOM.resizeElement, 'emitEvent'); + const eventSpy = vi.spyOn(DOM.resizeElement, 'emitEvent'); expect(DOM.ghostElement).to.be.null; @@ -153,19 +151,19 @@ describe('Tile resize', () => { await elementUpdated(DOM.resizeElement); expect(DOM.ghostElement).to.not.be.null; - expect(eventSpy).calledWith('igcResizeStart'); + expectCalledWith(eventSpy, 'igcResizeStart'); }); it('should update ghost element styles during pointer move', async () => { const DOM = getResizeContainerDOM(firstTile); - const eventSpy = spy(DOM.resizeElement, 'emitEvent'); + const eventSpy = vi.spyOn(DOM.resizeElement, 'emitEvent'); const tileRect = firstTile.getBoundingClientRect(); simulatePointerDown(DOM.adorners.corner); await elementUpdated(DOM.resizeElement); - expect(eventSpy).calledWith('igcResizeStart'); + expectCalledWith(eventSpy, 'igcResizeStart'); expect(DOM.ghostElement.getBoundingClientRect()).to.eql(tileRect); simulatePointerMove(DOM.adorners.corner, { @@ -176,7 +174,7 @@ describe('Tile resize', () => { const state = getResizeEventState(eventSpy); - expect(eventSpy).calledWith('igcResize'); + expectCalledWith(eventSpy, 'igcResize'); expect(state.ghost).to.equal(DOM.ghostElement); expect(state.initial).to.eql(tileRect); assertRectsAreEqual( @@ -187,12 +185,12 @@ describe('Tile resize', () => { it('Should correctly resize column with auto grid', async () => { const DOM = getResizeContainerDOM(firstTile); - const eventSpy = spy(DOM.resizeElement, 'emitEvent'); + const eventSpy = vi.spyOn(DOM.resizeElement, 'emitEvent'); simulatePointerDown(DOM.adorners.side); await elementUpdated(DOM.container); - expect(eventSpy).calledWith('igcResizeStart'); + expectCalledWith(eventSpy, 'igcResizeStart'); expect(getComputedStyle(firstTile).gridColumn).to.eql('auto / span 1'); simulatePointerMove(DOM.adorners.side, { @@ -201,13 +199,13 @@ describe('Tile resize', () => { await elementUpdated(DOM.resizeElement); - expect(eventSpy).calledWith('igcResize'); + expectCalledWith(eventSpy, 'igcResize'); simulateLostPointerCapture(DOM.adorners.side); await elementUpdated(DOM.resizeElement); await nextFrame(); - expect(eventSpy).calledWith('igcResizeEnd'); + expectCalledWith(eventSpy, 'igcResizeEnd'); expect(DOM.ghostElement).to.be.null; expect(getComputedStyle(firstTile).gridColumn).to.eql('auto / span 2'); @@ -406,19 +404,19 @@ describe('Tile resize', () => { await elementUpdated(DOM.resizeElement); await nextFrame(); - expect(getComputedStyle(firstTile).gridRow).to.eql('2 / span 2'); + expect(getComputedStyle(firstTile).gridRow).to.eql('2 / span 3'); }); it('should cancel resize by pressing ESC key', async () => { const DOM = getResizeContainerDOM(firstTile); - const eventSpy = spy(DOM.resizeElement, 'emitEvent'); + const eventSpy = vi.spyOn(DOM.resizeElement, 'emitEvent'); const tileRect = firstTile.getBoundingClientRect(); simulatePointerDown(DOM.adorners.corner); await elementUpdated(DOM.resizeElement); - expect(eventSpy).calledWith('igcResizeStart'); + expectCalledWith(eventSpy, 'igcResizeStart'); simulatePointerMove(DOM.adorners.corner, { clientX: tileRect.right * 2, @@ -431,24 +429,24 @@ describe('Tile resize', () => { simulateKeyboard(DOM.resizeElement, escapeKey); await elementUpdated(DOM.resizeElement); - expect(eventSpy).calledWith('igcResizeCancel'); + expectCalledWith(eventSpy, 'igcResizeCancel'); expect(DOM.ghostElement).to.be.null; assertRectsAreEqual(firstTile.getBoundingClientRect(), tileRect); }); it('should fire `igcTileResizeStart` when a resize operation begins', async () => { const DOM = getResizeContainerDOM(firstTile); - const eventSpy = spy(firstTile, 'emitEvent'); + const eventSpy = vi.spyOn(firstTile, 'emitEvent'); simulatePointerDown(DOM.adorners.corner); await elementUpdated(firstTile); - expect(eventSpy).calledWith('igcTileResizeStart'); + expectCalledWith(eventSpy, 'igcTileResizeStart'); }); it('should stop resize operations by canceling the `igcTileResizeStart` event', async () => { const DOM = getResizeContainerDOM(firstTile); - const eventSpy = spy(firstTile, 'emitEvent'); + const eventSpy = vi.spyOn(firstTile, 'emitEvent'); firstTile.addEventListener('igcTileResizeStart', (event) => event.preventDefault() @@ -457,19 +455,19 @@ describe('Tile resize', () => { simulatePointerDown(DOM.adorners.corner); await elementUpdated(firstTile); - expect(eventSpy).calledWith('igcTileResizeStart'); + expectCalledWith(eventSpy, 'igcTileResizeStart'); simulatePointerMove(DOM.adorners.corner); simulateLostPointerCapture(DOM.adorners.corner); await elementUpdated(firstTile); - expect(eventSpy.callCount).to.equal(1); - expect(eventSpy).not.calledWith('igcTileResizeEnd'); + expect(eventSpy).toHaveBeenCalledTimes(1); + expectNotCalledWith(eventSpy, 'igcTileResizeEnd'); }); it('should fire `igcTileResizeEnd` when a resize operation is performed successfully', async () => { const DOM = getResizeContainerDOM(firstTile); - const eventSpy = spy(firstTile, 'emitEvent'); + const eventSpy = vi.spyOn(firstTile, 'emitEvent'); const { colSpan: initialColumnSpan, rowSpan: initialRowSpan } = firstTile; @@ -478,7 +476,7 @@ describe('Tile resize', () => { simulatePointerDown(DOM.adorners.corner); await elementUpdated(firstTile); - expect(eventSpy).calledWith('igcTileResizeStart'); + expectCalledWith(eventSpy, 'igcTileResizeStart'); simulatePointerMove(DOM.adorners.corner, { clientX: tileRect.right * 2, @@ -487,7 +485,7 @@ describe('Tile resize', () => { simulateLostPointerCapture(DOM.adorners.corner); await viewTransitionComplete(); - expect(eventSpy).calledWith('igcTileResizeEnd'); + expectCalledWith(eventSpy, 'igcTileResizeEnd'); expect(DOM.ghostElement).to.be.null; const { colSpan, rowSpan } = firstTile; @@ -497,14 +495,14 @@ describe('Tile resize', () => { it('should fire `igcTileResizeCancel` when canceling a resize operation', async () => { const DOM = getResizeContainerDOM(firstTile); - const eventSpy = spy(firstTile, 'emitEvent'); + const eventSpy = vi.spyOn(firstTile, 'emitEvent'); const tileRect = firstTile.getBoundingClientRect(); simulatePointerDown(DOM.adorners.corner); await elementUpdated(firstTile); - expect(eventSpy).calledWith('igcTileResizeStart'); + expectCalledWith(eventSpy, 'igcTileResizeStart'); simulatePointerMove(DOM.adorners.corner, { clientX: tileRect.right * 2, @@ -517,7 +515,7 @@ describe('Tile resize', () => { simulateKeyboard(DOM.resizeElement, escapeKey); await elementUpdated(firstTile); - expect(eventSpy).calledWith('igcTileResizeCancel'); + expectCalledWith(eventSpy, 'igcTileResizeCancel'); expect(DOM.ghostElement).to.be.null; assertRectsAreEqual(firstTile.getBoundingClientRect(), tileRect); }); @@ -535,14 +533,14 @@ describe('Tile resize', () => { it('should update tile parts on resizing', async () => { const DOM = getResizeContainerDOM(firstTile); - const eventSpy = spy(DOM.resizeElement, 'emitEvent'); + const eventSpy = vi.spyOn(DOM.resizeElement, 'emitEvent'); expect(firstTile.part.length).to.equal(0); simulatePointerDown(DOM.adorners.bottom); await elementUpdated(DOM.resizeElement); - expect(eventSpy).calledWith('igcResizeStart'); + expectCalledWith(eventSpy, 'igcResizeStart'); expect(firstTile.part.length).to.be.greaterThan(0); expect(firstTile.part.contains('resizing')).to.be.true; }); @@ -591,15 +589,14 @@ function getResizeContainerDOM(tile: IgcTileComponent) { } function getResizeEventState( - eventSpy: Sinon.SinonSpy< - [ - type: keyof IgcResizeContainerComponentEventMap, - eventInitDict?: CustomEventInit | undefined, - ], - boolean - > + eventSpy: ReturnType ): ResizeCallbackParams['state'] { - return eventSpy.lastCall.lastArg.detail.state; + const calls = eventSpy.mock.calls; + const lastCall = calls[calls.length - 1] as [ + string, + CustomEventInit<{ state: ResizeCallbackParams['state'] }>, + ]; + return lastCall[1]!.detail!.state; } function assertRectsAreEqual(a: DOMRect, b: DOMRect, delta = 0.01) { diff --git a/src/components/toast/toast.spec.ts b/src/components/toast/toast.spec.ts index 0b00bd35e..5519e9374 100644 --- a/src/components/toast/toast.spec.ts +++ b/src/components/toast/toast.spec.ts @@ -1,21 +1,26 @@ import { - elementUpdated, + afterEach, + beforeAll, + beforeEach, + describe, expect, + it, + vi, +} from 'vitest'; +import { defineComponents } from '../common/definitions/defineComponents.js'; +import { + elementUpdated, fixture, html, nextFrame, -} from '@open-wc/testing'; - -import { type SinonFakeTimers, useFakeTimers } from 'sinon'; -import { defineComponents } from '../common/definitions/defineComponents.js'; +} from '../common/helpers.spec.js'; import { finishAnimationsFor } from '../common/utils.spec.js'; import IgcToastComponent from './toast.js'; describe('Toast', () => { - before(() => defineComponents(IgcToastComponent)); + beforeAll(() => defineComponents(IgcToastComponent)); let toast: IgcToastComponent; - let clock: SinonFakeTimers; describe('ARIA', () => { beforeEach(async () => { @@ -32,14 +37,14 @@ describe('Toast', () => { describe('API', () => { beforeEach(async () => { - clock = useFakeTimers({ toFake: ['setTimeout'] }); + vi.useFakeTimers({ toFake: ['setTimeout'] }); toast = await fixture( html`Hello world` ); }); afterEach(() => { - clock.restore(); + vi.useRealTimers(); }); const checkOpenState = (state = false) => { @@ -69,11 +74,11 @@ describe('Toast', () => { await toast.show(); checkOpenState(true); - await clock.tickAsync(399); + await vi.advanceTimersByTimeAsync(399); expect(toast.open).to.be.true; checkOpenState(true); - await clock.tickAsync(1); + await vi.advanceTimersByTimeAsync(1); finishAnimationsFor(toast); await nextFrame(); @@ -88,7 +93,7 @@ describe('Toast', () => { await toast.show(); checkOpenState(true); - await clock.tickAsync(400); + await vi.advanceTimersByTimeAsync(400); expect(toast.open).to.be.true; checkOpenState(true); }); diff --git a/src/components/tooltip/tooltip.spec.ts b/src/components/tooltip/tooltip.spec.ts index e9d086c7d..21a03e1ec 100644 --- a/src/components/tooltip/tooltip.spec.ts +++ b/src/components/tooltip/tooltip.spec.ts @@ -1,12 +1,20 @@ import { - elementUpdated, + afterEach, + beforeAll, + beforeEach, + describe, expect, + it, + type MockInstance, + vi, +} from 'vitest'; +import { defineComponents } from '../common/definitions/defineComponents.js'; +import { + elementUpdated, fixture, html, nextFrame, -} from '@open-wc/testing'; -import { type SinonFakeTimers, spy, useFakeTimers } from 'sinon'; -import { defineComponents } from '../common/definitions/defineComponents.js'; +} from '../common/helpers.spec.js'; import { finishAnimationsFor, simulateBlur, @@ -20,7 +28,7 @@ import IgcTooltipComponent from './tooltip.js'; describe('Tooltip', () => { let anchor: HTMLButtonElement; let tooltip: IgcTooltipComponent; - let clock: SinonFakeTimers; + let clock: ReturnType; const DIFF_OPTIONS = { ignoreAttributes: ['anchor'], @@ -31,7 +39,7 @@ describe('Tooltip', () => { const DEFAULT_SHOW_DELAY = 200; const DEFAULT_HIDE_DELAY = 300; - before(() => { + beforeAll(() => { defineComponents(IgcTooltipComponent); }); @@ -39,6 +47,7 @@ describe('Tooltip', () => { finishAnimationsFor(instance.shadowRoot!); await elementUpdated(instance); await nextFrame(); + await nextFrame(); } async function hideComplete(instance: IgcTooltipComponent = tooltip) { @@ -139,14 +148,14 @@ describe('Tooltip', () => { describe('Properties Tests', () => { beforeEach(async () => { - clock = useFakeTimers({ toFake: ['setTimeout'] }); + clock = vi.useFakeTimers({ toFake: ['setTimeout'] }); const container = await fixture(createTooltipWithTarget()); tooltip = container.querySelector(IgcTooltipComponent.tagName)!; anchor = container.querySelector('button')!; }); afterEach(() => { - clock.restore(); + clock.useRealTimers(); }); it('should set target via `anchor` property', async () => { @@ -168,12 +177,12 @@ describe('Tooltip', () => { // If no anchor is provided. // Considers the first preceding sibling that is an element as the target. simulatePointerEnter(third); - await clock.tickAsync(DEFAULT_SHOW_DELAY); + await clock.advanceTimersByTimeAsync(DEFAULT_SHOW_DELAY); await showComplete(); expect(tooltip.open).to.be.false; simulatePointerLeave(third); - await clock.tickAsync(endTick(DEFAULT_HIDE_DELAY)); + await clock.advanceTimersByTime(endTick(DEFAULT_HIDE_DELAY)); await hideComplete(); expect(tooltip.open).to.be.false; @@ -182,18 +191,18 @@ describe('Tooltip', () => { await elementUpdated(tooltip); simulatePointerEnter(first); - await clock.tickAsync(DEFAULT_SHOW_DELAY); + await clock.advanceTimersByTimeAsync(DEFAULT_SHOW_DELAY); await showComplete(); expect(tooltip.open).to.be.true; simulatePointerLeave(first); - await clock.tickAsync(endTick(DEFAULT_HIDE_DELAY)); + await clock.advanceTimersByTimeAsync(endTick(DEFAULT_HIDE_DELAY)); await hideComplete(); expect(tooltip.open).to.be.false; // By providing an Element simulatePointerEnter(second); - await clock.tickAsync(DEFAULT_SHOW_DELAY); + await clock.advanceTimersByTimeAsync(DEFAULT_SHOW_DELAY); await showComplete(); expect(tooltip.open).to.be.false; @@ -201,7 +210,7 @@ describe('Tooltip', () => { await elementUpdated(tooltip); simulatePointerEnter(second); - await clock.tickAsync(DEFAULT_SHOW_DELAY); + await clock.advanceTimersByTimeAsync(DEFAULT_SHOW_DELAY); await showComplete(); expect(tooltip.open).to.be.true; }); @@ -378,12 +387,12 @@ describe('Tooltip', () => { await elementUpdated(tooltip); simulatePointerEnter(anchor); - await clock.tickAsync(DEFAULT_SHOW_DELAY); + await clock.advanceTimersByTimeAsync(DEFAULT_SHOW_DELAY); await showComplete(); expect(tooltip.open).to.be.true; simulatePointerLeave(anchor); - await clock.tickAsync(endTick(DEFAULT_HIDE_DELAY)); + await clock.advanceTimersByTimeAsync(endTick(DEFAULT_HIDE_DELAY)); await hideComplete(); expect(tooltip.open).to.be.true; }); @@ -395,10 +404,10 @@ describe('Tooltip', () => { expect(tooltip.open).to.be.true; - const closeIcon = tooltip.shadowRoot!.querySelector('igc-icon')!; + const closeIcon = tooltip.renderRoot.querySelector('igc-icon')!; simulateClick(closeIcon); - await clock.tickAsync(endTick(DEFAULT_HIDE_DELAY)); + await clock.advanceTimersByTimeAsync(endTick(DEFAULT_HIDE_DELAY)); await hideComplete(); expect(tooltip.open).to.be.false; }); @@ -410,7 +419,7 @@ describe('Tooltip', () => { expect(tooltip.open).to.be.true; - document.documentElement.dispatchEvent( + tooltip.ownerDocument.body.dispatchEvent( new KeyboardEvent('keydown', { key: 'Escape', bubbles: true, @@ -418,7 +427,7 @@ describe('Tooltip', () => { }) ); - await clock.tickAsync(endTick(DEFAULT_HIDE_DELAY)); + await clock.advanceTimersByTimeAsync(endTick(DEFAULT_HIDE_DELAY)); await hideComplete(); expect(tooltip.open).to.be.false; }); @@ -426,14 +435,14 @@ describe('Tooltip', () => { describe('Methods` Tests', () => { beforeEach(async () => { - clock = useFakeTimers({ toFake: ['setTimeout'] }); + clock = vi.useFakeTimers({ toFake: ['setTimeout'] }); const container = await fixture(createTooltipWithTarget()); tooltip = container.querySelector(IgcTooltipComponent.tagName)!; anchor = container.querySelector('button')!; }); afterEach(() => { - clock.restore(); + clock.useRealTimers(); }); it('calls `show` and `hide` methods successfully and returns proper values', async () => { @@ -517,18 +526,18 @@ describe('Tooltip', () => { // the transient anchor should not reopen the tooltip once its hidden simulatePointerEnter(transientAnchor); - await clock.tickAsync(DEFAULT_SHOW_DELAY); + await clock.advanceTimersByTimeAsync(DEFAULT_SHOW_DELAY); await showComplete(); expect(tooltip.open).to.be.false; simulatePointerEnter(defaultAnchor); - await clock.tickAsync(DEFAULT_SHOW_DELAY); + await clock.advanceTimersByTimeAsync(DEFAULT_SHOW_DELAY); await showComplete(); expect(tooltip.open).to.be.true; }); it('should be able to pass and IDREF to `show` method', async () => { - const eventSpy = spy(tooltip, 'emitEvent'); + const spy = vi.spyOn(tooltip, 'emitEvent'); const [_, transientAnchor] = Array.from( tooltip.parentElement!.querySelectorAll('button') @@ -539,11 +548,11 @@ describe('Tooltip', () => { const result = await tooltip.show('custom-target'); expect(result).to.be.true; expect(tooltip.open).to.be.true; - expect(eventSpy.callCount).to.equal(0); + expect(spy).to.toHaveBeenCalledTimes(0); }); it('should correctly handle open state and events between default and transient anchors', async () => { - const eventSpy = spy(tooltip, 'emitEvent'); + const spy = vi.spyOn(tooltip, 'emitEvent'); const [defaultAnchor, transientAnchor] = Array.from( tooltip.parentElement!.querySelectorAll('button') @@ -552,30 +561,30 @@ describe('Tooltip', () => { const result = await tooltip.show(transientAnchor); expect(result).to.be.true; expect(tooltip.open).to.be.true; - expect(eventSpy.callCount).to.equal(0); + expect(spy).to.toHaveBeenCalledTimes(0); simulatePointerEnter(defaultAnchor); // Trigger on the initial default anchor. Tooltip must be hidden. expect(tooltip.open).to.be.false; - await clock.tickAsync(DEFAULT_SHOW_DELAY); + await clock.advanceTimersByTimeAsync(DEFAULT_SHOW_DELAY); await showComplete(); expect(tooltip.open).to.be.true; - expect(eventSpy).calledWith('igcOpening', { cancelable: true }); - expect(eventSpy).calledWith('igcOpened', { cancelable: false }); + expect(spy).toHaveBeenCalledWith('igcOpening', { cancelable: true }); + expect(spy).toHaveBeenCalledWith('igcOpened', { cancelable: false }); }); }); describe('Behaviors', () => { beforeEach(async () => { - clock = useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] }); + clock = vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] }); const container = await fixture(createTooltipWithTarget()); anchor = container.querySelector('button')!; tooltip = container.querySelector(IgcTooltipComponent.tagName)!; }); afterEach(() => { - clock.restore(); + clock.useRealTimers(); }); it('default triggers', async () => { @@ -583,12 +592,12 @@ describe('Tooltip', () => { expect(tooltip.hideTriggers).to.equal('pointerleave,click'); simulatePointerEnter(anchor); - await clock.tickAsync(DEFAULT_SHOW_DELAY); + await clock.advanceTimersByTimeAsync(DEFAULT_SHOW_DELAY); await showComplete(); expect(tooltip.open).to.be.true; simulatePointerLeave(anchor); - await clock.tickAsync(endTick(DEFAULT_HIDE_DELAY)); + await clock.advanceTimersByTimeAsync(endTick(DEFAULT_HIDE_DELAY)); await hideComplete(); expect(tooltip.open).to.be.false; }); @@ -598,22 +607,22 @@ describe('Tooltip', () => { tooltip.hideTriggers = 'blur, click'; simulateFocus(anchor); - await clock.tickAsync(DEFAULT_SHOW_DELAY); + await clock.advanceTimersByTimeAsync(DEFAULT_SHOW_DELAY); await showComplete(); expect(tooltip.open).to.be.true; simulateBlur(anchor); - await clock.tickAsync(endTick(DEFAULT_HIDE_DELAY)); + await clock.advanceTimersByTimeAsync(endTick(DEFAULT_HIDE_DELAY)); await hideComplete(); expect(tooltip.open).to.be.false; simulatePointerEnter(anchor); - await clock.tickAsync(DEFAULT_SHOW_DELAY); + await clock.advanceTimersByTimeAsync(DEFAULT_SHOW_DELAY); await showComplete(); expect(tooltip.open).to.be.true; simulateClick(anchor); - await clock.tickAsync(endTick(DEFAULT_HIDE_DELAY)); + await clock.advanceTimersByTimeAsync(endTick(DEFAULT_HIDE_DELAY)); await hideComplete(); expect(tooltip.open).to.be.false; }); @@ -635,29 +644,29 @@ describe('Tooltip', () => { anchor = container.querySelector('button')!; simulateFocus(anchor); - await clock.tickAsync(DEFAULT_SHOW_DELAY); + await clock.advanceTimersByTimeAsync(DEFAULT_SHOW_DELAY); await showComplete(); expect(tooltip.open).to.be.true; simulateBlur(anchor); - await clock.tickAsync(endTick(DEFAULT_HIDE_DELAY)); + await clock.advanceTimersByTimeAsync(endTick(DEFAULT_HIDE_DELAY)); await hideComplete(); expect(tooltip.open).to.be.false; simulateClick(anchor); - await clock.tickAsync(DEFAULT_SHOW_DELAY); + await clock.advanceTimersByTimeAsync(DEFAULT_SHOW_DELAY); await showComplete(); expect(tooltip.open).to.be.true; simulateBlur(anchor); - await clock.tickAsync(endTick(DEFAULT_HIDE_DELAY)); + await clock.advanceTimersByTimeAsync(endTick(DEFAULT_HIDE_DELAY)); await hideComplete(); expect(tooltip.open).to.be.false; }); it('pointerenter over tooltip prevents hiding and pointerleave triggers hiding', async () => { simulatePointerEnter(anchor); - await clock.tickAsync(DEFAULT_SHOW_DELAY); + await clock.advanceTimersByTimeAsync(DEFAULT_SHOW_DELAY); await showComplete(); expect(tooltip.open).to.be.true; @@ -671,7 +680,7 @@ describe('Tooltip', () => { // Move cursor outside the tooltip simulatePointerLeave(tooltip); - await clock.tickAsync(endTick(DEFAULT_HIDE_DELAY)); + await clock.advanceTimersByTimeAsync(endTick(DEFAULT_HIDE_DELAY)); await hideComplete(); expect(tooltip.open).to.be.false; }); @@ -679,82 +688,84 @@ describe('Tooltip', () => { it('should show/hide the tooltip based on `showDelay` and `hideDelay`', async () => { tooltip.showDelay = tooltip.hideDelay = 400; simulatePointerEnter(anchor); - await clock.tickAsync(399); + await clock.advanceTimersByTimeAsync(399); expect(tooltip.open).to.be.false; - await clock.tickAsync(1); + await clock.advanceTimersByTimeAsync(1); await showComplete(tooltip); expect(tooltip.open).to.be.true; simulatePointerLeave(anchor); - await clock.tickAsync(endTick(399)); + await clock.advanceTimersByTimeAsync(endTick(399)); expect(tooltip.open).to.be.true; - await clock.tickAsync(1); + await clock.advanceTimersByTimeAsync(1); await hideComplete(tooltip); expect(tooltip.open).to.be.false; }); it('prevents tooltip from showing when clicking the target - #1828', async () => { - const eventSpy = spy(tooltip, 'emitEvent'); + const spy = vi.spyOn(tooltip, 'emitEvent'); tooltip.showTriggers = 'pointerenter'; tooltip.hideTriggers = 'pointerleave'; simulatePointerEnter(anchor); - await clock.tickAsync(199); + await clock.advanceTimersByTimeAsync(199); expect(tooltip.open).to.be.false; // Click on the target before the tooltip is shown simulateClick(anchor); - await clock.tickAsync(1); + await clock.advanceTimersByTimeAsync(1); await showComplete(tooltip); expect(tooltip.open).to.be.false; - expect(eventSpy.callCount).to.equal(1); + expect(spy).toHaveBeenCalledTimes(1); - eventSpy.resetHistory(); + spy.mockClear(); // Does not prevent showing when showTriggers includes 'click' tooltip.showTriggers = 'click'; simulateClick(anchor); - await clock.tickAsync(DEFAULT_SHOW_DELAY); + await clock.advanceTimersByTimeAsync(DEFAULT_SHOW_DELAY); await showComplete(tooltip); expect(tooltip.open).to.be.true; simulatePointerLeave(anchor); - await clock.tickAsync(endTick(DEFAULT_HIDE_DELAY)); + await clock.advanceTimersByTimeAsync(endTick(DEFAULT_HIDE_DELAY)); await hideComplete(tooltip); expect(tooltip.open).to.be.false; - expect(eventSpy.callCount).to.equal(4); + expect(spy).toHaveBeenCalledTimes(4); }); }); describe('Events', () => { - let eventSpy: ReturnType; + let spy: MockInstance; beforeEach(async () => { - clock = useFakeTimers({ toFake: ['setTimeout'] }); + clock = vi.useFakeTimers({ toFake: ['setTimeout'] }); const container = await fixture(createTooltipWithTarget()); tooltip = container.querySelector(IgcTooltipComponent.tagName)!; anchor = container.querySelector('button')!; - eventSpy = spy(tooltip, 'emitEvent'); + spy = vi.spyOn(tooltip, 'emitEvent'); }); afterEach(() => { - clock.restore(); + clock.useRealTimers(); }); const verifyStateAndEventSequence = ( state: { open: boolean } = { open: false } ) => { expect(tooltip.open).to.equal(state.open); - expect(eventSpy.callCount).to.equal(2); - expect(eventSpy.firstCall).calledWith( + expect(spy).toHaveBeenCalledTimes(2); + expect(spy).toHaveBeenNthCalledWith( + 1, state.open ? 'igcOpening' : 'igcClosing', { cancelable: true } ); - expect(eventSpy.secondCall).calledWith( + expect(spy).toHaveBeenNthCalledWith( + 2, state.open ? 'igcOpened' : 'igcClosed', { cancelable: false } ); @@ -762,14 +773,14 @@ describe('Tooltip', () => { it('events are correctly emitted on user interaction', async () => { simulatePointerEnter(anchor); - await clock.tickAsync(DEFAULT_SHOW_DELAY); + await clock.advanceTimersByTimeAsync(DEFAULT_SHOW_DELAY); await showComplete(tooltip); verifyStateAndEventSequence({ open: true }); - eventSpy.resetHistory(); + spy.mockClear(); simulatePointerLeave(anchor); - await clock.tickAsync(endTick(DEFAULT_HIDE_DELAY)); + await clock.advanceTimersByTimeAsync(endTick(DEFAULT_HIDE_DELAY)); await hideComplete(tooltip); verifyStateAndEventSequence({ open: false }); }); @@ -780,13 +791,15 @@ describe('Tooltip', () => { }); simulatePointerEnter(anchor); - await clock.tickAsync(DEFAULT_SHOW_DELAY); + await clock.advanceTimersByTimeAsync(DEFAULT_SHOW_DELAY); await showComplete(tooltip); expect(tooltip.open).to.be.false; - expect(eventSpy).calledOnceWith('igcOpening', { cancelable: true }); + expect(spy).toHaveBeenCalledExactlyOnceWith('igcOpening', { + cancelable: true, + }); - eventSpy.resetHistory(); + spy.mockClear(); tooltip.open = true; await elementUpdated(tooltip); @@ -796,21 +809,23 @@ describe('Tooltip', () => { }); simulatePointerLeave(anchor); - await clock.tickAsync(endTick(DEFAULT_HIDE_DELAY)); + await clock.advanceTimersByTimeAsync(endTick(DEFAULT_HIDE_DELAY)); await hideComplete(tooltip); expect(tooltip.open).to.be.true; - expect(eventSpy).calledOnceWith('igcClosing', { cancelable: true }); + expect(spy).toHaveBeenCalledExactlyOnceWith('igcClosing', { + cancelable: true, + }); }); it('fires `igcClosed` when tooltip is hidden via Escape key', async () => { simulatePointerEnter(anchor); - await clock.tickAsync(DEFAULT_SHOW_DELAY); + await clock.advanceTimersByTimeAsync(DEFAULT_SHOW_DELAY); await showComplete(tooltip); - eventSpy.resetHistory(); + spy.mockClear(); - document.documentElement.dispatchEvent( + tooltip.ownerDocument.body.dispatchEvent( new KeyboardEvent('keydown', { key: 'Escape', bubbles: true, @@ -818,22 +833,22 @@ describe('Tooltip', () => { }) ); - await clock.tickAsync(endTick(DEFAULT_HIDE_DELAY)); + await clock.advanceTimersByTimeAsync(endTick(DEFAULT_HIDE_DELAY)); await hideComplete(tooltip); expect(tooltip.open).to.be.false; - expect(eventSpy.callCount).to.equal(1); - expect(eventSpy.firstCall).calledWith('igcClosed', { cancelable: false }); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith('igcClosed', { cancelable: false }); }); }); describe('Keyboard interactions', () => { beforeEach(async () => { - clock = useFakeTimers({ toFake: ['setTimeout'] }); + clock = vi.useFakeTimers({ toFake: ['setTimeout'] }); }); afterEach(() => { - clock.restore(); + clock.useRealTimers(); }); it('pressing Escape in an active page hides the tooltip', async () => { @@ -844,19 +859,19 @@ describe('Tooltip', () => { ); simulatePointerEnter(anchor); - await clock.tickAsync(DEFAULT_SHOW_DELAY); + await clock.advanceTimersByTimeAsync(DEFAULT_SHOW_DELAY); await showComplete(tooltip); expect(tooltip.open).to.be.true; - document.documentElement.dispatchEvent( + tooltip.ownerDocument.body.dispatchEvent( new KeyboardEvent('keydown', { key: 'Escape', bubbles: true, composed: true, }) ); - await clock.tickAsync(endTick(DEFAULT_HIDE_DELAY)); + await clock.advanceTimersByTimeAsync(endTick(DEFAULT_HIDE_DELAY)); await hideComplete(tooltip); expect(tooltip.open).to.be.false; @@ -874,7 +889,7 @@ describe('Tooltip', () => { expect(first.open).to.be.true; expect(last.open).to.be.true; - document.documentElement.dispatchEvent( + tooltip.ownerDocument.body.dispatchEvent( new KeyboardEvent('keydown', { key: 'Escape', bubbles: true, @@ -882,14 +897,14 @@ describe('Tooltip', () => { }) ); - await clock.tickAsync(endTick(DEFAULT_HIDE_DELAY)); + await clock.advanceTimersByTimeAsync(endTick(DEFAULT_HIDE_DELAY)); await hideComplete(first); await hideComplete(last); expect(last.open).to.be.false; expect(first.open).to.be.true; - document.documentElement.dispatchEvent( + tooltip.ownerDocument.body.dispatchEvent( new KeyboardEvent('keydown', { key: 'Escape', bubbles: true, @@ -897,7 +912,7 @@ describe('Tooltip', () => { }) ); - await clock.tickAsync(endTick(DEFAULT_HIDE_DELAY)); + await clock.advanceTimersByTimeAsync(endTick(DEFAULT_HIDE_DELAY)); await hideComplete(first); await hideComplete(last); diff --git a/src/components/tooltip/tooltip.ts b/src/components/tooltip/tooltip.ts index 8fd732ccf..8b0406250 100644 --- a/src/components/tooltip/tooltip.ts +++ b/src/components/tooltip/tooltip.ts @@ -97,7 +97,7 @@ export default class IgcTooltipComponent extends EventEmitterMixin< easing: EaseOut.Sine, }); - private _timeoutId?: number; + private _timeoutId?: ReturnType; private _autoHideDelay = 180; private _showDelay = 200; private _hideDelay = 300; diff --git a/src/components/tree/tree-navigation.spec.ts b/src/components/tree/tree-navigation.spec.ts index 141011fc5..8632546fa 100644 --- a/src/components/tree/tree-navigation.spec.ts +++ b/src/components/tree/tree-navigation.spec.ts @@ -1,7 +1,15 @@ -import { elementUpdated, expect, waitUntil } from '@open-wc/testing'; -import { spy } from 'sinon'; - -import { defineComponents } from '../../index.js'; +import { + beforeAll, + beforeEach, + describe, + expect, + it, + type MockInstance, + vi, +} from 'vitest'; +import { defineComponents } from '../common/definitions/defineComponents.js'; +import { elementUpdated, waitUntil } from '../common/helpers.spec.js'; +import { eventMatch } from '../common/utils.spec.js'; import type { TreeSelectionEventInit } from './tree.common.js'; import IgcTreeComponent from './tree.js'; import type { IgcTreeNavigationService } from './tree.navigation.js'; @@ -9,20 +17,20 @@ import IgcTreeItemComponent from './tree-item.js'; import { navigationTree, SLOTS, TreeTestFunctions } from './tree-utils.spec.js'; describe('Tree Navigation', () => { - before(() => { + beforeAll(() => { defineComponents(IgcTreeItemComponent, IgcTreeComponent); }); let tree: IgcTreeComponent; let treeNavService: IgcTreeNavigationService; let topLevelItems: IgcTreeItemComponent[]; - let eventSpy: any; + let spy: MockInstance; beforeEach(async () => { tree = await TreeTestFunctions.createTreeElement(navigationTree); treeNavService = tree.navService; topLevelItems = tree.items.filter((i) => i.level === 0); - eventSpy = spy(tree, 'emitEvent'); + spy = vi.spyOn(tree, 'emitEvent'); }); it('Should focus and activate the first tree item on Home key press and the last tree item on End key press', async () => { @@ -38,10 +46,10 @@ describe('Tree Navigation', () => { expect(treeNavService.activeItem).to.equal(tree.items[0]); expect(treeNavService.focusedItem).to.equal(tree.items[0]); - expect(eventSpy).calledOnceWithExactly('igcActiveItem', { + expect(spy).toHaveBeenCalledExactlyOnceWith('igcActiveItem', { detail: tree.items[0], }); - eventSpy.resetHistory(); + spy.mockClear(); treeNavService.focusedItem?.dispatchEvent( new KeyboardEvent('keydown', { @@ -59,10 +67,10 @@ describe('Tree Navigation', () => { expect(treeNavService.activeItem).to.equal(lastItem); expect(treeNavService.focusedItem).to.equal(lastItem); - expect(eventSpy).calledOnceWithExactly('igcActiveItem', { + expect(spy).toHaveBeenCalledExactlyOnceWith('igcActiveItem', { detail: lastItem, }); - eventSpy.resetHistory(); + spy.mockClear(); }); it('Should not navigate when a tree item has no parent and item is collapsed on Arrow Left key press', async () => { @@ -86,7 +94,7 @@ describe('Tree Navigation', () => { expect(topLevelItems[0].expanded).to.be.false; expect(treeNavService.activeItem).to.equal(topLevelItems[0]); expect(treeNavService.focusedItem).to.equal(topLevelItems[0]); - expect(eventSpy.called).to.be.false; + expect(spy).toHaveBeenCalledTimes(0); }); it('Should navigate to the parent item of a tree item w/ expanded === true on Arrow Left key press, moving focus and active', async () => { @@ -106,10 +114,10 @@ describe('Tree Navigation', () => { expect(treeNavService.activeItem).to.equal(item2); expect(treeNavService.focusedItem).to.equal(item2); - expect(eventSpy).calledOnceWithExactly('igcActiveItem', { + expect(spy).toHaveBeenCalledExactlyOnceWith('igcActiveItem', { detail: item2, }); - eventSpy.resetHistory(); + spy.mockClear(); // Should collapse expanded tree items on Arrow Left key press item2.dispatchEvent( @@ -119,7 +127,7 @@ describe('Tree Navigation', () => { cancelable: true, }) ); - await waitUntil(() => eventSpy.calledWith('igcItemCollapsed')); + await waitUntil(() => eventMatch(spy, 'igcItemCollapsed')); expect(item2.active).to.be.true; expect(item2.expanded).to.be.false; @@ -128,13 +136,13 @@ describe('Tree Navigation', () => { detail: item2, cancelable: true, }; - expect(eventSpy.callCount).to.equal(2); - expect(eventSpy.firstCall).calledWith('igcItemCollapsing', collapsingArgs); - expect(eventSpy.secondCall).calledWith('igcItemCollapsed', { + expect(spy).toHaveBeenCalledTimes(2); + expect(spy).toHaveBeenNthCalledWith(1, 'igcItemCollapsing', collapsingArgs); + expect(spy).toHaveBeenNthCalledWith(2, 'igcItemCollapsed', { detail: item2, }); - eventSpy.resetHistory(); + spy.mockClear(); }); it('Should not navigate when a tree item has no children on Arrow Right key press', async () => { @@ -150,7 +158,7 @@ describe('Tree Navigation', () => { expect(item4.active).to.be.true; expect(treeNavService.activeItem).to.equal(item4); expect(treeNavService.focusedItem).to.equal(item4); - expect(eventSpy.called).to.be.false; + expect(spy).toHaveBeenCalledTimes(0); }); it('Should navigate to the first child of an expanded on Arrow Right key press, moving focus and active', async () => { @@ -167,10 +175,10 @@ describe('Tree Navigation', () => { expect(treeNavService.activeItem).to.equal(item2Children[0]); expect(treeNavService.focusedItem).to.equal(item2Children[0]); - expect(eventSpy).calledOnceWithExactly('igcActiveItem', { + expect(spy).toHaveBeenCalledExactlyOnceWith('igcActiveItem', { detail: item2Children[0], }); - eventSpy.resetHistory(); + spy.mockClear(); const item21Children = item2Children[0].getChildren(); @@ -187,7 +195,7 @@ describe('Tree Navigation', () => { expect(treeNavService.activeItem).to.equal(item21Children[0]); expect(treeNavService.focusedItem).to.equal(item21Children[0]); - expect(eventSpy).calledOnceWithExactly('igcActiveItem', { + expect(spy).toHaveBeenCalledExactlyOnceWith('igcActiveItem', { detail: item21Children[0], }); }); @@ -202,7 +210,7 @@ describe('Tree Navigation', () => { await elementUpdated(tree); TreeTestFunctions.setFocusAndTriggerKeydown(item1, tree, 'ArrowRight'); - await waitUntil(() => eventSpy.calledWith('igcItemExpanded')); + await waitUntil(() => eventMatch(spy, 'igcItemExpanded')); expect(item1.expanded).to.be.true; @@ -210,9 +218,9 @@ describe('Tree Navigation', () => { detail: item1, cancelable: true, }; - expect(eventSpy.callCount).to.equal(2); - expect(eventSpy.firstCall).calledWith('igcItemExpanding', expandingArgs); - expect(eventSpy.secondCall).calledWith('igcItemExpanded', { + expect(spy).toHaveBeenCalledTimes(2); + expect(spy).toHaveBeenNthCalledWith(1, 'igcItemExpanding', expandingArgs); + expect(spy).toHaveBeenNthCalledWith(2, 'igcItemExpanded', { detail: item1, }); }); @@ -236,10 +244,10 @@ describe('Tree Navigation', () => { expect(treeNavService.activeItem).to.equal(item3Hierarchy[i + 1]); expect(treeNavService.focusedItem).to.equal(item3Hierarchy[i + 1]); - expect(eventSpy).calledOnceWithExactly('igcActiveItem', { + expect(spy).toHaveBeenCalledExactlyOnceWith('igcActiveItem', { detail: item3Hierarchy[i + 1], }); - eventSpy.resetHistory(); + spy.mockClear(); } // Arrow Down on last tree item @@ -251,7 +259,7 @@ describe('Tree Navigation', () => { expect(treeNavService.activeItem).to.equal(item4); expect(treeNavService.focusedItem).to.equal(item4); - expect(eventSpy.called).to.be.false; + expect(spy).toHaveBeenCalledTimes(0); }); it('Should only focus the next visible tree item on Arrow Down + Ctrl key press', async () => { @@ -276,7 +284,7 @@ describe('Tree Navigation', () => { expect(item3Hierarchy[i + 1].active).to.be.false; expect(treeNavService.activeItem).to.equal(item3Hierarchy[0]); expect(treeNavService.focusedItem).to.equal(item3Hierarchy[i + 1]); - expect(eventSpy.called).to.be.false; + expect(spy).toHaveBeenCalledTimes(0); } }); @@ -298,10 +306,10 @@ describe('Tree Navigation', () => { expect(item3Hierarchy[i - 1].active).to.be.true; expect(treeNavService.activeItem).to.equal(item3Hierarchy[i - 1]); expect(treeNavService.focusedItem).to.equal(item3Hierarchy[i - 1]); - expect(eventSpy).calledOnceWithExactly('igcActiveItem', { + expect(spy).toHaveBeenCalledExactlyOnceWith('igcActiveItem', { detail: item3Hierarchy[i - 1], }); - eventSpy.resetHistory(); + spy.mockClear(); } // Arrow Up on first tree item @@ -313,7 +321,7 @@ describe('Tree Navigation', () => { expect(treeNavService.activeItem).to.equal(item1); expect(treeNavService.focusedItem).to.equal(item1); - expect(eventSpy.called).to.be.false; + expect(spy).toHaveBeenCalledTimes(0); }); it('Should only focus the previous visible tree item on Arrow Up + Ctrl key press', async () => { @@ -338,7 +346,7 @@ describe('Tree Navigation', () => { item3Hierarchy[item3Hierarchy.length - 1] ); expect(treeNavService.focusedItem).to.equal(item3Hierarchy[i - 1]); - expect(eventSpy.called).to.be.false; + expect(spy).toHaveBeenCalledTimes(0); } }); @@ -351,7 +359,7 @@ describe('Tree Navigation', () => { item3.active = true; await elementUpdated(tree); TreeTestFunctions.setFocusAndTriggerKeydown(item3, tree, '*'); - await waitUntil(() => eventSpy.calledWith('igcItemExpanded')); + await waitUntil(() => eventMatch(spy, 'igcItemExpanded')); expect(topLevelItems[3].expanded).to.be.false; // Item4 does not have children => not expanded topLevelItems.pop(); @@ -386,13 +394,13 @@ describe('Tree Navigation', () => { cancelable: true, }; // Item2 is already expanded, and Item4 has no children => no expanding events emitted for them - expect(eventSpy.callCount).to.equal(4); - expect(eventSpy.firstCall).calledWith('igcItemExpanding', expandingArgs1); - expect(eventSpy.secondCall).calledWith('igcItemExpanding', expandingArgs2); - expect(eventSpy.thirdCall).calledWith('igcItemExpanded', { + expect(spy).toHaveBeenCalledTimes(4); + expect(spy).toHaveBeenNthCalledWith(1, 'igcItemExpanding', expandingArgs1); + expect(spy).toHaveBeenNthCalledWith(2, 'igcItemExpanding', expandingArgs2); + expect(spy).toHaveBeenNthCalledWith(3, 'igcItemExpanded', { detail: topLevelItems[0], }); - expect(eventSpy.lastCall).calledWith('igcItemExpanded', { + expect(spy).toHaveBeenNthCalledWith(4, 'igcItemExpanded', { detail: item3, }); }); @@ -416,7 +424,7 @@ describe('Tree Navigation', () => { expect(treeNavService.activeItem).to.equal(topLevelItems[0]); expect(treeNavService.focusedItem).to.equal(topLevelItems[0]); - expect(eventSpy).calledOnceWithExactly('igcActiveItem', { + expect(spy).toHaveBeenCalledExactlyOnceWith('igcActiveItem', { detail: topLevelItems[0], }); }); @@ -443,12 +451,12 @@ describe('Tree Navigation', () => { }, cancelable: true, }; - expect(eventSpy.callCount).to.equal(2); - expect(eventSpy.firstCall).calledWith('igcActiveItem', { + expect(spy).toHaveBeenCalledTimes(2); + expect(spy).toHaveBeenNthCalledWith(1, 'igcActiveItem', { detail: topLevelItems[0], }); - expect(eventSpy.secondCall).calledWith('igcSelection', args); - eventSpy.resetHistory(); + expect(spy).toHaveBeenNthCalledWith(2, 'igcSelection', args); + spy.mockClear(); const expectedSelection: IgcTreeItemComponent[] = []; @@ -472,8 +480,8 @@ describe('Tree Navigation', () => { cancelable: true, }; - expect(eventSpy.callCount).to.equal(1); - expect(eventSpy.firstCall).calledWith('igcSelection', args); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenNthCalledWith(1, 'igcSelection', args); }); it("Should only activate the tree item when tree.selection === 'None' on Space key press and also select it when tree.selection !== 'None'", async () => { @@ -494,11 +502,11 @@ describe('Tree Navigation', () => { expect(treeNavService.activeItem).to.equal(topLevelItems[0]); expect(treeNavService.focusedItem).to.equal(topLevelItems[0]); - expect(eventSpy).calledOnceWithExactly('igcActiveItem', { + expect(spy).toHaveBeenCalledExactlyOnceWith('igcActiveItem', { detail: topLevelItems[0], }); - eventSpy.resetHistory(); + spy.mockClear(); tree.selection = 'multiple'; await elementUpdated(tree); @@ -516,11 +524,11 @@ describe('Tree Navigation', () => { }, cancelable: true, }; - expect(eventSpy.callCount).to.equal(2); - expect(eventSpy.firstCall).calledWith('igcActiveItem', { + expect(spy).toHaveBeenCalledTimes(2); + expect(spy).toHaveBeenNthCalledWith(1, 'igcActiveItem', { detail: topLevelItems[1], }); - expect(eventSpy.secondCall).calledWith('igcSelection', args); + expect(spy).toHaveBeenNthCalledWith(2, 'igcSelection', args); }); it("Should select item range when tree.selection !== 'None' on Space + Shift keys press, moving active", async () => { @@ -549,12 +557,12 @@ describe('Tree Navigation', () => { }, cancelable: true, }; - expect(eventSpy.callCount).to.equal(2); - expect(eventSpy.firstCall).calledWith('igcActiveItem', { + expect(spy).toHaveBeenCalledTimes(2); + expect(spy).toHaveBeenNthCalledWith(1, 'igcActiveItem', { detail: topLevelItems[0], }); - expect(eventSpy.secondCall).calledWith('igcSelection', args); - eventSpy.resetHistory(); + expect(spy).toHaveBeenNthCalledWith(2, 'igcSelection', args); + spy.mockClear(); const expectedSelection = [ topLevelItems[0], @@ -582,11 +590,11 @@ describe('Tree Navigation', () => { cancelable: true, }; - expect(eventSpy.callCount).to.equal(2); - expect(eventSpy.firstCall).calledWith('igcActiveItem', { + expect(spy).toHaveBeenCalledTimes(2); + expect(spy).toHaveBeenNthCalledWith(1, 'igcActiveItem', { detail: topLevelItems[1], }); - expect(eventSpy.secondCall).calledWith('igcSelection', args); + expect(spy).toHaveBeenNthCalledWith(2, 'igcSelection', args); }); it('Should assign proper tabIndex for tree item labels containing tabbable elements on focus', async () => { diff --git a/src/components/tree/tree-selection.spec.ts b/src/components/tree/tree-selection.spec.ts index ea178dcc9..41accc97b 100644 --- a/src/components/tree/tree-selection.spec.ts +++ b/src/components/tree/tree-selection.spec.ts @@ -1,7 +1,15 @@ -import { elementUpdated, expect } from '@open-wc/testing'; -import { spy } from 'sinon'; - -import { defineComponents, type IgcCheckboxComponent } from '../../index.js'; +import { + beforeAll, + beforeEach, + describe, + expect, + it, + type MockInstance, + vi, +} from 'vitest'; +import type IgcCheckboxComponent from '../checkbox/checkbox.js'; +import { defineComponents } from '../common/definitions/defineComponents.js'; +import { elementUpdated } from '../common/helpers.spec.js'; import type { TreeSelectionEventInit } from './tree.common.js'; import IgcTreeComponent from './tree.js'; import type { IgcTreeSelectionService } from './tree.selection.js'; @@ -15,7 +23,7 @@ import { } from './tree-utils.spec.js'; describe('Tree Selection', () => { - before(() => { + beforeAll(() => { defineComponents(IgcTreeItemComponent, IgcTreeComponent); }); @@ -176,14 +184,14 @@ describe('Tree Selection', () => { describe('Multiple', () => { let topLevelItems: IgcTreeItemComponent[]; - let eventSpy: any; + let spy: MockInstance; beforeEach(async () => { tree = await TreeTestFunctions.createTreeElement(selectedItemsTree); treeSelectionService = tree.selectionService; initialSelection = tree.items.filter((item) => item.selected === true); topLevelItems = tree.items.filter((i) => i.level === 0); - eventSpy = spy(tree, 'emitEvent'); + spy = vi.spyOn(tree, 'emitEvent'); }); it('Should be able to set item.selected correctly', async () => { @@ -224,8 +232,8 @@ describe('Tree Selection', () => { }, cancelable: true, }; - expect(eventSpy).calledWith('igcSelection', args); - eventSpy.resetHistory(); + expect(spy).toHaveBeenCalledWith('igcSelection', args); + spy.mockClear(); const item12 = topLevelItems[0].getChildren()[1]; TreeTestFunctions.verifyItemSelection(item12, false); @@ -247,8 +255,8 @@ describe('Tree Selection', () => { }, cancelable: true, }; - expect(eventSpy).calledOnceWith('igcSelection', args); - eventSpy.resetHistory(); + expect(spy).toHaveBeenCalledExactlyOnceWith('igcSelection', args); + spy.mockClear(); }); it('Should be able to prevent the igcSelection event.', async () => { @@ -275,7 +283,7 @@ describe('Tree Selection', () => { cancelable: true, }; - expect(eventSpy).calledOnceWith('igcSelection', args); + expect(spy).toHaveBeenCalledExactlyOnceWith('igcSelection', args); TreeTestFunctions.verifyItemSelection(item12, false); tree.items.forEach((item) => { @@ -315,8 +323,8 @@ describe('Tree Selection', () => { await elementUpdated(tree); TreeTestFunctions.verifyItemSelection(topLevelItems[0], true); - expect(eventSpy).calledOnceWith('igcSelection', args); - eventSpy.resetHistory(); + expect(spy).toHaveBeenCalledExactlyOnceWith('igcSelection', args); + spy.mockClear(); const endOfSelectionRage = topLevelItems[1].getChildren()[1]; @@ -344,8 +352,8 @@ describe('Tree Selection', () => { } }); - expect(eventSpy).calledOnceWith('igcSelection', args); - eventSpy.resetHistory(); + expect(spy).toHaveBeenCalledExactlyOnceWith('igcSelection', args); + spy.mockClear(); // Select the same range and verify no event is emitted selectionPart = expectedSelection[0].shadowRoot!.querySelector( @@ -355,7 +363,7 @@ describe('Tree Selection', () => { cb?.dispatchEvent(new MouseEvent('click', { shiftKey: true })); await elementUpdated(tree); - expect(eventSpy).not.to.be.called; + expect(spy).not.toHaveBeenCalled(); }); it('Should select a single item when there are no selected items and selction is performed with Shift + click', async () => { @@ -377,8 +385,8 @@ describe('Tree Selection', () => { await elementUpdated(tree); TreeTestFunctions.verifyItemSelection(topLevelItems[2], true); - expect(eventSpy).calledOnceWith('igcSelection', args); - eventSpy.resetHistory(); + expect(spy).toHaveBeenCalledExactlyOnceWith('igcSelection', args); + spy.mockClear(); }); }); @@ -689,7 +697,7 @@ describe('Tree Selection', () => { }); it('Should be able to prevent the igcSelection event', async () => { - const eventSpy = spy(tree, 'emitEvent'); + const spy = vi.spyOn(tree, 'emitEvent'); const item2Children = topLevelItems[1].getChildren(); const item211 = item2Children[0].getChildren()[0]; @@ -714,7 +722,7 @@ describe('Tree Selection', () => { cancelable: true, }; - expect(eventSpy).calledOnceWith('igcSelection', args); + expect(spy).toHaveBeenCalledExactlyOnceWith('igcSelection', args); tree.items.forEach((item) => { if (initialSelection.indexOf(item) === -1) { diff --git a/src/components/tree/tree-utils.spec.ts b/src/components/tree/tree-utils.spec.ts index 37000a929..3d312e169 100644 --- a/src/components/tree/tree-utils.spec.ts +++ b/src/components/tree/tree-utils.spec.ts @@ -1,4 +1,5 @@ -import { expect, fixture, html, unsafeStatic } from '@open-wc/testing'; +import { expect } from 'vitest'; +import { fixture, html, unsafeStatic } from '../common/helpers.spec.js'; import type IgcTreeComponent from './tree.js'; import type IgcTreeItemComponent from './tree-item.js'; diff --git a/src/components/tree/tree.spec.ts b/src/components/tree/tree.spec.ts index 67b6de4aa..66964d4dc 100644 --- a/src/components/tree/tree.spec.ts +++ b/src/components/tree/tree.spec.ts @@ -1,9 +1,16 @@ -import { aTimeout, elementUpdated, expect, waitUntil } from '@open-wc/testing'; -import { spy } from 'sinon'; - +import { + beforeAll, + beforeEach, + describe, + expect, + it, + type MockInstance, + vi, +} from 'vitest'; import type IgcCheckboxComponent from '../checkbox/checkbox.js'; import { defineComponents } from '../common/definitions/defineComponents.js'; -import { scrolledIntoView } from '../common/utils.spec.js'; +import { aTimeout, elementUpdated, waitUntil } from '../common/helpers.spec.js'; +import { eventMatch, scrolledIntoView } from '../common/utils.spec.js'; import IgcTreeComponent from './tree.js'; import type IgcTreeItemComponent from './tree-item.js'; import { @@ -22,7 +29,7 @@ import { } from './tree-utils.spec.js'; describe('Tree', () => { - before(() => { + beforeAll(() => { defineComponents(IgcTreeComponent); }); @@ -370,7 +377,7 @@ describe('Tree', () => { it('Should emit igcActiveItem event when the active item changes', async () => { tree = await TreeTestFunctions.createTreeElement(simpleHierarchyTree); - const eventSpy = spy(tree, 'emitEvent'); + const spy = vi.spyOn(tree, 'emitEvent'); await elementUpdated(tree); tree.items.forEach((item) => { @@ -381,25 +388,25 @@ describe('Tree', () => { tree.items[0].dispatchEvent(new PointerEvent('click')); await elementUpdated(tree); expect(tree.navService.activeItem).to.equal(tree.items[0]); - expect(eventSpy).calledOnceWithExactly('igcActiveItem', { + expect(spy).toHaveBeenCalledExactlyOnceWith('igcActiveItem', { detail: tree.items[0], }); - eventSpy.resetHistory(); + spy.mockClear(); tree.navService.setActiveItem(tree.items[1], true); await elementUpdated(tree); expect(tree.navService.activeItem).to.equal(tree.items[1]); - expect(eventSpy).calledOnceWithExactly('igcActiveItem', { + expect(spy).toHaveBeenCalledExactlyOnceWith('igcActiveItem', { detail: tree.items[1], }); - eventSpy.resetHistory(); + spy.mockClear(); tree.navService.setActiveItem(tree.items[2], false); await elementUpdated(tree); expect(tree.navService.activeItem).to.equal(tree.items[2]); - expect(eventSpy).not.called; + expect(spy).not.toHaveBeenCalled(); }); it('Should activate the last tree item set as active if there are multiple', async () => { @@ -505,19 +512,19 @@ describe('Tree', () => { describe('Expand/Collapse', async () => { let topLevelItems: IgcTreeItemComponent[]; - let eventSpy: any; + let spy: MockInstance; beforeEach(async () => { tree = await TreeTestFunctions.createTreeElement(expandCollapseTree); topLevelItems = tree.items.filter((i) => i.level === 0); - eventSpy = spy(tree, 'emitEvent'); + spy = vi.spyOn(tree, 'emitEvent'); }); it('Should expand all collapsed (including the disabled) items w/ tree.expand()', async () => { tree.expand(); await elementUpdated(tree); - expect(eventSpy.called).to.be.false; // event is not emitted when expanding through API + expect(spy).not.toHaveBeenCalled(); // event is not emitted when expanding through API tree.items.forEach((item) => { expect(item.expanded).to.be.true; }); @@ -529,7 +536,7 @@ describe('Tree', () => { tree.expand(items); await elementUpdated(tree); - expect(eventSpy.called).to.be.false; // event is not emitted when expanding through API + expect(spy).not.toHaveBeenCalled(); // event is not emitted when expanding through API items.forEach((item) => { expect(item.expanded).to.be.true; }); @@ -539,7 +546,7 @@ describe('Tree', () => { tree.collapse(); await elementUpdated(tree); - expect(eventSpy.called).to.be.false; // event is not emitted when collapsing through API + expect(spy).not.toHaveBeenCalled(); // event is not emitted when collapsing through API tree.items.forEach((item) => { expect(item.expanded).to.be.false; }); @@ -551,7 +558,7 @@ describe('Tree', () => { tree.collapse(items); await elementUpdated(tree); - expect(eventSpy.called).to.be.false; // event is not emitted when collapsing through API + expect(spy).not.toHaveBeenCalled(); // event is not emitted when collapsing through API items.forEach((item) => { expect(item.expanded).to.be.false; }); @@ -568,7 +575,7 @@ describe('Tree', () => { ); item2IndSlot?.dispatchEvent(new MouseEvent('click')); - await waitUntil(() => eventSpy.calledWith('igcItemCollapsed')); + await waitUntil(() => eventMatch(spy, 'igcItemCollapsed')); TreeTestFunctions.verifyExpansionState(topLevelItems[1], false); @@ -577,16 +584,17 @@ describe('Tree', () => { detail: topLevelItems[1], cancelable: true, }; - expect(eventSpy.callCount).to.equal(2); - expect(eventSpy.firstCall).calledWith( + expect(spy).toHaveBeenCalledTimes(2); + expect(spy).toHaveBeenNthCalledWith( + 1, 'igcItemCollapsing', collapsingArgs ); - expect(eventSpy.secondCall).calledWith('igcItemCollapsed', { + expect(spy).toHaveBeenNthCalledWith(2, 'igcItemCollapsed', { detail: topLevelItems[1], }); - eventSpy.resetHistory(); + spy.mockClear(); const item21IndSlot = TreeTestFunctions.getSlot( childItem21, @@ -599,7 +607,7 @@ describe('Tree', () => { // Should not collapse disabled item expect(childItem21.disabled).to.be.true; TreeTestFunctions.verifyExpansionState(childItem21, true); - expect(eventSpy.called).to.be.false; + expect(spy).not.toHaveBeenCalled(); }); it('Should expand items when user interacts w/ indicator and item.expanded === true', async () => { @@ -613,7 +621,7 @@ describe('Tree', () => { ); item1IndSlot?.dispatchEvent(new MouseEvent('click')); - await waitUntil(() => eventSpy.calledWith('igcItemExpanded')); + await waitUntil(() => eventMatch(spy, 'igcItemExpanded')); TreeTestFunctions.verifyExpansionState(topLevelItems[0], true); @@ -622,13 +630,13 @@ describe('Tree', () => { detail: topLevelItems[0], cancelable: true, }; - expect(eventSpy.callCount).to.equal(2); - expect(eventSpy.firstCall).calledWith('igcItemExpanding', expandingArgs); - expect(eventSpy.secondCall).calledWith('igcItemExpanded', { + expect(spy).toHaveBeenCalledTimes(2); + expect(spy).toHaveBeenNthCalledWith(1, 'igcItemExpanding', expandingArgs); + expect(spy).toHaveBeenNthCalledWith(2, 'igcItemExpanded', { detail: topLevelItems[0], }); - eventSpy.resetHistory(); + spy.mockClear(); const item11IndSlot = TreeTestFunctions.getSlot( childItem11, @@ -641,7 +649,7 @@ describe('Tree', () => { // Should not expand disabled item expect(childItem11.disabled).to.be.true; TreeTestFunctions.verifyExpansionState(childItem11, false); - expect(eventSpy.called).to.be.false; + expect(spy).not.toHaveBeenCalled(); }); it('Should collapse items when item.expanded is set to false', async () => { @@ -652,7 +660,7 @@ describe('Tree', () => { TreeTestFunctions.verifyExpansionState(topLevelItems[1], false); // Should not emit event when collapsed through API - expect(eventSpy.called).to.be.false; + expect(spy).not.toHaveBeenCalled(); }); it('Should expand items when item.expanded is set to true', async () => { @@ -663,7 +671,7 @@ describe('Tree', () => { TreeTestFunctions.verifyExpansionState(topLevelItems[0], true); // Should not emit event when collapsed through API - expect(eventSpy.called).to.be.false; + expect(spy).not.toHaveBeenCalled(); }); it('Should expand items when item.expand() is called', async () => { @@ -674,14 +682,14 @@ describe('Tree', () => { TreeTestFunctions.verifyExpansionState(topLevelItems[0], true); // Should not emit event when collapsed through API - expect(eventSpy.called).to.be.false; + expect(spy).not.toHaveBeenCalled(); // Should not expand with event an already expanded item topLevelItems[0].expandWithEvent(); await elementUpdated(tree); TreeTestFunctions.verifyExpansionState(topLevelItems[0], true); - expect(eventSpy.called).to.be.false; + expect(spy).not.toHaveBeenCalled(); }); it('Should collapse items when item.collapse() is called', async () => { @@ -692,7 +700,7 @@ describe('Tree', () => { TreeTestFunctions.verifyExpansionState(topLevelItems[1], false); // Should not emit event when collapsed through API - expect(eventSpy.called).to.be.false; + expect(spy).not.toHaveBeenCalled(); }); it('Should expand/collapse nodes when clicking over them if `toggleNodeOnClick` is set to `true`', async () => { @@ -705,38 +713,39 @@ describe('Tree', () => { await elementUpdated(tree); TreeTestFunctions.verifyExpansionState(topLevelItems[0], true); - await waitUntil(() => eventSpy.calledWith('igcItemExpanded')); + await waitUntil(() => eventMatch(spy, 'igcItemExpanded')); // Should emit ing and ed events when item state is toggled through UI const expandingArgs = { detail: topLevelItems[0], cancelable: true, }; - expect(eventSpy.callCount).to.equal(2); - expect(eventSpy.firstCall).calledWith('igcItemExpanding', expandingArgs); - expect(eventSpy.secondCall).calledWith('igcItemExpanded', { + expect(spy).toHaveBeenCalledTimes(2); + expect(spy).toHaveBeenNthCalledWith(1, 'igcItemExpanding', expandingArgs); + expect(spy).toHaveBeenNthCalledWith(2, 'igcItemExpanded', { detail: topLevelItems[0], }); - eventSpy.resetHistory(); + spy.mockClear(); topLevelItems[0].dispatchEvent(new MouseEvent('click')); await elementUpdated(tree); TreeTestFunctions.verifyExpansionState(topLevelItems[0], false); - await waitUntil(() => eventSpy.calledWith('igcItemCollapsed')); + await waitUntil(() => eventMatch(spy, 'igcItemCollapsed')); // Should emit ing and ed events when item state is toggled through UI const collapsingArgs = { detail: topLevelItems[0], cancelable: true, }; - expect(eventSpy.callCount).to.equal(2); - expect(eventSpy.firstCall).calledWith( + expect(spy).toHaveBeenCalledTimes(2); + expect(spy).toHaveBeenNthCalledWith( + 1, 'igcItemCollapsing', collapsingArgs ); - expect(eventSpy.secondCall).calledWith('igcItemCollapsed', { + expect(spy).toHaveBeenNthCalledWith(2, 'igcItemCollapsed', { detail: topLevelItems[0], }); }); @@ -791,13 +800,13 @@ describe('Tree', () => { TreeTestFunctions.verifyExpansionState(topLevelItems[1], false); // Should not emit event when collapsed through API - expect(eventSpy.called).to.be.false; + expect(spy).not.toHaveBeenCalled(); topLevelItems[1].toggle(); await elementUpdated(tree); TreeTestFunctions.verifyExpansionState(topLevelItems[1], true); - expect(eventSpy.called).to.be.false; + expect(spy).not.toHaveBeenCalled(); }); it('Should be able to prevent the expansion/collapsing through the ing events.', async () => { @@ -819,9 +828,9 @@ describe('Tree', () => { detail: topLevelItems[0], cancelable: true, }; - expect(eventSpy.callCount).to.equal(1); - expect(eventSpy.firstCall).calledWith('igcItemExpanding', expandingArgs); - eventSpy.resetHistory(); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenNthCalledWith(1, 'igcItemExpanding', expandingArgs); + spy.mockClear(); const item2IndSlot = TreeTestFunctions.getSlot( topLevelItems[1], @@ -841,8 +850,9 @@ describe('Tree', () => { detail: topLevelItems[1], cancelable: true, }; - expect(eventSpy.callCount).to.equal(1); - expect(eventSpy.firstCall).calledWith( + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenNthCalledWith( + 1, 'igcItemCollapsing', collapsingArgs ); @@ -999,13 +1009,13 @@ describe('Tree', () => { describe('Disabled item', async () => { let disabledItems: IgcTreeItemComponent[]; let topLevelItems: IgcTreeItemComponent[]; - let eventSpy: any; + let spy: MockInstance; beforeEach(async () => { tree = await TreeTestFunctions.createTreeElement(disabledItemsTree); topLevelItems = tree.items.filter((i) => i.level === 0); disabledItems = tree.items.filter((i) => i.disabled === true); - eventSpy = spy(tree, 'emitEvent'); + spy = vi.spyOn(tree, 'emitEvent'); }); it('Should be able to select/activate/expand disabled item through API', async () => { @@ -1023,7 +1033,7 @@ describe('Tree', () => { expect(cb.checked).to.be.true; expect(cb.indeterminate).to.be.false; - expect(eventSpy).not.to.be.called; // event not emitted when interacting through API + expect(spy).not.toHaveBeenCalled(); // event not emitted when interacting through API const item11 = disabledItems[1]; expect(item11.disabled).to.be.true; @@ -1034,7 +1044,7 @@ describe('Tree', () => { TreeTestFunctions.verifyItemSelection(item11, true); expect(cb.checked).to.be.true; expect(cb.indeterminate).to.be.false; - expect(eventSpy).not.to.be.called; + expect(spy).not.toHaveBeenCalled(); const item12 = disabledItems[2]; expect(item12.disabled).to.be.true; @@ -1044,13 +1054,13 @@ describe('Tree', () => { await elementUpdated(tree); expect(item12.active).to.be.true; - expect(eventSpy).not.to.be.called; + expect(spy).not.toHaveBeenCalled(); item12.expand(); await elementUpdated(tree); expect(item12.expanded).to.be.true; - expect(eventSpy).not.to.be.called; + expect(spy).not.toHaveBeenCalled(); }); it('Should not be able to interact with disabled item through UI', async () => { @@ -1066,7 +1076,7 @@ describe('Tree', () => { TreeTestFunctions.verifyExpansionState(item11, true); expect(item11.active).to.be.false; - expect(eventSpy).not.to.be.called; + expect(spy).not.toHaveBeenCalled(); expect(tree.navService.focusedItem).not.to.equal(item11); const item11LabelSlot = TreeTestFunctions.getSlot(item11, SLOTS.label); @@ -1077,14 +1087,14 @@ describe('Tree', () => { TreeTestFunctions.verifyExpansionState(item11, true); expect(tree.navService.focusedItem).not.to.equal(item11); expect(item11.active).to.be.false; - expect(eventSpy).not.to.be.called; + expect(spy).not.toHaveBeenCalled(); item11.dispatchEvent(new MouseEvent('click')); await elementUpdated(tree); expect(item11.active).to.be.false; expect(tree.navService.focusedItem).not.to.equal(item11); - expect(eventSpy).not.to.be.called; + expect(spy).not.toHaveBeenCalled(); item11.dispatchEvent(new Event('focus')); await elementUpdated(tree); @@ -1288,12 +1298,12 @@ describe('Tree', () => { describe('RTL', () => { let topLevelItems: IgcTreeItemComponent[]; - let eventSpy: any; + let spy: MockInstance; beforeEach(async () => { tree = await TreeTestFunctions.createTreeElement(navigationTree); topLevelItems = tree.items.filter((i) => i.level === 0); - eventSpy = spy(tree, 'emitEvent'); + spy = vi.spyOn(tree, 'emitEvent'); tree.dir = 'rtl'; await elementUpdated(tree); }); @@ -1304,7 +1314,7 @@ describe('Tree', () => { await elementUpdated(tree); TreeTestFunctions.setFocusAndTriggerKeydown(item2, tree, 'ArrowRight'); - await waitUntil(() => eventSpy.calledWith('igcItemCollapsed')); + await waitUntil(() => eventMatch(spy, 'igcItemCollapsed')); expect(item2.expanded).to.be.false; @@ -1312,12 +1322,13 @@ describe('Tree', () => { detail: item2, cancelable: true, }; - expect(eventSpy.callCount).to.equal(2); - expect(eventSpy.firstCall).calledWith( + expect(spy).toHaveBeenCalledTimes(2); + expect(spy).toHaveBeenNthCalledWith( + 1, 'igcItemCollapsing', collapsingArgs ); - expect(eventSpy.secondCall).calledWith('igcItemCollapsed', { + expect(spy).toHaveBeenNthCalledWith(2, 'igcItemCollapsed', { detail: item2, }); }); @@ -1332,7 +1343,7 @@ describe('Tree', () => { await elementUpdated(tree); TreeTestFunctions.setFocusAndTriggerKeydown(item1, tree, 'ArrowLeft'); - await waitUntil(() => eventSpy.calledWith('igcItemExpanded')); + await waitUntil(() => eventMatch(spy, 'igcItemExpanded')); expect(item1.expanded).to.be.true; @@ -1340,9 +1351,9 @@ describe('Tree', () => { detail: item1, cancelable: true, }; - expect(eventSpy.callCount).to.equal(2); - expect(eventSpy.firstCall).calledWith('igcItemExpanding', expandingArgs); - expect(eventSpy.secondCall).calledWith('igcItemExpanded', { + expect(spy).toHaveBeenCalledTimes(2); + expect(spy).toHaveBeenNthCalledWith(1, 'igcItemExpanding', expandingArgs); + expect(spy).toHaveBeenNthCalledWith(2, 'igcItemExpanded', { detail: item1, }); }); diff --git a/src/components/validation-container/validation-container.spec.ts b/src/components/validation-container/validation-container.spec.ts index cb65141a6..ac61eb327 100644 --- a/src/components/validation-container/validation-container.spec.ts +++ b/src/components/validation-container/validation-container.spec.ts @@ -1,6 +1,7 @@ -import { elementUpdated, fixture, html } from '@open-wc/testing'; -import type { TemplateResult } from 'lit'; +import { html, type TemplateResult } from 'lit'; +import { beforeAll, describe, it } from 'vitest'; import { defineComponents } from '../common/definitions/defineComponents.js'; +import { elementUpdated, fixture } from '../common/helpers.spec.js'; import { ValidityHelpers } from '../common/validity-helpers.spec.js'; import IgcInputComponent from '../input/input.js'; @@ -10,7 +11,7 @@ describe('Validation container', () => { const helperSlot = 'helper-text'; const valueMissingSlot = 'value-missing'; - before(() => { + beforeAll(() => { defineComponents(IgcInputComponent); }); diff --git a/src/theming/config.spec.ts b/src/theming/config.spec.ts index ef09f7d9a..466282e7c 100644 --- a/src/theming/config.spec.ts +++ b/src/theming/config.spec.ts @@ -1,5 +1,5 @@ -import { aTimeout, expect, oneEvent } from '@open-wc/testing'; - +import { describe, expect, it } from 'vitest'; +import { aTimeout } from '../components/common/helpers.spec.js'; import { configureTheme, getTheme } from './config.js'; import { CHANGE_THEME_EVENT } from './theming-event.js'; import { getAllCssVariables } from './utils.js'; @@ -37,12 +37,20 @@ describe('Theming Config', () => { const theme = 'material'; const themeVariant = 'light'; + const eventPromise = new Promise((resolve) => { + window.addEventListener( + CHANGE_THEME_EVENT, + (e) => resolve(e as CustomEvent), + { once: true } + ); + }); + setTimeout(() => { configureTheme(theme, themeVariant); expect(getTheme()).to.deep.equal({ theme, themeVariant }); }); - const { detail } = await oneEvent(window, CHANGE_THEME_EVENT); + const { detail } = await eventPromise; expect(detail.theme).to.equal(theme); expect(detail.themeVariant).to.equal(themeVariant); }); diff --git a/src/vitest-browser.d.ts b/src/vitest-browser.d.ts new file mode 100644 index 000000000..c92a5348c --- /dev/null +++ b/src/vitest-browser.d.ts @@ -0,0 +1,57 @@ +/// + +import type { RenderOptions, TemplateResult } from 'lit'; +import type { DiffOptions } from '@open-wc/semantic-dom-diff/get-diffable-html'; +import type { Assertion } from 'vitest'; + +interface LitTemplateResult { + processor: any; + strings: TemplateStringsArray; + type: string; + values: readonly unknown[]; +} + +type RenderResult = + | LitTemplateResult + | TemplateResult + | TemplateResult[] + | Node + | Node[] + | string + | string[] + | number + | number[] + | boolean + | boolean[] + | null + | undefined; + +type FixtureOptions = { + container?: HTMLElement; + options?: RenderOptions; +}; + +interface DomAssertion extends Omit, 'equal' | 'to' | 'be'> { + to: DomAssertion; + be: DomAssertion; + equal(value: unknown, options?: DiffOptions): void; + equal(value: unknown, message?: string, options?: DiffOptions): void; +} + +declare module 'vitest' { + interface Assertion { + accessible(options?: object): Promise>; + dom: DomAssertion; + lightDom: DomAssertion; + shadowDom: DomAssertion; + } +} + +declare module '@vitest/browser/context' { + interface BrowserPage { + fixture( + template: RenderResult, + options?: FixtureOptions + ): Promise; + } +} diff --git a/tsconfig.json b/tsconfig.json index 35d225caa..35caa3a2d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -31,8 +31,7 @@ "no-incompatible-type-binding": "warning" } } - ], - "types": ["mocha"] + ] }, "include": ["src/**/*.ts"], "exclude": ["src/**/*.stories.ts"], diff --git a/vite.config.ts b/vite.config.ts index f12ba1861..08e355687 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,4 +1,23 @@ import { defineConfig } from 'vite'; +import { configDefaults } from 'vitest/config'; +import { playwright } from '@vitest/browser-playwright'; + +const externalize = (sources: string[]) => ({ + name: 'externalize-deps', + resolveId(source: string) { + return sources.includes(source) ? { id: source, external: true } : null; + }, +}); + +// Utilities for certain test suites in the library +const IGNORED_TESTS = [ + '**/stepper-utils.spec.ts', + '**/date-range-picker.utils.spec.ts', + '**/helpers.spec.ts', + '**/tree-utils.spec.ts', + '**/validity-helpers.spec.ts', + '**/utils.spec.ts', +]; // https://vitejs.dev/config/ export default defineConfig({ @@ -21,4 +40,36 @@ export default defineConfig({ }, }, }, + test: { + mockReset: true, + onConsoleLog(log, type) { + return !(type === 'stderr' && log.startsWith('Lit is in dev mode')); + }, + exclude: [...configDefaults.exclude, ...IGNORED_TESTS], + include: ['src/**/*.spec.ts'], + browser: { + enabled: true, + provider: playwright(), + instances: [{ browser: 'chromium', headless: true }], + screenshotFailures: false, + }, + setupFiles: ['./vitest.setup.ts'], + coverage: { + enabled: false, + provider: 'v8', + reportOnFailure: true, + reporter: ['lcov', 'html'], + reportsDirectory: './coverage', + include: ['src/**/*.ts'], + exclude: [ + 'src/**/*.spec.ts', + 'src/**/*.d.ts', + 'src/**/themes/**', + 'src/animations/presets/**', + 'src/index.ts', + 'src/extras/**', + ], + }, + }, + plugins: [externalize(['/__web-dev-server__web-socket.js'])], }); diff --git a/vitest.setup.ts b/vitest.setup.ts new file mode 100644 index 000000000..d750beb62 --- /dev/null +++ b/vitest.setup.ts @@ -0,0 +1,8 @@ +import { chai } from 'vitest'; +import chaiDom from 'chai-dom'; +import { chaiDomDiff } from '@open-wc/semantic-dom-diff'; +import { chaiA11yAxe } from 'chai-a11y-axe'; + +chai.use(chaiDom); +chai.use(chaiDomDiff); +chai.use(chaiA11yAxe); diff --git a/web-test-runner.config.mjs b/web-test-runner.config.mjs deleted file mode 100644 index 54210962f..000000000 --- a/web-test-runner.config.mjs +++ /dev/null @@ -1,36 +0,0 @@ -import { fileURLToPath } from 'node:url'; -import { playwrightLauncher } from '@web/test-runner-playwright'; -import { esbuildPlugin } from '@web/dev-server-esbuild'; - -export default /** @type {import("@web/test-runner").TestRunnerConfig} */ ({ - files: ['src/**/*.spec.ts'], - browsers: [playwrightLauncher({ product: 'chromium', headless: true })], - - /** Compile JS for older browsers. Requires @web/dev-server-esbuild plugin */ - // esbuildTarget: 'auto', - - /** Configure bare import resolve plugin */ - nodeResolve: { - exportConditions: ['browser', 'production'], - }, - - coverageConfig: { - exclude: ['node_modules/**/*', '**/themes/**'], - }, - - testFramework: { - config: { - timeout: 3000, - }, - }, - - plugins: [ - esbuildPlugin({ - ts: true, - tsconfig: fileURLToPath(new URL('./tsconfig.json', import.meta.url)), - }), - ], - - // See documentation for all available options - // https://modern-web.dev/docs/test-runner/cli-and-configuration/#configuration-file -});