diff options
Diffstat (limited to 'frontend')
| -rw-r--r-- | frontend/package-lock.json | 494 | ||||
| -rw-r--r-- | frontend/package.json | 9 | ||||
| -rw-r--r-- | frontend/src/components/Events.js | 13 | ||||
| -rw-r--r-- | frontend/src/components/Forms.js | 17 | ||||
| -rw-r--r-- | frontend/src/components/Header.js | 18 | ||||
| -rw-r--r-- | frontend/src/style.css | 1 | ||||
| -rw-r--r-- | frontend/test/components/Events.test.js | 144 | ||||
| -rw-r--r-- | frontend/test/components/Header.test.js | 73 | ||||
| -rw-r--r-- | frontend/vite.config.js | 8 |
9 files changed, 719 insertions, 58 deletions
diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 184aa49..a846104 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,9 +8,8 @@ "name": "event-me-client", "version": "0.0.0", "devDependencies": { - "@types/node": "^24.3.0", - "typescript": "^5.9.2", - "vite": "^7.1.2" + "vite": "^7.1.2", + "vitest": "^3.2.4" } }, "node_modules/@esbuild/aix-ppc64": { @@ -455,6 +454,13 @@ "node": ">=18" } }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.48.1", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.48.1.tgz", @@ -735,6 +741,23 @@ "win32" ] }, + "node_modules/@types/chai": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz", + "integrity": "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*" + } + }, + "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", @@ -742,16 +765,203 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/node": { - "version": "24.3.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz", - "integrity": "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==", + "node_modules/@vitest/expect": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", + "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.10.0" + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, + "node_modules/@vitest/mocker": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", + "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", + "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.4", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", + "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", + "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", + "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/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/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/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/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "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/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, "node_modules/esbuild": { "version": "0.25.9", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", @@ -794,6 +1004,26 @@ "@esbuild/win32-x64": "0.25.9" } }, + "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/expect-type": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", + "integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -827,6 +1057,37 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.18", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.18.tgz", + "integrity": "sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -846,6 +1107,23 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -935,6 +1213,13 @@ "fsevents": "~2.3.2" } }, + "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/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -945,6 +1230,47 @@ "node": ">=0.10.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/std-env": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", + "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", + "dev": true, + "license": "MIT" + }, + "node_modules/strip-literal": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.0.0.tgz", + "integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "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.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, "node_modules/tinyglobby": { "version": "0.2.14", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", @@ -962,26 +1288,35 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/typescript": { - "version": "5.9.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", - "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, + "license": "MIT", "engines": { - "node": ">=14.17" + "node": "^18.0.0 || >=20.0.0" } }, - "node_modules/undici-types": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", - "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==", + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.3.tgz", + "integrity": "sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } }, "node_modules/vite": { "version": "7.1.3", @@ -1057,6 +1392,119 @@ "optional": true } } + }, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", + "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.4", + "@vitest/mocker": "3.2.4", + "@vitest/pretty-format": "^3.2.4", + "@vitest/runner": "3.2.4", + "@vitest/snapshot": "3.2.4", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.4", + "@vitest/ui": "3.2.4", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "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" + } } } } diff --git a/frontend/package.json b/frontend/package.json index 98005ba..bde2683 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -5,13 +5,12 @@ "type": "module", "scripts": { "dev": "vite", - "build": "tsc && vite build", + "build": "vite build", "preview": "vite preview", - "check": "tsc" + "test": "vitest" }, "devDependencies": { - "@types/node": "^24.3.0", - "typescript": "^5.9.2", - "vite": "^7.1.2" + "vite": "^7.1.2", + "vitest": "^3.2.4" } } diff --git a/frontend/src/components/Events.js b/frontend/src/components/Events.js index bca948a..a84d291 100644 --- a/frontend/src/components/Events.js +++ b/frontend/src/components/Events.js @@ -1,6 +1,6 @@ import { Calendar } from './Icons.js'; -const API_URL = '/api'; +const API_URL = import.meta.env.VITE_API_URL; const loadEventsData = async () => { try { @@ -30,11 +30,11 @@ export const EventModal = (event) => { action="${API_URL}/events/${event.id}/rsvp" method="POST" > - <label for="rsvp-name">Name: - <input type="text" class="rsvp-name" name="name" required /> + <label for="rsvp-name-${event.id}">Name: + <input type="text" id="rsvp-name-${event.id}" class="rsvp-name" name="name" required /> </label> - <label for="rsvp-email">Email: - <input type="email" class="rsvp-email" name="email" required /> + <label for="rsvp-email-${event.id}">Email: + <input type="email" id="rsvp-email-${event.id}" class="rsvp-email" name="email" required /> </label> </form> @@ -69,8 +69,7 @@ export const EventCard = (e) => { </main> <footer> <span> - ${e.rsvps.length} - ${isPast ? 'went' : 'going'} + ${e.rsvps?.length || 0} ${isPast ? 'went' : 'going'} </span> ${!isPast ? ` <button role="button" data-target="modal-event-${e.id}" class="toggle-modal" diff --git a/frontend/src/components/Forms.js b/frontend/src/components/Forms.js index 9a0c087..8e9160f 100644 --- a/frontend/src/components/Forms.js +++ b/frontend/src/components/Forms.js @@ -46,15 +46,14 @@ export const setupForm = (form) => { }); if (btn) { - const inputs = form.querySelectorAll('input.rsvp-email'); - for (let input of inputs) { - input.addEventListener('input', () => { - if (!btn.textContent.startsWith('Submit')) { - btn.removeAttribute('disabled'); - btn.textContent = 'Submit RSVP'; - } - }); - } + const emailInput = form.querySelector('input.rsvp-email'); + emailInput.addEventListener('input', () => { + if (btn.textContent !== 'Submit RSVP') { + btn.removeAttribute('disabled'); + btn.textContent = 'Submit RSVP'; + } + }); + } }; diff --git a/frontend/src/components/Header.js b/frontend/src/components/Header.js index cd50ba6..f28f8e5 100644 --- a/frontend/src/components/Header.js +++ b/frontend/src/components/Header.js @@ -14,16 +14,16 @@ const Header = ` </header> `; -export function setupThemeToggle() { - const toggleDarkMode = () => { - const doc = document.documentElement; - const currentTheme = doc.getAttribute('data-theme'); - if (currentTheme === 'dark') { - doc.setAttribute('data-theme', 'lite'); - } else if (currentTheme === 'light') { - doc.setAttribute('data-theme', 'dark'); - } +const toggleDarkMode = () => { + const doc = document.documentElement; + const currentTheme = doc.getAttribute('data-theme'); + if (currentTheme === 'dark') { + doc.setAttribute('data-theme', 'lite'); + } else if (currentTheme === 'light') { + doc.setAttribute('data-theme', 'dark'); } +} +export function setupThemeToggle() { const themeToggle = document.getElementById(themeToggleId); themeToggle.addEventListener('click', toggleDarkMode); diff --git a/frontend/src/style.css b/frontend/src/style.css index c7ca91e..f13008b 100644 --- a/frontend/src/style.css +++ b/frontend/src/style.css @@ -69,6 +69,7 @@ article.event main { article.event img { object-fit: cover; width: 100%; + height: 200px; display: block; padding: 0px; margin: 0px; diff --git a/frontend/test/components/Events.test.js b/frontend/test/components/Events.test.js new file mode 100644 index 0000000..326c328 --- /dev/null +++ b/frontend/test/components/Events.test.js @@ -0,0 +1,144 @@ +import { describe, it, expect, beforeEach, vi } from 'vitest' +import { EventCard, EventModal, EventsSection } from '../../src/components/Events.js' + +describe('Events', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + describe('EventCard', () => { + it('should render event card with basic information', () => { + const event = { + id: 1, + host_id: 2, + title: 'Test Event', + date: '2024-12-25', + host: { name: 'John Doe' }, + description: 'A test event', + rsvps: [], + image_url: 'https://example.com/image.jpg' + } + + const result = EventCard(event) + + expect(result).toContain('Test Event') + expect(result).toContain('John Doe') + expect(result).toContain('A test event') + expect(result).toContain('https://example.com/image.jpg') + expect(result).toContain('0 went') + }) + + it('should handle missing optional fields', () => { + const event = { + id: 1, + title: 'Test Event', + date: '2024-12-25T10:00:00Z', + host_id: 123, + rsvps: [], + image_url: 'http://image.web' + } + + const result = EventCard(event) + + expect(result).toContain('Test Event') + expect(result).toContain('User 123') + expect(result).not.toContain('undefined') + }) + + it('should show past events correctly', () => { + + const event = { + id: 1, + host_id: 4, + title: 'Past Event', + date: '1995-01-01', + host: { name: 'John Doe' }, + image_url: 'http://image.web', + rsvps: [{}, {}] // 2 RSVPs + } + + const result = EventCard(event); + + expect(result).toContain('2 went'); + expect(result).not.toContain('<button role="button" data-target="modal-event-1" '); + }) + + it('should show upcoming events with RSVP button', () => { + const futureDate = new Date() + futureDate.setDate(futureDate.getDate() + 1) // Tomorrow + + const event = { + id: 1, + host_id: 6, + title: 'Future Event', + date: futureDate.toISOString(), + host: { name: 'John Doe' }, + image_url: 'http://image.web', + rsvps: [] + } + + const result = EventCard(event) + expect(result).toContain('0 going') + expect(result).toContain('RSVP') + }) + }) + + describe('EventModal', () => { + it('should render modal with correct form structure', () => { + const event = { + id: 1, + title: 'Test Event', + date: '2029-12-31', + host_id: 1, + image_url: 'http://image.web' + } + + const result = EventModal(event) + + expect(result).toContain('modal-event-1') + expect(result).toContain('rsvp-form-1') + expect(result).toContain('RSVP to Test Event') + expect(result).toContain('input type="text"') + expect(result).toContain('input type="email"') + }) + + }) + + describe('EventsSection', () => { + it('should render section with events', () => { + const events = [ + { + id: 1, + title: 'Event 1', + date: '2024-12-25T10:00:00Z', + host_id: 1, + host: { name: 'Host 1' }, + image_url: 'http://image.web', + rsvps: [] + }, + { + id: 2, + host_id: 5, + title: 'Event 2', + date: '2024-12-26T10:00:00Z', + host: { name: 'Host 2' }, + image_url: 'http://image.web', + rsvps: [] + } + ] + + const result = EventsSection('Upcoming', events) + + expect(result).toContain('Upcoming events') + expect(result).toContain('Event 1') + expect(result).toContain('Event 2') + }) + + it('should handle empty events array', () => { + const result = EventsSection('Past', []) + + expect(result).toContain('Past events') + expect(result).toContain('No events') + }) + }) +}) diff --git a/frontend/test/components/Header.test.js b/frontend/test/components/Header.test.js new file mode 100644 index 0000000..d6bf564 --- /dev/null +++ b/frontend/test/components/Header.test.js @@ -0,0 +1,73 @@ +import { describe, it, expect, beforeEach, vi } from 'vitest' +import { setupThemeToggle } from '../../src/components/Header.js' + +// Mock DOM elements +const mockDocument = { + documentElement: { + getAttribute: vi.fn(), + setAttribute: vi.fn() + }, + getElementById: vi.fn() +} + +global.document = mockDocument + +describe('Header', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + describe('toggleDarkMode', () => { + it('should toggle from dark to light theme', () => { + // Setup: current theme is dark + mockDocument.documentElement.getAttribute.mockReturnValue('dark') + + const mockElement = { + addEventListener: vi.fn() + } + mockDocument.getElementById.mockReturnValue(mockElement) + + // Get the toggle function by setting up the event listener + setupThemeToggle() + const clickHandler = mockElement.addEventListener.mock.calls[0][1] + + // Execute the toggle function + clickHandler() + + expect(mockDocument.documentElement.setAttribute).toHaveBeenCalledWith('data-theme', 'light') + }) + + it('should toggle from light to dark theme', () => { + // Setup: current theme is light + mockDocument.documentElement.getAttribute.mockReturnValue('light') + + const mockElement = { + addEventListener: vi.fn() + } + mockDocument.getElementById.mockReturnValue(mockElement) + + setupThemeToggle() + const clickHandler = mockElement.addEventListener.mock.calls[0][1] + + clickHandler() + + // This should work correctly + expect(mockDocument.documentElement.setAttribute).toHaveBeenCalledWith('data-theme', 'dark') + }) + + + describe('setupThemeToggle', () => { + it('should add click event listener to theme toggle button', () => { + const mockElement = { + addEventListener: vi.fn() + } + mockDocument.getElementById.mockReturnValue(mockElement) + + setupThemeToggle() + + expect(mockDocument.getElementById).toHaveBeenCalledWith('theme') + expect(mockElement.addEventListener).toHaveBeenCalledWith('click', expect.any(Function)) + }) + }) + }) +});
\ No newline at end of file diff --git a/frontend/vite.config.js b/frontend/vite.config.js index 5e738f3..e9817b5 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -1,10 +1,8 @@ import { defineConfig } from 'vite' export default defineConfig({ - - server: { - proxy: { - '/api': 'http://localhost:3000/api', - } + test: { + globals: true, + include: ['test/**/*.{test,spec}.{js,ts}'], } })
\ No newline at end of file |
