From 75cbf0957490e118a01bf89a8fe4442cb567e0b2 Mon Sep 17 00:00:00 2001 From: Anish Kancherla Date: Fri, 5 Dec 2025 22:46:37 -0800 Subject: [PATCH 1/2] adding tests --- eslint.config.mjs | 2 +- jest.config.js | 17 + jest.setup.js | 2 + package-lock.json | 1066 ++--------------- package.json | 2 + src/__tests__/api/gemini.test.ts | 170 +++ .../database/api/ingredients.test.ts | 90 ++ src/__tests__/database/api/recipes.test.ts | 107 ++ src/__tests__/lib/ai.test.ts | 61 + src/__tests__/mocks/gemini.mock.js | 27 + src/__tests__/mocks/supabase.mock.js | 21 + 11 files changed, 578 insertions(+), 987 deletions(-) create mode 100644 jest.config.js create mode 100644 jest.setup.js create mode 100644 src/__tests__/api/gemini.test.ts create mode 100644 src/__tests__/database/api/ingredients.test.ts create mode 100644 src/__tests__/database/api/recipes.test.ts create mode 100644 src/__tests__/lib/ai.test.ts create mode 100644 src/__tests__/mocks/gemini.mock.js create mode 100644 src/__tests__/mocks/supabase.mock.js diff --git a/eslint.config.mjs b/eslint.config.mjs index 810edbf..9eacdf7 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -11,7 +11,7 @@ export default [ }, { files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"] }, { - ignores: [".next/", "out/", "node_modules", "next-env.d.ts"], + ignores: [".next/", "out/", "node_modules", "next-env.d.ts", "**/__tests__/**"], }, { files: ["**/*.js"], languageOptions: { sourceType: "commonjs" } }, { languageOptions: { globals: { ...globals.node } } }, diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..154fdb8 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,17 @@ +const nextJest = require("next/jest"); + +const createJestConfig = nextJest({ + dir: "./", +}); + +const customJestConfig = { + testEnvironment: "jest-environment-node", + testMatch: ["**/__tests__/**/*.test.[jt]s?(x)", "**/?(*.)+(spec|test).[jt]s?(x)"], + moduleNameMapper: { + "^@/(.*)$": "/src/$1", + }, + setupFilesAfterEnv: ["/jest.setup.js"], +}; + +module.exports = createJestConfig(customJestConfig); + diff --git a/jest.setup.js b/jest.setup.js new file mode 100644 index 0000000..f279e64 --- /dev/null +++ b/jest.setup.js @@ -0,0 +1,2 @@ +// jest setup file + diff --git a/package-lock.json b/package-lock.json index b163f06..ba35490 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,6 +38,7 @@ "husky": "9.1.7", "jest": "^30.2.0", "jest-environment-jsdom": "^30.2.0", + "jest-environment-node": "^30.2.0", "postcss": "8.5.3", "prettier": "3.5.3", "prettier-plugin-tailwindcss": "0.6.11", @@ -772,6 +773,7 @@ "version": "1.7.1", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz", "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -1069,447 +1071,10 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@img/sharp-darwin-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", - "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.2.4" - } - }, - "node_modules/@img/sharp-darwin-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", - "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.2.4" - } - }, - "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", - "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", - "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", - "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", - "cpu": [ - "arm" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", - "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-ppc64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", - "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", - "cpu": [ - "ppc64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-riscv64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", - "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", - "cpu": [ - "riscv64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", - "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", - "cpu": [ - "s390x" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", - "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", - "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", - "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-linux-arm": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", - "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", - "cpu": [ - "arm" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.2.4" - } - }, - "node_modules/@img/sharp-linux-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", - "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.2.4" - } - }, - "node_modules/@img/sharp-linux-ppc64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", - "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", - "cpu": [ - "ppc64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-ppc64": "1.2.4" - } - }, - "node_modules/@img/sharp-linux-riscv64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", - "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", - "cpu": [ - "riscv64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-riscv64": "1.2.4" - } - }, - "node_modules/@img/sharp-linux-s390x": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", - "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", - "cpu": [ - "s390x" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.2.4" - } - }, - "node_modules/@img/sharp-linux-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", - "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.2.4" - } - }, - "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", - "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" - } - }, - "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", - "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.2.4" - } - }, - "node_modules/@img/sharp-wasm32": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", - "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", - "cpu": [ - "wasm32" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", - "optional": true, - "dependencies": { - "@emnapi/runtime": "^1.7.0" - }, - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", - "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-ia32": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", - "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", - "cpu": [ - "ia32" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", + "license": "MIT", "optional": true, - "os": [ - "win32" - ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" + "node": ">=18" } }, "node_modules/@img/sharp-win32-x64": { @@ -2139,118 +1704,6 @@ "integrity": "sha512-3qBGRW+sCGzgbpc5TS1a0p7eNxnOarGVQhZxfvTdnV0gFI61lX7QNtQ4V1TSREctXzYn5NetbUsLvyqwLFJM6Q==", "license": "MIT" }, - "node_modules/@next/swc-darwin-arm64": { - "version": "15.5.6", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.6.tgz", - "integrity": "sha512-ES3nRz7N+L5Umz4KoGfZ4XX6gwHplwPhioVRc25+QNsDa7RtUF/z8wJcbuQ2Tffm5RZwuN2A063eapoJ1u4nPg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-darwin-x64": { - "version": "15.5.6", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.6.tgz", - "integrity": "sha512-JIGcytAyk9LQp2/nuVZPAtj8uaJ/zZhsKOASTjxDug0SPU9LAM3wy6nPU735M1OqacR4U20LHVF5v5Wnl9ptTA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-gnu": { - "version": "15.5.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.6.tgz", - "integrity": "sha512-qvz4SVKQ0P3/Im9zcS2RmfFL/UCQnsJKJwQSkissbngnB/12c6bZTCB0gHTexz1s6d/mD0+egPKXAIRFVS7hQg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-musl": { - "version": "15.5.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.6.tgz", - "integrity": "sha512-FsbGVw3SJz1hZlvnWD+T6GFgV9/NYDeLTNQB2MXoPN5u9VA9OEDy6fJEfePfsUKAhJufFbZLgp0cPxMuV6SV0w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-gnu": { - "version": "15.5.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.6.tgz", - "integrity": "sha512-3QnHGFWlnvAgyxFxt2Ny8PTpXtQD7kVEeaFat5oPAHHI192WKYB+VIKZijtHLGdBBvc16tiAkPTDmQNOQ0dyrA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-musl": { - "version": "15.5.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.6.tgz", - "integrity": "sha512-OsGX148sL+TqMK9YFaPFPoIaJKbFJJxFzkXZljIgA9hjMjdruKht6xDCEv1HLtlLNfkx3c5w2GLKhj7veBQizQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-arm64-msvc": { - "version": "15.5.6", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.6.tgz", - "integrity": "sha512-ONOMrqWxdzXDJNh2n60H6gGyKed42Ieu6UTVPZteXpuKbLZTH4G4eBMsr5qWgOBA+s7F+uB4OJbZnrkEDnZ5Fg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, "node_modules/@next/swc-win32-x64-msvc": { "version": "15.5.6", "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.6.tgz", @@ -2404,248 +1857,78 @@ "license": "MIT", "dependencies": { "@supabase/node-fetch": "2.6.15", - "@types/phoenix": "^1.6.6", - "@types/ws": "^8.18.1", - "tslib": "2.8.1", - "ws": "^8.18.2" - } - }, - "node_modules/@supabase/storage-js": { - "version": "2.76.1", - "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.76.1.tgz", - "integrity": "sha512-OJiNT8tocI9tcTjTjv1SBVLabzgEnS1NorZuqivkiJ0gTYmeg2c2PFmqCARhoQ4whF6zR9MVsX/Mtj2oSv4i/w==", - "license": "MIT", - "dependencies": { - "@supabase/node-fetch": "2.6.15", - "tslib": "2.8.1" - } - }, - "node_modules/@supabase/supabase-js": { - "version": "2.76.1", - "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.76.1.tgz", - "integrity": "sha512-dYMh9EsTVXZ6WbQ0QmMGIhbXct5+x636tXXaaxUmwjj3kY1jyBTQU8QehxAIfjyRu1mWGV07hoYmTYakkxdSGQ==", - "license": "MIT", - "dependencies": { - "@supabase/auth-js": "2.76.1", - "@supabase/functions-js": "2.76.1", - "@supabase/node-fetch": "2.6.15", - "@supabase/postgrest-js": "2.76.1", - "@supabase/realtime-js": "2.76.1", - "@supabase/storage-js": "2.76.1" - } - }, - "node_modules/@swc/helpers": { - "version": "0.5.15", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", - "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.8.0" - } - }, - "node_modules/@tailwindcss/node": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.0.17.tgz", - "integrity": "sha512-LIdNwcqyY7578VpofXyqjH6f+3fP4nrz7FBLki5HpzqjYfXdF2m/eW18ZfoKePtDGg90Bvvfpov9d2gy5XVCbg==", - "dev": true, - "license": "MIT", - "dependencies": { - "enhanced-resolve": "^5.18.1", - "jiti": "^2.4.2", - "tailwindcss": "4.0.17" - } - }, - "node_modules/@tailwindcss/oxide": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.0.17.tgz", - "integrity": "sha512-B4OaUIRD2uVrULpAD1Yksx2+wNarQr2rQh65nXqaqbLY1jCd8fO+3KLh/+TH4Hzh2NTHQvgxVbPdUDOtLk7vAw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10" - }, - "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.0.17", - "@tailwindcss/oxide-darwin-arm64": "4.0.17", - "@tailwindcss/oxide-darwin-x64": "4.0.17", - "@tailwindcss/oxide-freebsd-x64": "4.0.17", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.0.17", - "@tailwindcss/oxide-linux-arm64-gnu": "4.0.17", - "@tailwindcss/oxide-linux-arm64-musl": "4.0.17", - "@tailwindcss/oxide-linux-x64-gnu": "4.0.17", - "@tailwindcss/oxide-linux-x64-musl": "4.0.17", - "@tailwindcss/oxide-win32-arm64-msvc": "4.0.17", - "@tailwindcss/oxide-win32-x64-msvc": "4.0.17" - } - }, - "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.0.17.tgz", - "integrity": "sha512-3RfO0ZK64WAhop+EbHeyxGThyDr/fYhxPzDbEQjD2+v7ZhKTb2svTWy+KK+J1PHATus2/CQGAGp7pHY/8M8ugg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.0.17.tgz", - "integrity": "sha512-e1uayxFQCCDuzTk9s8q7MC5jFN42IY7nzcr5n0Mw/AcUHwD6JaBkXnATkD924ZsHyPDvddnusIEvkgLd2CiREg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.0.17.tgz", - "integrity": "sha512-d6z7HSdOKfXQ0HPlVx1jduUf/YtBuCCtEDIEFeBCzgRRtDsUuRtofPqxIVaSCUTOk5+OfRLonje6n9dF6AH8wQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.0.17.tgz", - "integrity": "sha512-EjrVa6lx3wzXz3l5MsdOGtYIsRjgs5Mru6lDv4RuiXpguWeOb3UzGJ7vw7PEzcFadKNvNslEQqoAABeMezprxQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.0.17.tgz", - "integrity": "sha512-65zXfCOdi8wuaY0Ye6qMR5LAXokHYtrGvo9t/NmxvSZtCCitXV/gzJ/WP5ksXPhff1SV5rov0S+ZIZU+/4eyCQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.0.17.tgz", - "integrity": "sha512-+aaq6hJ8ioTdbJV5IA1WjWgLmun4T7eYLTvJIToiXLHy5JzUERRbIZjAcjgK9qXMwnvuu7rqpxzej+hGoEcG5g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" + "@types/phoenix": "^1.6.6", + "@types/ws": "^8.18.1", + "tslib": "2.8.1", + "ws": "^8.18.2" } }, - "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.0.17.tgz", - "integrity": "sha512-/FhWgZCdUGAeYHYnZKekiOC0aXFiBIoNCA0bwzkICiMYS5Rtx2KxFfMUXQVnl4uZRblG5ypt5vpPhVaXgGk80w==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@supabase/storage-js": { + "version": "2.76.1", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.76.1.tgz", + "integrity": "sha512-OJiNT8tocI9tcTjTjv1SBVLabzgEnS1NorZuqivkiJ0gTYmeg2c2PFmqCARhoQ4whF6zR9MVsX/Mtj2oSv4i/w==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" + "dependencies": { + "@supabase/node-fetch": "2.6.15", + "tslib": "2.8.1" } }, - "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.0.17.tgz", - "integrity": "sha512-gELJzOHK6GDoIpm/539Golvk+QWZjxQcbkKq9eB2kzNkOvrP0xc5UPgO9bIMNt1M48mO8ZeNenCMGt6tfkvVBg==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@supabase/supabase-js": { + "version": "2.76.1", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.76.1.tgz", + "integrity": "sha512-dYMh9EsTVXZ6WbQ0QmMGIhbXct5+x636tXXaaxUmwjj3kY1jyBTQU8QehxAIfjyRu1mWGV07hoYmTYakkxdSGQ==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" + "dependencies": { + "@supabase/auth-js": "2.76.1", + "@supabase/functions-js": "2.76.1", + "@supabase/node-fetch": "2.6.15", + "@supabase/postgrest-js": "2.76.1", + "@supabase/realtime-js": "2.76.1", + "@supabase/storage-js": "2.76.1" + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" } }, - "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "node_modules/@tailwindcss/node": { "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.0.17.tgz", - "integrity": "sha512-68NwxcJrZn94IOW4TysMIbYv5AlM6So1luTlbYUDIGnKma1yTFGBRNEJ+SacJ3PZE2rgcTBNRHX1TB4EQ/XEHw==", - "cpu": [ - "x64" - ], + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.0.17.tgz", + "integrity": "sha512-LIdNwcqyY7578VpofXyqjH6f+3fP4nrz7FBLki5HpzqjYfXdF2m/eW18ZfoKePtDGg90Bvvfpov9d2gy5XVCbg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" + "dependencies": { + "enhanced-resolve": "^5.18.1", + "jiti": "^2.4.2", + "tailwindcss": "4.0.17" } }, - "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "node_modules/@tailwindcss/oxide": { "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.0.17.tgz", - "integrity": "sha512-AkBO8efP2/7wkEXkNlXzRD4f/7WerqKHlc6PWb5v0jGbbm22DFBLbIM19IJQ3b+tNewQZa+WnPOaGm0SmwMNjw==", - "cpu": [ - "arm64" - ], + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.0.17.tgz", + "integrity": "sha512-B4OaUIRD2uVrULpAD1Yksx2+wNarQr2rQh65nXqaqbLY1jCd8fO+3KLh/+TH4Hzh2NTHQvgxVbPdUDOtLk7vAw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], "engines": { "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.0.17", + "@tailwindcss/oxide-darwin-arm64": "4.0.17", + "@tailwindcss/oxide-darwin-x64": "4.0.17", + "@tailwindcss/oxide-freebsd-x64": "4.0.17", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.0.17", + "@tailwindcss/oxide-linux-arm64-gnu": "4.0.17", + "@tailwindcss/oxide-linux-arm64-musl": "4.0.17", + "@tailwindcss/oxide-linux-x64-gnu": "4.0.17", + "@tailwindcss/oxide-linux-x64-musl": "4.0.17", + "@tailwindcss/oxide-win32-arm64-msvc": "4.0.17", + "@tailwindcss/oxide-win32-x64-msvc": "4.0.17" } }, "node_modules/@tailwindcss/oxide-win32-x64-msvc": { @@ -3973,9 +3256,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.8.31", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.31.tgz", - "integrity": "sha512-a28v2eWrrRWPpJSzxc+mKwm0ZtVx/G8SepdQZDArnXYU/XS+IF6mp8aB/4E+hH1tyGCoDo3KlUCdlSxGDsRkAw==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.3.tgz", + "integrity": "sha512-8QdH6czo+G7uBsNo0GiUfouPN1lRzKdJTGnKXwe12gkFbnnOUaUKGN55dMkfy+mnxmvjwl9zcI4VncczcVXDhA==", "dev": true, "license": "Apache-2.0", "bin": { @@ -4014,9 +3297,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz", - "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "dev": true, "funding": [ { @@ -4034,11 +3317,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.8.25", - "caniuse-lite": "^1.0.30001754", - "electron-to-chromium": "^1.5.249", + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", - "update-browserslist-db": "^1.1.4" + "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" @@ -4141,9 +3424,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001757", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001757.tgz", - "integrity": "sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==", + "version": "1.0.30001759", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001759.tgz", + "integrity": "sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw==", "funding": [ { "type": "opencollective", @@ -4703,9 +3986,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.262", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.262.tgz", - "integrity": "sha512-NlAsMteRHek05jRUxUR0a5jpjYq9ykk6+kO0yRaMi5moe7u0fVIOeQ3Y30A8dIiWFBNUoQGi1ljb1i5VtS9WQQ==", + "version": "1.5.266", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.266.tgz", + "integrity": "sha512-kgWEglXvkEfMH7rxP5OSZZwnaDWT7J9EoZCujhnpLbfi0bbNtRkgdX2E3gt0Uer11c61qCYktB3hwkAS325sJg==", "dev": true, "license": "ISC" }, @@ -7989,195 +7272,6 @@ "lightningcss-win32-x64-msvc": "1.29.2" } }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.29.2.tgz", - "integrity": "sha512-cK/eMabSViKn/PG8U/a7aCorpeKLMlK0bQeNHmdb7qUnBkNPnL+oV5DjJUo0kqWsJUapZsM4jCfYItbqBDvlcA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.29.2.tgz", - "integrity": "sha512-j5qYxamyQw4kDXX5hnnCKMf3mLlHvG44f24Qyi2965/Ycz829MYqjrVg2H8BidybHBp9kom4D7DR5VqCKDXS0w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.29.2.tgz", - "integrity": "sha512-wDk7M2tM78Ii8ek9YjnY8MjV5f5JN2qNVO+/0BAGZRvXKtQrBC4/cn4ssQIpKIPP44YXw6gFdpUF+Ps+RGsCwg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.29.2.tgz", - "integrity": "sha512-IRUrOrAF2Z+KExdExe3Rz7NSTuuJ2HvCGlMKoquK5pjvo2JY4Rybr+NrKnq0U0hZnx5AnGsuFHjGnNT14w26sg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.29.2.tgz", - "integrity": "sha512-KKCpOlmhdjvUTX/mBuaKemp0oeDIBBLFiU5Fnqxh1/DZ4JPZi4evEH7TKoSBFOSOV3J7iEmmBaw/8dpiUvRKlQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.29.2.tgz", - "integrity": "sha512-Q64eM1bPlOOUgxFmoPUefqzY1yV3ctFPE6d/Vt7WzLW4rKTv7MyYNky+FWxRpLkNASTnKQUaiMJ87zNODIrrKQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.29.2.tgz", - "integrity": "sha512-0v6idDCPG6epLXtBH/RPkHvYx74CVziHo6TMYga8O2EiQApnUPZsbR9nFNrg2cgBzk1AYqEd95TlrsL7nYABQg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.29.2.tgz", - "integrity": "sha512-rMpz2yawkgGT8RULc5S4WiZopVMOFWjiItBT7aSfDX4NQav6M44rhn5hjtkKzB+wMTRlLLqxkeYEtQ3dd9696w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.29.2.tgz", - "integrity": "sha512-nL7zRW6evGQqYVu/bKGK+zShyz8OVzsCotFgc7judbt6wnB2KbiKKJwBE4SGoDBQ1O94RjW4asrCjQL4i8Fhbw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, "node_modules/lightningcss-win32-x64-msvc": { "version": "1.29.2", "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.29.2.tgz", @@ -8629,9 +7723,9 @@ } }, "node_modules/nwsapi": { - "version": "2.2.22", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.22.tgz", - "integrity": "sha512-ujSMe1OWVn55euT1ihwCI1ZcAaAU3nxUiDwfDQldc51ZXaB9m2AyOn6/jh1BLe2t/G8xd6uKG1UBF2aZJeg2SQ==", + "version": "2.2.23", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.23.tgz", + "integrity": "sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==", "dev": true, "license": "MIT" }, @@ -10877,9 +9971,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", - "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.2.tgz", + "integrity": "sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA==", "dev": true, "funding": [ { diff --git a/package.json b/package.json index cb90eb2..add8b8b 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,8 @@ "format": "npx prettier --write .", "check": "npx prettier --check .", "eslint": "npx eslint -c eslint.config.mjs", + "test": "jest", + "test:watch": "jest --watch", "prepare": "husky" }, "dependencies": { diff --git a/src/__tests__/api/gemini.test.ts b/src/__tests__/api/gemini.test.ts new file mode 100644 index 0000000..f78d5a3 --- /dev/null +++ b/src/__tests__/api/gemini.test.ts @@ -0,0 +1,170 @@ +import { NextRequest } from "next/server"; + +const mockSupabase = { + auth: { + getUser: jest.fn(), + }, + from: jest.fn(), +}; + +const mockGeminiClient = { + models: { + generateContent: jest.fn(), + }, +}; + +jest.mock("@supabase/supabase-js", () => ({ + createClient: jest.fn(() => mockSupabase), +})); + +jest.mock("@google/genai", () => ({ + GoogleGenAI: jest.fn(() => mockGeminiClient), + Type: {}, +})); + +describe("gemini api route", () => { + beforeEach(() => { + jest.clearAllMocks(); + process.env.GEMINI_API_KEY = "test-key"; + }); + + test("returns error if no auth token", async () => { + const { POST } = await import("@/app/api/gemini/route"); + + const request = new NextRequest("http://localhost/api/gemini", { + method: "POST", + body: JSON.stringify({ + message: "test", + conversationHistory: [], + }), + }); + + const response = await POST(request); + const data = await response.json(); + + expect(response.status).toBe(401); + expect(data.error).toBe("Authentication required"); + }); + + test("calls gemini with correct parameters", async () => { + const { POST } = await import("@/app/api/gemini/route"); + + const mockUser = { id: "user123" }; + const mockResponse = { + text: "Here's a recipe!", + functionCalls: [], + candidates: [ + { + content: { + parts: [{ text: "Here's a recipe!" }], + }, + }, + ], + }; + + mockSupabase.auth.getUser.mockResolvedValue({ + data: { user: mockUser }, + }); + + mockGeminiClient.models.generateContent.mockResolvedValue(mockResponse); + + const request = new NextRequest("http://localhost/api/gemini", { + method: "POST", + body: JSON.stringify({ + message: "make me a recipe", + conversationHistory: [], + authToken: "token123", + useDietaryPreferences: true, + usePreferredCuisines: true, + onlyInventoryIngredients: false, + }), + }); + + const response = await POST(request); + const data = await response.json(); + + expect(mockGeminiClient.models.generateContent).toHaveBeenCalled(); + expect(data.text).toBe("Here's a recipe!"); + }); + + test("handles function calls for get_user_ingredients", async () => { + const { POST } = await import("@/app/api/gemini/route"); + + const mockUser = { id: "user123" }; + const mockIngredients = [ + { id: "1", name: "flour", quantity: 2, unit: "cups" }, + ]; + + mockSupabase.auth.getUser.mockResolvedValue({ + data: { user: mockUser }, + }); + + const mockQuery = { + select: jest.fn().mockReturnThis(), + order: jest.fn().mockReturnThis(), + eq: jest.fn().mockResolvedValue({ + data: mockIngredients, + error: null, + }), + }; + + mockSupabase.from.mockReturnValue(mockQuery); + + const functionCallResponse = { + text: "", + functionCalls: [ + { + name: "get_user_ingredients", + args: {}, + }, + ], + candidates: [ + { + content: { + parts: [ + { + functionCall: { + name: "get_user_ingredients", + args: {}, + }, + }, + ], + }, + }, + ], + }; + + const finalResponse = { + text: "Based on your ingredients, here's a recipe!", + functionCalls: [], + candidates: [ + { + content: { + parts: [{ text: "Based on your ingredients, here's a recipe!" }], + }, + }, + ], + }; + + mockGeminiClient.models.generateContent + .mockResolvedValueOnce(functionCallResponse) + .mockResolvedValueOnce(finalResponse); + + const request = new NextRequest("http://localhost/api/gemini", { + method: "POST", + body: JSON.stringify({ + message: "what can i make", + conversationHistory: [], + authToken: "token123", + onlyInventoryIngredients: true, + }), + }); + + const response = await POST(request); + const data = await response.json(); + + expect(data.text).toBe("Based on your ingredients, here's a recipe!"); + expect(mockSupabase.from).toHaveBeenCalledWith("ingredients"); + }); +}); + diff --git a/src/__tests__/database/api/ingredients.test.ts b/src/__tests__/database/api/ingredients.test.ts new file mode 100644 index 0000000..a364869 --- /dev/null +++ b/src/__tests__/database/api/ingredients.test.ts @@ -0,0 +1,90 @@ +import { getIngredients, createIngredient } from "@/database/api/ingredients"; +import { supabase } from "@/database/supabase"; + +jest.mock("@/database/supabase", () => { + const mockSupabase = { + auth: { + getUser: jest.fn(), + }, + from: jest.fn(), + }; + return { + supabase: mockSupabase, + }; +}); + +describe("ingredients api", () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + test("getIngredients returns user ingredients", async () => { + const mockUser = { id: "user123" }; + const mockIngredients = [ + { id: "1", name: "flour", quantity: 2, unit: "cups" }, + { id: "2", name: "sugar", quantity: 1, unit: "cup" }, + ]; + + (supabase.auth.getUser as jest.Mock).mockResolvedValue({ + data: { user: mockUser }, + }); + + const mockFinalOrder = { + order: jest.fn().mockResolvedValue({ + data: mockIngredients, + error: null, + }), + }; + + const mockFirstOrder = { + order: jest.fn().mockReturnValue(mockFinalOrder), + }; + + const mockEq = { + eq: jest.fn().mockReturnValue(mockFirstOrder), + }; + + const mockSelect = { + select: jest.fn().mockReturnValue(mockEq), + }; + + (supabase.from as jest.Mock).mockReturnValue(mockSelect); + + const result = await getIngredients(); + + expect(result).toEqual(mockIngredients); + expect(supabase.from).toHaveBeenCalledWith("ingredients"); + }); + + test("createIngredient adds new ingredient", async () => { + const mockUser = { id: "user123" }; + const newIngredient = { + name: "milk", + quantity: 1, + unit: "cup", + category: "Dairy", + notes: null, + }; + + (supabase.auth.getUser as jest.Mock).mockResolvedValue({ + data: { user: mockUser }, + }); + + const mockQuery = { + insert: jest.fn().mockReturnThis(), + select: jest.fn().mockReturnThis(), + single: jest.fn().mockResolvedValue({ + data: { id: "new123", ...newIngredient, user_id: mockUser.id }, + error: null, + }), + }; + + (supabase.from as jest.Mock).mockReturnValue(mockQuery); + + const result = await createIngredient(newIngredient); + + expect(result.name).toBe("milk"); + expect(mockQuery.insert).toHaveBeenCalled(); + }); +}); + diff --git a/src/__tests__/database/api/recipes.test.ts b/src/__tests__/database/api/recipes.test.ts new file mode 100644 index 0000000..a37e092 --- /dev/null +++ b/src/__tests__/database/api/recipes.test.ts @@ -0,0 +1,107 @@ +import { createRecipe, getRecipes } from "@/database/api/recipes"; +import { supabase } from "@/database/supabase"; + +jest.mock("@/database/supabase", () => { + const mockSupabase = { + auth: { + getUser: jest.fn(), + }, + from: jest.fn(), + }; + return { + supabase: mockSupabase, + }; +}); + +describe("recipes api", () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + test("getRecipes returns user recipes", async () => { + const mockUser = { id: "user123" }; + const mockRecipes = [ + { + id: "1", + name: "Pasta", + instructions: "Cook pasta", + user_id: "user123", + }, + ]; + + (supabase.auth.getUser as jest.Mock).mockResolvedValue({ + data: { user: mockUser }, + }); + + const mockQuery = { + select: jest.fn().mockReturnThis(), + eq: jest.fn().mockReturnThis(), + order: jest.fn().mockResolvedValue({ + data: mockRecipes, + error: null, + }), + }; + + (supabase.from as jest.Mock).mockReturnValue(mockQuery); + + const result = await getRecipes(); + + expect(result).toEqual(mockRecipes); + expect(supabase.from).toHaveBeenCalledWith("recipes"); + }); + + test("createRecipe saves recipe with ingredients", async () => { + const mockUser = { id: "user123" }; + const recipeData = { + name: "Pancakes", + description: "Yummy pancakes", + instructions: "Mix and cook", + prep_time: 10, + cook_time: 5, + difficulty: "easy" as const, + rating: null, + servings: 2, + cuisine_type: "American", + source: "user" as const, + parent_recipe_id: null, + image_url: null, + ingredients: [ + { + ingredient_name: "flour", + quantity: 1, + unit: "cup", + notes: null, + }, + ], + }; + + (supabase.auth.getUser as jest.Mock).mockResolvedValue({ + data: { user: mockUser }, + }); + + const mockRecipeQuery = { + insert: jest.fn().mockReturnThis(), + select: jest.fn().mockReturnThis(), + single: jest.fn().mockResolvedValue({ + data: { id: "recipe123", ...recipeData, user_id: mockUser.id }, + error: null, + }), + }; + + const mockIngredientQuery = { + insert: jest.fn().mockResolvedValue({ + error: null, + }), + }; + + (supabase.from as jest.Mock) + .mockReturnValueOnce(mockRecipeQuery) + .mockReturnValueOnce(mockIngredientQuery); + + const result = await createRecipe(recipeData); + + expect(result.name).toBe("Pancakes"); + expect(mockRecipeQuery.insert).toHaveBeenCalled(); + }); +}); + diff --git a/src/__tests__/lib/ai.test.ts b/src/__tests__/lib/ai.test.ts new file mode 100644 index 0000000..14c56f5 --- /dev/null +++ b/src/__tests__/lib/ai.test.ts @@ -0,0 +1,61 @@ +import { sendMessageToAI } from "@/lib/ai"; +import { supabase } from "@/database/supabase"; + +jest.mock("@/database/supabase"); + +describe("ai service", () => { + beforeEach(() => { + jest.clearAllMocks(); + global.fetch = jest.fn(); + }); + + test("sends message to api route", async () => { + const mockSession = { + data: { + session: { + access_token: "token123", + }, + }, + }; + + (supabase.auth.getSession as jest.Mock).mockResolvedValue(mockSession); + + const mockApiResponse = { + text: "Here's a recipe!", + }; + + (global.fetch as jest.Mock).mockResolvedValue({ + ok: true, + json: async () => mockApiResponse, + }); + + const result = await sendMessageToAI("make me pasta", []); + + expect(result.text).toBe("Here's a recipe!"); + expect(global.fetch).toHaveBeenCalledWith("/api/gemini", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + message: "make me pasta", + conversationHistory: [], + authToken: "token123", + useDietaryPreferences: true, + usePreferredCuisines: true, + onlyInventoryIngredients: false, + }), + }); + }); + + test("throws error if not authenticated", async () => { + (supabase.auth.getSession as jest.Mock).mockResolvedValue({ + data: { session: null }, + }); + + await expect(sendMessageToAI("test", [])).rejects.toThrow( + "Not authenticated", + ); + }); +}); + diff --git a/src/__tests__/mocks/gemini.mock.js b/src/__tests__/mocks/gemini.mock.js new file mode 100644 index 0000000..3467cad --- /dev/null +++ b/src/__tests__/mocks/gemini.mock.js @@ -0,0 +1,27 @@ +// mock gemini ai +const mockGeminiResponse = { + text: "Here's a recipe for you!", + functionCalls: [], + candidates: [ + { + content: { + parts: [{ text: "Here's a recipe for you!" }], + }, + }, + ], +}; + +const mockGeminiClient = { + models: { + generateContent: jest.fn(() => Promise.resolve(mockGeminiResponse)), + }, +}; + +const GoogleGenAI = jest.fn(() => mockGeminiClient); + +module.exports = { + GoogleGenAI, + mockGeminiClient, + mockGeminiResponse, +}; + diff --git a/src/__tests__/mocks/supabase.mock.js b/src/__tests__/mocks/supabase.mock.js new file mode 100644 index 0000000..c0d8dfe --- /dev/null +++ b/src/__tests__/mocks/supabase.mock.js @@ -0,0 +1,21 @@ +// mock supabase client +const mockSupabaseClient = { + auth: { + getUser: jest.fn(), + getSession: jest.fn(), + }, + from: jest.fn(() => mockSupabaseClient), + select: jest.fn(() => mockSupabaseClient), + insert: jest.fn(() => mockSupabaseClient), + update: jest.fn(() => mockSupabaseClient), + delete: jest.fn(() => mockSupabaseClient), + eq: jest.fn(() => mockSupabaseClient), + single: jest.fn(() => mockSupabaseClient), + order: jest.fn(() => mockSupabaseClient), +}; + +module.exports = { + createClient: jest.fn(() => mockSupabaseClient), + mockSupabaseClient, +}; + From 27eb52732b5d946cac97b11b1a1e50baf3cf7da6 Mon Sep 17 00:00:00 2001 From: Anish Kancherla Date: Fri, 5 Dec 2025 22:49:56 -0800 Subject: [PATCH 2/2] adding tests --- eslint.config.mjs | 8 +++++++- jest.config.js | 6 ++++-- jest.setup.js | 1 - src/__tests__/api/gemini.test.ts | 7 +++---- src/__tests__/database/api/ingredients.test.ts | 1 - src/__tests__/database/api/recipes.test.ts | 1 - src/__tests__/lib/ai.test.ts | 1 - src/__tests__/mocks/gemini.mock.js | 1 - src/__tests__/mocks/supabase.mock.js | 1 - 9 files changed, 14 insertions(+), 13 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 9eacdf7..355d606 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -11,7 +11,13 @@ export default [ }, { files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"] }, { - ignores: [".next/", "out/", "node_modules", "next-env.d.ts", "**/__tests__/**"], + ignores: [ + ".next/", + "out/", + "node_modules", + "next-env.d.ts", + "**/__tests__/**", + ], }, { files: ["**/*.js"], languageOptions: { sourceType: "commonjs" } }, { languageOptions: { globals: { ...globals.node } } }, diff --git a/jest.config.js b/jest.config.js index 154fdb8..3e51990 100644 --- a/jest.config.js +++ b/jest.config.js @@ -6,7 +6,10 @@ const createJestConfig = nextJest({ const customJestConfig = { testEnvironment: "jest-environment-node", - testMatch: ["**/__tests__/**/*.test.[jt]s?(x)", "**/?(*.)+(spec|test).[jt]s?(x)"], + testMatch: [ + "**/__tests__/**/*.test.[jt]s?(x)", + "**/?(*.)+(spec|test).[jt]s?(x)", + ], moduleNameMapper: { "^@/(.*)$": "/src/$1", }, @@ -14,4 +17,3 @@ const customJestConfig = { }; module.exports = createJestConfig(customJestConfig); - diff --git a/jest.setup.js b/jest.setup.js index f279e64..426dc62 100644 --- a/jest.setup.js +++ b/jest.setup.js @@ -1,2 +1 @@ // jest setup file - diff --git a/src/__tests__/api/gemini.test.ts b/src/__tests__/api/gemini.test.ts index f78d5a3..134c25a 100644 --- a/src/__tests__/api/gemini.test.ts +++ b/src/__tests__/api/gemini.test.ts @@ -30,7 +30,7 @@ describe("gemini api route", () => { test("returns error if no auth token", async () => { const { POST } = await import("@/app/api/gemini/route"); - + const request = new NextRequest("http://localhost/api/gemini", { method: "POST", body: JSON.stringify({ @@ -48,7 +48,7 @@ describe("gemini api route", () => { test("calls gemini with correct parameters", async () => { const { POST } = await import("@/app/api/gemini/route"); - + const mockUser = { id: "user123" }; const mockResponse = { text: "Here's a recipe!", @@ -89,7 +89,7 @@ describe("gemini api route", () => { test("handles function calls for get_user_ingredients", async () => { const { POST } = await import("@/app/api/gemini/route"); - + const mockUser = { id: "user123" }; const mockIngredients = [ { id: "1", name: "flour", quantity: 2, unit: "cups" }, @@ -167,4 +167,3 @@ describe("gemini api route", () => { expect(mockSupabase.from).toHaveBeenCalledWith("ingredients"); }); }); - diff --git a/src/__tests__/database/api/ingredients.test.ts b/src/__tests__/database/api/ingredients.test.ts index a364869..27711ef 100644 --- a/src/__tests__/database/api/ingredients.test.ts +++ b/src/__tests__/database/api/ingredients.test.ts @@ -87,4 +87,3 @@ describe("ingredients api", () => { expect(mockQuery.insert).toHaveBeenCalled(); }); }); - diff --git a/src/__tests__/database/api/recipes.test.ts b/src/__tests__/database/api/recipes.test.ts index a37e092..82d603b 100644 --- a/src/__tests__/database/api/recipes.test.ts +++ b/src/__tests__/database/api/recipes.test.ts @@ -104,4 +104,3 @@ describe("recipes api", () => { expect(mockRecipeQuery.insert).toHaveBeenCalled(); }); }); - diff --git a/src/__tests__/lib/ai.test.ts b/src/__tests__/lib/ai.test.ts index 14c56f5..e900ba8 100644 --- a/src/__tests__/lib/ai.test.ts +++ b/src/__tests__/lib/ai.test.ts @@ -58,4 +58,3 @@ describe("ai service", () => { ); }); }); - diff --git a/src/__tests__/mocks/gemini.mock.js b/src/__tests__/mocks/gemini.mock.js index 3467cad..8f3be1e 100644 --- a/src/__tests__/mocks/gemini.mock.js +++ b/src/__tests__/mocks/gemini.mock.js @@ -24,4 +24,3 @@ module.exports = { mockGeminiClient, mockGeminiResponse, }; - diff --git a/src/__tests__/mocks/supabase.mock.js b/src/__tests__/mocks/supabase.mock.js index c0d8dfe..3f3ef5b 100644 --- a/src/__tests__/mocks/supabase.mock.js +++ b/src/__tests__/mocks/supabase.mock.js @@ -18,4 +18,3 @@ module.exports = { createClient: jest.fn(() => mockSupabaseClient), mockSupabaseClient, }; -