diff --git a/README.md b/README.md index dc15a20d7093cddeffe23b4a6ba1208c93f46aa4..e8951d831fc8a1abefb7eb5a3319c69288bdd8ff 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,19 @@ The frontend component of the Trivio quiz application is built using Vue.js and Vite. It provides a user interface for interacting with the backend APIs. Unit testing is performed using vitest, while integration testing is conducted using Cypress. +## Content +- [Functionality](#functionality) +- [Security]("#security") +- [Installation](#installation) +- [Usage](#usage) +- [Tests](#tests) +- [Dependencies](#dependencies) +- [Future work](#future-work) +- [Authors](#authors) +# Frontend Trivio + +The frontend component of the Trivio quiz application is built using Vue.js and Vite. It provides a user interface for interacting with the backend APIs. Unit testing is performed using vitest, while integration testing is conducted using Cypress. + ## Content - [Functionality](#functionality) - [Security]("#security") @@ -44,6 +57,42 @@ git clone https://gitlab.stud.idi.ntnu.no/team_fullastack/frontend_fullstack.git cd frontend_fullstack ``` +4. Install dependencies: +```bash +npm install +``` +## Functionality +User +- Sign in and log in +- Discover quizzes made by other users +- Take quizzes +- Get score after quiz +- Watch previous quiz scores +- Make a quiz +- Collaborate on quizzes +- Contact admin +- Change username and password + +Admin +- Access to all users +- See all trivios + +## Security +Trivio uses JWT-Tokens for user authentication during API calls. Upon user login, the backend validates the provided email and encoded password comparing them with existing user records. A JTO token is generated using JWTSBuilder, incorporating the user's id and expiration time. This token serves as the user's authentication credential for making API calls. + +## Installation +1. Make sure you have Node.js and npm installed + +2. Clone the repository +```bash +git clone https://gitlab.stud.idi.ntnu.no/team_fullastack/frontend_fullstack.git +``` + +3. Navigate to frontend_fullstack: +```bash +cd frontend_fullstack +``` + 4. Install dependencies: ```bash npm install @@ -60,6 +109,20 @@ This will start the application locally and display the URL where it can be acce ## Tests +### Cypress +1. Ensure that both the backend and frontend applications are running. + +2. Run Cypress interaction tests: +```bash +npm run test:e2e +``` + +### Vitest +1. Run Vitest unit tests: +```bash +npm run test:unit +``` + ## Dependencies - Vue.js - Vite @@ -71,6 +134,15 @@ This will start the application locally and display the URL where it can be acce ## Future work +### Mobile Responsive UI +Implement a mobile-friendly user interface to ensure optimal user experience across various devices and screen sizes. This involves optimizing layouts, font sizes, and interactive elements for smaller screens. + +### Additional Admin Functionalities +Expand the capabilities of the admin dashboard to provide more control and management options. + +### Performance +As the project developed, we adjusted our functionalities to meet requirements and time constraints. Consequently, some aspects of our database queries and server-side logic may not be optimized for performance. To enhance the efficiency of our application, future work will prioritize optimizing these areas, resulting in improved server-side performance. + ## Authors Gia Hy Nguyen Tini Tran diff --git a/cypress.config.ts b/cypress.config.ts index 0f66080fd0637080f5e2f5151146e89797be2e54..f80530a2a66e24534ac18223b7603fd3d0206f49 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -3,6 +3,6 @@ import { defineConfig } from 'cypress' export default defineConfig({ e2e: { specPattern: 'cypress/e2e/**/*.{cy,spec}.{js,jsx,ts,tsx}', - baseUrl: 'http://localhost:4173' + baseUrl: 'http://localhost:5173' } }) diff --git a/cypress/e2e/example.cy.ts b/cypress/e2e/example.cy.ts deleted file mode 100644 index 7554c35d8d0ff1412457a5ae7f8649a9871cc21b..0000000000000000000000000000000000000000 --- a/cypress/e2e/example.cy.ts +++ /dev/null @@ -1,8 +0,0 @@ -// https://on.cypress.io/api - -describe('My First Test', () => { - it('visits the app root url', () => { - cy.visit('/') - cy.contains('h1', 'You did it!') - }) -}) diff --git a/cypress/e2e/frontpage/login.cy.ts b/cypress/e2e/frontpage/login.cy.ts new file mode 100644 index 0000000000000000000000000000000000000000..754f077603a66e9dad23cbdbee3b8faca98699db --- /dev/null +++ b/cypress/e2e/frontpage/login.cy.ts @@ -0,0 +1,29 @@ +describe('Login Form', () => { + beforeEach(() => { + cy.visit('/') + }) + + it('should log in with valid credentials', () => { + // Enter valid username and password + cy.get('#username').type('test') + cy.get('#password').type('password') + + // Click the login button + cy.get('#signinButton').click() + + // Ensure redirected to the homepage or appropriate route after successful login + cy.url().should('include', '/homepage') + }) + + it('should display error message with invalid password', () => { + // Enter password + cy.get('#username').type('test') + cy.get('#password').type('pass') + + // Click the login button + cy.get('#signinButton').click() + + // Check if error message is displayed + cy.get('#loginStatus').should('have.text', 'Login failed') + }) +}) \ No newline at end of file diff --git a/cypress/e2e/frontpage/signup.cy.ts b/cypress/e2e/frontpage/signup.cy.ts new file mode 100644 index 0000000000000000000000000000000000000000..806dc785dbbe466d0b50617deb85852d7064d6cf --- /dev/null +++ b/cypress/e2e/frontpage/signup.cy.ts @@ -0,0 +1,56 @@ +describe('Create Account Form', () => { + beforeEach(() => { + cy.visit('/createAccount') + }) + + it('should sign in with valid credentials', () => { + // Enter valid username and password + cy.get('#username').type('account') + cy.get('#password').type('password') + cy.get('#email').type('account@mail.com') + + // Click the sign-in button + cy.get('#signinButton').click() + + // Ensure redirected to the dashboard or appropriate route after successful sign-in + cy.url().should('include', '/homepage') + }) + + it('should display error message with invalid email', () => { + // Enter valid username and password, but invalid email + cy.get('#username').type('username') + cy.get('#password').type('password') + cy.get('#email').type('InvalidEmail') + + // Click the sign-in button + cy.get('#signinButton').click() + + // Check if error message is displayed + cy.get('#loginStatus').should('have.text', 'Please enter a valid email address') + }) + + it('should display error message when username is empty', () => { + // Enter empty username + cy.get('#password').type('password') + cy.get('#email').type('username@mail.com') + + // Click the sign-in button + cy.get('#signinButton').click() + + // Check if error message is displayed + cy.get('#loginStatus').should('have.text', 'Please enter a username') + }) + + it('should display error message when password is empty', () => { + // Enter empty password + cy.get('#username').type('username') + cy.get('#email').type('username@mail.com') + + // Click the sign-in button + cy.get('#signinButton').click() + + // Check if error message is displayed + cy.get('#loginStatus').should('have.text', 'Please enter a password') + }) + }) + \ No newline at end of file diff --git a/cypress/e2e/mainpage/contact.cy.ts b/cypress/e2e/mainpage/contact.cy.ts new file mode 100644 index 0000000000000000000000000000000000000000..3e6960c8c9eef2e562e753df12e34eda73d49fa0 --- /dev/null +++ b/cypress/e2e/mainpage/contact.cy.ts @@ -0,0 +1,39 @@ +describe('Contact Form', () => { + beforeEach(() => { + cy.login('test', 'password') + cy.visit('/homepage/contact') + }) + + it('should submit the form with valid input', () => { + // Fill in the form fields + cy.get('#name').type('John Doe'); + cy.get('#email').type('john.doe@example.com'); + cy.get('#message').type('This is a test message'); + + // Submit the form + cy.get('#submit_button').click(); + + // Check for success message + cy.get('.response').should('contain.text', 'Message sent. Thanks for feedback!'); + }); + + it('should display error message if email field is empty', () => { + // Submit the form without filling in the email field + cy.get('#name').type('John Doe'); + cy.get('#message').type('This is a test message'); + cy.get('#submit_button').click(); + + // Check for error message + cy.get('.response').should('contain.text', 'Email cannot be empty'); + }); + + it('should display error message if message field is empty', () => { + // Submit the form without filling in the message field + cy.get('#name').type('John Doe'); + cy.get('#email').type('john.doe@example.com'); + cy.get('#submit_button').click(); + + // Check for error message + cy.get('.response').should('contain.text', 'Message cannot be empty'); + }); +}) diff --git a/cypress/e2e/mainpage/discovery.cy.ts b/cypress/e2e/mainpage/discovery.cy.ts new file mode 100644 index 0000000000000000000000000000000000000000..7e9a3519de7f69a95c5ee26a65014129ef539f0c --- /dev/null +++ b/cypress/e2e/mainpage/discovery.cy.ts @@ -0,0 +1,72 @@ +describe('Discovery View', () => { + beforeEach(() => { + cy.login('test', 'password') + }) + + it('should display trivios', () => { + // Check if card container exists + cy.get('.card-container').should('exist') + + // Check if at least one TrivioCard component is visible inside the card container + cy.get('.card-container .box', { timeout: 5000 }).should('exist') + }) + + it('should display trivio with correct tags', () => { + // Click "Add Tag" button + cy.get('.addTag').click() + + // Click the first tag input box and enter "Capital" + cy.get('.tag-box input').first().click().type('Capital') + + // Press Enter after typing the first tag + cy.get('.tag-box input').first().type('{enter}') + + // Check if at least one TrivioCard component is visible inside the card container + cy.get('.card-container .box', { timeout: 5000 }).should('exist') + }) + + it('should not display any trivio with incorrect tags', () => { + // Click "Add Tag" button + cy.get('.addTag').click() + + // Click the first tag input box and enter "InvalidTag" + cy.get('.tag-box input').first().click().type('InvalidTag') + + // Press Enter after typing the first tag + cy.get('.tag-box input').first().type('{enter}') + + // Check if no TrivioCard component is visible inside the card container + cy.get('.card-container .box', { timeout: 5000 }).should('not.exist') + }) + + + it('should display trivio with correct difficulty', () => { + // Select "Medium" in the Difficulty dropdown + cy.get('.FilterOptions select').eq(1).select('Easy') + + // Assert that "Medium" is selected in the Difficulty dropdown + cy.get('.FilterOptions select').eq(1).should('have.value', 'Easy') + + // Check if at least one TrivioCard component is visible inside the card container + cy.get('.card-container .box', { timeout: 5000 }).should('exist') + }) + + it('should display trivio with incorrect difficulty', () => { + // Select "Medium" in the Difficulty dropdown + cy.get('.FilterOptions select').eq(1).select('Medium') + + // Assert that "Medium" is selected in the Difficulty dropdown + cy.get('.FilterOptions select').eq(1).should('have.value', 'Medium') + + // Check if at least one TrivioCard component is visible inside the card container + cy.get('.card-container .box', { timeout: 5000 }).should('not.exist') + }) + + it('should navigate to start page when a TrivioCard is clicked', () => { + // Click on the first TrivioCard + cy.get('.card-container .box').first().click() + + // Assert that the URL has changed to the expected route + cy.url().should('include', '/homepage/start') + }) +}) diff --git a/cypress/e2e/mainpage/editTrivios.cy.ts b/cypress/e2e/mainpage/editTrivios.cy.ts new file mode 100644 index 0000000000000000000000000000000000000000..0a8c0fcb0447eba59449161978b493bc7695ec7f --- /dev/null +++ b/cypress/e2e/mainpage/editTrivios.cy.ts @@ -0,0 +1,38 @@ +describe('EditTrivio Component', () => { + beforeEach(() => { + cy.login('test', 'password') + cy.visit('/homepage/trivios') + cy.get('.card-container .box').first().click() + cy.get('.user-button-one').first().click() + }) + + it('should display the component with correct initial data', () => { + // Check if the component title is displayed + cy.contains('h1', 'Edit Trivio').should('exist') + + // Check if the initial input fields are displayed + cy.get('.header').should('exist') + cy.get('.input-boxes').should('exist') + cy.get('.questions').should('exist') + cy.get('.add-box').should('exist') + + // Check if the difficulty, category, and visibility dropdowns are displayed + cy.get('.option').should('have.length', 3) + + // Check if the add question button is displayed + cy.get('.add-question').should('exist') + }) + + it('should appear correct alert message and route to start page when clicking save', () => { + // Click the save button + cy.get('.top-button').contains('Save').click() + + // Check if the alert box is displayed with the correct message + cy.on('window:alert', (alertMessage) => { + expect(alertMessage).to.equal('Trivio Updated!') + }) + + // Ensure that the router navigates back to the start page after saving + cy.url().should('include', '/start') + }) +}) diff --git a/cypress/e2e/mainpage/history.cy.ts b/cypress/e2e/mainpage/history.cy.ts new file mode 100644 index 0000000000000000000000000000000000000000..08cec4cc0233d8469bf00158016d07152412dbc7 --- /dev/null +++ b/cypress/e2e/mainpage/history.cy.ts @@ -0,0 +1,18 @@ +describe('History Page', () => { + beforeEach(() => { + cy.login('test', 'password') + cy.visit('/homepage/history') + }) + + it('should display history', () => { + // Check if history is displayed + cy.get('.History').should('be.visible') + + // Check if trivio titles are visible + cy.get('.filter select#titleFilter').should('be.visible') + + // Check if pagination buttons are visible + cy.get('.pagination button').should('be.visible') + }) +}) + \ No newline at end of file diff --git a/cypress/e2e/mainpage/makeTrivio.cy.ts b/cypress/e2e/mainpage/makeTrivio.cy.ts new file mode 100644 index 0000000000000000000000000000000000000000..0fc76e0397faa5d7bb63794b6b305c541c5689ff --- /dev/null +++ b/cypress/e2e/mainpage/makeTrivio.cy.ts @@ -0,0 +1,42 @@ +describe('MakeTrivio Component', () => { + beforeEach(() => { + cy.login('test', 'password') + cy.visit('/homepage/create-trivio') + }) + + it('should display the component with correct initial data', () => { + // Check if the component title is displayed + cy.contains('h1', 'Create new quiz').should('exist') + + // Check if the initial input fields are displayed + cy.get('.header').should('exist') + cy.get('.input-boxes').should('exist') + cy.get('.questions').should('exist') + cy.get('.add-box').should('exist') + + // Check if the difficulty, category, and visibility dropdowns are displayed + cy.get('.option').should('have.length', 3) + + // Check if the add question button is displayed + cy.get('.add-question').should('exist') + }) + + it('should add a question when the add question button is clicked', () => { + // Click the add question button + cy.get('.add-question').click() + + // Check if the question input boxes are displayed + cy.get('.questions').should('have.length', 1) + + // Check if the answer input boxes are displayed + cy.get('.answer').should('have.length', 4) + }) + + it('should route to my trivios page when clicking create', () => { + // Click the save button + cy.get('.top-button').contains('Create').click() + + // Ensure that the router navigates back to the start page after saving + cy.url().should('include', '/trivios') + }) +}) diff --git a/cypress/e2e/mainpage/myTrivios.cy.ts b/cypress/e2e/mainpage/myTrivios.cy.ts new file mode 100644 index 0000000000000000000000000000000000000000..bd3363f6c8bf5860fbbae9ac9466b3bd6bc93a13 --- /dev/null +++ b/cypress/e2e/mainpage/myTrivios.cy.ts @@ -0,0 +1,27 @@ +describe('My Trivios Component', () => { + beforeEach(() => { + cy.login('test', 'password') + cy.visit('/homepage/trivios') + }) + + it('should display the filter options', () => { + // Check if category and difficulty filter options are visible + cy.get('.FilterOptions').should('be.visible') + cy.get('.option').should('have.length', 4) + }) + + it('should display the tags section', () => { + // Check if the tags section is visible + cy.get('.tag-section').should('be.visible') + + // Check if the "Add Tag" button is visible + cy.contains('.addTag', 'Add Tag +').should('be.visible') + }) + + it('should display pagination', () => { + // Check if the pagination section is visible + cy.get('.pagination').should('be.visible') + + cy.get('.pagination').find('button').should('exist') + }) +}) diff --git a/cypress/e2e/mainpage/profile.cy.ts b/cypress/e2e/mainpage/profile.cy.ts new file mode 100644 index 0000000000000000000000000000000000000000..2cf0410fed3c338d1b5ef165c9a04c715aa87c12 --- /dev/null +++ b/cypress/e2e/mainpage/profile.cy.ts @@ -0,0 +1,25 @@ +describe('Profile Component', () => { + beforeEach(() => { + cy.login('test', 'password'); + cy.visit('/homepage/profile'); + }); + + it('fetches user information on mount', () => { + // Check if user information is displayed correctly + cy.get('#username').should('have.value', 'test'); + cy.get('#email').should('have.value', 'username@mail.com'); + }); + + it('should allow editing user information', () => { + // Type new username and email + cy.get('#username').clear().type('newUsername') + cy.get('#email').clear().type('newEmail@example.com') + + // Save changes + cy.get('.user-info-header .save-button').click() + + // Check if changes are saved + cy.get('#username').should('have.value', 'newUsername') + cy.get('#email').should('have.value', 'newEmail@example.com') + }) +}); diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index 9b7bb8e2584cf20c9f578c69dada5f6024b4b042..843daaee6a2de888289340586e8175ed0bb83c04 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -1,39 +1,23 @@ /// <reference types="cypress" /> -// *********************************************** -// This example commands.ts shows you how to -// create various custom commands and overwrite -// existing commands. -// -// For more comprehensive examples of custom -// commands please read more here: -// https://on.cypress.io/custom-commands -// *********************************************** -// -// -// -- This is a parent command -- -// Cypress.Commands.add('login', (email, password) => { ... }) -// -// -// -- This is a child command -- -// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) -// -// -// -- This is a dual command -- -// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) -// -// -// -- This will overwrite an existing command -- -// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) -// -// declare global { -// namespace Cypress { -// interface Chainable { -// login(email: string, password: string): Chainable<void> -// drag(subject: string, options?: Partial<TypeOptions>): Chainable<Element> -// dismiss(subject: string, options?: Partial<TypeOptions>): Chainable<Element> -// visit(originalFn: CommandOriginalFn, url: string, options: Partial<VisitOptions>): Chainable<Element> -// } -// } -// } -export {} +declare global { + namespace Cypress { + interface Chainable { + /** + * Custom command to log in. + * @example cy.login('username', 'password') + */ + login(username: string, password: string): Chainable<void>; + } + } +} + +Cypress.Commands.add('login', (username, password) => { + cy.visit('/login') // Adjust the URL as needed + cy.get('#username').type(username) + cy.get('#password').type(password) + cy.get('#signinButton').click() + cy.url().should('include', '/homepage') +}) + +export { } diff --git a/package.json b/package.json index d8f9c1c17ec11a2d96ca31afbd143866e5fafae3..7726d7324b2eb115decaf953c28da5662cbbbfad 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,8 @@ "build": "run-p type-check \"build-only {@}\" --", "preview": "vite preview", "test:unit": "vitest", - "test:e2e": "start-server-and-test preview http://localhost:4173 'cypress run --e2e'", - "test:e2e:dev": "start-server-and-test 'vite dev --port 4173' http://localhost:4173 'cypress open --e2e'", + "test:e2e": "start-server-and-test preview http://localhost:5173 'cypress run --e2e'", + "test:e2e:dev": "start-server-and-test 'vite dev --port 5173' http://localhost:5173 'cypress open --e2e'", "build-only": "vite build", "type-check": "vue-tsc --build --force", "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore", diff --git a/src/components/__tests__/HelloWorld.spec.ts b/src/components/__tests__/HelloWorld.spec.ts deleted file mode 100644 index 2533202008f7270910420c60a420efaf9b505c90..0000000000000000000000000000000000000000 --- a/src/components/__tests__/HelloWorld.spec.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { describe, it, expect } from 'vitest' - -import { mount } from '@vue/test-utils' -import HelloWorld from '../HelloWorld.vue' - -describe('HelloWorld', () => { - it('renders properly', () => { - const wrapper = mount(HelloWorld, { props: { msg: 'Hello Vitest' } }) - expect(wrapper.text()).toContain('Hello Vitest') - }) -}) diff --git a/src/components/__tests__/HistoryBox.spec.ts b/src/components/__tests__/HistoryBox.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..cf9a540b0ca2022988e667b222c5336cb8f8c41a --- /dev/null +++ b/src/components/__tests__/HistoryBox.spec.ts @@ -0,0 +1,39 @@ +import { describe, it, expect } from 'vitest' + +import { mount } from '@vue/test-utils'; +import HistoryBox from '@/components/HistoryBox.vue'; + +describe('HistoryBox', () => { + it('renders with correct props', () => { + // Mock props + const props = { + title: 'Sample Quiz', + image: '/sample-image.png', + numberOfQuestion: 10, + username: 'John Doe', + difficulty: 'Easy', + score: 8, + date: '2024-04-10', + }; + + // Mount the component with mock props + const wrapper = mount(HistoryBox, { + props, + }); + + // Assert that the rendered component contains the correct information + expect(wrapper.find('.quiz-title').text()).toBe(props.title); + expect(wrapper.find('.difficulty').text()).toContain(`Difficulty: ${props.difficulty}`); + expect(wrapper.find('.username').text()).toBe(props.username); + expect(wrapper.find('.score-text').text()).toContain(`${props.score}/${props.numberOfQuestion}`); + expect(wrapper.find('.date').text()).toBe(props.date); + + // Assert image source + const imageSrc = wrapper.find('.quiz-box img').attributes('src'); + if (props.image) { + expect(imageSrc).toBe(props.image); + } else { + expect(imageSrc).toBe('/src/assets/trivio.svg'); + } + }); +}); diff --git a/src/components/__tests__/QuestionBox.spec.ts b/src/components/__tests__/QuestionBox.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..ff17e65757cb2039d0065bb2b0083762df92d7b1 --- /dev/null +++ b/src/components/__tests__/QuestionBox.spec.ts @@ -0,0 +1,42 @@ +import { describe, it, expect } from 'vitest' + +import { mount } from '@vue/test-utils'; +import QuestionBox from '@/components/QuestionBox.vue'; + +describe('QuestionBox', () => { + it('renders props correctly', () => { + // Define props + const props = { + image: '/path/to/image.jpg', + questionNumber: 1, + tags: ['tag1', 'tag2'], + question: ['Sample question line 1', 'Sample question line 2'], + }; + + // Mount the component with props + const wrapper = mount(QuestionBox, { + props, + }); + + // Assert that props are rendered correctly + expect(wrapper.find('img').attributes('src')).toBe(props.image); + expect(wrapper.find('label[for="number"]').text()).toBe(props.questionNumber.toString()); + + // Construct the expected question string + const expectedQuestion = JSON.stringify(props.question, null, 2); // Use JSON.stringify + const receivedQuestion = wrapper.find('label[for="question"]').text(); + expect(receivedQuestion).toBe(expectedQuestion); + }); + + it('renders default image if image prop is not provided', () => { + const wrapper = mount(QuestionBox, { + props: { + questionNumber: 1, + tags: ['tag1', 'tag2'], + question: ['Sample question line 1', 'Sample question line 2'], + }, + }); + + expect(wrapper.find('img').attributes('src')).toBe('/src/assets/trivio.svg'); + }); +}); diff --git a/src/components/__tests__/TrivioCard.spec.ts b/src/components/__tests__/TrivioCard.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..b53907d668652a3675e6b0e9e13533dc13d68e5c --- /dev/null +++ b/src/components/__tests__/TrivioCard.spec.ts @@ -0,0 +1,48 @@ +import { describe, it, expect } from 'vitest' + +import { mount } from '@vue/test-utils'; +import TrivioCard from '@/components/TrivioCard.vue'; + +describe('TrivioCard', () => { + it('renders props correctly', () => { + // Define props + const props = { + title: 'Sample Quiz', + image: '/path/to/image.jpg', + category: 'Sample Category', + numberOfQuestion: 10, + username: 'sampleUser', + difficulty: 'Easy', + media: '/path/to/media.jpg', + }; + + // Mount the component with props + const wrapper = mount(TrivioCard, { + props, + }); + + // Assert that props are rendered correctly + expect(wrapper.find('.title').text()).toBe(props.title); + expect(wrapper.find('.category').text()).toContain(props.category); + expect(wrapper.find('.question').text()).toContain(`${props.numberOfQuestion} Questions`); + expect(wrapper.find('.user').text()).toBe(props.username); + expect(wrapper.find('.difficulty').text()).toBe(props.difficulty); + expect(wrapper.find('img').attributes('src')).toBe(props.media); + }); + + it('renders default image if image prop is not provided', () => { + // Mount the component without image prop + const wrapper = mount(TrivioCard, { + props: { + title: 'Sample Quiz', + category: 'Sample Category', + numberOfQuestion: 10, + username: 'sampleUser', + difficulty: 'Easy', + }, + }); + + // Assert that default image is rendered + expect(wrapper.find('img').attributes('src')).toBe('/src/assets/trivio.svg'); + }); +}); diff --git a/src/components/__tests__/UserSchema.spec.ts b/src/components/__tests__/UserSchema.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..8d9d14990020891add86545f773db8bc3785a0da --- /dev/null +++ b/src/components/__tests__/UserSchema.spec.ts @@ -0,0 +1,40 @@ +import { describe, it, expect } from 'vitest' + +import { mount } from '@vue/test-utils'; +import UserSchema from '@/components/UserSchema.vue'; + +describe('UserSchema Component', () => { + it('renders correctly with default props', async () => { + // Mount the component + const wrapper = mount(UserSchema); + + // Assert that the component renders correctly + expect(wrapper.exists()).toBe(true); + expect(wrapper.find('h1').text()).toBe('Login'); + expect(wrapper.find('#username').exists()).toBe(true); + expect(wrapper.find('#password').exists()).toBe(true); + expect(wrapper.find('#email').exists()).toBe(false); // Email input should not be rendered by default + expect(wrapper.find('button').text()).toBe('Login'); + expect(wrapper.find('#loginStatus').text()).toBe(''); + }); + + it('renders correctly with custom props', async () => { + // Mount the component with custom props + const wrapper = mount(UserSchema, { + props: { + buttonText: 'Sign Up', + headerText: 'Create Account', + status: 'Invalid username or password', + }, + }); + + // Assert that the component renders correctly with custom props + expect(wrapper.exists()).toBe(true); + expect(wrapper.find('h1').text()).toBe('Create Account'); + expect(wrapper.find('#username').exists()).toBe(true); + expect(wrapper.find('#password').exists()).toBe(true); + expect(wrapper.find('#email').exists()).toBe(true); // Email input should be rendered with custom props + expect(wrapper.find('button').text()).toBe('Sign Up'); + expect(wrapper.find('#loginStatus').text()).toBe('Invalid username or password'); + }); +});