summaryrefslogtreecommitdiff
path: root/frontend
diff options
context:
space:
mode:
authorAnjana Vakil <contact@anjana.dev>2025-08-27 08:49:51 -0500
committerAnjana Vakil <contact@anjana.dev>2025-08-27 08:49:51 -0500
commit0a413efa2f7784f176d1ee533d9b404b423713a4 (patch)
tree553ef412d606bfbc456374f0b12af3f2311da23e /frontend
parent1dc4f56425209d4ce1d7bb78ec8b5e7b5a755a82 (diff)
exercise
Diffstat (limited to 'frontend')
-rw-r--r--frontend/package-lock.json494
-rw-r--r--frontend/package.json9
-rw-r--r--frontend/src/components/Events.js13
-rw-r--r--frontend/src/components/Forms.js17
-rw-r--r--frontend/src/components/Header.js18
-rw-r--r--frontend/src/style.css1
-rw-r--r--frontend/test/components/Events.test.js144
-rw-r--r--frontend/test/components/Header.test.js73
-rw-r--r--frontend/vite.config.js8
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