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:
Matthieu
2026-02-09 11:20:28 +01:00
parent 6152848957
commit 634184c2be
9 changed files with 1099 additions and 11 deletions

550
package-lock.json generated
View File

@@ -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",

View File

@@ -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"
}
}

View 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(),
})

View 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)
})
})

View 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)
})
})

View 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([])
})
})

View 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()
})
})

View 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
View 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'),
},
},
})