From 905f98c8226b4f3ed6c47a9a98527a3d07b6bba8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valdemar=20=C3=85storp=20Beere?= <valdemb@stud.ntnu.no> Date: Mon, 22 Apr 2024 15:19:56 +0200 Subject: [PATCH] Fix(type): Fix type check --- package-lock.json | 86 +++++------ package.json | 2 + src/components/ButtonAddGoalOrChallange.vue | 7 +- src/components/InteractiveSpare.vue | 15 +- src/components/__tests__/HomeViewTest.spec.ts | 44 ++++-- .../__tests__/InteractiveSpareTest.spec.ts | 17 ++- src/types/goal.ts | 44 ++++++ src/views/HomeView.vue | 134 ++++++++---------- 8 files changed, 195 insertions(+), 154 deletions(-) create mode 100644 src/types/goal.ts diff --git a/package-lock.json b/package-lock.json index f146b6a..e033404 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,8 @@ "@types/animejs": "^3.1.12", "@types/jsdom": "^21.1.6", "@types/node": "^20.12.5", + "@typescript-eslint/eslint-plugin": "^7.7.0", + "@typescript-eslint/parser": "^7.7.0", "@vitejs/plugin-vue": "^5.0.4", "@vitest/coverage-v8": "^1.5.0", "@vue/eslint-config-prettier": "^9.0.0", @@ -1218,16 +1220,16 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.6.0.tgz", - "integrity": "sha512-gKmTNwZnblUdnTIJu3e9kmeRRzV2j1a/LUO27KNNAnIC5zjy1aSvXSRp4rVNlmAoHlQ7HzX42NbKpcSr4jF80A==", + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.7.0.tgz", + "integrity": "sha512-GJWR0YnfrKnsRoluVO3PRb9r5aMZriiMMM/RHj5nnTrBy1/wIgk76XCtCKcnXGjpZQJQRFtGV9/0JJ6n30uwpQ==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "7.6.0", - "@typescript-eslint/type-utils": "7.6.0", - "@typescript-eslint/utils": "7.6.0", - "@typescript-eslint/visitor-keys": "7.6.0", + "@typescript-eslint/scope-manager": "7.7.0", + "@typescript-eslint/type-utils": "7.7.0", + "@typescript-eslint/utils": "7.7.0", + "@typescript-eslint/visitor-keys": "7.7.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.3.1", @@ -1253,15 +1255,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.6.0.tgz", - "integrity": "sha512-usPMPHcwX3ZoPWnBnhhorc14NJw9J4HpSXQX4urF2TPKG0au0XhJoZyX62fmvdHONUkmyUe74Hzm1//XA+BoYg==", + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.7.0.tgz", + "integrity": "sha512-fNcDm3wSwVM8QYL4HKVBggdIPAy9Q41vcvC/GtDobw3c4ndVT3K6cqudUmjHPw8EAp4ufax0o58/xvWaP2FmTg==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "7.6.0", - "@typescript-eslint/types": "7.6.0", - "@typescript-eslint/typescript-estree": "7.6.0", - "@typescript-eslint/visitor-keys": "7.6.0", + "@typescript-eslint/scope-manager": "7.7.0", + "@typescript-eslint/types": "7.7.0", + "@typescript-eslint/typescript-estree": "7.7.0", + "@typescript-eslint/visitor-keys": "7.7.0", "debug": "^4.3.4" }, "engines": { @@ -1281,13 +1283,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.6.0.tgz", - "integrity": "sha512-ngttyfExA5PsHSx0rdFgnADMYQi+Zkeiv4/ZxGYUWd0nLs63Ha0ksmp8VMxAIC0wtCFxMos7Lt3PszJssG/E6w==", + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.7.0.tgz", + "integrity": "sha512-/8INDn0YLInbe9Wt7dK4cXLDYp0fNHP5xKLHvZl3mOT5X17rK/YShXaiNmorl+/U4VKCVIjJnx4Ri5b0y+HClw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.6.0", - "@typescript-eslint/visitor-keys": "7.6.0" + "@typescript-eslint/types": "7.7.0", + "@typescript-eslint/visitor-keys": "7.7.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -1298,13 +1300,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.6.0.tgz", - "integrity": "sha512-NxAfqAPNLG6LTmy7uZgpK8KcuiS2NZD/HlThPXQRGwz6u7MDBWRVliEEl1Gj6U7++kVJTpehkhZzCJLMK66Scw==", + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.7.0.tgz", + "integrity": "sha512-bOp3ejoRYrhAlnT/bozNQi3nio9tIgv3U5C0mVDdZC7cpcQEDZXvq8inrHYghLVwuNABRqrMW5tzAv88Vy77Sg==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "7.6.0", - "@typescript-eslint/utils": "7.6.0", + "@typescript-eslint/typescript-estree": "7.7.0", + "@typescript-eslint/utils": "7.7.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -1325,9 +1327,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.6.0.tgz", - "integrity": "sha512-h02rYQn8J+MureCvHVVzhl69/GAfQGPQZmOMjG1KfCl7o3HtMSlPaPUAPu6lLctXI5ySRGIYk94clD/AUMCUgQ==", + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.7.0.tgz", + "integrity": "sha512-G01YPZ1Bd2hn+KPpIbrAhEWOn5lQBrjxkzHkWvP6NucMXFtfXoevK82hzQdpfuQYuhkvFDeQYbzXCjR1z9Z03w==", "dev": true, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -1338,13 +1340,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.6.0.tgz", - "integrity": "sha512-+7Y/GP9VuYibecrCQWSKgl3GvUM5cILRttpWtnAu8GNL9j11e4tbuGZmZjJ8ejnKYyBRb2ddGQ3rEFCq3QjMJw==", + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.7.0.tgz", + "integrity": "sha512-8p71HQPE6CbxIBy2kWHqM1KGrC07pk6RJn40n0DSc6bMOBBREZxSDJ+BmRzc8B5OdaMh1ty3mkuWRg4sCFiDQQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.6.0", - "@typescript-eslint/visitor-keys": "7.6.0", + "@typescript-eslint/types": "7.7.0", + "@typescript-eslint/visitor-keys": "7.7.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -1366,17 +1368,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.6.0.tgz", - "integrity": "sha512-x54gaSsRRI+Nwz59TXpCsr6harB98qjXYzsRxGqvA5Ue3kQH+FxS7FYU81g/omn22ML2pZJkisy6Q+ElK8pBCA==", + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.7.0.tgz", + "integrity": "sha512-LKGAXMPQs8U/zMRFXDZOzmMKgFv3COlxUQ+2NMPhbqgVm6R1w+nU1i4836Pmxu9jZAuIeyySNrN/6Rc657ggig==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.15", "@types/semver": "^7.5.8", - "@typescript-eslint/scope-manager": "7.6.0", - "@typescript-eslint/types": "7.6.0", - "@typescript-eslint/typescript-estree": "7.6.0", + "@typescript-eslint/scope-manager": "7.7.0", + "@typescript-eslint/types": "7.7.0", + "@typescript-eslint/typescript-estree": "7.7.0", "semver": "^7.6.0" }, "engines": { @@ -1391,12 +1393,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.6.0.tgz", - "integrity": "sha512-4eLB7t+LlNUmXzfOu1VAIAdkjbu5xNSerURS9X/S5TUKWFRpXRQZbmtPqgKmYx8bj3J0irtQXSiWAOY82v+cgw==", + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.7.0.tgz", + "integrity": "sha512-h0WHOj8MhdhY8YWkzIF30R379y0NqyOHExI9N9KCzvmu05EgG4FumeYa3ccfKUSphyWkWQE1ybVrgz/Pbam6YA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.6.0", + "@typescript-eslint/types": "7.7.0", "eslint-visitor-keys": "^3.4.3" }, "engines": { diff --git a/package.json b/package.json index f15dbce..53e12f2 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,8 @@ "@types/animejs": "^3.1.12", "@types/jsdom": "^21.1.6", "@types/node": "^20.12.5", + "@typescript-eslint/eslint-plugin": "^7.7.0", + "@typescript-eslint/parser": "^7.7.0", "@vitejs/plugin-vue": "^5.0.4", "@vitest/coverage-v8": "^1.5.0", "@vue/eslint-config-prettier": "^9.0.0", diff --git a/src/components/ButtonAddGoalOrChallange.vue b/src/components/ButtonAddGoalOrChallange.vue index 5ca7b20..820233b 100644 --- a/src/components/ButtonAddGoalOrChallange.vue +++ b/src/components/ButtonAddGoalOrChallange.vue @@ -1,6 +1,6 @@ <template> <button - class="w-full max-w-60 max-h-12 bg-green-500 text-white font-bold py-2 rounded-full flex items-center justify-start pl-4 pl-4 space-x-2 hover:bg-green-600 drop-shadow-lg focus:outline-none focus:ring-2 focus:ring-green-700 focus:ring-opacity-50 shadow-md transition duration-300 ease-in-out text-xs md:text-sm lg:text-base" + class="w-full max-w-60 max-h-12 bg-green-500 text-white font-bold py-2 rounded-full flex items-center justify-start pl-4 space-x-2 hover:bg-green-600 drop-shadow-lg focus:outline-none focus:ring-2 focus:ring-green-700 focus:ring-opacity-50 shadow-md transition duration-300 ease-in-out text-xs md:text-sm lg:text-base" > <svg xmlns="http://www.w3.org/2000/svg" @@ -24,10 +24,7 @@ import { defineProps, ref } from 'vue' interface Props { - buttonText: { - type: String - required: true - } + buttonText: string } const props = defineProps<Props>() diff --git a/src/components/InteractiveSpare.vue b/src/components/InteractiveSpare.vue index 5002069..5d50f1e 100644 --- a/src/components/InteractiveSpare.vue +++ b/src/components/InteractiveSpare.vue @@ -25,21 +25,14 @@ import { ref, defineProps, computed } from 'vue' import spareImageSrc from '@/assets/spare.png' interface Props { - speech?: Array<String> - // direction string should be either 'left' or 'right' - direction: { - type: String - required: true - } - pngSize: { - type: Number - default: 20 - } + speech?: string[] // Using TypeScript's type for speech as an array of strings + direction: 'left' | 'right' // This restricts direction to either 'left' or 'right' + pngSize: number // Just declaring the type directly since it's simple } const props = defineProps<Props>() -const speech = ref<string[]>(props.speech || []) +const speech = ref<String[]>(props.speech || []) const currentSpeechIndex = ref(0) const currentSpeech = computed(() => speech.value[currentSpeechIndex.value]) diff --git a/src/components/__tests__/HomeViewTest.spec.ts b/src/components/__tests__/HomeViewTest.spec.ts index d887807..a241e46 100644 --- a/src/components/__tests__/HomeViewTest.spec.ts +++ b/src/components/__tests__/HomeViewTest.spec.ts @@ -2,27 +2,37 @@ import { describe, it, expect, vi, beforeEach } from 'vitest' import { mount } from '@vue/test-utils' import HomeView from '@/views/HomeView.vue' // Adjust the import path as needed. import anime from 'animejs' +import type {Challenge} from "../../types/challenge"; // Setup localStorage mock const localStorageMock = (function () { - let store = {} + let store = {} as { [key: string]: string }; return { - getItem: vi.fn((key) => store[key] || null), - setItem: vi.fn((key, value) => { - store[key] = value.toString() + getItem: vi.fn((key: string) => store[key] || null), + setItem: vi.fn((key: string, value: any) => { + store[key] = value.toString(); }), clear: vi.fn(() => { - store = {} + store = {}; }), - removeItem: vi.fn((key) => { - delete store[key] + removeItem: vi.fn((key: string) => { + delete store[key]; + }), + get length() { + return Object.keys(store).length; + }, + key: vi.fn((index: number): string | null => { + const keys = Object.keys(store); + return keys[index] || null; }), __store: store // expose store for assertions - } -})() + }; +})(); +Object.defineProperty(global, 'localStorage', { + value: localStorageMock, + writable: true +}); -// Apply the mock -global.localStorage = localStorageMock // Mocking animejs with a default export vi.mock('animejs', () => ({ @@ -35,7 +45,7 @@ vi.mock('animejs', () => ({ })) describe('HomeView', () => { - let wrapper + let wrapper: any beforeEach(() => { // Clear localStorage and reset all mocks @@ -58,16 +68,20 @@ describe('HomeView', () => { }) it('handles incrementSaved correctly', async () => { - const challenge = { title: 'Kaffe', saved: 90, target: 100, completion: 90 } + const challenge: Challenge = { + createdOn: new Date(), + description: "", + title: 'Kaffe', saved: 90, target: 100, completion: 90 } wrapper.vm.incrementSaved(challenge) expect(challenge.saved).toBe(100) expect(challenge.completion).toBe(100) }) it('animates on challenge completion', async () => { - const challenge = { + const challenge: Challenge = { + createdOn: new Date(), description: "", title: 'Mat og Drikke', - challengeType: 'SNACKS', + type: 'SNACKS', saved: 100, target: 100, completion: 100 diff --git a/src/components/__tests__/InteractiveSpareTest.spec.ts b/src/components/__tests__/InteractiveSpareTest.spec.ts index 947a5ae..5a523d9 100644 --- a/src/components/__tests__/InteractiveSpareTest.spec.ts +++ b/src/components/__tests__/InteractiveSpareTest.spec.ts @@ -4,10 +4,11 @@ import SpeechBubbleComponent from '@/components/InteractiveSpare.vue' // Adjust describe('SpeechBubbleComponent', () => { it('renders correctly with default props', () => { - const wrapper = mount(SpeechBubbleComponent, { + const wrapper:any = mount(SpeechBubbleComponent, { props: { direction: 'left', - speech: ['Hello', 'World'] + speech: ['Hello', 'World'], + pngSize: 100 } }) expect(wrapper.exists()).toBeTruthy() @@ -17,14 +18,16 @@ describe('SpeechBubbleComponent', () => { const wrapper = mount(SpeechBubbleComponent, { props: { direction: 'right', - speech: ['Hello', 'World'] + speech: ['Hello', 'World'], + pngSize:100 } }) expect(wrapper.find('div').classes()).toContain('flex-row') const wrapperReverse = mount(SpeechBubbleComponent, { props: { direction: 'left', - speech: ['Hello', 'World'] + speech: ['Hello', 'World'], + pngSize: 100, } }) expect(wrapperReverse.find('div').classes()).toContain('flex-row-reverse') @@ -34,7 +37,8 @@ describe('SpeechBubbleComponent', () => { const wrapper = mount(SpeechBubbleComponent, { props: { direction: 'right', - speech: ['Hello', 'World'] + speech: ['Hello', 'World'], + pngSize:100, } }) expect(wrapper.find('img').classes()).toContain('scale-x-[-1]') @@ -44,7 +48,8 @@ describe('SpeechBubbleComponent', () => { const wrapper = mount(SpeechBubbleComponent, { props: { direction: 'left', - speech: ['First speech', 'Second speech'] + speech: ['First speech', 'Second speech'], + pngSize: 100 } }) expect(wrapper.find('span').text()).toBe('First speech') diff --git a/src/types/goal.ts b/src/types/goal.ts new file mode 100644 index 0000000..f1bbd1b --- /dev/null +++ b/src/types/goal.ts @@ -0,0 +1,44 @@ +export interface Goal { + /** The unique identifier for the Goal, must not be null. */ + id: number; + + /** + * The title of the Goal, must not be null, empty, or only whitespace. + */ + title: string; + + /** + * The amount saved towards the Goal so far. Must not be null and must be zero or positive. + */ + saved: number; + + /** + * The target amount to achieve for the Goal. Must not be null and must be positive. + */ + target: number; + + /** + * Completion percentage of the Goal. Must not be null and must be zero or positive. + */ + completion: number; + + /** + * A description of the Goal, must not be null, empty, or only whitespace. + */ + description: string; + + /** + * The priority of the Goal, must not be null and must be zero or positive. + */ + priority: number; + + /** + * The date and time when the Goal was created. Must be a date in the past. + */ + createdOn: Date; + + /** + * The date and time by which the Goal is due. Must be a date in the future. + */ + due: Date; +} diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue index 5913a09..89f6cf4 100644 --- a/src/views/HomeView.vue +++ b/src/views/HomeView.vue @@ -51,7 +51,7 @@ /> <!-- Progress Bar, if the challenge is not complete --> <div - v-if="challenge.completion < 100" + v-if="challenge.completion !=undefined && challenge.completion< 100" class="flex-grow w-full mt-2" > <div class="flex flex-row"> @@ -88,8 +88,9 @@ > </div> <!-- Check Icon --> - <div v-if="challenge.completion >= 100" class="max-w-16 max-h-16"> - <img src="@/assets/completed.png" alt="" />ï¸ + <div v-if="challenge.completion !== undefined && challenge.completion >= 100" class="max-w-16 max-h-16"> + + <img src="@/assets/completed.png" alt="" />ï¸ </div> <div v-else class="max-w-16 max-h-16"> <img src="@/assets/pending.png" alt="" />ï¸ @@ -137,21 +138,21 @@ <img src="@/assets/finishLine.png" class="w-1/2 max-h-4 mx-auto" alt="Finish Line" /> <!-- Goal --> - <div v-if="currentGoal" class="flex flex-row gap-24 m-t-2 pt-6 mx-auto"> + <div v-if="goal" class="flex flex-row gap-24 m-t-2 pt-6 mx-auto"> <div class="flex flex-col items-start"> <img - :src="getGoalIcon(currentGoal)" + :src="getGoalIcon(goal)" class="w-12 h-12 mx-auto" - :alt="currentGoal.title" + :alt="goal.title" /> - <div class="text-lg font-bold">{{ currentGoal.title }}</div> + <div class="text-lg font-bold">{{ goal.title }}</div> </div> <div class="flex flex-col items-end"> <div @click="goToEditGoal" class="cursor-pointer"> <h3 class="text-blue-500 text-base">Endre mÃ¥l</h3> </div> <div ref="targetRef" class="bg-yellow-400 px-4 py-1 rounded-full text-black"> - {{ currentGoal.saved }}kr / {{ currentGoal.target }}kr + {{ goal.saved }}kr / {{ goal.target }}kr </div> </div> </div> @@ -168,11 +169,13 @@ </template> <script setup lang="ts"> -import { computed, nextTick, onMounted, ref, watch } from 'vue' +import { nextTick, onMounted, ref, watch } from 'vue' import anime from 'animejs' import InteractiveSpare from '@/components/InteractiveSpare.vue' import ButtonAddGoalOrChallenge from '@/components/ButtonAddGoalOrChallange.vue' import router from '@/router' +import type {Challenge} from "@/types/challenge"; +import type {Goal} from "@/types/goal" // Define your speech array const speechArray = [ @@ -188,66 +191,44 @@ const iconRef = ref<HTMLElement | null>(null) const containerRef = ref<HTMLElement | null>(null) const targetRef = ref<HTMLElement | null>(null) -const goals = ref([ - { - title: 'Gaming', - saved: 280, - target: 100, - description: 'Gaming console', - priority: 1, - completion: 0 - } - // Other goals... -]) -const challenges = ref([ - { - title: 'Kaffe', - challengeType: 'COFFEE', - saved: 100, - target: 100, - description: 'Morning boost', - completion: 100 - }, - { - title: 'Mat og Drikke', - challengeType: 'SNACKS', - saved: 80, - target: 100, - description: 'Morning boost', - completion: 80 - }, - { - title: 'Gaming', - challengeType: 'GAMING', - saved: 20, - target: 100, - description: 'Morning boost', - completion: 20 - }, - { - title: 'Kaffe', - challengeType: 'COFFEE', - saved: 90, - target: 100, - description: 'Morning boost', - completion: 90 - }, - { - title: 'Mat og Drikke', - challengeType: 'SNACKS', - saved: 80, - target: 100, - description: 'Morning boost', - completion: 80 - } - // Other challenges... -]) -// Computed current goal -const currentGoal = computed(() => { - // Logic to determine the current goal - return goals.value.find((goal) => goal.completion < 100) -}) +const goal: Goal ={ + id: 1, + title: "gaming", + saved: 200, + description: "none", + target: 400, + completion: 50, + priority: 0, + createdOn: new Date(), + due: new Date, +} + +const challenge: Challenge = { + title: "Coffe", + saved: 1200.50, + target: 3000, + description: "Saving monthly for a year-end vacation to Bali", + createdOn: new Date('2023-01-01T00:00:00Z'), + dueDate: new Date('2023-12-31T23:59:59Z'), + type: "COFFE", + completion: 40, + completedOn: undefined // Not yet completed +}; +const challenge1: Challenge = { + title: "Snacks", + saved: 200, + target: 400, + description: "Saving monthly for a year-end vacation to Bali", + createdOn: new Date('2023-01-01T00:00:00Z'), + dueDate: new Date('2023-12-31T23:59:59Z'), + type: "SNACKS", + completion: 50, + completedOn: undefined // Not yet completed +}; + +const challenges = ref([challenge, challenge1]); + // AddSpareUtfordring function addSpareUtfordring() { @@ -255,7 +236,7 @@ function addSpareUtfordring() { } // Increment saved amount -function incrementSaved(challenge) { +function incrementSaved(challenge: Challenge) { challenge.saved += 10 if (challenge.saved >= challenge.target) { challenge.completion = 100 @@ -279,13 +260,13 @@ const loadAnimatedStates = () => { animatedChallenges.value = animated ? new Set(JSON.parse(animated)) : new Set() } -const saveAnimatedState = (title) => { +const saveAnimatedState = (title: String) => { animatedChallenges.value.add(title) localStorage.setItem('animatedChallenges', JSON.stringify([...animatedChallenges.value])) } -const animateChallenge = (challenge) => { - if (challenge.completion >= 100 && !animatedChallenges.value.has(challenge.title)) { +const animateChallenge = (challenge: Challenge) => { + if (challenge.completion !== undefined && challenge.completion >= 100 && !animatedChallenges.value.has(challenge.title)) { console.log('Animating for:', challenge.title) recalculateAndAnimate() // Assumes this function triggers the actual animation saveAnimatedState(challenge.title) @@ -383,11 +364,14 @@ function animateIcon() { } // Helper methods to get icons -function getChallengeIcon(challenge) { - return `src/assets/${challenge.challengeType.toLowerCase()}.png` +function getChallengeIcon(challenge: Challenge): string { + if (challenge.type === undefined) { + throw new Error("Challenge type is undefined"); + } + return `src/assets/${challenge.type.toLowerCase()}.png` } -function getGoalIcon(goal) { +function getGoalIcon(goal: Goal): string { return `src/assets/${goal.title.toLowerCase()}.png` } function getPigStepsIcon() { -- GitLab