test: configure Vitest and add 54 unit tests (F6.1, F6.2)
Set up Vitest with happy-dom, mock Nuxt auto-imports via #imports alias. Add tests for: inventory-types validators (9), apiHelpers (10), modelUtils (18), useConfirm (8), useToast (9). All 54 tests pass. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
550
package-lock.json
generated
550
package-lock.json
generated
@@ -22,10 +22,13 @@
|
||||
"@types/node": "^25.2.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.44.1",
|
||||
"@typescript-eslint/parser": "^8.44.1",
|
||||
"@vue/test-utils": "^2.4.6",
|
||||
"eslint": "^9.36.0",
|
||||
"eslint-plugin-vue": "^10.5.0",
|
||||
"happy-dom": "^20.5.3",
|
||||
"typescript": "^5.7.3",
|
||||
"unplugin-icons": "^0.19.3",
|
||||
"vitest": "^4.0.18",
|
||||
"vue-eslint-parser": "^10.2.0"
|
||||
}
|
||||
},
|
||||
@@ -2119,6 +2122,13 @@
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@one-ini/wasm": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz",
|
||||
"integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@oxc-minify/binding-android-arm64": {
|
||||
"version": "0.87.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-minify/binding-android-arm64/-/binding-android-arm64-0.87.0.tgz",
|
||||
@@ -3747,6 +3757,13 @@
|
||||
"integrity": "sha512-0dxmVj4gxg3Jg879kvFS/msl4s9F3T9UXC1InxgOf7t5NvcPD97u/WTA5vL/IxWHMn7qSxBozqrnnE2wvl1m8g==",
|
||||
"license": "CC0-1.0"
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"node_modules/@stylistic/eslint-plugin": {
|
||||
"version": "5.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-5.4.0.tgz",
|
||||
@@ -4040,6 +4057,24 @@
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"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",
|
||||
"dependencies": {
|
||||
"@types/deep-eql": "*",
|
||||
"assertion-error": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/deep-eql": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
|
||||
"integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||
@@ -4075,6 +4110,23 @@
|
||||
"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/whatwg-mimetype": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.2.tgz",
|
||||
"integrity": "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/ws": {
|
||||
"version": "8.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
|
||||
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.44.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.44.1.tgz",
|
||||
@@ -4672,6 +4724,127 @@
|
||||
"integrity": "sha512-s3GeJKSQOwBlzdUrj4ISjJj5SfSh+aqn0wjOar4Bx95iV1ETI7F6S/5hLcfAxZ9kXDcyrAkxPlqmd1ZITttf+w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@vitest/expect": {
|
||||
"version": "4.0.18",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz",
|
||||
"integrity": "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@standard-schema/spec": "^1.0.0",
|
||||
"@types/chai": "^5.2.2",
|
||||
"@vitest/spy": "4.0.18",
|
||||
"@vitest/utils": "4.0.18",
|
||||
"chai": "^6.2.1",
|
||||
"tinyrainbow": "^3.0.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/mocker": {
|
||||
"version": "4.0.18",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.18.tgz",
|
||||
"integrity": "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/spy": "4.0.18",
|
||||
"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/mocker/node_modules/estree-walker": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
|
||||
"integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/pretty-format": {
|
||||
"version": "4.0.18",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.18.tgz",
|
||||
"integrity": "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tinyrainbow": "^3.0.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/runner": {
|
||||
"version": "4.0.18",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.18.tgz",
|
||||
"integrity": "sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/utils": "4.0.18",
|
||||
"pathe": "^2.0.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/snapshot": {
|
||||
"version": "4.0.18",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.18.tgz",
|
||||
"integrity": "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/pretty-format": "4.0.18",
|
||||
"magic-string": "^0.30.21",
|
||||
"pathe": "^2.0.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/spy": {
|
||||
"version": "4.0.18",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.18.tgz",
|
||||
"integrity": "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/utils": {
|
||||
"version": "4.0.18",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.18.tgz",
|
||||
"integrity": "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/pretty-format": "4.0.18",
|
||||
"tinyrainbow": "^3.0.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@volar/language-core": {
|
||||
"version": "2.4.23",
|
||||
"resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.23.tgz",
|
||||
@@ -4951,6 +5124,17 @@
|
||||
"integrity": "sha512-F4yc6palwq3TT0u+FYf0Ns4Tfl9GRFURDN2gWG7L1ecIaS/4fCIuFOjMTnCyjsu/OK6vaDKLCrGAa+KvvH+h4w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@vue/test-utils": {
|
||||
"version": "2.4.6",
|
||||
"resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.4.6.tgz",
|
||||
"integrity": "sha512-FMxEjOpYNYiFe0GkaHsnJPXFHxQ6m4t8vI/ElPGpMWxZKpmRvQ33OIrvRXemy6yha03RxhOlQuy+gZMC3CQSow==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"js-beautify": "^1.14.9",
|
||||
"vue-component-type-helpers": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/abbrev": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz",
|
||||
@@ -5207,6 +5391,16 @@
|
||||
"devOptional": true,
|
||||
"license": "Python-2.0"
|
||||
},
|
||||
"node_modules/assertion-error": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
|
||||
"integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/ast-kit": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ast-kit/-/ast-kit-2.1.2.tgz",
|
||||
@@ -5660,6 +5854,16 @@
|
||||
],
|
||||
"license": "CC-BY-4.0"
|
||||
},
|
||||
"node_modules/chai": {
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
|
||||
"integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
@@ -5958,6 +6162,24 @@
|
||||
"integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/config-chain": {
|
||||
"version": "1.1.13",
|
||||
"resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz",
|
||||
"integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ini": "^1.3.4",
|
||||
"proto-list": "~1.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/config-chain/node_modules/ini": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
|
||||
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/consola": {
|
||||
"version": "3.4.2",
|
||||
"resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz",
|
||||
@@ -6609,6 +6831,51 @@
|
||||
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/editorconfig": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz",
|
||||
"integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@one-ini/wasm": "0.1.1",
|
||||
"commander": "^10.0.0",
|
||||
"minimatch": "9.0.1",
|
||||
"semver": "^7.5.3"
|
||||
},
|
||||
"bin": {
|
||||
"editorconfig": "bin/editorconfig"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/editorconfig/node_modules/commander": {
|
||||
"version": "10.0.1",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz",
|
||||
"integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/editorconfig/node_modules/minimatch": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz",
|
||||
"integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
@@ -7301,6 +7568,16 @@
|
||||
"url": "https://github.com/sindresorhus/execa?sponsor=1"
|
||||
}
|
||||
},
|
||||
"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": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/exsolve": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz",
|
||||
@@ -7829,6 +8106,37 @@
|
||||
"uncrypto": "^0.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/happy-dom": {
|
||||
"version": "20.5.3",
|
||||
"resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-20.5.3.tgz",
|
||||
"integrity": "sha512-xqAxGnkRU0KNhheHpxb3uScqg/aehqUiVto/a9ApWMyNvnH9CAqHYq9dEPAovM6bOGbLstmTfGIln5ZIezEU0g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": ">=20.0.0",
|
||||
"@types/whatwg-mimetype": "^3.0.2",
|
||||
"@types/ws": "^8.18.1",
|
||||
"entities": "^6.0.1",
|
||||
"whatwg-mimetype": "^3.0.0",
|
||||
"ws": "^8.18.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/happy-dom/node_modules/entities": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
|
||||
"integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
@@ -8442,6 +8750,64 @@
|
||||
"jiti": "lib/jiti-cli.mjs"
|
||||
}
|
||||
},
|
||||
"node_modules/js-beautify": {
|
||||
"version": "1.15.4",
|
||||
"resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.4.tgz",
|
||||
"integrity": "sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"config-chain": "^1.1.13",
|
||||
"editorconfig": "^1.0.4",
|
||||
"glob": "^10.4.2",
|
||||
"js-cookie": "^3.0.5",
|
||||
"nopt": "^7.2.1"
|
||||
},
|
||||
"bin": {
|
||||
"css-beautify": "js/bin/css-beautify.js",
|
||||
"html-beautify": "js/bin/html-beautify.js",
|
||||
"js-beautify": "js/bin/js-beautify.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/js-beautify/node_modules/abbrev": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz",
|
||||
"integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/js-beautify/node_modules/nopt": {
|
||||
"version": "7.2.1",
|
||||
"resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz",
|
||||
"integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"abbrev": "^2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"nopt": "bin/nopt.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/js-cookie": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
|
||||
"integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
@@ -9208,9 +9574,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/magic-string": {
|
||||
"version": "0.30.19",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz",
|
||||
"integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==",
|
||||
"version": "0.30.21",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
||||
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/sourcemap-codec": "^1.5.5"
|
||||
@@ -9978,6 +10344,17 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"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/ofetch": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.4.1.tgz",
|
||||
@@ -11256,6 +11633,13 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/proto-list": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
|
||||
"integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/protocols": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/protocols/-/protocols-2.0.2.tgz",
|
||||
@@ -11943,6 +12327,13 @@
|
||||
"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"
|
||||
},
|
||||
"node_modules/signal-exit": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
|
||||
@@ -12089,6 +12480,13 @@
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"node_modules/standard-as-callback": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
|
||||
@@ -12105,9 +12503,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/std-env": {
|
||||
"version": "3.9.0",
|
||||
"resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz",
|
||||
"integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==",
|
||||
"version": "3.10.0",
|
||||
"resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
|
||||
"integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/streamx": {
|
||||
@@ -12536,12 +12934,22 @@
|
||||
"integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tinyexec": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz",
|
||||
"integrity": "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==",
|
||||
"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"
|
||||
},
|
||||
"node_modules/tinyexec": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz",
|
||||
"integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tinyglobby": {
|
||||
"version": "0.2.15",
|
||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
|
||||
@@ -12558,6 +12966,16 @@
|
||||
"url": "https://github.com/sponsors/SuperchupuDev"
|
||||
}
|
||||
},
|
||||
"node_modules/tinyrainbow": {
|
||||
"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": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
@@ -13617,6 +14035,84 @@
|
||||
"@types/estree": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vitest": {
|
||||
"version": "4.0.18",
|
||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz",
|
||||
"integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/expect": "4.0.18",
|
||||
"@vitest/mocker": "4.0.18",
|
||||
"@vitest/pretty-format": "4.0.18",
|
||||
"@vitest/runner": "4.0.18",
|
||||
"@vitest/snapshot": "4.0.18",
|
||||
"@vitest/spy": "4.0.18",
|
||||
"@vitest/utils": "4.0.18",
|
||||
"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": "^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.18",
|
||||
"@vitest/browser-preview": "4.0.18",
|
||||
"@vitest/browser-webdriverio": "4.0.18",
|
||||
"@vitest/ui": "4.0.18",
|
||||
"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/vscode-uri": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz",
|
||||
@@ -13653,6 +14149,13 @@
|
||||
"ufo": "^1.6.1"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-component-type-helpers": {
|
||||
"version": "2.2.12",
|
||||
"resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-2.2.12.tgz",
|
||||
"integrity": "sha512-YbGqHZ5/eW4SnkPNR44mKVc6ZKQoRs/Rux1sxC6rdwXb4qpbOSYfDr9DsTHolOTGmIKgM9j141mZbBeg05R1pw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vue-devtools-stub": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/vue-devtools-stub/-/vue-devtools-stub-0.1.0.tgz",
|
||||
@@ -13710,6 +14213,16 @@
|
||||
"integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/whatwg-mimetype": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz",
|
||||
"integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/whatwg-url": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||
@@ -13735,6 +14248,23 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"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",
|
||||
"dependencies": {
|
||||
"siginfo": "^2.0.0",
|
||||
"stackback": "0.0.2"
|
||||
},
|
||||
"bin": {
|
||||
"why-is-node-running": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/word-wrap": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
"postinstall": "nuxt prepare",
|
||||
"start": "nuxt start",
|
||||
"lint": "eslint . --ext .js,.ts,.vue",
|
||||
"lint:fix": "npm run lint -- --fix"
|
||||
"lint:fix": "npm run lint -- --fix",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nuxtjs/tailwindcss": "^6.14.0",
|
||||
@@ -28,10 +30,13 @@
|
||||
"@types/node": "^25.2.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.44.1",
|
||||
"@typescript-eslint/parser": "^8.44.1",
|
||||
"@vue/test-utils": "^2.4.6",
|
||||
"eslint": "^9.36.0",
|
||||
"eslint-plugin-vue": "^10.5.0",
|
||||
"happy-dom": "^20.5.3",
|
||||
"typescript": "^5.7.3",
|
||||
"unplugin-icons": "^0.19.3",
|
||||
"vitest": "^4.0.18",
|
||||
"vue-eslint-parser": "^10.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
35
tests/__mocks__/imports.ts
Normal file
35
tests/__mocks__/imports.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Minimal mock for Nuxt's #imports auto-import.
|
||||
* Add stubs here as tests require them.
|
||||
*/
|
||||
|
||||
import { ref } from 'vue'
|
||||
|
||||
export const useRuntimeConfig = () => ({
|
||||
public: {
|
||||
apiBaseUrl: 'http://localhost:8081/api',
|
||||
appVersion: '0.0.0-test',
|
||||
},
|
||||
})
|
||||
|
||||
export const useRoute = () => ({
|
||||
path: '/',
|
||||
params: {},
|
||||
query: {},
|
||||
})
|
||||
|
||||
export const useRouter = () => ({
|
||||
push: () => Promise.resolve(),
|
||||
replace: () => Promise.resolve(),
|
||||
})
|
||||
|
||||
export const navigateTo = () => Promise.resolve()
|
||||
|
||||
export const useRequestFetch = () => fetch
|
||||
|
||||
export const useFetch = () => ({
|
||||
data: ref(null),
|
||||
error: ref(null),
|
||||
pending: ref(false),
|
||||
refresh: () => Promise.resolve(),
|
||||
})
|
||||
81
tests/composables/useConfirm.test.ts
Normal file
81
tests/composables/useConfirm.test.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { useConfirm } from '~/composables/useConfirm'
|
||||
|
||||
describe('useConfirm', () => {
|
||||
it('returns confirm function and state', () => {
|
||||
const { confirm, confirmState, handleConfirm, handleCancel } = useConfirm()
|
||||
expect(typeof confirm).toBe('function')
|
||||
expect(typeof handleConfirm).toBe('function')
|
||||
expect(typeof handleCancel).toBe('function')
|
||||
expect(confirmState.open).toBe(false)
|
||||
})
|
||||
|
||||
it('opens modal with correct options', () => {
|
||||
const { confirm, confirmState } = useConfirm()
|
||||
// Don't await — we'll manually resolve
|
||||
confirm({ message: 'Delete this item?' })
|
||||
expect(confirmState.open).toBe(true)
|
||||
expect(confirmState.message).toBe('Delete this item?')
|
||||
expect(confirmState.title).toBe('Confirmation')
|
||||
expect(confirmState.confirmText).toBe('Supprimer')
|
||||
expect(confirmState.cancelText).toBe('Annuler')
|
||||
expect(confirmState.dangerous).toBe(true)
|
||||
// Clean up by canceling
|
||||
const { handleCancel } = useConfirm()
|
||||
handleCancel()
|
||||
})
|
||||
|
||||
it('resolves true on confirm', async () => {
|
||||
const { confirm, handleConfirm } = useConfirm()
|
||||
const promise = confirm({ message: 'Confirm?' })
|
||||
handleConfirm()
|
||||
const result = await promise
|
||||
expect(result).toBe(true)
|
||||
})
|
||||
|
||||
it('resolves false on cancel', async () => {
|
||||
const { confirm, handleCancel } = useConfirm()
|
||||
const promise = confirm({ message: 'Cancel?' })
|
||||
handleCancel()
|
||||
const result = await promise
|
||||
expect(result).toBe(false)
|
||||
})
|
||||
|
||||
it('closes modal after confirm', async () => {
|
||||
const { confirm, confirmState, handleConfirm } = useConfirm()
|
||||
confirm({ message: 'Test' })
|
||||
expect(confirmState.open).toBe(true)
|
||||
handleConfirm()
|
||||
expect(confirmState.open).toBe(false)
|
||||
})
|
||||
|
||||
it('closes modal after cancel', async () => {
|
||||
const { confirm, confirmState, handleCancel } = useConfirm()
|
||||
confirm({ message: 'Test' })
|
||||
expect(confirmState.open).toBe(true)
|
||||
handleCancel()
|
||||
expect(confirmState.open).toBe(false)
|
||||
})
|
||||
|
||||
it('supports custom options', () => {
|
||||
const { confirm, confirmState, handleCancel } = useConfirm()
|
||||
confirm({
|
||||
title: 'Custom Title',
|
||||
message: 'Custom message',
|
||||
confirmText: 'Yes',
|
||||
cancelText: 'No',
|
||||
dangerous: false,
|
||||
})
|
||||
expect(confirmState.title).toBe('Custom Title')
|
||||
expect(confirmState.confirmText).toBe('Yes')
|
||||
expect(confirmState.cancelText).toBe('No')
|
||||
expect(confirmState.dangerous).toBe(false)
|
||||
handleCancel()
|
||||
})
|
||||
|
||||
it('shares state across calls (singleton)', () => {
|
||||
const a = useConfirm()
|
||||
const b = useConfirm()
|
||||
expect(a.confirmState).toBe(b.confirmState)
|
||||
})
|
||||
})
|
||||
83
tests/composables/useToast.test.ts
Normal file
83
tests/composables/useToast.test.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||
import { useToast } from '~/composables/useToast'
|
||||
|
||||
describe('useToast', () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers()
|
||||
const { clearAll } = useToast()
|
||||
clearAll()
|
||||
})
|
||||
|
||||
it('returns all expected functions', () => {
|
||||
const toast = useToast()
|
||||
expect(typeof toast.showToast).toBe('function')
|
||||
expect(typeof toast.showSuccess).toBe('function')
|
||||
expect(typeof toast.showError).toBe('function')
|
||||
expect(typeof toast.showWarning).toBe('function')
|
||||
expect(typeof toast.showInfo).toBe('function')
|
||||
expect(typeof toast.removeToast).toBe('function')
|
||||
expect(typeof toast.clearAll).toBe('function')
|
||||
})
|
||||
|
||||
it('adds a toast with correct properties', () => {
|
||||
const { showToast, toasts } = useToast()
|
||||
const id = showToast('Hello', 'info')
|
||||
expect(toasts.value).toHaveLength(1)
|
||||
expect(toasts.value[0].message).toBe('Hello')
|
||||
expect(toasts.value[0].type).toBe('info')
|
||||
expect(toasts.value[0].visible).toBe(true)
|
||||
expect(toasts.value[0].id).toBe(id)
|
||||
})
|
||||
|
||||
it('showSuccess creates a success toast', () => {
|
||||
const { showSuccess, toasts } = useToast()
|
||||
showSuccess('Saved!')
|
||||
expect(toasts.value[0].type).toBe('success')
|
||||
expect(toasts.value[0].message).toBe('Saved!')
|
||||
})
|
||||
|
||||
it('showError creates an error toast', () => {
|
||||
const { showError, toasts } = useToast()
|
||||
showError('Failed!')
|
||||
expect(toasts.value[0].type).toBe('error')
|
||||
})
|
||||
|
||||
it('showWarning creates a warning toast', () => {
|
||||
const { showWarning, toasts } = useToast()
|
||||
showWarning('Caution!')
|
||||
expect(toasts.value[0].type).toBe('warning')
|
||||
})
|
||||
|
||||
it('limits to MAX_TOASTS (3)', () => {
|
||||
const { showToast, toasts } = useToast()
|
||||
showToast('A', 'info')
|
||||
showToast('B', 'info')
|
||||
showToast('C', 'info')
|
||||
showToast('D', 'info')
|
||||
expect(toasts.value).toHaveLength(3)
|
||||
expect(toasts.value[0].message).toBe('B')
|
||||
expect(toasts.value[2].message).toBe('D')
|
||||
})
|
||||
|
||||
it('clearAll removes all toasts', () => {
|
||||
const { showToast, toasts, clearAll } = useToast()
|
||||
showToast('A', 'info')
|
||||
showToast('B', 'info')
|
||||
expect(toasts.value.length).toBeGreaterThan(0)
|
||||
clearAll()
|
||||
expect(toasts.value).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('shares state across calls (singleton)', () => {
|
||||
const a = useToast()
|
||||
const b = useToast()
|
||||
expect(a.toasts).toBe(b.toasts)
|
||||
})
|
||||
|
||||
it('removeToast sets visible to false', () => {
|
||||
const { showToast, toasts, removeToast } = useToast()
|
||||
const id = showToast('Test', 'info')
|
||||
removeToast(id)
|
||||
expect(toasts.value[0].visible).toBe(false)
|
||||
})
|
||||
})
|
||||
50
tests/shared/apiHelpers.test.ts
Normal file
50
tests/shared/apiHelpers.test.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { extractCollection } from '~/shared/utils/apiHelpers'
|
||||
|
||||
describe('extractCollection', () => {
|
||||
it('returns the input if it is already an array', () => {
|
||||
const items = [{ id: 1 }, { id: 2 }]
|
||||
expect(extractCollection(items)).toEqual(items)
|
||||
})
|
||||
|
||||
it('extracts from hydra:member', () => {
|
||||
const payload = { 'hydra:member': [{ id: 1 }], 'hydra:totalItems': 1 }
|
||||
expect(extractCollection(payload)).toEqual([{ id: 1 }])
|
||||
})
|
||||
|
||||
it('extracts from member', () => {
|
||||
const payload = { member: [{ id: 1 }, { id: 2 }] }
|
||||
expect(extractCollection(payload)).toEqual([{ id: 1 }, { id: 2 }])
|
||||
})
|
||||
|
||||
it('extracts from items', () => {
|
||||
const payload = { items: [{ id: 1 }] }
|
||||
expect(extractCollection(payload)).toEqual([{ id: 1 }])
|
||||
})
|
||||
|
||||
it('extracts from data', () => {
|
||||
const payload = { data: [{ id: 1 }] }
|
||||
expect(extractCollection(payload)).toEqual([{ id: 1 }])
|
||||
})
|
||||
|
||||
it('prefers member over hydra:member', () => {
|
||||
const payload = { member: [{ id: 'member' }], 'hydra:member': [{ id: 'hydra' }] }
|
||||
expect(extractCollection(payload)).toEqual([{ id: 'member' }])
|
||||
})
|
||||
|
||||
it('returns empty array for null', () => {
|
||||
expect(extractCollection(null)).toEqual([])
|
||||
})
|
||||
|
||||
it('returns empty array for undefined', () => {
|
||||
expect(extractCollection(undefined)).toEqual([])
|
||||
})
|
||||
|
||||
it('returns empty array for empty object', () => {
|
||||
expect(extractCollection({})).toEqual([])
|
||||
})
|
||||
|
||||
it('returns empty array for string', () => {
|
||||
expect(extractCollection('not an object')).toEqual([])
|
||||
})
|
||||
})
|
||||
118
tests/shared/inventory-types.test.ts
Normal file
118
tests/shared/inventory-types.test.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import {
|
||||
componentModelStructureValidator,
|
||||
createEmptyComponentModelStructure,
|
||||
createEmptyPieceModelStructure,
|
||||
createEmptyProductModelStructure,
|
||||
} from '~/shared/types/inventory'
|
||||
|
||||
describe('createEmptyComponentModelStructure', () => {
|
||||
it('returns a valid empty structure', () => {
|
||||
const result = createEmptyComponentModelStructure()
|
||||
expect(result).toEqual({
|
||||
customFields: [],
|
||||
pieces: [],
|
||||
products: [],
|
||||
subcomponents: [],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('createEmptyPieceModelStructure', () => {
|
||||
it('returns a valid empty piece structure', () => {
|
||||
const result = createEmptyPieceModelStructure()
|
||||
expect(result).toEqual({
|
||||
customFields: [],
|
||||
products: [],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('createEmptyProductModelStructure', () => {
|
||||
it('returns a valid empty product structure', () => {
|
||||
const result = createEmptyProductModelStructure()
|
||||
expect(result).toEqual({
|
||||
customFields: [],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('componentModelStructureValidator', () => {
|
||||
it('parses a minimal valid structure', () => {
|
||||
const input = {
|
||||
customFields: [],
|
||||
pieces: [],
|
||||
subcomponents: [],
|
||||
}
|
||||
const result = componentModelStructureValidator.safeParse(input)
|
||||
expect(result.success).toBe(true)
|
||||
if (result.success) {
|
||||
expect(result.data.customFields).toEqual([])
|
||||
expect(result.data.pieces).toEqual([])
|
||||
expect(result.data.subcomponents).toEqual([])
|
||||
}
|
||||
})
|
||||
|
||||
it('rejects a non-object input', () => {
|
||||
const result = componentModelStructureValidator.safeParse('not an object')
|
||||
expect(result.success).toBe(false)
|
||||
if (!result.success) {
|
||||
expect(result.issues.length).toBeGreaterThan(0)
|
||||
}
|
||||
})
|
||||
|
||||
it('validates custom fields with name and type', () => {
|
||||
const input = {
|
||||
customFields: [
|
||||
{ name: 'Color', type: 'text', required: true },
|
||||
{ name: 'Size', type: 'select', required: false, options: ['S', 'M', 'L'] },
|
||||
],
|
||||
pieces: [],
|
||||
subcomponents: [],
|
||||
}
|
||||
const result = componentModelStructureValidator.safeParse(input)
|
||||
expect(result.success).toBe(true)
|
||||
if (result.success) {
|
||||
expect(result.data.customFields).toHaveLength(2)
|
||||
expect(result.data.customFields[0].name).toBe('Color')
|
||||
expect(result.data.customFields[1].options).toEqual(['S', 'M', 'L'])
|
||||
}
|
||||
})
|
||||
|
||||
it('rejects custom fields without name', () => {
|
||||
const input = {
|
||||
customFields: [{ type: 'text', required: false }],
|
||||
pieces: [],
|
||||
subcomponents: [],
|
||||
}
|
||||
const result = componentModelStructureValidator.safeParse(input)
|
||||
expect(result.success).toBe(false)
|
||||
if (!result.success) {
|
||||
expect(result.issues.some((i) => i.includes('name'))).toBe(true)
|
||||
}
|
||||
})
|
||||
|
||||
it('validates nested subcomponents', () => {
|
||||
const input = {
|
||||
customFields: [],
|
||||
pieces: [],
|
||||
subcomponents: [
|
||||
{
|
||||
typeComposantId: 'abc-123',
|
||||
alias: 'Motor',
|
||||
subcomponents: [],
|
||||
},
|
||||
],
|
||||
}
|
||||
const result = componentModelStructureValidator.safeParse(input)
|
||||
expect(result.success).toBe(true)
|
||||
if (result.success) {
|
||||
expect(result.data.subcomponents).toHaveLength(1)
|
||||
expect(result.data.subcomponents[0].alias).toBe('Motor')
|
||||
}
|
||||
})
|
||||
|
||||
it('parse throws on invalid input', () => {
|
||||
expect(() => componentModelStructureValidator.parse(null)).toThrow()
|
||||
})
|
||||
})
|
||||
169
tests/shared/modelUtils.test.ts
Normal file
169
tests/shared/modelUtils.test.ts
Normal file
@@ -0,0 +1,169 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import {
|
||||
isPlainObject,
|
||||
defaultStructure,
|
||||
cloneStructure,
|
||||
computeStructureStats,
|
||||
formatStructurePreview,
|
||||
} from '~/shared/model/componentStructure'
|
||||
import {
|
||||
defaultPieceStructure,
|
||||
defaultProductStructure,
|
||||
clonePieceStructure,
|
||||
cloneProductStructure,
|
||||
} from '~/shared/model/pieceProductStructure'
|
||||
|
||||
describe('isPlainObject', () => {
|
||||
it('returns true for plain objects', () => {
|
||||
expect(isPlainObject({})).toBe(true)
|
||||
expect(isPlainObject({ key: 'value' })).toBe(true)
|
||||
})
|
||||
|
||||
it('returns false for arrays', () => {
|
||||
expect(isPlainObject([])).toBe(false)
|
||||
})
|
||||
|
||||
it('returns false for null', () => {
|
||||
expect(isPlainObject(null)).toBe(false)
|
||||
})
|
||||
|
||||
it('returns false for primitives', () => {
|
||||
expect(isPlainObject('string')).toBe(false)
|
||||
expect(isPlainObject(42)).toBe(false)
|
||||
expect(isPlainObject(undefined)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('defaultStructure', () => {
|
||||
it('returns a fresh empty structure each time', () => {
|
||||
const a = defaultStructure()
|
||||
const b = defaultStructure()
|
||||
expect(a).toEqual(b)
|
||||
expect(a).not.toBe(b)
|
||||
expect(a.customFields).toEqual([])
|
||||
expect(a.pieces).toEqual([])
|
||||
expect(a.products).toEqual([])
|
||||
expect(a.subcomponents).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('cloneStructure', () => {
|
||||
it('deep clones a structure', () => {
|
||||
const original = defaultStructure()
|
||||
original.customFields.push({ name: 'test', type: 'text', required: false })
|
||||
const cloned = cloneStructure(original)
|
||||
expect(cloned.customFields).toHaveLength(1)
|
||||
expect(cloned.customFields[0].name).toBe('test')
|
||||
// Ensure deep clone — mutating original doesn't affect clone
|
||||
original.customFields[0].name = 'mutated'
|
||||
expect(cloned.customFields[0].name).toBe('test')
|
||||
})
|
||||
|
||||
it('returns default structure for null input', () => {
|
||||
const result = cloneStructure(null)
|
||||
expect(result).toEqual(defaultStructure())
|
||||
})
|
||||
|
||||
it('returns default structure for undefined input', () => {
|
||||
const result = cloneStructure(undefined)
|
||||
expect(result).toEqual(defaultStructure())
|
||||
})
|
||||
|
||||
it('preserves typeComposantId and alias', () => {
|
||||
const input = {
|
||||
...defaultStructure(),
|
||||
typeComposantId: 'abc-123',
|
||||
alias: 'Motor',
|
||||
}
|
||||
const result = cloneStructure(input)
|
||||
expect(result.typeComposantId).toBe('abc-123')
|
||||
expect(result.alias).toBe('Motor')
|
||||
})
|
||||
})
|
||||
|
||||
describe('computeStructureStats', () => {
|
||||
it('counts elements in a structure', () => {
|
||||
const structure = {
|
||||
...defaultStructure(),
|
||||
customFields: [
|
||||
{ name: 'A', type: 'text' as const, required: false },
|
||||
{ name: 'B', type: 'number' as const, required: true },
|
||||
],
|
||||
pieces: [{ typePieceId: 'p1' }],
|
||||
products: [{ typeProductId: 'pr1' }],
|
||||
subcomponents: [{ subcomponents: [] }],
|
||||
}
|
||||
const stats = computeStructureStats(structure)
|
||||
expect(stats.customFields).toBe(2)
|
||||
expect(stats.pieces).toBe(1)
|
||||
expect(stats.products).toBe(1)
|
||||
expect(stats.subcomponents).toBe(1)
|
||||
})
|
||||
|
||||
it('returns zeros for empty structure', () => {
|
||||
const stats = computeStructureStats(defaultStructure())
|
||||
expect(stats).toEqual({
|
||||
customFields: 0,
|
||||
pieces: 0,
|
||||
products: 0,
|
||||
subcomponents: 0,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('formatStructurePreview', () => {
|
||||
it('returns "Structure vide" for empty structure', () => {
|
||||
const result = formatStructurePreview(defaultStructure())
|
||||
expect(result).toBe('Structure vide')
|
||||
})
|
||||
|
||||
it('formats a non-empty structure', () => {
|
||||
const structure = {
|
||||
...defaultStructure(),
|
||||
customFields: [{ name: 'A', type: 'text' as const, required: false }],
|
||||
pieces: [{ typePieceId: 'p1' }, { typePieceId: 'p2' }],
|
||||
}
|
||||
const result = formatStructurePreview(structure)
|
||||
expect(result).toContain('1 champ')
|
||||
expect(result).toContain('2 pièce')
|
||||
})
|
||||
})
|
||||
|
||||
describe('defaultPieceStructure', () => {
|
||||
it('returns a valid empty piece structure', () => {
|
||||
const result = defaultPieceStructure()
|
||||
expect(result.customFields).toEqual([])
|
||||
expect(result.products).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('defaultProductStructure', () => {
|
||||
it('returns a valid empty product structure', () => {
|
||||
const result = defaultProductStructure()
|
||||
expect(result.customFields).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('clonePieceStructure', () => {
|
||||
it('deep clones a piece structure', () => {
|
||||
const original = defaultPieceStructure()
|
||||
original.customFields.push({ name: 'Weight', type: 'number', required: true })
|
||||
const cloned = clonePieceStructure(original)
|
||||
expect(cloned.customFields).toHaveLength(1)
|
||||
original.customFields[0].name = 'mutated'
|
||||
expect(cloned.customFields[0].name).toBe('Weight')
|
||||
})
|
||||
|
||||
it('returns default for null', () => {
|
||||
expect(clonePieceStructure(null)).toEqual(defaultPieceStructure())
|
||||
})
|
||||
})
|
||||
|
||||
describe('cloneProductStructure', () => {
|
||||
it('deep clones a product structure', () => {
|
||||
const original = defaultProductStructure()
|
||||
original.customFields.push({ name: 'Color', type: 'text', required: false })
|
||||
const cloned = cloneProductStructure(original)
|
||||
expect(cloned.customFields).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
17
vitest.config.ts
Normal file
17
vitest.config.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { defineConfig } from 'vitest/config'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
globals: true,
|
||||
environment: 'happy-dom',
|
||||
root: '.',
|
||||
include: ['tests/**/*.test.ts'],
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'~': resolve(__dirname, 'app'),
|
||||
'#imports': resolve(__dirname, 'tests/__mocks__/imports.ts'),
|
||||
},
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user