diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index ed64bc80..9a00e57a 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -29,7 +29,9 @@ "EditorConfig.EditorConfig", "github.vscode-github-actions", "AlexShen.classdiagram-ts", - "ms-azuretools.vscode-bicep" + "ms-azuretools.vscode-bicep", + "vitest.explorer", + "kingwl.vscode-vitest-runner" ] } } diff --git a/.github/workflows/azure-deploy.yml b/.github/workflows/azure-deploy.yml index 7bb6b52d..54273e82 100644 --- a/.github/workflows/azure-deploy.yml +++ b/.github/workflows/azure-deploy.yml @@ -111,8 +111,8 @@ jobs: run: npm run build - name: Generate PWA Assets run: npm run generate-pwa-assets - # - name: Run Unit Tests - # run: npm run test + - name: Run Unit Tests + run: npm run test - name: Upload artifact uses: actions/upload-artifact@v4 with: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b4c54dec..c75426c8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,5 +32,5 @@ jobs: run: npm run build - name: Generate PWA Assets run: npm run generate-pwa-assets - # - name: Run Unit Tests - # run: npm run test \ No newline at end of file + - name: Run Unit Tests + run: npm run test \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 91a4d071..8a7e06c5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,9 @@ { "editor.formatOnSave": true, - "git.pruneOnFetch": true + "git.pruneOnFetch": true, + "explorer.fileNesting.enabled": true, + "explorer.fileNesting.patterns": { + "*.ts": "${capture}.test.ts", + "*.vue": "${capture}.test.ts" + } } \ No newline at end of file diff --git a/components/TopNavigation.test.ts.disabled b/components/TopNavigation.test.ts.disabled new file mode 100644 index 00000000..cbccb46a --- /dev/null +++ b/components/TopNavigation.test.ts.disabled @@ -0,0 +1,10 @@ +import { it, expect, describe } from 'vitest'; +import { mountSuspended } from '@nuxt/test-utils/runtime' +import TopNavigation from './TopNavigation.vue'; + +describe('TopNavigation', () => { + it('should render the title', async () => { + const component = await mountSuspended(TopNavigation); + expect(component.html()).toContain('Home'); + }); +}); \ No newline at end of file diff --git a/nuxt.config.ts b/nuxt.config.ts index ad5378e0..adfa78cf 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -49,10 +49,11 @@ export default defineNuxtConfig({ // The latest version (^1.5.0) is needed due to https://github.com/nuxt/devtools/issues/723 // It looks like this won't be integrated into Nuxt until after 3.14.0 '@nuxt/devtools', - "nuxt-primevue", - "@vite-pwa/nuxt", - "@sidebase/nuxt-auth", - "nuxt-security" + 'nuxt-primevue', + '@vite-pwa/nuxt', + '@sidebase/nuxt-auth', + 'nuxt-security', + '@nuxt/test-utils/module', ], runtimeConfig: { // The private keys which are only available within server-side diff --git a/package-lock.json b/package-lock.json index 6eacbe98..45f937e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,13 +38,18 @@ "@mikro-orm/cli": "^6.3.13", "@mikro-orm/migrations": "^6.3.13", "@nuxt/devtools": "^1.6.0", + "@nuxt/test-utils": "^3.14.4", "@types/node": "^22.8.1", "@types/pg": "^8.11.10", "@types/uuid": "^10.0.0", "@vite-pwa/assets-generator": "^0.2.6", + "@vue/test-utils": "^2.4.6", + "happy-dom": "^15.7.4", + "playwright-core": "^1.48.2", "ts-node": "^10.9.2", "tsx": "^4.19.2", "typescript": "^5.6.3", + "vitest": "^2.1.4", "vue-tsc": "^2.1.8" } }, @@ -3471,6 +3476,169 @@ "node": "^18 || >=20" } }, + "node_modules/@nuxt/test-utils": { + "version": "3.14.4", + "resolved": "https://registry.npmjs.org/@nuxt/test-utils/-/test-utils-3.14.4.tgz", + "integrity": "sha512-1rSYMXjN651t+c8zSaPAoP78YE1WVcI3baPC2cic9my+J5FIsT1IuTU6M9XwDFBUnwGL6/sV5pPsyEumkIl3eA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nuxt/kit": "^3.13.2", + "@nuxt/schema": "^3.13.2", + "c12": "^2.0.1", + "consola": "^3.2.3", + "defu": "^6.1.4", + "destr": "^2.0.3", + "estree-walker": "^3.0.3", + "fake-indexeddb": "^6.0.0", + "get-port-please": "^3.1.2", + "local-pkg": "^0.5.0", + "magic-string": "^0.30.12", + "node-fetch-native": "^1.6.4", + "ofetch": "^1.4.1", + "pathe": "^1.1.2", + "perfect-debounce": "^1.0.0", + "radix3": "^1.1.2", + "scule": "^1.3.0", + "std-env": "^3.7.0", + "tinyexec": "^0.3.1", + "ufo": "^1.5.4", + "unenv": "^1.10.0", + "unplugin": "^1.14.1", + "vitest-environment-nuxt": "^1.0.1" + }, + "engines": { + "node": ">=18.20.4" + }, + "peerDependencies": { + "@cucumber/cucumber": "^10.3.1 || ^11.0.0", + "@jest/globals": "^29.5.0", + "@playwright/test": "^1.43.1", + "@testing-library/vue": "^7.0.0 || ^8.0.1", + "@vitest/ui": "^0.34.6 || ^1.0.0 || ^2.0.0", + "@vue/test-utils": "^2.4.2", + "h3": "*", + "happy-dom": "^9.10.9 || ^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0", + "jsdom": "^22.0.0 || ^23.0.0 || ^24.0.0 || ^25.0.0", + "nitropack": "*", + "playwright-core": "^1.43.1", + "vite": "*", + "vitest": "^0.34.6 || ^1.0.0 || ^2.0.0", + "vue": "^3.3.4", + "vue-router": "^4.0.0" + }, + "peerDependenciesMeta": { + "@cucumber/cucumber": { + "optional": true + }, + "@jest/globals": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "@testing-library/vue": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "@vue/test-utils": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "playwright-core": { + "optional": true + }, + "vitest": { + "optional": true + } + } + }, + "node_modules/@nuxt/test-utils/node_modules/c12": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/c12/-/c12-2.0.1.tgz", + "integrity": "sha512-Z4JgsKXHG37C6PYUtIxCfLJZvo6FyhHJoClwwb9ftUkLpPSkuYqn6Tr+vnaN8hymm0kIbcg6Ey3kv/Q71k5w/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.1", + "confbox": "^0.1.7", + "defu": "^6.1.4", + "dotenv": "^16.4.5", + "giget": "^1.2.3", + "jiti": "^2.3.0", + "mlly": "^1.7.1", + "ohash": "^1.1.4", + "pathe": "^1.1.2", + "perfect-debounce": "^1.0.0", + "pkg-types": "^1.2.0", + "rc9": "^2.1.2" + }, + "peerDependencies": { + "magicast": "^0.3.5" + }, + "peerDependenciesMeta": { + "magicast": { + "optional": true + } + } + }, + "node_modules/@nuxt/test-utils/node_modules/chokidar": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", + "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nuxt/test-utils/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/@nuxt/test-utils/node_modules/jiti": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.3.3.tgz", + "integrity": "sha512-EX4oNDwcXSivPrw2qKH2LB5PoFxEvgtv2JgwW0bU858HoLQ+kutSvjLMUqBd0PeJYEinLWhoI9Ol0eYMqj/wNQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/@nuxt/test-utils/node_modules/readdirp": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz", + "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@nuxt/vite-builder": { "version": "3.13.2", "resolved": "https://registry.npmjs.org/@nuxt/vite-builder/-/vite-builder-3.13.2.tgz", @@ -3934,6 +4102,13 @@ "@types/estree": "^1.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/@opentelemetry/api": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", @@ -6080,6 +6255,129 @@ "vue": "^3.0.0" } }, + "node_modules/@vitest/expect": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.4.tgz", + "integrity": "sha512-DOETT0Oh1avie/D/o2sgMHGrzYUFFo3zqESB2Hn70z6QB1HrS2IQ9z5DfyTqU8sg4Bpu13zZe9V4+UTNQlUeQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.4", + "@vitest/utils": "2.1.4", + "chai": "^5.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.4.tgz", + "integrity": "sha512-Ky/O1Lc0QBbutJdW0rqLeFNbuLEyS+mIPiNdlVlp2/yhJ0SbyYqObS5IHdhferJud8MbbwMnexg4jordE5cCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.12" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.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": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.4.tgz", + "integrity": "sha512-L95zIAkEuTDbUX1IsjRl+vyBSLh3PwLLgKpghl37aCK9Jvw0iP+wKwIFhfjdUtA2myLgjrG6VU6JCFLv8q/3Ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.4.tgz", + "integrity": "sha512-sKRautINI9XICAMl2bjxQM8VfCMTB0EbsBc/EDFA57V6UQevEKY/TOPOF5nzcvCALltiLfXWbq4MaAwWx/YxIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "2.1.4", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.4.tgz", + "integrity": "sha512-3Kab14fn/5QZRog5BPj6Rs8dc4B+mim27XaKWFWHWA87R56AKjHTGcBFKpvZKDzC4u5Wd0w/qKsUIio3KzWW4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.4", + "magic-string": "^0.30.12", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.4.tgz", + "integrity": "sha512-4JOxa+UAizJgpZfaCPKK2smq9d8mmjZVPMt2kOsg/R8QkoRzydHH1qHxIYNvr1zlEaFj4SXiaaJWxq/LPLKaLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^3.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.4.tgz", + "integrity": "sha512-MXDnZn0Awl2S86PSNIim5PWXgIAx8CIkzu35mBdSApUip6RFOGXBCf3YFyeEu8n1IHk4bWD46DeYFu9mQlFIRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.4", + "loupe": "^3.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/@volar/language-core": { "version": "2.4.8", "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.8.tgz", @@ -6381,6 +6679,17 @@ "integrity": "sha512-L2RPSAwUFbgZH20etwrXyVyCBu9OxRSi8T/38QsvnkJyvq2LufW2lDCOzm7t/U9C1mkhJGWYfCuFBCmIuNivrg==", "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": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -6760,6 +7069,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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": "1.2.1", "resolved": "https://registry.npmjs.org/ast-kit/-/ast-kit-1.2.1.tgz", @@ -7314,6 +7633,23 @@ ], "license": "CC-BY-4.0" }, + "node_modules/chai": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.2.tgz", + "integrity": "sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==", + "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": ">=12" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -7330,6 +7666,16 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, "node_modules/chevrotain": { "version": "11.0.3", "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz", @@ -7683,6 +8029,24 @@ "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", "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.2.3", "resolved": "https://registry.npmjs.org/consola/-/consola-3.2.3.tgz", @@ -8707,6 +9071,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -8997,6 +9371,41 @@ "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/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", @@ -9406,6 +9815,16 @@ "node": ">=6" } }, + "node_modules/expect-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.1.0.tgz", + "integrity": "sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/externality": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/externality/-/externality-1.0.2.tgz", @@ -9418,6 +9837,16 @@ "ufo": "^1.1.2" } }, + "node_modules/fake-indexeddb": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/fake-indexeddb/-/fake-indexeddb-6.0.0.tgz", + "integrity": "sha512-YEboHE5VfopUclOck7LncgIqskAqnv4q0EWbYCaxKKjAvO93c+TJIaBuGy8CBFdbg9nKdpN3AuPRwVBJ4k7NrQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -10078,6 +10507,31 @@ "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==", "license": "MIT" }, + "node_modules/happy-dom": { + "version": "15.7.4", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-15.7.4.tgz", + "integrity": "sha512-r1vadDYGMtsHAAsqhDuk4IpPvr6N8MGKy5ntBo7tSdim+pWDxus2PNqOcOt8LuDZ4t3KJHE+gCuzupcx/GKnyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^4.5.0", + "webidl-conversions": "^7.0.0", + "whatwg-mimetype": "^3.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/happy-dom/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/has-bigints": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", @@ -10991,6 +11445,111 @@ "url": "https://github.com/sponsors/panva" } }, + "node_modules/js-beautify": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.1.tgz", + "integrity": "sha512-ESjNzSlt/sWE8sciZH8kBF8BPlwXPwhR6pWKAw8bw4Bwj+iZcnKW6ONWUutJ7eObuBZQpiIb8S7OYspWrKt7rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "config-chain": "^1.1.13", + "editorconfig": "^1.0.4", + "glob": "^10.3.3", + "js-cookie": "^3.0.5", + "nopt": "^7.2.0" + }, + "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/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/js-beautify/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "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/js-beautify/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "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", @@ -11446,6 +12005,13 @@ "loose-envify": "cli.js" } }, + "node_modules/loupe": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz", + "integrity": "sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==", + "dev": true, + "license": "MIT" + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -13500,6 +14066,16 @@ "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", "license": "MIT" }, + "node_modules/pathval": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, "node_modules/perfect-debounce": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", @@ -13705,6 +14281,19 @@ "pathe": "^1.1.2" } }, + "node_modules/playwright-core": { + "version": "1.48.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.48.2.tgz", + "integrity": "sha512-sjjw+qrLFlriJo64du+EK0kJgZzoQPsabGF4lBvsid+3CNIZIYLgnMj9V6JY5VhM2Peh20DJWIVpVljLLnlawA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/points-on-curve": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz", @@ -14414,6 +15003,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/protobufjs": { "version": "7.4.0", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", @@ -15338,6 +15934,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": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -15529,6 +16132,13 @@ "node": ">= 0.6" } }, + "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", @@ -16101,10 +16711,17 @@ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", "license": "MIT" }, + "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": "0.3.0", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.0.tgz", - "integrity": "sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.1.tgz", + "integrity": "sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==", "license": "MIT" }, "node_modules/tinyglobby": { @@ -16146,6 +16763,36 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/tinypool": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.1.tgz", + "integrity": "sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/to-data-view": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/to-data-view/-/to-data-view-1.1.0.tgz", @@ -17490,13 +18137,13 @@ } }, "node_modules/vite-node": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.2.tgz", - "integrity": "sha512-HPcGNN5g/7I2OtPjLqgOtCRu/qhVvBxTUD3qzitmL0SrG1cWFzxzhMDWussxSbrRYWqnKf8P2jiNhPMSN+ymsQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.4.tgz", + "integrity": "sha512-kqa9v+oi4HwkG6g8ufRnb5AeplcRw8jUF6/7/Qz1qRQOXHImG8YnLbB+LLszENwFnoBl9xIf9nVdCFzNd7GQEg==", "license": "MIT", "dependencies": { "cac": "^6.7.14", - "debug": "^4.3.6", + "debug": "^4.3.7", "pathe": "^1.1.2", "vite": "^5.0.0" }, @@ -18126,6 +18773,82 @@ "@esbuild/win32-x64": "0.21.5" } }, + "node_modules/vitest": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.4.tgz", + "integrity": "sha512-eDjxbVAJw1UJJCHr5xr/xM86Zx+YxIEXGAR+bmnEID7z9qWfoxpHw0zdobz+TQAFOLT+nEXz3+gx6nUJ7RgmlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "2.1.4", + "@vitest/mocker": "2.1.4", + "@vitest/pretty-format": "^2.1.4", + "@vitest/runner": "2.1.4", + "@vitest/snapshot": "2.1.4", + "@vitest/spy": "2.1.4", + "@vitest/utils": "2.1.4", + "chai": "^5.1.2", + "debug": "^4.3.7", + "expect-type": "^1.1.0", + "magic-string": "^0.30.12", + "pathe": "^1.1.2", + "std-env": "^3.7.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.1", + "tinypool": "^1.0.1", + "tinyrainbow": "^1.2.0", + "vite": "^5.0.0", + "vite-node": "2.1.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "2.1.4", + "@vitest/ui": "2.1.4", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest-environment-nuxt": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/vitest-environment-nuxt/-/vitest-environment-nuxt-1.0.1.tgz", + "integrity": "sha512-eBCwtIQriXW5/M49FjqNKfnlJYlG2LWMSNFsRVKomc8CaMqmhQPBS5LZ9DlgYL9T8xIVsiA6RZn2lk7vxov3Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nuxt/test-utils": ">=3.13.1" + } + }, "node_modules/vscode-jsonrpc": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz", @@ -18266,6 +18989,13 @@ "ufo": "^1.5.4" } }, + "node_modules/vue-component-type-helpers": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-2.1.8.tgz", + "integrity": "sha512-ii36gDzrYAfOQIkOlo44yceDdT5269gKmNGxf07Qx6seH2U50+tQ2ol02XLhYPmxrh6YabAsOdte8WDrpaO6Tw==", + "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", @@ -18340,6 +19070,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", @@ -18400,6 +19140,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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/wide-align": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", diff --git a/package.json b/package.json index 0f09f50a..cb94d453 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,8 @@ "orm-list-executed-migrations": "mikro-orm-esm migration:list", "orm-run-pending-migrations": "mikro-orm-esm migration:up", "orm-rollback-migration": "mikro-orm-esm migration:down", - "orm-seed-users": "mikro-orm-esm seeder:run -c AppUserSeeder" + "orm-seed-users": "mikro-orm-esm seeder:run -c AppUserSeeder", + "test": "vitest" }, "dependencies": { "@azure/monitor-opentelemetry": "^1.8.0", @@ -62,13 +63,18 @@ "@mikro-orm/cli": "^6.3.13", "@mikro-orm/migrations": "^6.3.13", "@nuxt/devtools": "^1.6.0", + "@nuxt/test-utils": "^3.14.4", "@types/node": "^22.8.1", "@types/pg": "^8.11.10", "@types/uuid": "^10.0.0", "@vite-pwa/assets-generator": "^0.2.6", + "@vue/test-utils": "^2.4.6", + "happy-dom": "^15.7.4", + "playwright-core": "^1.48.2", "ts-node": "^10.9.2", "tsx": "^4.19.2", "typescript": "^5.6.3", + "vitest": "^2.1.4", "vue-tsc": "^2.1.8" } -} +} \ No newline at end of file diff --git a/pages/index.client.vue b/pages/index.vue similarity index 100% rename from pages/index.client.vue rename to pages/index.vue diff --git a/server/utils/dedent.test.ts b/server/utils/dedent.test.ts new file mode 100644 index 00000000..3ae55b81 --- /dev/null +++ b/server/utils/dedent.test.ts @@ -0,0 +1,56 @@ +import { describe, it, expect } from 'vitest'; +import dedent from './dedent'; + +describe('dedent', () => { + it('should remove leading spaces from each line', () => { + const input = ` + line1 + line2 + line3 + `; + const expected = ` +line1 +line2 +line3 + `; + expect(dedent(input)).toBe(expected); + }); + + it('should handle tabs correctly', () => { + const input = ` +\t\tline1 +\t\tline2 +\t\tline3 +\t\t`; + const expected = ` +line1 +line2 +line3 +`; + expect(dedent(input)).toBe(expected); + }); + + it('should return the same string if no indentation is found', () => { + const input = `line1\nline2\nline3`; + expect(dedent(input)).toBe(input); + }); + + it('should handle empty strings', () => { + const input = ``; + expect(dedent(input)).toBe(input); + }); + + it('should handle strings with inconsistent indentation', () => { + const input = ` + line1 + line2 + line3 + `; + const expected = ` + line1 +line2 + line3 + `; + expect(dedent(input)).toBe(expected); + }); +}); \ No newline at end of file diff --git a/server/utils/dedent.ts b/server/utils/dedent.ts index 64f89e2a..6baf707a 100644 --- a/server/utils/dedent.ts +++ b/server/utils/dedent.ts @@ -7,6 +7,7 @@ export default (str: string) => { if (!match) return str + // Find the smallest indentation const indent = Math.min(...match.map(x => x.length)) const re = new RegExp(`^[ \\t]{${indent}}`, 'gm') diff --git a/server/utils/groupBy.test.ts b/server/utils/groupBy.test.ts new file mode 100644 index 00000000..d12d4a1c --- /dev/null +++ b/server/utils/groupBy.test.ts @@ -0,0 +1,46 @@ +import { describe, it, expect } from 'vitest'; +import { groupBy } from './groupBy'; + +describe('groupBy', () => { + it('should group items by a string key', () => { + const items = [ + { category: 'fruit', name: 'apple' }, + { category: 'fruit', name: 'banana' }, + { category: 'vegetable', name: 'carrot' } + ]; + const result = groupBy(items, item => item.category); + expect(result).toEqual({ + fruit: [ + { category: 'fruit', name: 'apple' }, + { category: 'fruit', name: 'banana' } + ], + vegetable: [ + { category: 'vegetable', name: 'carrot' } + ] + }); + }); + + it('should group items by a numeric key', () => { + const items = [1.1, 2.2, 1.3, 2.4]; + const result = groupBy(items, item => Math.floor(item)); + expect(result).toEqual({ + 1: [1.1, 1.3], + 2: [2.2, 2.4] + }); + }); + + it('should handle an empty array', () => { + const items: any[] = []; + const result = groupBy(items, item => item); + expect(result).toEqual({}); + }); + + it('should handle grouping by index', () => { + const items = ['a', 'b', 'c', 'd']; + const result = groupBy(items, (item, index) => index % 2); + expect(result).toEqual({ + 0: ['a', 'c'], + 1: ['b', 'd'] + }); + }); +}); \ No newline at end of file diff --git a/utils/camelCaseToSlug.test.ts b/utils/camelCaseToSlug.test.ts new file mode 100644 index 00000000..456e0a5f --- /dev/null +++ b/utils/camelCaseToSlug.test.ts @@ -0,0 +1,37 @@ +import { describe, it, expect } from 'vitest'; +import camelCaseToSlug from './camelCaseToSlug'; + +describe('camelCaseToSlug', () => { + it('should convert single camelCase word to slug', () => { + expect(camelCaseToSlug('camelCase')).toBe('camel-case'); + }); + + it('should convert multiple camelCase words to slug', () => { + expect(camelCaseToSlug('thisIsATest')).toBe('this-is-a-test'); + }); + + it('should handle strings with no camelCase', () => { + expect(camelCaseToSlug('test')).toBe('test'); + }); + + it('should handle empty strings', () => { + expect(camelCaseToSlug('')).toBe(''); + }); + + it('should handle strings with multiple uppercase letters', () => { + expect(camelCaseToSlug('camelCaseToSlug')).toBe('camel-case-to-slug'); + }); + + it('should handle strings with numbers', () => { + expect(camelCaseToSlug('test123Case')).toBe('test123-case'); + }); + + it('should handle strings with special characters by stripping them out', () => { + expect(camelCaseToSlug('test!@#$%^&*()_+Case')).toBe('test-case'); + }); + + it('should trim leading and trailing whitespace', () => { + expect(camelCaseToSlug(' test ')).toBe('test'); + }); +}); + diff --git a/utils/camelCaseToSlug.ts b/utils/camelCaseToSlug.ts index 6e899c94..218ccaeb 100644 --- a/utils/camelCaseToSlug.ts +++ b/utils/camelCaseToSlug.ts @@ -3,5 +3,11 @@ * @example * camelCaseToSlug('camelCase') // 'camel-case' */ -export default (str: string) => - str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); \ No newline at end of file + +export default (input: string): string => + input.trim() // Remove leading and trailing whitespace + .replace(/[^a-zA-Z0-9]+/g, '-') // Replace non-alphanumeric characters with hyphens + .replace(/([a-z][^A-Z]*?)([A-Z])/g, '$1-$2') // Insert hyphen between lowercase and uppercase letters + .replace(/([A-Z])([A-Z][^A-Z]*?)/g, '$1-$2') // Insert hyphen between two uppercase letters + .replace(/-+/g, '-') // replace multiple hyphens with a single hyphen + .toLowerCase(); // Convert the entire string to lowercase diff --git a/utils/camelCaseToTitle.test.ts b/utils/camelCaseToTitle.test.ts new file mode 100644 index 00000000..df5d2ff0 --- /dev/null +++ b/utils/camelCaseToTitle.test.ts @@ -0,0 +1,29 @@ +import { describe, it, expect } from 'vitest'; +import camelCaseToTitle from './camelCaseToTitle'; + +describe('camelCaseToTitle', () => { + it('should convert camelCase to Title Case', () => { + expect(camelCaseToTitle('camelCaseString')).toBe('Camel Case String'); + }); + + it('should handle single word', () => { + expect(camelCaseToTitle('word')).toBe('Word'); + }); + + it('should handle empty string', () => { + expect(camelCaseToTitle('')).toBe(''); + }); + + it('should handle multiple camelCase words', () => { + expect(camelCaseToTitle('thisIsATestString')).toBe('This Is A Test String'); + }); + + it('should handle strings with numbers', () => { + expect(camelCaseToTitle('testString123')).toBe('Test String123'); + }); + + // trim leading and trailing whitespace + it('should trim leading and trailing whitespace', () => { + expect(camelCaseToTitle(' test ')).toBe('Test'); + }); +}); \ No newline at end of file diff --git a/utils/camelCaseToTitle.ts b/utils/camelCaseToTitle.ts index ccb2a1d8..bca4ab1a 100644 --- a/utils/camelCaseToTitle.ts +++ b/utils/camelCaseToTitle.ts @@ -4,5 +4,6 @@ * camelCaseToTitle('camelCaseString'); // 'Camel Case String' */ export default (str: string) => - str.replace(/([a-z\d])([A-Z])/g, '$1 $2') - .replace(/\b\w/g, char => char.toUpperCase()); + str.trim() // Remove leading and trailing whitespace + .replace(/([A-Z])/g, ' $1') // Add space before uppercase letters + .replace(/^./, str => str.toUpperCase()) // Capitalize the first letter \ No newline at end of file diff --git a/utils/deSlugify.test.ts b/utils/deSlugify.test.ts new file mode 100644 index 00000000..48478e91 --- /dev/null +++ b/utils/deSlugify.test.ts @@ -0,0 +1,24 @@ +import { describe, it, expect } from 'vitest'; +import deSlugify from './deSlugify'; + +describe('deSlugify', () => { + it('should replace hyphens with spaces', () => { + expect(deSlugify('hello-world')).toBe('Hello World'); + }); + + it('should convert to Title Case', () => { + expect(deSlugify('hello-world')).toBe('Hello World'); + }); + + it('should handle multiple hyphens', () => { + expect(deSlugify('this-is-a-test')).toBe('This Is A Test'); + }); + + it('should handle single word', () => { + expect(deSlugify('test')).toBe('Test'); + }); + + it('should handle empty string', () => { + expect(deSlugify('')).toBe(''); + }); +}); \ No newline at end of file diff --git a/utils/debounce.test.ts b/utils/debounce.test.ts new file mode 100644 index 00000000..936d7fb7 --- /dev/null +++ b/utils/debounce.test.ts @@ -0,0 +1,71 @@ +import { describe, it, expect, vi, beforeEach, afterEach, } from 'vitest'; +import debounce from './debounce'; + +describe('debounce', () => { + beforeEach(() => { + vi.useFakeTimers(); + }); + + afterEach(() => { + vi.clearAllTimers(); + }); + + it('should call the function after the specified delay', () => { + // Arrange + const func = vi.fn(); + const debouncedFunc = debounce(func, 200); + + // Act + debouncedFunc(); + + // Assert + expect(func).not.toHaveBeenCalled(); + vi.advanceTimersByTime(200); + expect(func).toHaveBeenCalled(); + }); + + it('should not call the function if called again within the delay', () => { + // Arrange + const func = vi.fn(); + const debouncedFunc = debounce(func, 200); + + // Act + debouncedFunc(); + debouncedFunc(); + + // Assert + expect(func).not.toHaveBeenCalled(); + vi.advanceTimersByTime(200); + expect(func).toHaveBeenCalledTimes(1); + }); + + it('should call the function with the correct arguments', () => { + // Arrange + const func = vi.fn(); + const debouncedFunc = debounce(func, 200); + + // Act + debouncedFunc('arg1', 'arg2'); + + // Assert + vi.advanceTimersByTime(200); + expect(func).toHaveBeenCalledWith('arg1', 'arg2'); + }); + + it('should reset the delay if called again within the delay period', () => { + // Arrange + const func = vi.fn(); + const debouncedFunc = debounce(func, 200); + + // Act + debouncedFunc(); + vi.advanceTimersByTime(100); + debouncedFunc(); + vi.advanceTimersByTime(100); + + // Assert + expect(func).not.toHaveBeenCalled(); + vi.advanceTimersByTime(100); + expect(func).toHaveBeenCalled(); + }); +}); \ No newline at end of file diff --git a/utils/debounce.ts b/utils/debounce.ts index 04c80900..1c61d4d5 100644 --- a/utils/debounce.ts +++ b/utils/debounce.ts @@ -3,12 +3,10 @@ * after the delay has passed without any other calls to the function. */ export default function (func: Function, delay: number) { - let timeoutId: number; + let timeoutId: any return function (...args: any[]) { if (timeoutId) clearTimeout(timeoutId); - timeoutId = self.setTimeout(() => { - func(...args); - }, delay); + timeoutId = setTimeout(() => { func(...args); }, delay); }; } \ No newline at end of file diff --git a/utils/slugify.test.ts b/utils/slugify.test.ts new file mode 100644 index 00000000..a78b3d56 --- /dev/null +++ b/utils/slugify.test.ts @@ -0,0 +1,40 @@ +import { describe, it, expect } from 'vitest'; +import slugify from './slugify'; + +describe('slugify', () => { + it('should convert a string to lowercase', () => { + expect(slugify('Hello World')).toBe('hello-world'); + }); + + it('should replace spaces with hyphens', () => { + expect(slugify('Hello World')).toBe('hello-world'); + }); + + it('should remove non-word characters', () => { + expect(slugify('Hello, World!')).toBe('hello-world'); + }); + + it('should trim leading and trailing whitespace', () => { + expect(slugify(' Hello World ')).toBe('hello-world'); + }); + + it('should replace multiple spaces with a single hyphen', () => { + expect(slugify('Hello World')).toBe('hello-world'); + }); + + it('should replace multiple hyphens with a single hyphen', () => { + expect(slugify('Hello--World')).toBe('hello-world'); + }); + + it('should handle empty strings', () => { + expect(slugify('')).toBe(''); + }); + + it('should handle strings with only non-word characters', () => { + expect(slugify('!@#$%^&*()')).toBe(''); + }); + + it('should handle strings with mixed case and special characters', () => { + expect(slugify('Hello-World! This is a Test.')).toBe('hello-world-this-is-a-test'); + }); +}); \ No newline at end of file diff --git a/utils/snakeCaseToSlug.test.ts b/utils/snakeCaseToSlug.test.ts new file mode 100644 index 00000000..93cd1dc2 --- /dev/null +++ b/utils/snakeCaseToSlug.test.ts @@ -0,0 +1,28 @@ +import { describe, it, expect } from 'vitest'; +import snakeCaseToSlug from './snakeCaseToSlug'; + +describe('snakeCaseToSlug', () => { + it('should convert snake_case to slug', () => { + expect(snakeCaseToSlug('snake_case_string')).toBe('snake-case-string'); + }); + + it('should handle empty strings', () => { + expect(snakeCaseToSlug('')).toBe(''); + }); + + it('should handle strings without underscores', () => { + expect(snakeCaseToSlug('noUnderscores')).toBe('noUnderscores'); + }); + + it('should handle strings with multiple underscores', () => { + expect(snakeCaseToSlug('multiple__underscores')).toBe('multiple--underscores'); + }); + + it('should handle strings with leading and trailing underscores', () => { + expect(snakeCaseToSlug('_leading_and_trailing_')).toBe('-leading-and-trailing-'); + }); + + it('should handle strings with only underscores', () => { + expect(snakeCaseToSlug('_____')).toBe('-----'); + }); +}); \ No newline at end of file diff --git a/utils/snakeCaseToTitle.test.ts b/utils/snakeCaseToTitle.test.ts new file mode 100644 index 00000000..2fa2b5da --- /dev/null +++ b/utils/snakeCaseToTitle.test.ts @@ -0,0 +1,32 @@ +import { describe, it, expect } from 'vitest'; +import snakeCaseToTitle from './snakeCaseToTitle'; + +describe('snakeCaseToTitle', () => { + it('should convert snake_case to Title Case', () => { + expect(snakeCaseToTitle('snake_case_string')).toBe('Snake Case String'); + }); + + it('should handle single word', () => { + expect(snakeCaseToTitle('word')).toBe('Word'); + }); + + it('should handle empty string', () => { + expect(snakeCaseToTitle('')).toBe(''); + }); + + it('should handle multiple underscores', () => { + expect(snakeCaseToTitle('multiple__underscores')).toBe('Multiple Underscores'); + }); + + it('should handle leading and trailing underscores', () => { + expect(snakeCaseToTitle('_leading_trailing_')).toBe(' Leading Trailing '); + }); + + it('should handle numbers in the string', () => { + expect(snakeCaseToTitle('snake_case_123')).toBe('Snake Case 123'); + }); + + it('should handle mixed case input', () => { + expect(snakeCaseToTitle('sNaKe_CaSe')).toBe('SNaKe CaSe'); + }); +}); \ No newline at end of file diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 00000000..84f1e5ed --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineVitestConfig } from '@nuxt/test-utils/config' + +export default defineVitestConfig({ + test: { + environment: 'nuxt' + } +}) \ No newline at end of file