Skip to content
Snippets Groups Projects
Commit a9ed0a95 authored by Eline Evje's avatar Eline Evje
Browse files

Merge branch 'test/unit-tests-component' into 'dev'

Test/unit tests component

See merge request !59
parents 3c7ae333 708e95f7
Branches
No related tags found
3 merge requests!66Final merge,!59Test/unit tests component,!4Pipeline fix
Pipeline #284875 passed
import { beforeEach, describe, expect, it } from 'vitest'
import { mount } from '@vue/test-utils'
import DisplayInfoForChallengeOrGoal from '@/components/DisplayInfoForChallengeOrGoal.vue'
interface Props {
displayInfoCard: boolean
screenSize: number
challenge: any
goal: any
isChallenge: boolean
}
describe('InfoCardComponent', () => {
let wrapper: any
const mockChallenge = {
description: 'Complete daily tasks',
title: 'Daily Challenge',
saved: 100,
completion: 50,
due: '2024-01-01T00:00:00Z',
perPurchase: 10,
target: 500
}
beforeEach(() => {
wrapper = mount(DisplayInfoForChallengeOrGoal, {
props: {
challenge: mockChallenge, // Passing only recognized props
goal: null,
isChallenge: true
}
})
})
it('toggles displayInfoCard when the button is clicked', async () => {
expect(wrapper.vm.displayInfoCard).toBe(false)
await wrapper.find('button').trigger('click')
expect(wrapper.vm.displayInfoCard).toBe(true)
await wrapper.find('button').trigger('click')
expect(wrapper.vm.displayInfoCard).toBe(false)
})
it('does not render the info card when displayInfoCard is false', () => {
expect(wrapper.find('.w-[40vh]').exists()).toBe(false)
})
})
import { beforeEach, describe, expect, it } from 'vitest'
import { mount } from '@vue/test-utils'
import HelpComponent from '@/components/HelpComponent.vue'
import InteractiveSpare from '@/components/InteractiveSpare.vue'
import ModalComponent from '@/components/ModalComponent.vue'
describe('HelpComponent', () => {
let wrapper: any
beforeEach(() => {
wrapper = mount(HelpComponent, {
props: {
speech: ['Sample Speech'] // Pass as an array
}
})
})
it('initially, the modal should not be open', () => {
expect(wrapper.vm.isModalOpen).toBe(false)
})
it('should open the modal when the image is clicked', async () => {
await wrapper.find('.fixed').trigger('click')
expect(wrapper.vm.isModalOpen).toBe(true)
})
it('should close the modal when the skip link is clicked', async () => {
wrapper.vm.isModalOpen = true
await wrapper.vm.$nextTick()
if (wrapper.find('a').exists()) {
await wrapper.find('a').trigger('click')
await wrapper.vm.$nextTick()
expect(wrapper.vm.isModalOpen).toBe(false)
} else {
throw new Error('Skip link is not rendered')
}
})
it('modal should render the correct speech text to the InteractiveSpare component when modal is open', async () => {
wrapper.vm.isModalOpen = true
await wrapper.vm.$nextTick()
const interactiveSpare = wrapper.findComponent(InteractiveSpare)
if (interactiveSpare.exists()) {
expect(interactiveSpare.props('speech')).toEqual(['Sample Speech']) // Pass as an array
} else {
throw new Error('InteractiveSpare component is not rendered')
}
})
it('should close the modal when close event is emitted by the ModalComponent', async () => {
wrapper.vm.isModalOpen = true
await wrapper.vm.$nextTick()
wrapper.findComponent(ModalComponent).vm.$emit('close')
await wrapper.vm.$nextTick()
expect(wrapper.vm.isModalOpen).toBe(false)
})
})
import { describe, expect, it } from 'vitest'
import { mount } from '@vue/test-utils'
import { createPinia } from 'pinia'
import { createApp } from 'vue'
import ModalEditAvatar from '@/components/ModalEditAvatar.vue'
const pinia = createPinia()
const app = createApp({
setup() {
app.use(pinia)
},
render: () => null
})
const wrapper = mount(ModalEditAvatar, {
global: {
plugins: [pinia]
}
})
describe('ModalEditAvatar', () => {
it('renders properly when modal is closed', async () => {
const wrapper = mount(ModalEditAvatar)
expect(wrapper.find('.fixed').exists()).toBe(false)
})
it('opens modal when button is clicked', async () => {
const wrapper = mount(ModalEditAvatar)
await wrapper.find('button').trigger('click')
expect(wrapper.find('.fixed').exists()).toBe(true)
})
it('closes modal when close button is clicked', async () => {
const wrapper = mount(ModalEditAvatar)
await wrapper.find('button').trigger('click')
expect(wrapper.find('.fixed').exists()).toBe(true)
await wrapper.find('.bg-white button').trigger('click')
expect(wrapper.find('.fixed').exists()).toBe(false)
})
it('cycles avatars to the next one when next button is clicked', async () => {
const wrapper = mount(ModalEditAvatar)
await wrapper.find('button').trigger('click')
const currentAvatarSrc = wrapper.find('.avatar').attributes('src')
await wrapper.find('.avatar-container button:last-child').trigger('click')
const newAvatarSrc = wrapper.find('.avatar').attributes('src')
expect(newAvatarSrc).not.toBe(currentAvatarSrc)
})
it('cycles avatars to the previous one when previous button is clicked', async () => {
const wrapper = mount(ModalEditAvatar)
await wrapper.find('button').trigger('click')
await wrapper.find('.avatar-container button:last-child').trigger('click')
const currentAvatarSrc = wrapper.find('.avatar').attributes('src')
await wrapper.find('.avatar-container button:first-child').trigger('click')
const newAvatarSrc = wrapper.find('.avatar').attributes('src')
expect(newAvatarSrc).not.toBe(currentAvatarSrc)
})
})
// store/challengeStore.js
import { defineStore } from 'pinia'
import { ref } from 'vue'
import authInterceptor from '@/services/authInterceptor'
import type { Challenge } from '@/types/challenge'
export const useChallengeStore = defineStore('challenge', () => {
// Reactive state to hold the list of challenges
const challenges = ref<Challenge[]>([])
// Function to fetch challenges for the current user
const getUserChallenges = async () => {
try {
const response = await authInterceptor('/challenges')
......@@ -22,7 +23,7 @@ export const useChallengeStore = defineStore('challenge', () => {
}
}
// Assuming 'challenges' is a reactive state in your store that holds the list of challenges
// Function to edit a user challenge
const editUserChallenge = async (challenge: Challenge) => {
try {
const response = await authInterceptor.put(`/challenges/${challenge.id}`, challenge)
......@@ -43,6 +44,8 @@ export const useChallengeStore = defineStore('challenge', () => {
return null
}
}
// Function to mark a user challenge as completed
const completeUserChallenge = async (challenge: Challenge) => {
try {
const response = await authInterceptor.put(
......@@ -68,6 +71,7 @@ export const useChallengeStore = defineStore('challenge', () => {
}
}
// Return reactive state and functions to be used by components
return {
challenges,
getUserChallenges,
......
......@@ -4,13 +4,20 @@ import { ref } from 'vue'
import authInterceptor from '@/services/authInterceptor'
export const useGoalStore = defineStore('goal', () => {
// Reactive state to hold the list of goals
const goals = ref<Goal[]>([])
// Reactive state to hold the priority goal
const priorityGoal = ref<Goal | null>(null)
// Function to fetch goals for the current user
const getUserGoals = async () => {
try {
const response = await authInterceptor('/goals')
if (response.data && response.data.content) {
goals.value = response.data.content
goals.value = response.data.content // Update goals state with fetched data
// Iterate through goals to find the priority goal
for (const goal of goals.value) {
if (goal.priority === 1) {
priorityGoal.value = goal
......@@ -30,7 +37,7 @@ export const useGoalStore = defineStore('goal', () => {
}
}
// Assuming 'challenges' is a reactive state in your store that holds the list of challenges
// Function to edit a user goal
const editUserGoal = async (goal: Goal) => {
if (!goal || goal.id === null) {
console.error('Invalid goal or goal ID.')
......@@ -51,6 +58,8 @@ export const useGoalStore = defineStore('goal', () => {
console.error('Error updating goal:', error)
}
}
// Return reactive states and functions to be used by components
return {
goals,
priorityGoal,
......
......@@ -6,18 +6,21 @@ import type { ChallengeConfig } from '@/types/challengeConfig'
import router from '@/router'
export const useUserConfigStore = defineStore('userConfig', () => {
// Reactive state to hold the challenge configuration
const challengeConfig = ref<ChallengeConfig>({
experience: '',
motivation: '',
challengeTypeConfigs: []
})
// Reactive state to hold the saving account information
const savingAccount = ref({
accountType: 'SAVING',
accNumber: 0,
balance: 0
})
// Reactive state to hold the spending account information
const spendingAccount = ref({
accountType: 'SPENDING',
accNumber: 0,
......@@ -26,14 +29,17 @@ export const useUserConfigStore = defineStore('userConfig', () => {
const errorMessage = ref<string>('')
// Function to set experience in the challenge configuration
const setExperience = (value: string) => {
challengeConfig.value.experience = value
}
// Function to set motivation in the challenge configuration
const setMotivation = (value: string) => {
challengeConfig.value.motivation = value
}
// Function to add challenge type configuration
const addChallengeTypeConfig = (
type: string,
specificAmount: number,
......@@ -46,6 +52,7 @@ export const useUserConfigStore = defineStore('userConfig', () => {
})
}
// Function to set account information
const setAccount = (type: 'SAVING' | 'SPENDING', accNumber: number) => {
if (type === 'SAVING') {
savingAccount.value.accNumber = accNumber
......@@ -54,11 +61,16 @@ export const useUserConfigStore = defineStore('userConfig', () => {
}
}
// Function to create user configuration
const createConfig = () => {
authInterceptor
// Create saving account
.post('/accounts', savingAccount.value)
// Create spending account
.then(() => authInterceptor.post('/accounts', spendingAccount.value))
// Create challenge configuration
.then(() => authInterceptor.post('/config/challenge', challengeConfig.value))
// Navigate to home page after configuration creation
.then(() => {
resetConfig()
return router.push({ name: 'home', query: { firstLogin: 'true' } })
......@@ -70,6 +82,7 @@ export const useUserConfigStore = defineStore('userConfig', () => {
})
}
// Function to reset configuration states
const resetConfig = () => {
challengeConfig.value = {
experience: '',
......@@ -88,6 +101,7 @@ export const useUserConfigStore = defineStore('userConfig', () => {
}
}
// Return reactive states and functions to be used by components
return {
challengeConfig,
errorMessage,
......
......@@ -11,6 +11,7 @@ import { base64urlToUint8array, initialCheckStatus, uint8arrayToBase64url } from
import type { CredentialCreationOptions } from '@/types/CredentialCreationOptions'
export const useUserStore = defineStore('user', () => {
// Reactive state to hold the user information
const user = ref<User>({
firstName: '',
lastName: '',
......@@ -21,6 +22,7 @@ export const useUserStore = defineStore('user', () => {
const streak = ref<Streak>()
const profilePicture = ref<string>('')
// Function to register a new user
const register = async (
firstName: string,
lastName: string,
......@@ -37,8 +39,10 @@ export const useUserStore = defineStore('user', () => {
password: password
})
.then((response) => {
// Save access token in session storage
sessionStorage.setItem('accessToken', response.data.accessToken)
// Update user information
user.value.firstName = firstName
user.value.lastName = lastName
user.value.username = username
......@@ -51,6 +55,7 @@ export const useUserStore = defineStore('user', () => {
})
}
// Function to log in a user
const login = (username: string, password: string) => {
axios
.post(`http://localhost:8080/auth/login`, {
......@@ -67,11 +72,13 @@ export const useUserStore = defineStore('user', () => {
return authInterceptor('/profile')
})
.then((profileResponse) => {
// Check if the user has a passkey and store spare username if needed
if (profileResponse.data.hasPasskey === true) {
localStorage.setItem('spareStiUsername', username)
} else {
localStorage.removeItem('spareStiUsername')
}
// Check if the user is configured and redirect accordingly
return checkIfUserConfigured()
})
.then(() => {
......@@ -85,10 +92,13 @@ export const useUserStore = defineStore('user', () => {
})
}
// Method to log out a user
const logout = () => {
// Remove access token and spare username from storage
sessionStorage.removeItem('accessToken')
localStorage.removeItem('spareStiUsername')
// Clear user information
user.value.firstName = ''
user.value.lastName = ''
user.value.username = ''
......@@ -97,6 +107,7 @@ export const useUserStore = defineStore('user', () => {
router.push({ name: 'login' })
}
// Method to retrieve user's streak
const getUserStreak = () => {
authInterceptor('/profile/streak')
.then((response) => {
......@@ -108,14 +119,18 @@ export const useUserStore = defineStore('user', () => {
})
}
// Method to register biometric data
const bioRegister = async () => {
// Send biometric registration request to the server
authInterceptor
.post('/auth/bioRegistration')
.then((response) => {
initialCheckStatus(response)
// Process credential creation options
const credentialCreateJson: CredentialCreationOptions = response.data
// Transform credential creation options
const credentialCreateOptions: CredentialCreationOptions = {
publicKey: {
...credentialCreateJson.publicKey,
......@@ -138,11 +153,13 @@ export const useUserStore = defineStore('user', () => {
}
}
// Create credential using browser's credentials API
return navigator.credentials.create(
credentialCreateOptions
) as Promise<PublicKeyCredential>
})
.then((publicKeyCredential) => {
// Process the created credential
const publicKeyResponse =
publicKeyCredential.response as AuthenticatorAttestationResponse
const encodedResult = {
......@@ -158,6 +175,7 @@ export const useUserStore = defineStore('user', () => {
clientExtensionResults: publicKeyCredential.getClientExtensionResults()
}
// Send encoded result to complete biometric registration
return authInterceptor.post('/auth/finishBioRegistration', {
credential: JSON.stringify(encodedResult)
})
......@@ -170,6 +188,7 @@ export const useUserStore = defineStore('user', () => {
})
}
// Method to login using biometric data
const bioLogin = (username: string) => {
axios
.post(`http://localhost:8080/auth/bioLogin/${username}`)
......@@ -236,6 +255,7 @@ export const useUserStore = defineStore('user', () => {
})
}
// Method to check if the user is configured
const checkIfUserConfigured = async () => {
await authInterceptor('/config')
.then((response) => {
......@@ -245,7 +265,8 @@ export const useUserStore = defineStore('user', () => {
user.value.isConfigured = false
})
}
// Inside your store or component methods
// Method to upload user's profile picture
const uploadProfilePicture = async (formData: FormData) => {
try {
const response = await authInterceptor.post('/profile/picture', formData, {
......@@ -257,6 +278,7 @@ export const useUserStore = defineStore('user', () => {
}
}
// Method to retrieve user's profile picture
const getProfilePicture = async () => {
try {
const imageResponse = await authInterceptor.get('/profile/picture', {
......@@ -277,6 +299,7 @@ export const useUserStore = defineStore('user', () => {
}
}
// Return the variables and methods to be used by components
return {
user,
checkIfUserConfigured,
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment