diff --git a/cypress/e2e/register-validation.cy.js b/cypress/e2e/register-validation.cy.js new file mode 100644 index 0000000000000000000000000000000000000000..a23727121309e4ef04bd0c5d899f2e0a7dafee42 --- /dev/null +++ b/cypress/e2e/register-validation.cy.js @@ -0,0 +1,16 @@ +describe('Cannot register with wrong fields', () => { + it('passes', () => { + cy.visit('http://localhost:4173/registerAccount') + cy.get('#register-button').trigger('click') + cy.get('#error-message').contains("Vennligst fyll inn alle feltene") + + cy.get('#email-input').type('en ugyldig epost') + cy.get('#register-button').trigger('click') + cy.get('#error-message').contains("Vennligst fyll inn alle feltene") + + cy.get('#password-input').type('hei') + cy.get('#register-button').trigger('click') + cy.get('#error-message').contains("Vennligst fyll inn alle feltene") + }) + + }) \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 8044f3728e71a4c621509fb927837c39418cb592..bf54e141361a865bcc5b8dd054901d1ade5886cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ }, "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", @@ -690,6 +691,47 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/@pinia/testing": { + "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": "*" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "pinia": ">=2.0.34" + } + }, + "node_modules/@pinia/testing/node_modules/vue-demi": { + "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", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, "node_modules/@sideway/address": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", diff --git a/package.json b/package.json index a0159c2e3f5fca6f48b2df64ff0d7b7882d9ff38..103fbd2a1db13c5e6262701aa8d05624babfb997 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ }, "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/router/index.js b/src/router/index.js index 5d1e0dfe3dd59b3431e97d04b7e31c5ed260d93c..f5e1f1d502bf0a951df30a0af638f360af166336 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -2,6 +2,7 @@ import { createRouter, createWebHistory } from 'vue-router' import HomeView from '../views/HomeView.vue' import LoginView from '../views/LoginView.vue' import SelectProfileView from '../views/SelectProfileView.vue' +import RegisterAccountView from '../views/RegisterAccountView.vue' const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), @@ -20,6 +21,11 @@ const router = createRouter({ path: '/selectProfile', name: "selectProfile", component: SelectProfileView + }, + { + path: '/registerAccount', + name: 'registerAccount', + component: RegisterAccountView } ] }) diff --git a/src/stores/authStore.js b/src/stores/authStore.js index 79696ca8029380771af493c2e45726ccd9347c17..70f611f62d6072269cf375c59fd42826cc0c2ca0 100644 --- a/src/stores/authStore.js +++ b/src/stores/authStore.js @@ -3,8 +3,9 @@ export const useAuthStore = defineStore("auth", { state: () => { return { token: "", - user: {}, + account: {}, profile: {}, + profiles: [] }; }, persist: { @@ -19,14 +20,17 @@ export const useAuthStore = defineStore("auth", { setToken(token) { this.token = token; }, - setUser(user) { - this.user = user; + setAccount(account) { + this.account = account; }, logout() { this.$reset(); }, setProfile(profile) { this.profile = profile; + }, + setProfiles(profiles) { + this.profiles = profiles; } } }); diff --git a/src/util/API.js b/src/util/API.js index b932e12ab318fd729555e56849553579cc4a83a0..08c1a8af53845299b022368a6fe9ada95329c2c1 100644 --- a/src/util/API.js +++ b/src/util/API.js @@ -17,6 +17,7 @@ export const API = { login: async (request) => { const authStore = useAuthStore(); let token; + authStore.logout(); //in case someone for some reason is logged in return axios.post( `${import.meta.env.VITE_BACKEND_URL}/login`, @@ -28,8 +29,11 @@ export const API = { return API.getAccount(id, token) .then((user) => { - authStore.setUser(user); + authStore.setAccount(user); authStore.setToken(token); + API.getProfiles() + .then(response => {authStore.setProfiles(response)}) + .catch(() => {throw new Error()}) return; }) .catch(() => { @@ -90,17 +94,26 @@ export const API = { throw new Error(); } - return axios.get(import.meta.env.VITE_BACKEND_URL + '/profile', { headers: { Authorization: "Bearer " + authStore.token }, }, ) .then(response => { - - console.log(response.data) return response.data }).catch(() => { throw new Error(); }); + }, + + // Registers a new account and logs into it + addAccount: async (request) => { + const authStore = useAuthStore(); + + axios.post(import.meta.env.VITE_BACKEND_URL + '/account', request) + .then(() => { + API.login({email: request.email, password: request.password}) + .catch(err => {console.log(err)}) + }) + .catch(() => {throw new Error()}) } -} +} \ No newline at end of file diff --git a/src/views/LoginView.vue b/src/views/LoginView.vue index e1593a5bc8f147f81395ae692d2a692569ee1be7..fe18b47e8ece9dd949df35e5b0517c8c9c22c3a5 100644 --- a/src/views/LoginView.vue +++ b/src/views/LoginView.vue @@ -31,7 +31,7 @@ <div class="login-container"> <img id="logo" src="../components/icons/logo.png" alt="Logo"> <h1>{{ welcomemsg }}</h1> - <form @submit.prevent="login"> + <form @submit.prevent> <div class="field-container"> <label for="email">E-post</label> <input id="email-input" name="email" type="text" v-model="email" /> @@ -46,12 +46,12 @@ <button @click="login" id="login-button">Logg inn</button> </form> - <p><RouterLink to="/newuser">Ny bruker</RouterLink> - <a href="#">Glemt passord?</a></p> + <p><RouterLink to="/registerAccount">Ny bruker</RouterLink> - <a href="#">Glemt passord?</a></p> </div> </main> </template> -<style lang="scss"> +<style lang="scss" scoped> .login-container { display: flex; diff --git a/src/views/RegisterAccountView.vue b/src/views/RegisterAccountView.vue new file mode 100644 index 0000000000000000000000000000000000000000..2a5b360e64494f39cc0c804b18dc16f43500fa8b --- /dev/null +++ b/src/views/RegisterAccountView.vue @@ -0,0 +1,126 @@ +<template> + <div class="content"> + <img id="logo" src="../components/icons/logo.png" alt="Logo"> + <h1>Registrer konto</h1> + <form @submit.prevent> + <div class="field-container"> + <label for="name">Navn</label> + <input id="input" name="name" type="text" v-model="name" /> + </div> + + <div class="field-container"> + <label for="email">E-post</label> + <input id="email-input" name="email" type="email" v-model="email" /> + </div> + + <div class="field-container"> + <label for="password">Passord</label> + <input id="password-input" name="password" type="password" v-model="password" /> + </div> + + <p id="error-message">{{ errormsg }}</p> + <button @click="register" id="register-button">Registrer</button> + </form> + </div> +</template> + +<script> + import { API } from '@/util/API.js'; + import router from '@/router/index.js'; + + + export default { + data() { + return { + email: "", + name: "", + password: "", + errormsg: "" + } + }, + methods: { + register() { + if (this.name === "" || this.password === "") { + this.errormsg = "Vennligst fyll inn alle feltene" + } + else { + API.addAccount({firstname: this.name, email: this.email, password: this.password}).then(() => { + router.push("/selectProfile") + }) + .catch(() => { + throw new Error() + }); + } + + + } + } + } +</script> + +<style scoped> + .content { + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + + min-width: 300px; + margin-top: 40px; + } + + form { + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + } + + .field-container { + padding: 10px; + display: flex; + flex-direction: column; + } + + input { + height: 40px; + font-size: 16px; + padding-left: 10px; + } + + label { + font-size: 18px; + } + + #register-button { + background-color: #00663C; + color: #FFFFFF; + border-radius: 5px; + border-style: none; + width: 150px; + height: 40px; + font-size: 18px; + font-weight: bold; + margin: 20px; + } + + #register-button:hover { + background-color: #04be80; + } + + #logo { + width: 100px; + height: 100px; + } + + @media (min-width: 1024px) { + .login-container { + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + } + } + + +</style> \ No newline at end of file diff --git a/src/views/SelectProfileView.vue b/src/views/SelectProfileView.vue index d8c581f64f820e3a35e4d7010d1a63d6de3d0730..279444d041daf0ee66407b1230c29977022ddf86 100644 --- a/src/views/SelectProfileView.vue +++ b/src/views/SelectProfileView.vue @@ -1,12 +1,19 @@ <script> import { API } from '@/util/API.js'; + import { useAuthStore } from "@/stores/authStore.js"; + import { mapState } from 'pinia' export default { data() { return { - profiles: [] + } }, + + computed: { + ...mapState(useAuthStore, ['profiles']) + }, + methods: { // Sends the user into the home page logged in as the profile they clicked on selectProfile(id) { @@ -21,7 +28,6 @@ // Receives all profiles from this user async getProfiles() { await API.getProfiles() - .then(response => {this.profiles = response}) .catch(() => new Error()); } }, @@ -95,6 +101,7 @@ height: 130px; width: 130px; border-radius: 50%; + padding: 10px; } diff --git a/src/views/__tests__/LoginView.spec.js b/src/views/__tests__/LoginView.spec.js index 64e4c4d57d34842cdf969823f594099879b081a0..c5a2e66dac3d2a54c86566b09c025a57946fe10a 100644 --- a/src/views/__tests__/LoginView.spec.js +++ b/src/views/__tests__/LoginView.spec.js @@ -7,10 +7,11 @@ describe('Login', () => { it('renders properly', () => { const wrapper = mount(LoginView) expect(wrapper.text()).toContain('E-post') + expect(wrapper.text()).toContain('Passord') }) it('login button exists', () => { const wrapper = mount(LoginView) - wrapper.find('#login-button').exists() + expect(wrapper.find('#login-button').exists()).toBe(true); }) }) \ No newline at end of file diff --git a/src/views/__tests__/RegisterAccountView.spec.js b/src/views/__tests__/RegisterAccountView.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..64bf3723c5bc9358c69ba75cb34087607074cf90 --- /dev/null +++ b/src/views/__tests__/RegisterAccountView.spec.js @@ -0,0 +1,18 @@ +import { describe, it, expect } from 'vitest' + +import { mount } from '@vue/test-utils' +import RegisterAccountView from '../RegisterAccountView.vue' + +describe('Login', () => { + it('renders properly', () => { + const wrapper = mount(RegisterAccountView) + expect(wrapper.text()).toContain('E-post') + expect(wrapper.text()).toContain('Navn') + expect(wrapper.text()).toContain('Passord') + }) + + it('register button exists', () => { + const wrapper = mount(RegisterAccountView) + expect(wrapper.find('#register-button').exists()).toBe(true) + }) +}) \ No newline at end of file diff --git a/src/views/__tests__/SelectProfileView.spec.js b/src/views/__tests__/SelectProfileView.spec.js index eb411366d7832837edd1aec94a2c68f9d2e7fe18..858b394a2c36720176fd2f1cf9e157e4b6673907 100644 --- a/src/views/__tests__/SelectProfileView.spec.js +++ b/src/views/__tests__/SelectProfileView.spec.js @@ -1,17 +1,28 @@ -import { describe, it, expect } from 'vitest' - +import { describe, it, expect, vi } from 'vitest' +import { createTestingPinia } from '@pinia/testing' import { mount } from '@vue/test-utils' import SelectProfileView from '../SelectProfileView.vue' describe('Select profile', () => { it('renders properly', () => { - const wrapper = mount(SelectProfileView) + const wrapper = mount(SelectProfileView, { + global: { + plugins: [createTestingPinia({ + createSpy: vi.fn, + })], + }, + }) expect(wrapper.text()).toContain('Hvem bruker appen?') expect(wrapper.text()).toContain('+') }) it('loads with one profile', () => { const wrapper = mount(SelectProfileView, { + global: { + plugins: [createTestingPinia({ + createSpy: vi.fn, + })], + }, data() { return { profiles: [{