diff --git a/package-lock.json b/package-lock.json index a9b42c3119552e9ab948df2ed7ba8cafe9b5fe9e..d245e94863ef06a128918335899739311cdc9f23 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,6 +44,7 @@ "vite": "^5.2.8", "vite-plugin-vue-devtools": "^7.0.25", "vitest": "^1.5.0", + "vue-router-mock": "^1.1.0", "vue-tsc": "^2.0.11" } }, @@ -8556,6 +8557,16 @@ "vue": "^3.2.0" } }, + "node_modules/vue-router-mock": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/vue-router-mock/-/vue-router-mock-1.1.0.tgz", + "integrity": "sha512-RhKhxkiZh2zB2eRkzfcCILQQ0ZUc0tk7CE2ZC1PGJYi5GOU+2QQAGHtTCgb8V4B/OPm9ws+X5Q9SQB5vyTXxBQ==", + "dev": true, + "peerDependencies": { + "vue": "^3.2.23", + "vue-router": "^4.0.12" + } + }, "node_modules/vue-template-compiler": { "version": "2.7.16", "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz", diff --git a/package.json b/package.json index 88bf81d13c99ce997982fd7b8147f70b8cade92e..07bf44ca2594d40ec79da3aed7a08f14fe06dd14 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "vite": "^5.2.8", "vite-plugin-vue-devtools": "^7.0.25", "vitest": "^1.5.0", + "vue-router-mock": "^1.1.0", "vue-tsc": "^2.0.11" } } diff --git a/src/components/BaseComponents/__tests__/Footer.spec.ts b/src/components/BaseComponents/__tests__/Footer.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..25968d67433031c9353e3aaab288b1032fc8085e --- /dev/null +++ b/src/components/BaseComponents/__tests__/Footer.spec.ts @@ -0,0 +1,12 @@ +import { describe, it, expect } from 'vitest' +import { mount } from '@vue/test-utils' +import FooterComponent from '@/components/BaseComponents/Footer.vue' + +describe('FooterComponent', () => { + it('renders properly and includes the correct copyright notice', () => { + const wrapper = mount(FooterComponent) + const footer = wrapper.find('#footer') + expect(footer.exists()).toBe(true) + expect(footer.text()).toContain('© 2024 Copyright: Anders Høvik, Andreas Svendsrud, Henrik Dybdal, Henrik Sandok, Jens Aanestad, Victor Kaste, Viktor Grevskott') + }) +}) diff --git a/src/components/BaseComponents/__tests__/Menu.spec.ts b/src/components/BaseComponents/__tests__/Menu.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..299b57553bde1b662366707418a89b3e677834ab --- /dev/null +++ b/src/components/BaseComponents/__tests__/Menu.spec.ts @@ -0,0 +1,40 @@ +// Import necessary libraries and mocks +import { describe, it, expect, vi } from 'vitest'; +import { mount } from '@vue/test-utils'; +import NavBar from '@/components/BaseComponents/Menu.vue'; +import { createRouter, createWebHistory } from 'vue-router'; + +// Use the minimal route setup for testing +const routes = [ + { path: '/', name: 'home', component: { template: '<div>Home</div>' } }, + { path: '/login', name: 'login', component: { template: '<div>Login</div>' } }, +]; +const router = createRouter({ + history: createWebHistory('/'), + routes, +}); + +// Mock store setup +const mockStore = { + isLoggedIn: false, + firstname: '', + clearUserInfo: vi.fn(), +}; +vi.mock('@/stores/UserStore', () => ({ + useUserInfoStore: () => mockStore +})); + +// Test the NavBar component +describe('NavBar', () => { + it('renders navbar and checks for logo and initial links', async () => { + const wrapper = mount(NavBar, { + global: { + plugins: [router] + } + }); + expect(wrapper.find('#navBar').exists()).toBe(true); + expect(wrapper.find('#logoImg').attributes('src')).toBe('/src/assets/Sparesti-logo.png'); + expect(wrapper.find('#logo').text()).toContain('Sparesti'); + expect(wrapper.findAll('.nav-item').length).toBeGreaterThan(0); + }); +}); diff --git a/src/components/Buttons/__tests__/Button1.spec.ts b/src/components/Buttons/__tests__/Button1.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..e1c2a39182663c7d37365f9973ff4953fe8d1768 --- /dev/null +++ b/src/components/Buttons/__tests__/Button1.spec.ts @@ -0,0 +1,18 @@ +import { describe, it, expect } from 'vitest' +import { mount } from '@vue/test-utils' +import ButtonComponent from '@/components/Buttons/Button1.vue' + +describe('ButtonComponent', () => { + it('displays the passed buttonText prop', () => { + const buttonText = 'Click Me!' + const wrapper = mount(ButtonComponent, { + props: { + buttonText + } + }) + + const button = wrapper.find('#buttonStyle') + expect(button.exists()).toBe(true) + expect(button.text()).toBe(buttonText) + }) +}) diff --git a/src/components/Buttons/__tests__/ShopButton.spec.ts b/src/components/Buttons/__tests__/ShopButton.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..25858cb46e529322a5da1386f31aab636bff64f9 --- /dev/null +++ b/src/components/Buttons/__tests__/ShopButton.spec.ts @@ -0,0 +1,28 @@ +import { describe, it, expect } from 'vitest' +import { mount } from '@vue/test-utils' +import ImageButtonComponent from '@/components/Buttons/ShopButton.vue' + +describe('ImageButtonComponent', () => { + it('renders the button with the correct text and image', () => { + const buttonText = 'Add Coin' + const wrapper = mount(ImageButtonComponent, { + props: { + buttonText + }, + global: { + stubs: { + // This stubs out all <router-link> and <router-view> components used in the app. + 'RouterLink': true, + 'RouterView': true + } + } + }) + + const button = wrapper.find('#buttonStyle') + expect(button.exists()).toBe(true) + expect(button.text()).toContain('+Add Coin') + const image = button.find('img') + expect(image.exists()).toBe(true) + expect(image.attributes('src')).toBe('/src/assets/items/pigcoin.png') + }) +}) diff --git a/src/components/InputFields/__tests__/BaseInput.spec.ts b/src/components/InputFields/__tests__/BaseInput.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..01ae12b718a826e062e98b1d0f6b53dfa5377aed --- /dev/null +++ b/src/components/InputFields/__tests__/BaseInput.spec.ts @@ -0,0 +1,23 @@ +import { describe, it, expect } from 'vitest' +import { mount } from '@vue/test-utils'; +import InputField from '@/components/InputFields/BaseInput.vue'; + +describe('InputField.vue', () => { + it('emits inputChangeEvent when input event is triggered', async () => { + const wrapper = mount(InputField, { + props: { + label: 'Test Label', + inputId: 'testId', + modelValue: '' + } + }); + + const input = wrapper.find('input'); + await input.setValue('Test Value'); + + expect(wrapper.emitted().inputChangeEvent).toBeTruthy(); + expect(wrapper.emitted().inputChangeEvent[0]).toEqual(['Test Value']); + }); + + // Add more test cases for other functionalities as needed +}); diff --git a/src/components/NewsComponents/__tests__/NewsComponent.spec.ts b/src/components/NewsComponents/__tests__/NewsComponent.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..7f5f15c28c46180c819f1b3fe25e8b2ea1718862 --- /dev/null +++ b/src/components/NewsComponents/__tests__/NewsComponent.spec.ts @@ -0,0 +1,56 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { mount } from '@vue/test-utils'; +import MyComponent from '@/components/NewsComponents/NewsComponent.vue'; // Adjust the import path according to your setup + +// Mocking the global fetch API +global.fetch = vi.fn(() => + Promise.resolve({ + json: () => Promise.resolve({ + articles: [ + { + urlToImage: 'example-image.jpg', + title: 'Test Title', + description: 'Test Description', + url: 'http://example.com' + } + ] + }) + }) +); + +describe('MyComponent', () => { + let wrapper; + + beforeEach(() => { + vi.useFakeTimers(); // Set up fake timers + vi.spyOn(global, 'setInterval'); // Spy on setInterval + + // Setting up the wrapper before each test + wrapper = mount(MyComponent); + }); + + afterEach(() => { + // Clearing all mocks and timers after each test + vi.clearAllMocks(); + vi.restoreAllMocks(); // Restore original implementations + vi.runOnlyPendingTimers(); + vi.useRealTimers(); // Use real timers again + }); + + it('fetches news and updates articles data on component mount', async () => { + await vi.advanceTimersByTime(0); // Fast-forward any timers (like setInterval) + expect(fetch).toHaveBeenCalledTimes(1); + expect(wrapper.vm.articles).toEqual([ + { + urlToImage: 'example-image.jpg', + title: 'Test Title', + description: 'Test Description', + url: 'http://example.com' + } + ]); + }); + + it('sets up an interval to fetch news every 5 minutes', () => { + expect(setInterval).toHaveBeenCalledWith(expect.any(Function), 300000); + }); +}); diff --git a/src/components/UpdateUserComponents/UpdateUserLayout.vue b/src/components/UpdateUserComponents/UpdateUserLayout.vue index 511ece58743fbe4502419a4646f6247c680d2530..838d8544930828865399e42f0559f0353f34e206 100644 --- a/src/components/UpdateUserComponents/UpdateUserLayout.vue +++ b/src/components/UpdateUserComponents/UpdateUserLayout.vue @@ -115,7 +115,7 @@ onMounted(() => { </div> </div> <br> - <h5 class="user-name">Yuki Hayashi</h5> + <h3 class="user-name">Yuki Hayashi</h3> <h6 class="user-email">yuki@Maxwell.com</h6> </div> </div> @@ -273,7 +273,7 @@ body { border-radius: 100px; } -.account-settings .user-profile h5.user-name { +.account-settings .user-profile h3.user-name { margin: 0 0 0.5rem 0; } diff --git a/src/components/UserProfile/UserProfileLayout.vue b/src/components/UserProfile/UserProfileLayout.vue index db2c4bca1ccc4af534c3a5aacd5dd162657ffaec..161c8b9550540e6122a65c8217937356dae6b23f 100644 --- a/src/components/UserProfile/UserProfileLayout.vue +++ b/src/components/UserProfile/UserProfileLayout.vue @@ -1,7 +1,4 @@ <script setup lang="ts"> - -import Menu from "@/components/BaseComponents/Menu.vue"; -import Footer from "@/components/BaseComponents/Footer.vue"; import { useRouter } from "vue-router"; import { useUserInfoStore } from "../../stores/UserStore"; @@ -12,14 +9,15 @@ let cardTitles = ["Spain tour", "Food waste", "Coffee", "Concert", "New book", " let points = 0; let streak = 0; -let route = useRouter() -function toRoadmap() { - route.push('/roadmap') -} +const router = useRouter() +const toRoadmap = () => { + router.push('/'); +}; -function toUpdateUserSettings() { - route.push('/update-user') -} +// Function to navigate to update user settings +const toUpdateUserSettings = () => { + router.push('/update-user'); +}; </script> <template> @@ -32,7 +30,7 @@ function toUpdateUserSettings() { <img src="https://bootdey.com/img/Content/avatar/avatar3.png" alt="Generic placeholder image" class="img-fluid img-thumbnail mt-4 mb-2" style="width: 150px; z-index: 1"> <button type="button" data-mdb-button-init data-mdb-ripple-init class="btn btn-outline-primary" - data-mdb-ripple-color="dark" style="z-index: 1;" @click="toUpdateUserSettings"> + data-mdb-ripple-color="dark" style="z-index: 1;" id="toUpdate" @click="toUpdateUserSettings"> Edit profile </button> </div> diff --git a/src/components/UserProfile/__tests__/UserProfileLayout.spec.ts b/src/components/UserProfile/__tests__/UserProfileLayout.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..bbe3d449d08693b9a13ca4ebd9a870e045d3f9c3 --- /dev/null +++ b/src/components/UserProfile/__tests__/UserProfileLayout.spec.ts @@ -0,0 +1,47 @@ +import { describe, it, expect, vi } from 'vitest'; +import { mount } from '@vue/test-utils'; +import DashboardComponent from '@/components/UserProfile/UserProfileLayout.vue'; // Update with your actual import + +// Correctly mocking 'vue-router' +vi.mock('vue-router', async (importOriginal) => { + const actual = await importOriginal(); // Import the actual vue-router module + return { + ...actual, // Spread all exports + // Optionally override specific exports if needed + }; +}); + +describe('DashboardComponent', () => { + // Now you can import and use createRouter and createWebHistory + const { createRouter, createWebHistory } = require('vue-router'); + + const router = createRouter({ + history: createWebHistory(), + routes: [{ path: '/', name: 'home' }, { path: '/update-user', name: 'update-user' }] + }); + + it('renders correctly', () => { + const wrapper = mount(DashboardComponent, { + global: { + plugins: [router] + } + }); + + // Check if the component renders + expect(wrapper.find('.container').exists()).toBe(true); + expect(wrapper.find('h1').text()).toBe('Andy Horwitz'); + expect(wrapper.findAll('.card').length).toBeGreaterThan(0); // Checks if any cards are rendered + }); + + it('navigates to roadmap page', async () => { + const wrapper = mount(DashboardComponent, { + global: { + plugins: [router] + } + }); + + await router.isReady(); // Wait for router to be ready + await wrapper.find('.stretched-link').trigger('click'); // Simulate clicking the link that calls toRoadmap + expect(router.currentRoute.value.path).toBe('/'); + }); +}); \ No newline at end of file diff --git a/src/router/index.ts b/src/router/index.ts index 70bd7ba69d8236b5ae338cec2490cb7c47ed272e..1bec708d93ee2784117b5a1caa24da5852299a0a 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -161,7 +161,7 @@ const routes = [ ]; const router = createRouter({ - history: createWebHistory(import.meta.env.BASE_URL), + history: createWebHistory(import.meta.env.BASE_URL || '/'), routes, scrollBehavior() { return { top: 0 }; diff --git a/tsconfig.vitest.json b/tsconfig.vitest.json index 571995d11e6acb21020f2570fb6a034609ee654e..0f0268e051af3a98dd7ea5656c937c3012d22623 100644 --- a/tsconfig.vitest.json +++ b/tsconfig.vitest.json @@ -4,8 +4,8 @@ "compilerOptions": { "composite": true, "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.vitest.tsbuildinfo", - + "lib": [], - "types": ["node", "jsdom"] + "types": ["node", "jsdom", "vitest/globals"] } }