diff --git a/cypress/fixtures/exampleItem.json b/cypress/fixtures/exampleItem.json new file mode 100644 index 0000000000000000000000000000000000000000..fd91203abd4b81b3f1440f40147260560da28c9f --- /dev/null +++ b/cypress/fixtures/exampleItem.json @@ -0,0 +1,10 @@ +{ + "item":{ + "id": "1", + "name":"eple", + "amount": {"quantity": "4","unit": "stk"}, + "image_url": "" + }, + "exp_date": "2222-02-22T00:00:00+00:00", + "amount": {"quantity": "2","unit": "stk"} +} diff --git a/package-lock.json b/package-lock.json index bf54e141361a865bcc5b8dd054901d1ade5886cc..325009f43a1e9ac5e917359abdc88e3764ea8973 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,17 +8,15 @@ "name": "frontend", "version": "0.0.0", "dependencies": { - "@iconify/iconify": "^3.1.0", + "@iconify/vue": "^4.1.1", + "@pinia/testing": "^0.0.16", "jwt-decode": "^3.1.2", - "pinia": "^2.0.28", + "pinia": "^2.0.35", "pinia-plugin-persistedstate": "^3.1.0", - "sass": "^1.62.0", "vue": "^3.2.45", "vue-router": "^4.1.6" }, "devDependencies": { - "@iconify/vue": "^4.1.1", - "@pinia/testing": "^0.0.16", "@vitejs/plugin-vue": "^4.0.0", "@vue/test-utils": "^2.2.6", "cypress": "^12.0.2", @@ -569,17 +567,6 @@ "@hapi/hoek": "^9.0.0" } }, - "node_modules/@iconify/iconify": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@iconify/iconify/-/iconify-3.1.0.tgz", - "integrity": "sha512-Xyz+N5NSIiHj7G228CvFUcMKBz3BbpUTAuPnK2ariEUM7891F8ysD6Gh8782f3En1U4Qz70F0tzuyF8BhCfTQg==", - "dependencies": { - "@iconify/types": "^2.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/cyberalien" - } - }, "node_modules/@iconify/types": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", @@ -589,7 +576,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/@iconify/vue/-/vue-4.1.1.tgz", "integrity": "sha512-RL85Bm/DAe8y6rT6pux7D2FJSiUEM/TPfyK7GrbAOfTSwrhvwJW+S5yijdGcmtXouA8MtuH9C7l4hiSE4mLMjg==", - "dev": true, "dependencies": { "@iconify/types": "^2.0.0" }, @@ -647,9 +633,9 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", "dev": true, "peer": true }, @@ -664,6 +650,13 @@ "@jridgewell/sourcemap-codec": "1.4.14" } }, + "node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true, + "peer": true + }, "node_modules/@npmcli/fs": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", @@ -695,7 +688,6 @@ "version": "0.0.16", "resolved": "https://registry.npmjs.org/@pinia/testing/-/testing-0.0.16.tgz", "integrity": "sha512-oa5kO82hzWekqMq1HTnS/b+ZM+ZKEcEApuuCTelvKK79jTxg7P026Qw8/2RbVn5eUGAvRWQu4ubObrshVqCRjQ==", - "dev": true, "dependencies": { "vue-demi": "*" }, @@ -710,7 +702,6 @@ "version": "0.14.0", "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.0.tgz", "integrity": "sha512-gt58r2ogsNQeVoQ3EhoUAvUsH9xviydl0dWJj7dabBC/2L4uBId7ujtCwDRD0JhkGsV1i0CtfLAeyYKBht9oWg==", - "dev": true, "hasInstallScript": true, "bin": { "vue-demi-fix": "bin/vue-demi-fix.js", @@ -1337,6 +1328,9 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "optional": true, + "peer": true, "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -1543,6 +1537,9 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "optional": true, + "peer": true, "engines": { "node": ">=8" } @@ -1572,6 +1569,9 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "optional": true, + "peer": true, "dependencies": { "fill-range": "^7.0.1" }, @@ -1829,12 +1829,15 @@ "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, "funding": [ { "type": "individual", "url": "https://paulmillr.com/funding/" } ], + "optional": true, + "peer": true, "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -2338,9 +2341,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.369", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.369.tgz", - "integrity": "sha512-LfxbHXdA/S+qyoTEA4EbhxGjrxx7WK2h6yb5K2v0UCOufUKX+VZaHbl3svlzZfv9sGseym/g3Ne4DpsgRULmqg==", + "version": "1.4.374", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.374.tgz", + "integrity": "sha512-dNP9tQNTrjgVlSXMqGaj0BdrCS+9pcUvy5/emB6x8kh0YwCoDZ0Z4ce1+7aod+KhybHUd5o5LgKrc5al4kVmzQ==", "dev": true, "peer": true }, @@ -2737,6 +2740,9 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "optional": true, + "peer": true, "dependencies": { "to-regex-range": "^5.0.1" }, @@ -2843,6 +2849,7 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, "hasInstallScript": true, "optional": true, "os": [ @@ -2986,6 +2993,9 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "optional": true, + "peer": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -3267,7 +3277,10 @@ "node_modules/immutable": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.0.tgz", - "integrity": "sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg==" + "integrity": "sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg==", + "dev": true, + "optional": true, + "peer": true }, "node_modules/imurmurhash": { "version": "0.1.4", @@ -3334,6 +3347,9 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "optional": true, + "peer": true, "dependencies": { "binary-extensions": "^2.0.0" }, @@ -3369,6 +3385,9 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "optional": true, + "peer": true, "engines": { "node": ">=0.10.0" } @@ -3386,6 +3405,9 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "optional": true, + "peer": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -3419,6 +3441,9 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "optional": true, + "peer": true, "engines": { "node": ">=0.12.0" } @@ -4617,6 +4642,9 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "optional": true, + "peer": true, "engines": { "node": ">=0.10.0" } @@ -4863,6 +4891,9 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "optional": true, + "peer": true, "engines": { "node": ">=8.6" }, @@ -4880,9 +4911,9 @@ } }, "node_modules/pinia": { - "version": "2.0.34", - "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.0.34.tgz", - "integrity": "sha512-cgOoGUiyqX0SSgX8XelK9+Ri4XA2/YyNtgjogwfzIx1g7iZTaZPxm7/bZYMCLU2qHRiHhxG7SuQO0eBacFNc2Q==", + "version": "2.0.35", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.0.35.tgz", + "integrity": "sha512-P1IKKQWhxGXiiZ3atOaNI75bYlFUbRxtJdhPLX059Z7+b9Z04rnTZdSY8Aph1LA+/4QEMAYHsTQ638Wfe+6K5g==", "dependencies": { "@vue/devtools-api": "^6.5.0", "vue-demi": "*" @@ -5203,6 +5234,9 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "optional": true, + "peer": true, "dependencies": { "picomatch": "^2.2.1" }, @@ -5404,6 +5438,9 @@ "version": "1.62.0", "resolved": "https://registry.npmjs.org/sass/-/sass-1.62.0.tgz", "integrity": "sha512-Q4USplo4pLYgCi+XlipZCWUQz5pkg/ruSSgJ0WRDSb/+3z9tXUOkQ7QPYn4XrhZKYAK4HlpaQecRwKLJX6+DBg==", + "dev": true, + "optional": true, + "peer": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", @@ -6198,6 +6235,9 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "optional": true, + "peer": true, "dependencies": { "is-number": "^7.0.0" }, @@ -6619,9 +6659,9 @@ } }, "node_modules/webpack": { - "version": "5.80.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.80.0.tgz", - "integrity": "sha512-OIMiq37XK1rWO8mH9ssfFKZsXg4n6klTEDL7S8/HqbAOBBaiy8ABvXvz0dDCXeEF9gqwxSvVk611zFPjS8hJxA==", + "version": "5.81.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.81.0.tgz", + "integrity": "sha512-AAjaJ9S4hYCVODKLQTgG5p5e11hiMawBwV2v8MYLE0C/6UAGLuAF4n1qa9GOwdxnicaP+5k6M5HrLmD4+gIB8Q==", "dev": true, "peer": true, "dependencies": { diff --git a/package.json b/package.json index 103fbd2a1db13c5e6262701aa8d05624babfb997..0eb0aa1f4754b48a7823428f190f1a111aca44af 100644 --- a/package.json +++ b/package.json @@ -11,17 +11,15 @@ "test:e2e:dev": "start-server-and-test 'vite dev --port 4173' :4173 'cypress run --e2e'" }, "dependencies": { - "@iconify/iconify": "^3.1.0", + "@iconify/vue": "^4.1.1", + "@pinia/testing": "^0.0.16", "jwt-decode": "^3.1.2", - "pinia": "^2.0.28", - "sass": "^1.62.0", + "pinia": "^2.0.35", "pinia-plugin-persistedstate": "^3.1.0", "vue": "^3.2.45", "vue-router": "^4.1.6" }, "devDependencies": { - "@iconify/vue": "^4.1.1", - "@pinia/testing": "^0.0.16", "@vitejs/plugin-vue": "^4.0.0", "@vue/test-utils": "^2.2.6", "cypress": "^12.0.2", diff --git a/src/components/EatFridgeItemModal.vue b/src/components/EatFridgeItemModal.vue new file mode 100644 index 0000000000000000000000000000000000000000..eacb71ff0f8409bb286506a7ef6f28c913ebb835 --- /dev/null +++ b/src/components/EatFridgeItemModal.vue @@ -0,0 +1,201 @@ +<template> + <div v-if="visible" id="popup"> + <div id="exitBtn" @click="close"><Icon icon="mdi:alpha-x-box" :color="redIconColor" :style="{ fontSize: redIconSize }" /></div> + + <h2>{{ this.fridgeItem.item.name }}</h2> + <p id="sliderDisplay">{{sliderValue}} {{this.fridgeItem.amount.unit}}</p> + <label for="slider">Mengde tatt av varen:</label> + + <input type = "range" id = "slider" name = "slider" min="0" :max = "this.maxValue" :step = this.getStep v-model="sliderValue"> + + <div id="buttons"> + <button @click="logFoodAsDiscarded">Ble kastet</button> + <button id="eatenBtn" @click="logFoodAsEaten" >Ble spist</button> + </div> + <div v-if="isExpired" id = "itemHasExpiredMessage"> + <h2>Varen har gått ut på dato</h2> + <p>Se, lukt, smak : Hvis varen fortsatt ser fin ut, kan du forlenge utløpsdatoen med 5 dager</p> + <button id= "eatenBtn" @click="extendExpiration">Utsett utløpsdato med 5 dager</button> + </div> + </div> +</template> + +<script> +import {Icon} from "@iconify/vue"; +import fridgeItem from "@/components/FridgeItem.vue"; +import {API} from "@/util/API"; + +export default { + name: "EatFridgeItemModal", + components: {Icon}, + props: { + fridgeItem: { + type: Object, + required: false + }, + }, + data() { + return { + visible:true, + sliderValue: 0, + } + }, + methods:{ + close(){ + this.$emit('closeModal',this); + }, + logFoodAsEaten(){ + const fridgeAction = "CONSUMED"; + this.updateFridgeItem(fridgeAction) + }, + logFoodAsDiscarded(){ + const fridgeAction = "DISCARDED"; + this.updateFridgeItem(fridgeAction) + }, + updateFridgeItem(fridgeAction){ + const ingredientId = this.fridgeItem.ingredient_id; + + const request = { + action: fridgeAction, + ingredients: { + [ingredientId]:{ + "quantity": this.sliderValue, + "unit": this.fridgeItem.amount.unit + } + } + }; + + API.updateFridge(request) + .catch((error)=> + console.log(error) + ); + this.close(); + }, + extendExpiration(){ + const id = this.fridgeItem.ingredient_id; + const today = new Date(); + const fiveDays = new Date(); + fiveDays.setDate(today.getDate()+5); + + API.changeExpirationOfIngredient({"ingredientId": id, "newExpDate":fiveDays}).catch((error)=> { + console.log("could not change date") + }) + this.close(); + } + }, + computed:{ + redIconColor() { + return '#EE6D6D'; + }, + redIconSize() { + return '40px'; + }, + maxValue(){ + return this.fridgeItem.amount.quantity; + }, + isExpired(){ + let date = this.fridgeItem.exp_date; + const expirationDate = new Date(date); + + const parsedDate = Date.parse(expirationDate) + const today = new Date(); + + const ms = parsedDate-today; + const numOfDays = Math.ceil(ms/(86400000)) + + return numOfDays < 1 ; + }, + getStep(){ + const itemQuantity = this.fridgeItem.amount.quantity.toString(); + const isDecimal = itemQuantity.split('.').length === 2 || itemQuantity==='1'; + + switch(isDecimal){ + case true: + return '0.1'; + case false: + return '1'; + } + return 0.1; + }, + } +} +</script> +<style scoped lang="scss"> +#popup { + background-color: base.$white; + color: black; + width: 90%; + padding:2em; + display:flex; + flex-direction: column; + align-items: center; + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 999; + box-shadow: 0 0 0 2000px rgba(0, 0, 0, 0.5); + border-radius: 2px; +} + +@media (min-width: 800px){ + #popup { + width: 40%; + } +} + +#buttons { + justify-content: space-between; + display: flex; + width: 100%; + margin: 1em; + margin-top: 3em; +} + +#exitBtn{ + width:100%; + display: flex; + justify-content: right; + +} + +button { + margin:.1em; + font-weight: bolder; + width: 50%; + padding:1em; + background-color: base.$red; + color:white; + border:none; +} +button:hover { + background-color: base.$red-hover; + cursor:pointer; +} +#eatenBtn:hover{ + background-color: base.$green-hover; +} +#eatenBtn { + background-color: base.$green; +} + +#itemHasExpiredMessage { + border: 3px dashed base.$red; + padding: 1em; +} + +#itemHasExpiredMessage button { + padding: 1em; + width:100%; + border:none; + border-radius:5px; +} + +input:hover{ + cursor:pointer; +} + +#exitBtn:hover{ + cursor:pointer; +} +</style> \ No newline at end of file diff --git a/src/components/FridgeItem.vue b/src/components/FridgeItem.vue new file mode 100644 index 0000000000000000000000000000000000000000..d259d5db29f607aa03b9464483dd6c51132e4164 --- /dev/null +++ b/src/components/FridgeItem.vue @@ -0,0 +1,147 @@ +<template> +<div id ="item"> + <img :src="getImage" alt=""> + <div id="itemInfo"> + <p id="fridgeItemName">{{this.actualItem.item.name }} {{ this.actualItem.amount.quantity }}{{this.actualItem.item.amount.unit}}</p> + <p class="expText" :style="{color:expirationTextColor}">{{expirationText}}</p> + </div> + <div id = "appleBtn" @click="appleBtnPressed"> + <Icon icon="game-icons:apple-core" :color="iconColor" :style="{ fontSize: iconSize }" /> + </div> + +</div> + <hr> +</template> + +<script> +import {Icon} from "@iconify/vue"; + +export default { + name: "FridgeItem", + components: {Icon}, + emits: ['appleBtnPressed'], + computed: { + iconColor(){ + return 'black'; + }, + iconSize() { + return '3rem' + }, + expirationTextColor(){ + if(this.expirationText.split("Utgikk").length===2){ + return '#EE6D6D'; //rød + } + else{ + return '#737573'; //grå + } + }, + getImage(){ + return this.actualItem.item.image_url; + + }, + expirationText() { + const expirationDays = this.getDateDifference(); + const dateString = this.formatDate(); + + if(expirationDays<0) { + return "Utgikk " + dateString + } + + switch (expirationDays){ + case expirationDays < -1: + return "Utgikk " + dateString + case 0: + return "Utgår i dag" + case -0: + return "Utgår i dag" + case 1: + return "Utgår i morgen" + case 2: + return "Utgår om to dager" + case 3: + return "Utgår om tre dager" + default: + return "Utgår " + dateString + } + } + }, + props: { + item: { + type:Object, + required: false, + }, + actualItem: { + type: Object, + required:false, + }, + itemName: String, + expiration: String, + image: String, + quantity: String, + quantityUnit: String, + }, + methods: { + getDateDifference(){ //returns the difference in days between the expiration date and today + let date = this.actualItem.exp_date; + const epDate = new Date(date); + + const parsedDate = Date.parse(epDate) + const today = new Date(); + + const ms = parsedDate-today; + const numOfDays = Math.ceil(ms/(86400000)) + + return numOfDays; + }, + appleBtnPressed(){ + this.$emit('appleBtnPressed', this.actualItem); + }, + formatDate(){ //formats expiration date as dd.mm.yyyy + let fullExpirationDate = new Date(this.actualItem.exp_date); + let day = fullExpirationDate.getDate(); + let month= (fullExpirationDate.getMonth()+1).toString(); + let year= fullExpirationDate.getFullYear().toString(); + return day + "." + month + "." + year; + } + + } +} +</script> + +<style scoped lang="scss"> +#item { + background-color: base.$white; + color: black; + qborder-radius: 10px; + padding: 1em; + padding-left: 2em; + padding-right:2em; + display:flex; + align-items: center; + justify-content: space-between; +} + +#fridgeItemName { + font-weight: normal; + font-size: 1.2em; +} + + +img { + width: 3rem; + height: 3em; + border-radius: 50%; + background-color: #00663C; + margin-right:.6em; + object-fit: cover; +} + +#itemInfo{ + width: 100%; +} + +#appleBtn:hover { + cursor:pointer; +} + +</style> \ No newline at end of file diff --git a/src/components/ItemSearch.vue b/src/components/ItemSearch.vue new file mode 100644 index 0000000000000000000000000000000000000000..cfa6e6c5d551e11c2a17317003dafdd9055ef909 --- /dev/null +++ b/src/components/ItemSearch.vue @@ -0,0 +1,115 @@ +<template> + <div v-if="showSearch" id="wrapper"> + <h3>SØK ETTER VARE</h3> + <div id="searchBoxDiv"> + <input type="text" id="searchBox" v-model="itemSearch"> + <button @click="search">Søk</button> + </div> + + <p>Resultater: ({{searchResult.length}})</p> + + <select v-model="selectedItem" name="searchR" id="searchR"> + <option v-for ="item in searchResult" :value="item" :key="item.ean">{{item.name}} ({{item.amount.quantity}}{{item.amount.unit}})</option> + </select> + <p>Antall varer: <span v-if="numOfItemsToAdd>1 && selectedItem!=null">(totalt: {{this.totalNumOfItems}} {{selectedItem.amount.unit}})</span></p> + <input type="number" min='1' v-model="numOfItemsToAdd"><br> + + <button id = "addToFridgeBtn" @click="addToFridge">Legg i kjøleskap</button> + + + </div> +</template> + +<script> +import {API} from "@/util/API"; + + +export default { + name: "itemSearch", + data(){ + return{ + itemSearch:'', + searchResult: [], + selectedItem: null, + showSearch:true, + numOfItemsToAdd:1, + } + }, + computed:{ + totalNumOfItems(){ + return this.selectedItem.amount.quantity*this.numOfItemsToAdd + } + }, + methods: { + async search() { + this.searchResult = await API.searchItems(this.itemSearch); + this.selectedItem = this.searchResult[0]; + + }, + addToFridge(){ + const num = this.numOfItemsToAdd; + API.addToFridge( + { + "itemId": Number(this.selectedItem.id), + "amount": + { + "quantity": this.selectedItem.amount.quantity*num, + "unit": this.selectedItem.amount.unit}} + ).then(() => this.$emit('itemsAdded',this.selectedItem)).catch((_)=> console.log("No items were added to the fridge")) + } + } +} +</script> + +<style scoped lang = "scss"> +select { + width:100%; +} + +#wrapper{ + background-color: base.$white; + padding: 2em; + border: 3px dashed base.$light-green; + + +} + +#searchBoxDiv { + display:flex; + width:100%; +} + +input[type="text"], +input[type="select"], +input[type="number"], +select{ + padding: .4em; + margin: .1em; +} + +button { + padding: .5em; + background-color: base.$light-green; + border-radius: 5%; + + border: 1px solid base.$green; + +} + +button:hover { + border: 1px solid base.$grey; + background-color: base.$light-green-hover; + cursor: pointer; + + +} + +addToFridgeBtn { + width: 100%; +} + +span { + qfont-size: 1.3em; +} +</style> + diff --git a/src/components/Navbar.vue b/src/components/Navbar.vue index 80ceeca2377f531da73e3e274237efbeea684928..788592a89408f9515e0cedc16804a5f890e546db 100644 --- a/src/components/Navbar.vue +++ b/src/components/Navbar.vue @@ -8,7 +8,7 @@ </RouterLink> </li> <li> - <RouterLink :to="'/'" :aria-label="'link to fridge page'"> + <RouterLink :to="'/myFridge'" :aria-label="'link to fridge page'"> <Icon icon="mingcute:fridge-line" :color="iconColor" :style="{ fontSize: iconSize }"/> </RouterLink> </li> @@ -34,6 +34,7 @@ <script> import { Icon } from '@iconify/vue'; import Logo from "@/components/Logo.vue"; +import { RouterLink } from 'vue-router' export default { name: "Navbar", diff --git a/src/components/__tests__/FridgeItem.spec.js b/src/components/__tests__/FridgeItem.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..5a7b241b63107426dc1f3aad53e793c412764e74 --- /dev/null +++ b/src/components/__tests__/FridgeItem.spec.js @@ -0,0 +1,83 @@ +import { describe, it, expect } from 'vitest' +import { mount } from '@vue/test-utils' +import FridgeItem from "@/components/FridgeItem.vue"; + +const normalItem = { + "item":{ + "id": "1", + "name":"eple", + "amount": {"quantity": "4","unit": "stk"}, + "image_url": "https://bilder.ngdata.no/7040512550818/meny/large.jpg" + }, + "exp_date": "2222-02-22T00:00:00+00:00", + "amount": {"quantity": "6","unit": "stk"} +} + +const expiredItem = { + "item":{ + "id": "1", + "name":"eple", + "amount": {"quantity": "4","unit": "stk"}, + "image_url": "https://bilder.ngdata.no/7040512550818/meny/large.jpg" + }, + "exp_date": "2002-02-22T00:00:00+00:00", + "amount": {"quantity": "6","unit": "stk"} +} +describe('Fridge items render correctly', () => { + + it('displays the name of the item', () => { + const wrapper = mount(FridgeItem, { + props: { + actualItem: normalItem, + }, + }); + expect(wrapper.text()).toContain('eple'); + }); + + it('displays the amount of the item in the fridge' , () => { + const wrapper = mount(FridgeItem, { + props: { + actualItem: normalItem, + }, + }); + expect(wrapper.text()).toContain('6'); + expect(wrapper.text()).toContain('stk'); + }); + + it('displays item image', () => { + const wrapper = mount(FridgeItem, {props: { + actualItem: normalItem, + }, + }); + const itemImage = wrapper.find('img'); + expect(itemImage.exists()).toBe(true); + expect(itemImage.element.getAttribute('src')).not.toBe(''); + expect(itemImage.element.getAttribute('src')).toBe('https://bilder.ngdata.no/7040512550818/meny/large.jpg'); + }); + + it('displays text of different color when item has expired', () => { + const wrapper = mount(FridgeItem, { + props: { + actualItem: expiredItem, + }, + }); + + const expiration = wrapper.find('.expText'); + expect(expiration.element.style.color).not.toBe('black'); + }); + +}) +describe('Behaves as expected', () => { + it('emits when the apple button is pressed', async () => { + const wrapper = mount(FridgeItem, { + props: { + actualItem: normalItem, + }, + }); + + await wrapper.find('#appleBtn').trigger('click'); + await wrapper.vm.$nextTick(); + expect(wrapper.emitted().appleBtnPressed).toBeTruthy(); + + }) +}) diff --git a/src/components/__tests__/FridgeItemModal.spec.js b/src/components/__tests__/FridgeItemModal.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..cc180e0fede1939270d6467a1cc723feff53cf4d --- /dev/null +++ b/src/components/__tests__/FridgeItemModal.spec.js @@ -0,0 +1,88 @@ +import { describe, it, expect } from 'vitest' +import { mount } from '@vue/test-utils' +import EatFridgeItemModal from "@/components/EatFridgeItemModal.vue"; + +const normalItem = { + "item":{ + "id": "1", + "name":"purre", + "amount": {"quantity": "500","unit": "g"}, + "image_url": "https://bilder.ngdata.no/7040512550818/meny/large.jpg" + }, + "exp_date": "2222-02-22T00:00:00+00:00", + "amount": {"quantity": "500","unit": "g"} +} + +const expiredItem = { + "item":{ + "id": "1", + "name":"purre", + "amount": {"quantity": "500","unit": "g"}, + "image_url": "https://bilder.ngdata.no/7040512550818/meny/large.jpg" + }, + "exp_date": "2002-02-22T00:00:00+00:00", + "amount": {"quantity": "500","unit": "g"} +} +describe('Renders correctly', () => { + it('displays the name of the item', () => { + const wrapper = mount(EatFridgeItemModal, { + props: { + fridgeItem: normalItem, + }, + }); + + const itemName = wrapper.find('h2') + expect(itemName.text()).toContain('purre') + + }); + + it('displays the correct unit', () => { + const wrapper = mount(EatFridgeItemModal, { + props: { + fridgeItem: normalItem, + }, + }); + + const sliderValueUnit = wrapper.find('#sliderDisplay') + expect(sliderValueUnit.text()).toContain('g') + + }); + + it('Displays a message if item has expired', () => { + const wrapper = mount(EatFridgeItemModal, { + props: { + fridgeItem: expiredItem, + }, + }); + + expect(wrapper.find('#itemHasExpiredMessage').exists()).toBe(true); + const expiredMsgBox = wrapper.find('#itemHasExpiredMessage') + expect(expiredMsgBox.isVisible()).toBe(true) + + }); + + it('Does not display message if item has not expired', () => { + const wrapper = mount(EatFridgeItemModal, { + props: { + fridgeItem: normalItem, + }, + }); + expect(wrapper.find('#itemHasExpiredMessage').exists()).toBe(false); + }); + +}) + +describe('Behaves correctly', () => { + it('emits close when X is pressed', async () => { + const wrapper = mount(EatFridgeItemModal, { + props: { + fridgeItem: normalItem, + }, + }); + + await wrapper.find('#exitBtn').trigger('click'); + await wrapper.vm.$nextTick(); + expect(wrapper.emitted().closeModal).toBeTruthy(); + + }); +}) \ No newline at end of file diff --git a/src/components/__tests__/ItemSearch.spec.js b/src/components/__tests__/ItemSearch.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..49c6597c263c7f2161eeb11a6024b02cfa42864d --- /dev/null +++ b/src/components/__tests__/ItemSearch.spec.js @@ -0,0 +1,68 @@ +import { describe, it, expect } from 'vitest' +import { mount } from '@vue/test-utils' +import ItemSearch from "@/components/ItemSearch.vue"; + +const SearchReturn = [ + { + "id": "1", + "ean": "7040512550214", + "name":"eplekake", + "amount": {"quantity": "4","unit": "stk"}, + "image_url": "https://bilder.ngdata.no/7040512550818/meny/large.jpg" + }, + { + "id": "2", + "ean": "7040512550254", + "name":"eple", + "amount": {"quantity": "4","unit": "stk"}, + "image_url": "https://bilder.ngdata.no/7040512550818/meny/large.jpg" + }]; + +describe('Behaves as expected', () => { + it('shows correct number of results', () => { + const wrapper = mount(ItemSearch, { + data() { + return { + searchResult: SearchReturn, + selectedItem: SearchReturn[0], + numOfItemsToAdd:3, + }; + }, + }); + + expect(wrapper.text()).toContain('Resultater: ') + expect(wrapper.text()).toContain('(2)') + }); + + it('When adding more items, the total ingredient amount is shown', () => { + const wrapper = mount(ItemSearch, { + data() { + return { + searchResult: SearchReturn, + selectedItem: SearchReturn[0], + numOfItemsToAdd:3, + }; + }, + }); + + // total = 3*4 + const totalItemCount = wrapper.find('span') + expect(totalItemCount.text()).toContain('12') + expect(totalItemCount.text()).toContain('stk') + }); + + it('Select contains all items ', () => { + const wrapper = mount(ItemSearch, { + data() { + return { + searchResult: SearchReturn, + selectedItem: SearchReturn[0], + numOfItemsToAdd:3, + }; + }, + }); + const itemSelect = wrapper.find('select') + expect(itemSelect.text()).toContain('eple') + expect(itemSelect.text()).toContain('eplekake') + }); +}) diff --git a/src/components/__tests__/Navbar.spec.js b/src/components/__tests__/Navbar.spec.js index a212573ecee960d8a98e37d63d093b3b2b2a2066..86579bf2b896c4c3a22df76bf4eb7013a334acae 100644 --- a/src/components/__tests__/Navbar.spec.js +++ b/src/components/__tests__/Navbar.spec.js @@ -3,6 +3,8 @@ import { describe, it, expect } from 'vitest' import { mount } from '@vue/test-utils' import Navbar from "@/components/Navbar.vue"; import { Icon } from '@iconify/vue'; +import { RouterLink } from 'vue-router'; + describe('Navbar', () => { it('contains 5 links', () => { const wrapper = mount(Navbar) diff --git a/src/router/index.js b/src/router/index.js index 568aa495e35286dbcae3024e3e4f2a160998287b..0e3a31e3b2d83717d2f78cc1d3c278ab49f9d5be 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -5,6 +5,9 @@ import SelectProfileView from '../views/SelectProfileView.vue' import ProfileCreationView from '../views/ProfileCreationView.vue' import RegisterAccountView from '../views/RegisterAccountView.vue' +import FridgeView from "@/views/FridgeView.vue"; + + const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ @@ -32,7 +35,11 @@ const router = createRouter({ path: '/registerAccount', name: 'registerAccount', component: RegisterAccountView - } + },{ + path: '/myFridge', + name: 'myFridge', + component: FridgeView + }, ] }) diff --git a/src/stores/fridgeStore.js b/src/stores/fridgeStore.js new file mode 100644 index 0000000000000000000000000000000000000000..cf82e0189950e1d14d2060f13049af87b704993a --- /dev/null +++ b/src/stores/fridgeStore.js @@ -0,0 +1,29 @@ +import { defineStore } from "pinia"; + +export const useFridgeStore = defineStore("fridge", { + state: () => { + return { + items: [] + }; + }, + persist: { + storage: localStorage + }, + getters: { + getItems() { + return this.items; + }, + getItem(state){ + return(id) => state.items.filter(item => + item.id === id) + } + }, + actions: { + reset() { + this.$reset(); + }, + addToFridge(item){ + this.items.push(item); + } + } +}); \ No newline at end of file diff --git a/src/style.scss b/src/style.scss index df31d235654012419a14e80b1004c0998fb4b68b..c7158435ec70a7af663f63d364e5946679272560 100644 --- a/src/style.scss +++ b/src/style.scss @@ -5,6 +5,10 @@ $white:#FFFFFF; $grey:#D9D9D9; $red:#EE6D6D; +$red-hover: darken( $red, 5% ); +$green-hover: darken( $green, 8% ); +$light-green-hover: darken( $light-green, 10% ); + $desktop-min: 800px; $phone-min : 360px; diff --git a/src/util/API.js b/src/util/API.js index d6134cfb94f4b8d764395e8347ac37f74e023c47..f199e8d12b4fb8121fbf9780f19df42df0fd2efe 100644 --- a/src/util/API.js +++ b/src/util/API.js @@ -26,7 +26,7 @@ export const API = { .then(async (response) => { token = response.data; const id = (jwt_decode(token)).id; - + return API.getAccount(id, token) .then((user) => { authStore.setAccount(user); @@ -73,7 +73,7 @@ export const API = { .then((response) => { authStore.setProfile(response.data) router.push("/") - + }) .catch(() => { throw new Error("Profile not found or not accessible") @@ -84,10 +84,10 @@ export const API = { /** * Sends a request to create a new profile on the currently logged in account - * + * * @typedef {{name: string, profileImageUrl: string, isRestricted: boolean}} ProfileType - * @param {ProfileType} profile - * @returns + * @param {ProfileType} profile + * @returns */ addProfile: async (profile) => { const authStore = useAuthStore(); @@ -134,5 +134,105 @@ export const API = { .catch(err => {console.log(err)}) }) .catch(() => {throw new Error()}) - } + }, + + /** + * fetches all fridge items belonging to the user that is currently logged in + * @returns {Promise<*>} + */ + getFridgeItems: async () =>{ + const authStore = useAuthStore(); + + return axios.get(`${import.meta.env.VITE_BACKEND_URL}/fridge`, { + headers: { Authorization: `Bearer ${authStore.token}` }, + }) + .then((response) => { + return response.data; + }).catch(() => { + throw new Error("Could not fetch fridge items"); + }); + }, + + /** + * Adds item(s) to the fridge + * @param request List<Ingredient> listOfIngredients + * @returns {Promise<void>} + */ + addToFridge: async(request) =>{ + + const authStore = useAuthStore(); + axios.post(`${import.meta.env.VITE_BACKEND_URL}/fridge/items`, request,{ + headers: { Authorization: `Bearer ${authStore.token}` }, + }).then((response) => { + return response.data; + }).catch(()=> { + throw new Error("Could not add item to fridge: "); + }) +}, + + /** + * Searches for registered items. + * @param searchPhrase Name of the item that one is looking for (e.g: "purre") + * @returns {Promise<*>} list containing items that match the search phrase + */ + searchItems: async(searchPhrase)=> { + return axios.get(`${import.meta.env.VITE_BACKEND_URL}/item/search?name=${searchPhrase}`, { + }).then((response) => { + console.log(response.data.content); + return response.data.content; + }).catch(()=> { + throw new Error("Error when searching for item "); + }) + }, + + /** + * Removes an amount of an ingredient from the frodge, and the action is then logged + * @param request Log.Action action, Map<Integer, Amount> + * Action available: CONSUMED, DISCARDED,ADDED_TO_FRIDGE + * @returns {Promise<void>} + */ + updateFridge: async(request) => { + const authStore = useAuthStore(); + + axios.put(`${import.meta.env.VITE_BACKEND_URL}/fridge/ingredientsAmount`, request,{ + headers: { Authorization: `Bearer ${authStore.token}` }, + }).then((response) => { + return response.data; + }).catch(()=> { + throw new Error("Could modify ingredient. "); + }) + }, + + /** + * Changes the expiration date of an ingredient. + * @param request ingredientId: id of the ingredient, newExpDate: the new expiration date of the ingredient + * @returns {Promise<void>} + */ + changeExpirationOfIngredient: async(request) => { + const authStore = useAuthStore(); + + axios.put(`${import.meta.env.VITE_BACKEND_URL}/fridge/ingredient`, request,{ + headers: { Authorization: `Bearer ${authStore.token}` }, + }).then((response) => { + return response.data; + }).catch(()=> { + throw new Error("Could modify ingredient. "); + }) + }, + + + //returns fridgeItem of specific id + getFridgeItem: async (id) =>{ + const authStore = useAuthStore(); + + axios.get(`${import.meta.env.VITE_BACKEND_URL}/fridge/${id}`, { + headers: { Authorization: `Bearer ${authStore.token}` }, + }) + .then((response) => { + return response.data; + }) + .catch(() => { + throw new Error("Could not fetch fridge item"); + }); + }, } \ No newline at end of file diff --git a/src/views/FridgeView.vue b/src/views/FridgeView.vue new file mode 100644 index 0000000000000000000000000000000000000000..cd8088e689767f9daa549a4dcaa98a65b30c4b68 --- /dev/null +++ b/src/views/FridgeView.vue @@ -0,0 +1,138 @@ +<template><h1>Kjøleskap</h1><br><br> + <main> + <ItemSearch v-if="searchVisible" @itemsAdded="updateFridge"></ItemSearch> + + <div id = "fridgeMsg"><p>Melding fra kjøleskapet:</p><span>{{this.fridgeMsg}}</span></div> + <eat-fridge-item-modal @closeModal="hideModal" v-if="visible" :fridge-item="selectedItem"></eat-fridge-item-modal> + + <div id = "itemContainer" > + <FridgeItem v-for="item in fridgeItems" :actualItem = "item" @appleBtnPressed="showModal"></FridgeItem> + </div> + + <div id="addItemBtn-container"> + <button @click="showItemSearch" id="addItemBtn"> + <span class="plus">+</span> + </button> + </div> + </main> +</template> + +<script> +import FridgeItem from "@/components/FridgeItem.vue"; +import EatFridgeItemModal from "@/components/EatFridgeItemModal.vue"; +import {API} from "@/util/API"; +import {mapState, mapStores} from "pinia"; +import {useAuthStore} from "@/stores/authStore"; +import {useFridgeStore} from "@/stores/fridgeStore"; +import ItemSearch from "@/components/ItemSearch.vue"; +import fridgeItem from "@/components/FridgeItem.vue"; + +export default { + name: "FridgeView", + components: {ItemSearch, EatFridgeItemModal, FridgeItem}, + computed:{ + ...mapState(useAuthStore, ['account']), + ...mapStores(useFridgeStore), + }, + data() { + return { + visible: false, //is the useitemModal visible + selectedItem: null, + fridgeItems: [], + searchVisible: false, + fridgeMsg: "" + } + }, + methods: { + showModal(item){ + this.visible = true; + this.selectedItem = item; + }, + hideModal(){ + this.visible=false; + }, + showItemSearch() { + this.searchVisible=!this.searchVisible; + window.scrollTo({ top: 0, behavior: 'smooth' }); + }, + hideItemSearch() { + this.searchVisible=false; + }, + updateFridgeMessage(msg){ + this.fridgeMsg=msg; + }, + async updateFridge(addedItem) { + this.fridgeItems = await API.getFridgeItems(); + this.fridgeItems = await API.getFridgeItems(); + this.hideItemSearch(); + this.updateFridgeMessage(addedItem.name + " ble lagt i kjøleskapet.") + }, + + }, + async mounted() { + this.fridgeItems = await API.getFridgeItems(); + if(this.fridgeItems.length===0){ + this.fridgeMsg="Kjøleskapet ditt er tomt. Legg inn varer ved å trykke på pluss-knappen nederst i høyre hjørne" + } + } +} +</script> + +<style scoped lang="scss"> +main { + + color:black; + background-color: base.$grey; + padding: 2em; + min-height: 600px; +} + +h1 { + width:100%; + padding-left: 1em; + padding-top: 2em; + font-weight: bold; +} + +#itemContainer { + background-color: base.$grey; + padding-bottom: 5em; +} + +#addItemBtn { + width: 2.5em; + height: 2.5em; + border-radius: 50%; + font-size: 30px; + border:none; + background-color: base.$light-green; + color: white; + box-shadow: 0px 0px 20px rgba(0,0,0,0.3); +} + +#addItemBtn:hover { + background-color: base.$light-green-hover; + color: white; + box-shadow: 0px 0px 20px rgba(0,0,0,0.5); + cursor: pointer; +} + +#addItemBtn-container { + padding-bottom: 3em; + z-index: 9999; + position: fixed; + bottom: 15px; + right: 1em; +} + +#fridgeMsg { + background-color: base.$light-green; + padding: 1em; +} + +#fridgeMsg span, #fridgeMsg p{ + + font-weight: bolder; + font-size: 1.2em; +} +</style> \ No newline at end of file diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue index 7d3e71b91795ab41ea9b2e4b7f737b83015409c8..0f9f671c983e1870a64046c51cca21bd25d04cd7 100644 --- a/src/views/HomeView.vue +++ b/src/views/HomeView.vue @@ -27,7 +27,7 @@ </div> <div class="gamification"> - + </div> <div class="tips"> @@ -82,7 +82,7 @@ background-color: rgb(232, 232, 232); margin-left: 10px; margin-right: 10px; - + } diff --git a/src/views/SelectProfileView.vue b/src/views/SelectProfileView.vue index 279444d041daf0ee66407b1230c29977022ddf86..8b7130a3a16826fec45442d429cb7458b1a3c7f6 100644 --- a/src/views/SelectProfileView.vue +++ b/src/views/SelectProfileView.vue @@ -45,11 +45,11 @@ <h1>Hvem bruker appen?</h1> <div class="icons"> - <div v-for="profile in this.profiles" @click=selectProfile(profile.id) class="icon"> + <div v-for="profile in this.profiles" @click=selectProfile(profile.id) class="icon" > <img v-if="profile.profileImageUrl == ''" src="https://t4.ftcdn.net/jpg/02/15/84/43/360_F_215844325_ttX9YiIIyeaR7Ne6EaLLjMAmy4GvPC69.jpg" alt="profile image"> <img v-else :src=profile.profileImageUrl alt="profile image"> - <p>{{profile.name}}</p> + <button id="selectProfileBtn" @click=selectProfile(profile.id)><p>{{profile.name}}</p></button> </div> </div> @@ -117,4 +117,12 @@ padding-bottom: 10px; } + #selectProfileBtn { + background-color: transparent; + border:none; + font-size: 1em; + border-radius: 0; + + } + </style> \ No newline at end of file diff --git a/src/views/__tests__/FridgeView.spec.js b/src/views/__tests__/FridgeView.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..4790f4ad2639935f3da4a5694dafcb3a27738181 --- /dev/null +++ b/src/views/__tests__/FridgeView.spec.js @@ -0,0 +1,70 @@ +import { describe, it, expect, vi } from 'vitest' +import { createTestingPinia } from '@pinia/testing' +import { mount } from '@vue/test-utils' +import FridgeView from "@/views/FridgeView.vue"; + + +const fridgeIngredients = [ + { + "item":{ + "id": "1", + "name":"eple", + "amount": {"quantity": "4","unit": "stk"}, + "image_url": "https://bilder.ngdata.no/7040512550818/meny/large.jpg" + }, + "exp_date": "2222-02-22T00:00:00+00:00", + "amount": {"quantity": "6","unit": "stk"} + }]; + +describe('Fridge', () => { + it('renders', () => { + const wrapper = mount(FridgeView, { + global: { + plugins: [createTestingPinia({ + createSpy: vi.fn, + })], + }, + }) + expect(wrapper.text()).toContain('Kjøleskap') + }); + + it('pressing the plus button makes search visible', async () => { + const wrapper = mount(FridgeView, { + global: { + plugins: [createTestingPinia({ + createSpy: vi.fn, + })], + }, + + }) + expect(wrapper.find('ItemSearch').exists()).toBe(false); + await wrapper.find('#addItemBtn').trigger('click'); + + setTimeout(() => { + expect(wrapper.find('ItemSearch').exists()).toBe(true); + }, 1000); + }); + + it('when appleBtnEmit, the EatFridgeItemModal is displayed', () => { + const wrapper = mount(FridgeView, { + global: { + plugins: [createTestingPinia({ + createSpy: vi.fn, + })], + }, + data() { + return { + fridgeItems: fridgeIngredients, + }; + }, + + }) + + expect(wrapper.find('eat-fridge-item-modal').exists()).toBe(false); + + wrapper.find('#appleBtn').trigger('click'); + setTimeout(() => { + expect(wrapper.find('eat-fridge-item-modal').exists()).toBe(true); + }, 1000); + }) +}) \ No newline at end of file