diff --git a/app/src/routes/admin.tsx b/app/src/routes/admin.tsx index d0e2680e..955fa576 100644 --- a/app/src/routes/admin.tsx +++ b/app/src/routes/admin.tsx @@ -8,36 +8,31 @@ import leaf_white from "../assets/leaf-white.svg"; import axios from "axios"; import { useAuth } from "../contexts/AuthContext"; - - function dishStatus(dishNumbers: number[]) { - return(
-

{dishNumbers[0]}

+

{dishNumbers[0]}

Currently in use

-

{dishNumbers[1]}

+

{dishNumbers[1]}

Available

-

{dishNumbers[2]}

-

Overdue

+

{dishNumbers[2]}

+

Overdue

-

{dishNumbers[3]}

+

{dishNumbers[3]}

Dishes Lost

-
- ); } @@ -87,14 +82,12 @@ function dishTable() {

Dish ID

Dish type

Dish Status

-

Overdue

-

Email

+

Overdue

+

Email

); } - - function findDish(ID, dishesUsed) { if (ID == null) { return('') @@ -151,7 +144,7 @@ function createRows(dishesUsed, transactionsUsed) { else { dish = dish[0]; } - const id = dish.qid; + const id = dish.qid; // this is a number const type = dish.type; let email = ''; if(transaction.user == undefined) { @@ -235,11 +228,11 @@ function Rows(tableRows, search) {
{records.map(row =>
-

{row.id}

-
{dishTag(row.type)}
-

{dishTag(row.status)}

-

{row.overdue}

-

{row.email}

+

{row.id}

+
{dishTag(row.type)}
+

{dishTag(row.status)}

+

{row.overdue}

+

{row.email}

)}
diff --git a/app/src/test/adminHome.test.tsx b/app/src/test/adminHome.test.tsx new file mode 100644 index 00000000..6650e8ca --- /dev/null +++ b/app/src/test/adminHome.test.tsx @@ -0,0 +1,376 @@ +import { render, act, screen, waitFor, fireEvent } from "@testing-library/react"; +import { BrowserRouter as Router } from 'react-router-dom'; +import axios from 'axios'; +import '@testing-library/jest-dom'; + +import Admin from '../routes/admin'; + +jest.mock('axios'); +const mockedAxios = axios as jest.Mocked; +const useAuthMock = jest.spyOn(require('../contexts/AuthContext'), 'useAuth'); + +jest.mock('../contexts/AuthContext', () => ({ + ...jest.requireActual('../contexts/AuthContext'), + useAuth: () => ({ + currentUser: { + id: 'mocked-user-id', + role: 'admin', + email: 'mocked-email@ualberta.ca' + }, + sessionToken: 'mocked-session-token', + login: jest.fn(), + logout: jest.fn(), + }) +})); + +const mockDishesData = [ + { + borrowed: true, + borrowedAt: "2023-11-11T02:40:20.230Z", + condition: "alright", + id: "dish1", + qid: 123, + registered: "2023-07-22T20:19:47.144Z", + status: "available", + timesBorrowed: 3, + type: "mug", + userId: "user123" + }, + + { + borrowed: false, + borrowedAt: null, + condition: "alright", + id: "dish2", + qid: 23, + registered: "2023-07-22T20:19:50.144Z", + status: "available", + timesBorrowed: 7, + type: "mug", + userId: null + }, + + { + borrowed: true, + borrowedAt: "2023-11-16T02:40:20.230Z", + condition: "alright", + id: "dish3", + qid: 30, + registered: "2023-07-22T20:19:50.144Z", + status: "available", + timesBorrowed: 2, + type: "plate", + userId: "hello123" + }, + + { + borrowed: true, + borrowedAt: "2023-11-21T02:40:20.230Z", + condition: "alright", + id: "dish2", + qid: 23, + registered: "2023-07-22T20:19:50.144Z", + status: "available", + timesBorrowed: 9, + type: "plate", + userId: "test123" + }, + + { + borrowed: true, + borrowedAt: "2022-11-21T02:40:20.230Z", + condition: "alright", + id: "dish4", + qid: 51, + registered: "2020-07-22T20:19:50.144Z", + status: "available", + timesBorrowed: 1, + type: "plate", + userId: "lost123" + }, +] + +const mockTransactionsData = [ + { + dish: "dish1", + id: '123', + returned: { + condition: "alright", + timestamp: "" // An empty string to indicate not returned + }, + timestamp: "2023-11-11T02:40:20.230Z", + user: { + email: "user@example.com", + id: "user123", + role: "customer" + } + }, + + { + dish: "dish2", + id: '23', + returned: { + condition: "large_crack_chunk", + timestamp: "2023-09-26T23:44:52.548Z" + }, + timestamp: "2023-09-25T02:40:20.230Z", + user: { + email: "test@example.com", + id: "test123", + role: "volunteer" + } + }, + + { + dish: "dish3", + id: '30', + returned: { + condition: "alright", + timestamp: "" // An empty string to indicate not returned + }, + timestamp: "2023-11-16T02:40:20.230Z", + user: { + email: "hello@example.com", + id: "hello123", + role: "admin" + } + }, + + { + dish: "dish2", + id: '23', + returned: { + condition: "large_crack_chunk", + timestamp: "" + }, + timestamp: "2023-11-21T02:40:20.230Z", + user: { + email: "test@example.com", + id: "test123", + role: "volunteer" + } + }, + + { + dish: "dish4", + id: '51', + returned: { + condition: "alright", + timestamp: "" + }, + timestamp: "2022-11-21T02:40:20.230Z", + user: { + email: "test@example.com", + id: "lost123", + role: "customer" + } + }, + +]; + +beforeEach(async () => { + jest.clearAllMocks(); + + // Mock implementation for useAuth + useAuthMock.mockImplementation(() => ({ + currentUser: { + id: 'mocked-user-id', + role: 'admin', + email: 'mocked-email@ualberta.ca' + }, + sessionToken: 'mocked-session-token', + login: jest.fn(), + logout: jest.fn(), + })); + + mockedAxios.get.mockResolvedValue({ + data: { + dishes: mockDishesData, + transactions: mockTransactionsData + } + }); + +}); + +test("Renders homepage without crashing", async () => { + await act(async () => { + render( + + + + ); + }); + + expect(screen.getByPlaceholderText("Type text here...")).toBeInTheDocument(); + expect(screen.getByText("Dish ID")).toBeInTheDocument(); + expect(screen.getByText("Dish type")).toBeInTheDocument(); +}); + +describe('Dish Status', () => { + + it('check currently used and available dishes', async () => { + await act(async () => { + render( + + + + ); + }); + + await waitFor(() => { + expect(screen.getByText("Currently in use")).toBeInTheDocument(); + expect(screen.getByText("Available")).toBeInTheDocument(); + }); + + // Check the numbers displayed on the screen + const inUseDishes = mockDishesData.filter(dish => dish.borrowed == true).length; + const availableDishes = mockDishesData.filter(dish => dish.borrowed == false).length; + + await waitFor(() => { + expect(screen.getByTestId("in-use")).toHaveTextContent(inUseDishes.toString()) + expect(screen.getByTestId("returned")).toHaveTextContent(availableDishes.toString()) + + }); + + }); + + // Add more tests as needed + it('check for overdue dishes', async () => { + await act(async () => { + render( + + + + ); + }); + + await waitFor(() => { + expect(screen.getByTestId("overdue-text")).toBeInTheDocument(); + }); + + // Calculate the number of overdue dishes (more than 2 days but less than 30 days) + const timeToday = new Date(); + const overdueDishes = mockTransactionsData.filter(transaction => { + if (transaction.returned.timestamp) return false; // Skip if already returned + let borrowTime = new Date(transaction.timestamp); + let timeDifference = (timeToday - borrowTime) / (1000 * 60 * 60 * 24); + return timeDifference > 2 && timeDifference < 30; + }).length; + + // Check if the number of overdue dishes is displayed correctly + await waitFor(() => { + expect(screen.getByTestId("overdue-count")).toHaveTextContent(overdueDishes.toString()); + }); +}); + + it('check for lost dishes', async () => { + await act(async () => { + render( + + + + ); + }); + + await waitFor(() => { + expect(screen.getByText("Dishes Lost")).toBeInTheDocument(); + }); + + // Check the numbers displayed on the screen + // Calculate the number of lost dishes + const timeToday = new Date(); + const dishesLost = mockTransactionsData.filter(transaction => { + if (transaction.returned.timestamp) return false; // Skip if already returned + let borrowTime = new Date(transaction.timestamp); + let timeDifference = (timeToday - borrowTime) / (1000 * 60 * 60 * 24); + return timeDifference >= 30; + }).length; + + await waitFor(() => { + expect(screen.getByTestId("lost-count")).toHaveTextContent(dishesLost.toString()); + }); + }); +}); + +describe("Table Functionalities", () => { + it('renders the five column headers', async () => { + + await act(async () => { + render( + + + + ); + }); + + await waitFor(() => { + expect(screen.getByText('Dish ID')).toBeInTheDocument(); + expect(screen.getByText('Dish type')).toBeInTheDocument(); + expect(screen.getByText('Dish Status')).toBeInTheDocument(); + expect(screen.getByTestId('overdue-table')).toBeInTheDocument(); + expect(screen.getByTestId('email-table')).toBeInTheDocument(); + }); + + useAuthMock.mockRestore(); + }); + + it('fetches and displays transactions data', async () => { + await act(async () => { + render( + + + + ); + }); + + for (const dish of mockDishesData) { + const correspondingTransaction = mockTransactionsData.find(transaction => transaction.dish === dish.id); + + // Verify the columns + const idElements = await screen.findAllByTestId(`row-${dish.qid}`); + const rowEmails = await screen.findAllByTestId(`row-${correspondingTransaction?.user.email}`) + const dishTypes = await screen.findAllByTestId(`row-${dish.type}`); + + for (const idElement of idElements) { + expect(idElement.textContent).toBe(correspondingTransaction?.id); + } + for (const email of rowEmails) { + expect(email.textContent).toBe(correspondingTransaction?.user.email); + } + for (const dishType of dishTypes) { + expect(dishType.textContent).toBe(dish.type); + } + + } + + useAuthMock.mockRestore(); + + }) + + it('search functionality', async () => { + await act(async () => { + render( + + + + ); + }); + + // Simulate typing into the search bar + const searchInput = screen.getByPlaceholderText('Type text here...'); + fireEvent.change(searchInput, { target: { value: mockTransactionsData[0].id } }); + + // Simulate click on the search button + const searchButton = screen.getByText('Search'); + fireEvent.click(searchButton); + + // Assert that the table contains the expected data + const expectedData = screen.getByText(mockTransactionsData[0].user.email); + expect(expectedData).toBeInTheDocument(); + + + useAuthMock.mockRestore(); + + }) + +});