diff --git a/src/App.tsx b/src/App.tsx index 0e88f3f..24114e6 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,257 +1,359 @@ -import './App.css' -import { AppRoutes } from './Routes'; +import "./App.css"; +import { AppRoutes } from "./Routes"; import theme from "./theme.tsx"; import { ThemeProvider } from "@mui/material/styles"; -import { AppBar, Box, Button, Divider, Drawer, IconButton, Toolbar, Typography } from "@mui/material"; +import { + AppBar, + Box, + Button, + Divider, + Drawer, + IconButton, + Toolbar, + Typography, +} from "@mui/material"; import MenuIcon from "@mui/icons-material/Menu"; import { useNavigate } from "react-router-dom"; -import type { User } from 'firebase/auth'; +import type { User } from "firebase/auth"; import { useContext, useEffect, useState } from "react"; import { AuthContext } from "./contexts/AuthContext.tsx"; import { checkAdmin } from "./config/firebaseUtility.ts"; -import Menu from "@mui/material/Menu" -import MenuItem from "@mui/material/MenuItem" +import Menu from "@mui/material/Menu"; +import MenuItem from "@mui/material/MenuItem"; import { db } from "./config/firebase.ts"; function App() { - // set up navigate object - const navigate = useNavigate(); - - // get the firebase user object from context - const user: User | null = useContext(AuthContext); - - // state to store whether current user is admin - const [isAdmin, setIsAdmin] = useState(false); - - // anchor EL (position) where the pop up menu of admin opens - const [anchorEl, setAnchorEl] = useState(null); - const handleAdminClick = (e: React.MouseEvent): void => { - setAnchorEl(e.currentTarget); - } - - const handleAdminUsersClick = (e: React.MouseEvent): void => { - e.stopPropagation(); // prevent the click propagating to the parent button, causing reappear - setAnchorEl(null); // remove the pop up - navigate("/admin-users"); - } - - const handleAdminJobsClick = (e: React.MouseEvent): void => { - e.stopPropagation(); // prevent the click propagating to the parent button, causing reappear - setAnchorEl(null); // remove the pop up - navigate("/admin-jobs"); - } - - const handleAdminClose = (e: React.MouseEvent): void => { - e.stopPropagation(); - setAnchorEl(null); - } - - const [openDrawer, setOpenDrawer] = useState(false); - - // load admin status from firebase - useEffect(() => { - if (!user) { - setIsAdmin(false); - } else { - checkAdmin(db, user.email ?? "").then(setIsAdmin); - } - }, [user]) - - - - const left_buttons = [ - { name: "Finance jobs", route: "/finance/internships" }, // default to the internship page (can be customised to the user, not implemented) - { name: "Engineering jobs", route: "/engineering/internships" }, - { name: "Technology jobs", route: "/technology/internships" }, - ]; - - const right_buttons = [ - { name: "About", route: "/about" }, - { name: user ? "Profile" : "Login", route: user ? "/profile" : "/login" } - ] - - // add the my added jobs button if signed in - if (user) { - right_buttons.unshift( - { name: "My added jobs", route: "/myaddedjobs" } - ) + // set up navigate object + const navigate = useNavigate(); + + // get the firebase user object from context + const user: User | null = useContext(AuthContext); + + // state to store whether current user is admin + const [isAdmin, setIsAdmin] = useState(false); + + // anchor EL (position) where the pop up menu of admin opens + const [anchorEl, setAnchorEl] = useState(null); + const handleAdminClick = (e: React.MouseEvent): void => { + setAnchorEl(e.currentTarget); + }; + + const handleAdminUsersClick = (e: React.MouseEvent): void => { + e.stopPropagation(); // prevent the click propagating to the parent button, causing reappear + setAnchorEl(null); // remove the pop up + navigate("/admin-users"); + }; + + const handleAdminJobsClick = (e: React.MouseEvent): void => { + e.stopPropagation(); // prevent the click propagating to the parent button, causing reappear + setAnchorEl(null); // remove the pop up + navigate("/admin-jobs"); + }; + + const handleAdminClose = (e: React.MouseEvent): void => { + e.stopPropagation(); + setAnchorEl(null); + }; + + const [openDrawer, setOpenDrawer] = useState(false); + + // load admin status from firebase + useEffect(() => { + if (!user) { + setIsAdmin(false); + } else { + checkAdmin(db, user.uid ?? "").then(setIsAdmin); } - - return ( - - - {/* The top bar of the app */} - - - - {/* The logo of the app */} - { - // navigate to the home page when clicked on the icon - navigate("/"); - }}> - Tracker - - - {/* The left buttons */} - {left_buttons.map((button) => - - )} - - - - {/* AdminUsersManagement button and menu */} - {isAdmin && <> - - - Users Management - Jobs Management - } - - - {/* The right buttons */} - {right_buttons.map((button) => - - )} - - - {/* The menu icon */} - setOpenDrawer(true)}> - - - - {/* The drawer for showing toolbar options when the screen is too small */} - setOpenDrawer(false)} anchor='right'> - - - {/* Home button */} - - - - - {/* Left buttons */} - {left_buttons.map((button) => - - )} - - - - - {/* Right buttons */} - {right_buttons.map((button) => - - )} - - - - - - {/* Admin buttons */} - {isAdmin && } - - {isAdmin && } - - - - - - - - - - - - - - {/* App routes determines the content displayed under the appbar above */} - < AppRoutes /> - - - ) + }, [user]); + + const left_buttons = [ + { name: "Finance jobs", route: "/finance/internships" }, // default to the internship page (can be customised to the user, not implemented) + { name: "Engineering jobs", route: "/engineering/internships" }, + { name: "Technology jobs", route: "/technology/internships" }, + ]; + + const right_buttons = [ + { name: "About", route: "/about" }, + { name: user ? "Profile" : "Login", route: user ? "/profile" : "/login" }, + ]; + + // add the my added jobs button if signed in + if (user) { + right_buttons.unshift({ name: "My added jobs", route: "/myaddedjobs" }); + } + + return ( + + {/* The top bar of the app */} + + + + {/* The logo of the app */} + { + // navigate to the home page when clicked on the icon + navigate("/"); + }} + > + Tracker + + + {/* The left buttons */} + {left_buttons.map((button) => ( + + ))} + + + + {/* AdminUsersManagement button and menu */} + {isAdmin && ( + <> + + + + + Users Management + + + Jobs Management + + + + )} + + {/* The right buttons */} + {right_buttons.map((button) => ( + + ))} + + {/* The menu icon */} + setOpenDrawer(true)} + > + + + + {/* The drawer for showing toolbar options when the screen is too small */} + setOpenDrawer(false)} + anchor="right" + > + + {/* Home button */} + + + + {/* Left buttons */} + {left_buttons.map((button) => ( + + ))} + + + {/* Right buttons */} + {right_buttons.map((button) => ( + + ))} + + + + {/* Admin buttons */} + {isAdmin && ( + + )} + + {isAdmin && ( + + )} + + + + + + + {/* App routes determines the content displayed under the appbar above */} + + + ); } -export default App +export default App; diff --git a/src/config/firebaseUtility.test.ts b/src/config/firebaseUtility.test.ts index 1ea399a..4334baa 100644 --- a/src/config/firebaseUtility.test.ts +++ b/src/config/firebaseUtility.test.ts @@ -1,7 +1,10 @@ import { addDoc, collection, doc, setDoc } from "firebase/firestore"; import type { Firestore } from "firebase/firestore"; import { terminate } from "firebase/firestore"; -import { initializeFirebase, initializeFirebaseUnit } from "./firebaseUtilityTest"; +import { + initializeFirebase, + initializeFirebaseUnit, +} from "./firebaseUtilityTest"; import { clearFirestore } from "./firebaseUtilityTest"; import { checkAdmin, checkDuplicatedUrl } from "./firebaseUtility"; import { Sector } from "../utils/Sector"; @@ -10,77 +13,84 @@ import { Level } from "../utils/Level"; let db: Firestore; beforeAll(async () => { - db = initializeFirebaseUnit(); -}) + db = initializeFirebaseUnit(); +}); beforeEach(async () => { - await clearFirestore(db); + await clearFirestore(db); }); afterAll(async () => { - await terminate(db); + await terminate(db); }); const dummyJob = { - company_name: "testname1", - programme_name: "testprogramme1", - url: "https://www.google.com/", - closing_date: Date.now(), - salary: 0, - cv: false, - cover_letter: false, - questions: false, - visa_sponsorship: false, - sector: Sector.Technology, - level: Level.Graduate, - admin_approved_on: 200 + company_name: "testname1", + programme_name: "testprogramme1", + url: "https://www.google.com/", + closing_date: Date.now(), + salary: 0, + cv: false, + cover_letter: false, + questions: false, + visa_sponsorship: false, + sector: Sector.Technology, + level: Level.Graduate, + admin_approved_on: 200, }; - test("existing_user_admin", async () => { - // add the user to the database before the test starts - const userRef = await setDoc(doc(db, "users", "user123@gmail.com"), { - admin_granted_on: "0", - is_admin: true, - last_posted_on: "0" - }); - - const result = await checkAdmin(db, "user123@gmail.com"); - expect(result).toBe(true); -}) + // add the user to the database before the test starts + const userRef = await setDoc(doc(db, "users", "uid123"), { + admin_granted_on: "0", + is_admin: true, + last_posted_on: "0", + }); + + const result = await checkAdmin(db, "uid123"); + expect(result).toBe(true); +}); test("existing_user_not_admin", async () => { - // add the user to the database before the test starts - const userRef = await setDoc(doc(db, "users", "user123@gmail.com"), { - admin_granted_on: "0", - is_admin: false, - last_posted_on: "0" - }); - - const result = await checkAdmin(db, "user123@gmail.com"); - expect(result).toBe(false); + // add the user to the database before the test starts + const userRef = await setDoc(doc(db, "users", "uid123"), { + admin_granted_on: "0", + is_admin: false, + last_posted_on: "0", + }); + + const result = await checkAdmin(db, "uid123"); + expect(result).toBe(false); }); -// if a user does not exist, checkAdmin should return false +// if a user does not exist, checkAdmin should return false test("non_existing_user", async () => { - const result = await checkAdmin(db, "not-exist@gmail.com"); - expect(result).toBe(false); + const result = await checkAdmin(db, "not-exist-uid"); + expect(result).toBe(false); }); -// check that duplicated urls in the same sector and same level return true +// check that duplicated urls in the same sector and same level return true test("duplicated_url", async () => { - const job1 = await addDoc(collection(db, "jobs"), dummyJob); - - const duplicated = await checkDuplicatedUrl(db, Sector.Technology, Level.Graduate, dummyJob.url); - expect(duplicated).toBeTruthy(); - + const job1 = await addDoc(collection(db, "jobs"), dummyJob); + + const duplicated = await checkDuplicatedUrl( + db, + Sector.Technology, + Level.Graduate, + dummyJob.url + ); + expect(duplicated).toBeTruthy(); }); -// check that duplicated urls in different sector or level return false +// check that duplicated urls in different sector or level return false test("duplicated_different_sector", async () => { - const job1 = await addDoc(collection(db, "jobs"), dummyJob); - - const duplicated = await checkDuplicatedUrl(db, Sector.Finance, Level.Graduate, dummyJob.url); - expect(duplicated).toBeFalsy(); - -}); \ No newline at end of file + const job1 = await addDoc(collection(db, "jobs"), dummyJob); + + const duplicated = await checkDuplicatedUrl( + db, + Sector.Finance, + Level.Graduate, + dummyJob.url + ); + expect(duplicated).toBeFalsy(); +}); diff --git a/src/config/firebaseUtility.ts b/src/config/firebaseUtility.ts index b78eaef..127e942 100644 --- a/src/config/firebaseUtility.ts +++ b/src/config/firebaseUtility.ts @@ -1,38 +1,49 @@ -import { collection, doc, getDoc, getDocs, query, where } from "firebase/firestore"; +import { + collection, + doc, + getDoc, + getDocs, + query, + where, +} from "firebase/firestore"; import type { Firestore } from "firebase/firestore"; // async function to check if user is admin from the database -export async function checkAdmin(db: Firestore, email: string): Promise { - try { - const docRef = doc(db, "users", email); - const docSnap = await getDoc(docRef); - const data = docSnap.data(); - if (docSnap.exists()) { - return data ? data.is_admin : false; - } else { - return false; - } - } catch (error) { - console.log(error); - return false; +export async function checkAdmin(db: Firestore, uid: string): Promise { + try { + const docRef = doc(db, "users", uid); + const docSnap = await getDoc(docRef); + const data = docSnap.data(); + if (docSnap.exists()) { + return data ? data.is_admin : false; + } else { + return false; } + } catch (error) { + console.log(error); + return false; + } } -// check if a job corresponding to a url already exists in the job tab -// to prevent duplicated urls -export async function checkDuplicatedUrl(db: Firestore, sector: string, level: string, url: string): Promise { - try { - const q = query( - collection(db, 'jobs'), - where("sector", "==", sector), - where("level", "==", level), - where("url", "==", url) - ); - const querySnapshot = await getDocs(q); - return querySnapshot.size !== 0; - - } catch (error) { - console.log(error); - return false; - } -} \ No newline at end of file +// check if a job corresponding to a url already exists in the job tab +// to prevent duplicated urls +export async function checkDuplicatedUrl( + db: Firestore, + sector: string, + level: string, + url: string +): Promise { + try { + const q = query( + collection(db, "jobs"), + where("sector", "==", sector), + where("level", "==", level), + where("url", "==", url) + ); + const querySnapshot = await getDocs(q); + return querySnapshot.size !== 0; + } catch (error) { + console.log(error); + return false; + } +} diff --git a/src/config/firebaseUtilityFetch.ts b/src/config/firebaseUtilityFetch.ts index 99082ab..fd0a128 100644 --- a/src/config/firebaseUtilityFetch.ts +++ b/src/config/firebaseUtilityFetch.ts @@ -167,6 +167,21 @@ export async function fetchJobs( } } +export async function fetchUserIDFromEmail( + db: Firestore, + email: string +): Promise { + try { + const q = query(collection(db, "users"), where("email", "==", email)); + const querySnapshot = await getDocs(q); + const uid = querySnapshot.docs[0].id; + return uid; + } catch (error: any) { + console.log(error); + return ""; + } +} + export async function fetchAdmins( db: Firestore ): Promise<{ email: string; granted_on: Date }[]> { @@ -174,13 +189,16 @@ export async function fetchAdmins( const q = query( collection(db, "users"), where("is_admin", "==", true), - orderBy("admin_granted_on") + orderBy("admin_granted_on") // this field must be present for the admin to be returned ); const querySnapshot = await getDocs(q); const adminData: { email: string; granted_on: Date }[] = []; querySnapshot.forEach((elem) => { const data = elem.data(); - adminData.push({ email: elem.id, granted_on: data.admin_granted_on }); + adminData.push({ + email: data.email, + granted_on: data.admin_granted_on, + }); }); return adminData; diff --git a/src/pages/AdminJobsManagement.test.tsx b/src/pages/AdminJobsManagement.test.tsx index b5d8351..b944be0 100644 --- a/src/pages/AdminJobsManagement.test.tsx +++ b/src/pages/AdminJobsManagement.test.tsx @@ -63,7 +63,8 @@ const mockUserUnverified = { // test that non-admins cannot access the page test("Not admin", async () => { // in the firestore database, set admin to be false - const userRef = await setDoc(doc(db, "users", "user123@gmail.com"), { + const userRef = await setDoc(doc(db, "users", "123"), { + email: "user123@gmail.com", admin_granted_on: "0", is_admin: false, last_posted_on: "0", @@ -83,7 +84,8 @@ test("Not admin", async () => { // test that an admin but with unverified email cannot access the page test("Unverified", async () => { // grant admin - const userRef = await setDoc(doc(db, "users", "user1232@gmail.com"), { + const userRef = await setDoc(doc(db, "users", "123"), { + email: "user123@gmail.com", admin_granted_on: "0", is_admin: true, last_posted_on: "0", @@ -105,7 +107,8 @@ test("Unverified", async () => { // test that a message is displayed when there are no jobs posted test("No jobs", async () => { // in the firestore database, set admin to be false - const userRef = await setDoc(doc(db, "users", "user123@gmail.com"), { + const userRef = await setDoc(doc(db, "users", "123"), { + email: "user123@gmail.com", admin_granted_on: "0", is_admin: true, last_posted_on: "0", @@ -124,7 +127,8 @@ test("No jobs", async () => { // test that both approve and reject button should be enabled for pending job test("View pending job", async () => { - const userRef = await setDoc(doc(db, "users", "user123@gmail.com"), { + const userRef = await setDoc(doc(db, "users", "123"), { + email: "user123@gmail.com", admin_granted_on: "0", is_admin: true, last_posted_on: "0", @@ -163,7 +167,8 @@ test("View pending job", async () => { // test that the rejection message can be seen in a rejected job test("View rejected job", async () => { - const userRef = await setDoc(doc(db, "users", "user123@gmail.com"), { + const userRef = await setDoc(doc(db, "users", "123"), { + email: "user123@gmail.com", admin_granted_on: "0", is_admin: true, last_posted_on: "0", @@ -214,7 +219,8 @@ test("View rejected job", async () => { // test that the correct rejection message is displayed test("Show rejection message", async () => { - const userRef = await setDoc(doc(db, "users", "user123@gmail.com"), { + const userRef = await setDoc(doc(db, "users", "123"), { + email: "user123@gmail.com", admin_granted_on: "0", is_admin: true, last_posted_on: "0", @@ -252,7 +258,8 @@ test("Show rejection message", async () => { // test that no message is shown when rejection message is not provided test("No rejection message", async () => { - const userRef = await setDoc(doc(db, "users", "user123@gmail.com"), { + const userRef = await setDoc(doc(db, "users", "123"), { + email: "user123@gmail.com", admin_granted_on: "0", is_admin: true, last_posted_on: "0", @@ -290,7 +297,8 @@ test("No rejection message", async () => { // test that a job can be approved test("Approve job", async () => { - const userRef = await setDoc(doc(db, "users", "user123@gmail.com"), { + const userRef = await setDoc(doc(db, "users", "123"), { + email: "user123@gmail.com", admin_granted_on: "0", is_admin: true, last_posted_on: "0", @@ -365,7 +373,8 @@ test("Approve job", async () => { // test that a job can be rejected test("Reject job", async () => { - const userRef = await setDoc(doc(db, "users", "user123@gmail.com"), { + const userRef = await setDoc(doc(db, "users", "123"), { + email: "user123@gmail.com", admin_granted_on: "0", is_admin: true, last_posted_on: "0", @@ -413,7 +422,8 @@ test("Reject job", async () => { }); test("Filtering jobs by status", async () => { - const userRef = await setDoc(doc(db, "users", "user123@gmail.com"), { + const userRef = await setDoc(doc(db, "users", "123"), { + email: "user123@gmail.com", admin_granted_on: "0", is_admin: true, last_posted_on: "0", @@ -459,7 +469,8 @@ test("Filtering jobs by status", async () => { }); test("Rejecting jobs in the correct position after filtering", async () => { - const userRef = await setDoc(doc(db, "users", "user123@gmail.com"), { + const userRef = await setDoc(doc(db, "users", "123"), { + email: "user123@gmail.com", admin_granted_on: "0", is_admin: true, last_posted_on: "0", @@ -529,7 +540,8 @@ test("Rejecting jobs in the correct position after filtering", async () => { }); test("Filtering jobs by status scraped", async () => { - const userRef = await setDoc(doc(db, "users", "user123@gmail.com"), { + const userRef = await setDoc(doc(db, "users", "123"), { + email: "user123@gmail.com", admin_granted_on: "0", is_admin: true, last_posted_on: "0", @@ -575,7 +587,8 @@ test("Filtering jobs by status scraped", async () => { // test that the approve form rejects scraped jobs with empty sector or level test("Do not approve scraped job if missing sector or level", async () => { - const userRef = await setDoc(doc(db, "users", "user123@gmail.com"), { + const userRef = await setDoc(doc(db, "users", "123"), { + email: "user123@gmail.com", admin_granted_on: "0", is_admin: true, last_posted_on: "0", @@ -611,7 +624,8 @@ test("Do not approve scraped job if missing sector or level", async () => { // test that scraped jobs can be approved test("Approve scraped job", async () => { - const userRef = await setDoc(doc(db, "users", "user123@gmail.com"), { + const userRef = await setDoc(doc(db, "users", "123"), { + email: "user123@gmail.com", admin_granted_on: "0", is_admin: true, last_posted_on: "0", @@ -684,7 +698,8 @@ test("Approve scraped job", async () => { }); test("Reject scraped job", async () => { - const userRef = await setDoc(doc(db, "users", "user123@gmail.com"), { + const userRef = await setDoc(doc(db, "users", "123"), { + email: "user123@gmail.com", admin_granted_on: "0", is_admin: true, last_posted_on: "0", @@ -732,7 +747,8 @@ test("Reject scraped job", async () => { // test that the correct rejection message is displayed test("Show rejection message scraped jobs", async () => { - const userRef = await setDoc(doc(db, "users", "user123@gmail.com"), { + const userRef = await setDoc(doc(db, "users", "123"), { + email: "user123@gmail.com", admin_granted_on: "0", is_admin: true, last_posted_on: "0", diff --git a/src/pages/AdminJobsManagement.tsx b/src/pages/AdminJobsManagement.tsx index 76e7a35..4b2b06f 100644 --- a/src/pages/AdminJobsManagement.tsx +++ b/src/pages/AdminJobsManagement.tsx @@ -400,9 +400,9 @@ export default function AdminJobsManagement() { // check if the user is admin useEffect(() => { - if (user && user.email) { + if (user && user.uid) { setLoadingAdmin(true); - checkAdmin(db, user.email).then((result) => { + checkAdmin(db, user.uid).then((result) => { setLoadingAdmin(false); setIsAdmin(result); }); diff --git a/src/pages/AdminUsersManagement.test.tsx b/src/pages/AdminUsersManagement.test.tsx index 715809c..a95e7f6 100644 --- a/src/pages/AdminUsersManagement.test.tsx +++ b/src/pages/AdminUsersManagement.test.tsx @@ -2,12 +2,12 @@ import AdminUsersManagement from "./AdminUsersManagement"; import { AuthContext } from "../contexts/AuthContext"; import { render, screen, waitFor } from "@testing-library/react"; import type { Auth, User } from "firebase/auth"; -import { addDoc, collection, doc, getDocs, query, getDoc, setDoc, where } from "firebase/firestore"; +import { doc, getDoc, setDoc, where } from "firebase/firestore"; import type { Firestore } from "firebase/firestore"; import { terminate } from "firebase/firestore"; import { initializeFirebase } from "../config/firebaseUtilityTest"; import { clearFirestore } from "../config/firebaseUtilityTest"; -import '@testing-library/jest-dom' +import "@testing-library/jest-dom"; import { act } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; @@ -15,148 +15,154 @@ let auth: Auth; let db: Firestore; beforeAll(() => { - ({ auth, db } = initializeFirebase()); + ({ auth, db } = initializeFirebase()); }); beforeEach(async () => { - await clearFirestore(db); // clean state before each test + await clearFirestore(db); // clean state before each test }); afterAll(async () => { - await terminate(db); + await terminate(db); }); const mockUser = { - uid: "123", - email: "user123@gmail.com", - displayName: "Test User", - emailVerified: true, + uid: "123", + email: "user123@gmail.com", + displayName: "Test User", + emailVerified: true, } as User; - const mockUserUnverified = { - uid: "123", - email: "user1232@gmail.com", - displayName: "Test User2", - emailVerified: false, + uid: "1232", + email: "user1232@gmail.com", + displayName: "Test User2", + emailVerified: false, } as User; - -// test that non-admins cannot access the page +// test that non-admins cannot access the page test("Not admin", async () => { - // in the firestore database, set admin to be false - const userRef = await setDoc(doc(db, "users", "user123@gmail.com"), { - admin_granted_on: "0", - is_admin: false, - last_posted_on: "0" - }); - - render( - - - - ); - - // The screen should show Page not found - const notFound = await screen.findByText("Page not found"); - expect(notFound).toBeInTheDocument(); + // in the firestore database, set admin to be false + const userRef = await setDoc(doc(db, "users", "123"), { + admin_granted_on: "0", + is_admin: false, + last_posted_on: "0", + }); + + render( + + + + ); + + // The screen should show Page not found + const notFound = await screen.findByText("Page not found"); + expect(notFound).toBeInTheDocument(); }); // test that an admin but with unverified email cannot access the page test("Unverified", async () => { - // grant admin - const userRef = await setDoc(doc(db, "users", "user1232@gmail.com"), { - admin_granted_on: "0", - is_admin: true, - last_posted_on: "0" - }); - - render( - - - - ); - - // The screen should show Unverified - const notFound = await screen.findByText("Please click here to verify your email."); - expect(notFound).toBeInTheDocument(); + // grant admin + const userRef = await setDoc(doc(db, "users", "1232"), { + admin_granted_on: "0", + is_admin: true, + last_posted_on: "0", + }); + + render( + + + + ); + + // The screen should show Unverified + const notFound = await screen.findByText( + "Please click here to verify your email." + ); + expect(notFound).toBeInTheDocument(); }); -// test that a list of admins are displayed +// test that a list of admins are displayed test("Display admins", async () => { - // in the firestore database, set 2 admins and one regular user - await setDoc(doc(db, "users", "user123@gmail.com"), { - admin_granted_on: "33", - is_admin: true, - last_posted_on: "0" - }); - - await setDoc(doc(db, "users", "adm@gmail.com"), { - admin_granted_on: "44", - is_admin: true, - last_posted_on: "0" - }); - - await setDoc(doc(db, "users", "not@gmail.com"), { - is_admin: false, - last_posted_on: "0" - }); - - - render( - - - - ); - - // the two correct emails should be displayed (in ascending order) - const emails = await screen.findAllByText(/.+@gmail\.com/i); - expect(emails.length).toBe(2); - expect(emails[0].textContent).toBe("user123@gmail.com"); - expect(emails[1].textContent).toBe("adm@gmail.com"); + // in the firestore database, set 2 admins and one regular user + await setDoc(doc(db, "users", "123"), { + email: "user123@gmail.com", + admin_granted_on: "33", + is_admin: true, + last_posted_on: "0", + }); + + await setDoc(doc(db, "users", "adm"), { + email: "adm@gmail.com", + admin_granted_on: "44", + is_admin: true, + last_posted_on: "0", + }); + + await setDoc(doc(db, "users", "not"), { + email: "not@gmail.com", + is_admin: false, + last_posted_on: "0", + }); + + render( + + + + ); + + // the two correct emails should be displayed (in ascending order) + const emails = await screen.findAllByText(/.+@gmail\.com/i); + expect(emails.length).toBe(2); + expect(emails[0].textContent).toBe("user123@gmail.com"); + expect(emails[1].textContent).toBe("adm@gmail.com"); }); test("Add admins", async () => { - // in the firestore database, set 2 admins and one regular user - await setDoc(doc(db, "users", "user123@gmail.com"), { - admin_granted_on: "33", - is_admin: true, - last_posted_on: "0" - }); - - await setDoc(doc(db, "users", "adm@gmail.com"), { - admin_granted_on: "44", - is_admin: true, - last_posted_on: "0" - }); - - await setDoc(doc(db, "users", "not@gmail.com"), { - is_admin: false, - last_posted_on: "0" - }); - - const snapshot = await getDoc(doc(db, "users", "not@gmail.com")); - expect(snapshot.data()?.is_admin).toBeFalsy(); - - - await act(() => render( - - - - )); - - // input email - const emailTextfield = await screen.findByTestId('admin-email-textfield') - const input = emailTextfield.querySelector('input'); - await act(() => userEvent.type(input!, "not@gmail.com")); - - // click add btn - const btn = await screen.getByRole("button", { name: "Add" }); - await act(() => userEvent.click(btn)); - - // the not user should be an admin now - await waitFor(async () => { - const snapshot = await getDoc(doc(db, "users", "not@gmail.com")); - expect(snapshot.data()?.is_admin).toBeTruthy(); - }); -}) + // in the firestore database, set 2 admins and one regular user + await setDoc(doc(db, "users", "123"), { + email: "user123@gmail.com", + admin_granted_on: "33", + is_admin: true, + last_posted_on: "0", + }); + + await setDoc(doc(db, "users", "adm"), { + email: "adm@gmail.com", + admin_granted_on: "44", + is_admin: true, + last_posted_on: "0", + }); + + await setDoc(doc(db, "users", "not"), { + email: "not@gmail.com", + is_admin: false, + last_posted_on: "0", + }); + + const snapshot = await getDoc(doc(db, "users", "not")); + expect(snapshot.data()?.is_admin).toBeFalsy(); + + await act(() => + render( + + + + ) + ); + + // input email + const emailTextfield = await screen.findByTestId("admin-email-textfield"); + const input = emailTextfield.querySelector("input"); + await act(() => userEvent.type(input!, "not@gmail.com")); + + // click add btn + const btn = await screen.getByRole("button", { name: "Add" }); + await act(() => userEvent.click(btn)); + + // the not user should be an admin now + await waitFor(async () => { + const snapshot = await getDoc(doc(db, "users", "not")); + expect(snapshot.data()?.is_admin).toBeTruthy(); + }); +}); diff --git a/src/pages/AdminUsersManagement.tsx b/src/pages/AdminUsersManagement.tsx index 7361c08..08b70ea 100644 --- a/src/pages/AdminUsersManagement.tsx +++ b/src/pages/AdminUsersManagement.tsx @@ -5,8 +5,19 @@ import type { User } from "firebase/auth"; import { AuthContext } from "../contexts/AuthContext.tsx"; import NotFound from "./Notfound.tsx"; import { db } from "../config/firebase.ts"; -import { doc, getDoc, updateDoc } from "firebase/firestore"; -import { fetchAdmins } from "../config/firebaseUtilityFetch.ts"; +import { + collection, + doc, + getDoc, + getDocs, + query, + updateDoc, + where, +} from "firebase/firestore"; +import { + fetchAdmins, + fetchUserIDFromEmail, +} from "../config/firebaseUtilityFetch.ts"; import { AdminUsersTable } from "../components/AdminUsersTable.tsx"; import { LoadingTable } from "../components/LoadingTable.tsx"; import Unauthorised from "./Unauthorised.tsx"; @@ -39,20 +50,24 @@ export default function AdminUsersManagement() { return; } - // check if the user is already an admin. If yes, return - const docSnap = await getDoc(doc(db, "users", newAdminEmail)); - if (!docSnap.exists()) { + // get the user id of the admin email + const newAdminUserID = await fetchUserIDFromEmail(db, newAdminEmail); + + // check if the user exists + if (!newAdminUserID) { setNewAdminError("User does not exist"); return; } - const data = docSnap.data(); - if (data["is_admin"]) { - setNewAdminError("User is already an admin"); - return; - } + // check if the user is already an admin + // const data = docSnap.data(); + // if (data["is_admin"]) { + // setNewAdminError("User is already an admin"); + // return; + // } - await updateDoc(doc(db, "users", newAdminEmail), { + // update the is_admin field of the user with this email + await updateDoc(doc(db, "users", newAdminUserID), { admin_granted_on: Date.now(), is_admin: true, }); @@ -68,9 +83,9 @@ export default function AdminUsersManagement() { // before loading the screen, load the value of isAdmin by checking whether the current user is an admin // from firebase database useEffect(() => { - if (user && user.email) { + if (user && user.uid) { setLoadingAdmin(true); - checkAdmin(db, user.email).then((result) => { + checkAdmin(db, user.uid).then((result) => { setLoadingAdmin(false); setIsAdmin(result); }); diff --git a/src/pages/Graduates.test.tsx b/src/pages/Graduates.test.tsx index 0eb09ee..a748bcc 100644 --- a/src/pages/Graduates.test.tsx +++ b/src/pages/Graduates.test.tsx @@ -1,12 +1,25 @@ import { AuthContext } from "../contexts/AuthContext"; import { render, screen, waitFor } from "@testing-library/react"; -import { createUserWithEmailAndPassword, type Auth, type User } from "firebase/auth"; -import { addDoc, collection, doc, getDocs, query, getDoc, setDoc, where } from "firebase/firestore"; +import { + createUserWithEmailAndPassword, + type Auth, + type User, +} from "firebase/auth"; +import { + addDoc, + collection, + doc, + getDocs, + query, + getDoc, + setDoc, + where, +} from "firebase/firestore"; import type { Firestore } from "firebase/firestore"; import { terminate } from "firebase/firestore"; import { initializeFirebase } from "../config/firebaseUtilityTest"; import { clearFirestore } from "../config/firebaseUtilityTest"; -import '@testing-library/jest-dom'; +import "@testing-library/jest-dom"; import { act } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { MemoryRouter, Router } from "react-router-dom"; @@ -21,199 +34,217 @@ let auth: Auth; let createdUser: User | null = null; beforeAll(() => { - ({ auth, db } = initializeFirebase()); + ({ auth, db } = initializeFirebase()); }); beforeEach(async () => { - await clearFirestore(db); + await clearFirestore(db); }); afterAll(async () => { - await terminate(db); + await terminate(db); }); const mockUser = { - uid: "123", - email: "user123@gmail.com", - displayName: "Test User", - emailVerified: true, + uid: "123", + email: "user123@gmail.com", + displayName: "Test User", + emailVerified: true, } as User; const dummyJob = { - company_name: "testname1", - programme_name: "testprogramme1", - url: "https://www.google.com/", - closing_date: Date.now(), - salary: 0, - cv: false, - cover_letter: false, - questions: false, - visa_sponsorship: false, - sector: Sector.Technology, - level: Level.Graduate, - admin_approved_on: 200 + company_name: "testname1", + programme_name: "testprogramme1", + url: "https://www.google.com/", + closing_date: Date.now(), + salary: 0, + cv: false, + cover_letter: false, + questions: false, + visa_sponsorship: false, + sector: Sector.Technology, + level: Level.Graduate, + admin_approved_on: 200, }; - test("Update status when user is provided", async () => { - // add tech grad jobs - // programme 1 should appear before programme 2 - const job1 = await addDoc(collection(db, "jobs"), dummyJob); - const job2 = await addDoc(collection(db, "jobs"), { ...dummyJob, programme_name: "testprogramme2", closing_date: Date.now() - 2000 }); - - await act(() => render( - - - - - - )); - - await sleep(1000); - - // update the state of programme 2 - const dropdownBtns = screen.getAllByLabelText("change-status-button") - - // change the status of the second programme - await act(() => userEvent.click(dropdownBtns[1])); - - // the menu labels are duplicated so use [1] - const onlineAssessment = screen.getAllByText(Status.OnlineAssessment); - await userEvent.click(onlineAssessment[1]); - await sleep(1000); - - // the status of the second job should be changed - const ref2 = await getDoc(doc(db, "jobs", job2.id, "statuses", "123")); - expect(ref2.get("status")).toBe(Status.OnlineAssessment); - - // the new status should be displayed on the screen - const notAppliedAll = screen.getAllByText(Status.NotApplied); - const onlineAssessmentAll = screen.getAllByText(Status.OnlineAssessment); - expect(notAppliedAll.length).toBe(1); - expect(onlineAssessmentAll.length).toBe(1); + // add tech grad jobs + // programme 1 should appear before programme 2 + const job1 = await addDoc(collection(db, "jobs"), dummyJob); + const job2 = await addDoc(collection(db, "jobs"), { + ...dummyJob, + programme_name: "testprogramme2", + closing_date: Date.now() - 2000, + }); + + await act(() => + render( + + + + + + ) + ); + + await sleep(1000); + + // update the state of programme 2 + const dropdownBtns = screen.getAllByLabelText("change-status-button"); + + // change the status of the second programme + await act(() => userEvent.click(dropdownBtns[1])); + + // the menu labels are duplicated so use [1] + const onlineAssessment = screen.getAllByText(Status.OnlineAssessment); + await userEvent.click(onlineAssessment[1]); + await sleep(1000); + + // the status of the second job should be changed + const ref2 = await getDoc(doc(db, "jobs", job2.id, "statuses", "123")); + expect(ref2.get("status")).toBe(Status.OnlineAssessment); + + // the new status should be displayed on the screen + const notAppliedAll = screen.getAllByText(Status.NotApplied); + const onlineAssessmentAll = screen.getAllByText(Status.OnlineAssessment); + expect(notAppliedAll.length).toBe(1); + expect(onlineAssessmentAll.length).toBe(1); }); test("Dont update status when no user is provided", async () => { - // add tech grad jobs - // programme 1 should appear before programme 2 - const job1 = await addDoc(collection(db, "jobs"), dummyJob); - const job2 = await addDoc(collection(db, "jobs"), { ...dummyJob, programme_name: "testprogramme2", closing_date: Date.now() - 2000 }); - - await act(() => render( - - - - - - )); - - await sleep(1000); - - // update the state of programme 2 - const dropdownBtns = screen.getAllByLabelText("change-status-button") - - // change the status of the second programme - const alertMock = jest.spyOn(window, "alert").mockImplementation(() => { }); - await act(() => userEvent.click(dropdownBtns[1])); - - // an alert should show to tell the user to login - expect(alertMock).toHaveBeenCalledTimes(1); + // add tech grad jobs + // programme 1 should appear before programme 2 + const job1 = await addDoc(collection(db, "jobs"), dummyJob); + const job2 = await addDoc(collection(db, "jobs"), { + ...dummyJob, + programme_name: "testprogramme2", + closing_date: Date.now() - 2000, + }); + + await act(() => + render( + + + + + + ) + ); + + await sleep(1000); + + // update the state of programme 2 + const dropdownBtns = screen.getAllByLabelText("change-status-button"); + + // change the status of the second programme + const alertMock = jest.spyOn(window, "alert").mockImplementation(() => {}); + await act(() => userEvent.click(dropdownBtns[1])); + + // an alert should show to tell the user to login + expect(alertMock).toHaveBeenCalledTimes(1); }); test("Admin deleting jobs", async () => { - - const userRef = await setDoc(doc(db, "users", "user123@gmail.com"), { - admin_granted_on: "0", - is_admin: true, - last_posted_on: "0" - }); - - const job1 = await addDoc(collection(db, "jobs"), dummyJob); - const job2 = await addDoc(collection(db, "jobs"), { ...dummyJob, programme_name: "testprogramme2", closing_date: Date.now() - 2000 }); - - await act(() => render( - - - - - - )); - - await sleep(1000); - - // delete the second programme - const deleteBtns = screen.getAllByLabelText("delete-button") - await act(() => userEvent.click(deleteBtns[1])); - await sleep(500); - - // click the confirm button - const deleteBtn = await screen.findByRole("button", { name: "Delete" }); - await act(() => userEvent.click(deleteBtn)); - await sleep(1000); - - // Implement the test below when changed from window reload to set job list - // to remove the deleted job from the screen - - // // assert that the data does not exist in the db - // const deletedJobText = await screen.queryByText("testprogramme2"); - // expect(deletedJobText).not.toBeInTheDocument(); - - - // assert that the data does not exist in the list - await waitFor(async () => { - const q2 = doc(db, "postings", job2.id); - const jobSnapshot = await getDoc(q2); - expect(jobSnapshot.exists()).toBeFalsy(); - }); - + const userRef = await setDoc(doc(db, "users", "123"), { + email: "user123@gmail.com", + admin_granted_on: "0", + is_admin: true, + last_posted_on: "0", + }); + + const job1 = await addDoc(collection(db, "jobs"), dummyJob); + const job2 = await addDoc(collection(db, "jobs"), { + ...dummyJob, + programme_name: "testprogramme2", + closing_date: Date.now() - 2000, + }); + + await act(() => + render( + + + + + + ) + ); + + await sleep(1000); + + // delete the second programme + const deleteBtns = screen.getAllByLabelText("delete-button"); + await act(() => userEvent.click(deleteBtns[1])); + await sleep(500); + + // click the confirm button + const deleteBtn = await screen.findByRole("button", { name: "Delete" }); + await act(() => userEvent.click(deleteBtn)); + await sleep(1000); + + // Implement the test below when changed from window reload to set job list + // to remove the deleted job from the screen + + // // assert that the data does not exist in the db + // const deletedJobText = await screen.queryByText("testprogramme2"); + // expect(deletedJobText).not.toBeInTheDocument(); + + // assert that the data does not exist in the list + await waitFor(async () => { + const q2 = doc(db, "postings", job2.id); + const jobSnapshot = await getDoc(q2); + expect(jobSnapshot.exists()).toBeFalsy(); + }); }); test("Admin editing jobs", async () => { - const userRef = await setDoc(doc(db, "users", "user123@gmail.com"), { - admin_granted_on: "0", - is_admin: true, - last_posted_on: "0" - }); - - const job1 = await addDoc(collection(db, "jobs"), dummyJob); - - await act(() => render( - - - - - - )); - - await sleep(1000); - - // edit the second programme - const editBtns = screen.getAllByLabelText("edit-button") - await act(() => userEvent.click(editBtns[0])); - await sleep(500); - - // click the cover letter null button - const nullbtn = screen.getByLabelText("cover-letter-null-btn"); - await act(() => userEvent.click(nullbtn)); - - // change the programme name - const programmeTextfield = await screen.findByTestId('programme-name-textfield') - const input = programmeTextfield.querySelector('input'); - await userEvent.clear(input!); - await userEvent.type(input!, "updatedName"); - - // click the confirm button - const editBtn = await screen.findByRole("button", { name: "Done" }); - await act(() => userEvent.click(editBtn)); - await sleep(1000); - - - // assert that the data is updated in the database - await waitFor(async () => { - const ref = await getDoc(doc(db, "jobs", job1.id)); - expect(ref.get("company_name")).toBe(dummyJob.company_name); - expect(ref.get("programme_name")).toBe("updatedName"); - expect(ref.get("cv")).toBe(dummyJob.cv); - expect(ref.get("cover_letter")).toBe(null); - }); - -}); \ No newline at end of file + const userRef = await setDoc(doc(db, "users", "123"), { + email: "user123@gmail.com", + admin_granted_on: "0", + is_admin: true, + last_posted_on: "0", + }); + + const job1 = await addDoc(collection(db, "jobs"), dummyJob); + + await act(() => + render( + + + + + + ) + ); + + await sleep(1000); + + // edit the second programme + const editBtns = screen.getAllByLabelText("edit-button"); + await act(() => userEvent.click(editBtns[0])); + await sleep(500); + + // click the cover letter null button + const nullbtn = screen.getByLabelText("cover-letter-null-btn"); + await act(() => userEvent.click(nullbtn)); + + // change the programme name + const programmeTextfield = await screen.findByTestId( + "programme-name-textfield" + ); + const input = programmeTextfield.querySelector("input"); + await userEvent.clear(input!); + await userEvent.type(input!, "updatedName"); + + // click the confirm button + const editBtn = await screen.findByRole("button", { name: "Done" }); + await act(() => userEvent.click(editBtn)); + await sleep(1000); + + // assert that the data is updated in the database + await waitFor(async () => { + const ref = await getDoc(doc(db, "jobs", job1.id)); + expect(ref.get("company_name")).toBe(dummyJob.company_name); + expect(ref.get("programme_name")).toBe("updatedName"); + expect(ref.get("cv")).toBe(dummyJob.cv); + expect(ref.get("cover_letter")).toBe(null); + }); +}); diff --git a/src/pages/Graduates.tsx b/src/pages/Graduates.tsx index 219ce2b..d72e72e 100644 --- a/src/pages/Graduates.tsx +++ b/src/pages/Graduates.tsx @@ -28,8 +28,8 @@ export default function Graduates({ sector }: { sector: string }) { }, 2000); // fetch admin here, otherwise the joblist will be rendered more than once when either user or isAdmin changes - if (user && user.email) { - checkAdmin(db, user.email).then((result) => { + if (user && user.uid) { + checkAdmin(db, user.uid).then((result) => { setIsAdmin(result); }); } else { diff --git a/src/pages/Internships.tsx b/src/pages/Internships.tsx index c963c0d..07b0a10 100644 --- a/src/pages/Internships.tsx +++ b/src/pages/Internships.tsx @@ -27,8 +27,8 @@ export default function Internships({ sector }: { sector: string }) { setShowLoading(true); }, 2000); - if (user && user.email) { - checkAdmin(db, user.email).then((result) => { + if (user && user.uid) { + checkAdmin(db, user.uid).then((result) => { setIsAdmin(result); }); } else { diff --git a/src/pages/Login.test.tsx b/src/pages/Login.test.tsx index 6a68d1a..9f5330b 100644 --- a/src/pages/Login.test.tsx +++ b/src/pages/Login.test.tsx @@ -1,12 +1,25 @@ import { AuthContext } from "../contexts/AuthContext"; import { render, screen, waitFor } from "@testing-library/react"; -import { createUserWithEmailAndPassword, type Auth, type User } from "firebase/auth"; -import { addDoc, collection, doc, getDocs, query, getDoc, setDoc, where } from "firebase/firestore"; +import { + createUserWithEmailAndPassword, + type Auth, + type User, +} from "firebase/auth"; +import { + addDoc, + collection, + doc, + getDocs, + query, + getDoc, + setDoc, + where, +} from "firebase/firestore"; import type { Firestore } from "firebase/firestore"; import { terminate } from "firebase/firestore"; import { initializeFirebase } from "../config/firebaseUtilityTest"; import { clearFirestore } from "../config/firebaseUtilityTest"; -import '@testing-library/jest-dom' +import "@testing-library/jest-dom"; import { act } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { MemoryRouter, Router } from "react-router-dom"; @@ -17,87 +30,95 @@ let db: Firestore; let auth: Auth; let createdUser: User | null = null; - beforeAll(() => { - ({ auth, db } = initializeFirebase()); + ({ auth, db } = initializeFirebase()); }); beforeEach(async () => { - await clearFirestore(db); + await clearFirestore(db); }); afterEach(async () => { - if (createdUser !== null) { - await createdUser.delete(); - } - await auth.currentUser?.delete(); + if (createdUser !== null) { + await createdUser.delete(); + } + await auth.currentUser?.delete(); }); afterAll(async () => { - await terminate(db); + await terminate(db); }); - - // test that signed in users cannot access this page test("Users signed in", async () => { - // create and sign in user - const user = await createUserWithEmailAndPassword(auth, "test12345@gmail.com", "28y8h2hU*9"); - createdUser = user.user; - - await act(() => render( - - - - - - )); - - // wait for 2 seconds - await sleep(2000); - - // the word unauthorised should show - const unauthorised = screen.getByText("Unauthorised"); - expect(unauthorised).not.toBeNull(); - + // create and sign in user + const user = await createUserWithEmailAndPassword( + auth, + "test12345@gmail.com", + "28y8h2hU*9" + ); + createdUser = user.user; + + await act(() => + render( + + + + + + ) + ); + + // wait for 2 seconds + await sleep(2000); + + // the word unauthorised should show + const unauthorised = screen.getByText("Unauthorised"); + expect(unauthorised).not.toBeNull(); }); - // test that user can login with correct credentials test("Login successful", async () => { - // create user - const user = await createUserWithEmailAndPassword(auth, "test12345@gmail.com", "28y8h2hU*9"); - createdUser = user.user; - const userRef = await setDoc(doc(db, "users", "test12345@gmail.com"), { - admin_granted_on: "0", - is_admin: false, - last_posted_on: "0" - }); - auth?.signOut(); - - await act(() => render( - - - - - - )); - - // set email input - const emailTextField = await screen.findByTestId('email-textfield') - const inputEmail = emailTextField.querySelector('input'); - await act(() => userEvent.type(inputEmail!, "test12345@gmail.com")); - - // set password input - const passwordTextField = await screen.findByTestId('password-textfield') - const inputPw = passwordTextField.querySelector('input'); - await act(() => userEvent.type(inputPw!, "28y8h2hU*9")); - - // click login button - const loginBtn = await screen.getByRole("button", { name: "Login" }) - await act(() => userEvent.click(loginBtn)); - await sleep(2000); // necessary to wait for Firebase to process the credentials - - // verify that user is logged in - expect(auth.currentUser?.email).toBe("test12345@gmail.com"); -}); + // create user + const user = await createUserWithEmailAndPassword( + auth, + "test12345@gmail.com", + "28y8h2hU*9" + ); + createdUser = user.user; + const userRef = await setDoc(doc(db, "users", createdUser.uid), { + emaiL: "test12345@gmail.com", + admin_granted_on: "0", + is_admin: false, + last_posted_on: "0", + }); + auth?.signOut(); + + await act(() => + render( + + + + + + ) + ); + + // set email input + const emailTextField = await screen.findByTestId("email-textfield"); + const inputEmail = emailTextField.querySelector("input"); + await act(() => userEvent.type(inputEmail!, "test12345@gmail.com")); + + // set password input + const passwordTextField = await screen.findByTestId("password-textfield"); + const inputPw = passwordTextField.querySelector("input"); + await act(() => userEvent.type(inputPw!, "28y8h2hU*9")); + + // click login button + const loginBtn = await screen.getByRole("button", { name: "Login" }); + await act(() => userEvent.click(loginBtn)); + await sleep(2000); // necessary to wait for Firebase to process the credentials + + // verify that user is logged in + expect(auth.currentUser?.email).toBe("test12345@gmail.com"); +}); diff --git a/src/pages/MyAddedJobs.test.tsx b/src/pages/MyAddedJobs.test.tsx index 2814c74..9d7298e 100644 --- a/src/pages/MyAddedJobs.test.tsx +++ b/src/pages/MyAddedJobs.test.tsx @@ -1,12 +1,25 @@ import { AuthContext } from "../contexts/AuthContext"; import { render, screen, waitFor } from "@testing-library/react"; -import { createUserWithEmailAndPassword, type Auth, type User } from "firebase/auth"; -import { addDoc, collection, doc, getDocs, query, getDoc, setDoc, where } from "firebase/firestore"; +import { + createUserWithEmailAndPassword, + type Auth, + type User, +} from "firebase/auth"; +import { + addDoc, + collection, + doc, + getDocs, + query, + getDoc, + setDoc, + where, +} from "firebase/firestore"; import type { Firestore } from "firebase/firestore"; import { terminate } from "firebase/firestore"; import { initializeFirebase } from "../config/firebaseUtilityTest"; import { clearFirestore } from "../config/firebaseUtilityTest"; -import '@testing-library/jest-dom' +import "@testing-library/jest-dom"; import { act } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { MemoryRouter, Router } from "react-router-dom"; @@ -20,27 +33,25 @@ let db: Firestore; let auth: Auth; const mockUser = { - uid: "123", - email: "test12345@gmail.com", - displayName: "Test User", - emailVerified: true, + uid: "123", + email: "user123@gmail.com", + displayName: "Test User", + emailVerified: true, } as User; const mockUserUnverified = { - uid: "123", - email: "test12345@gmail.com", - displayName: "Test User", - emailVerified: false, + uid: "123", + email: "user123@gmail.com", + displayName: "Test User", + emailVerified: false, } as User; - - beforeAll(() => { - ({ auth, db } = initializeFirebase()); + ({ auth, db } = initializeFirebase()); }); beforeEach(async () => { - await clearFirestore(db); + await clearFirestore(db); }); // afterEach(async () => { @@ -48,283 +59,287 @@ beforeEach(async () => { // }); afterAll(async () => { - await terminate(db); + await terminate(db); }); // test that users not signed in cannot access this page test("Users not signed in", async () => { - - await act(() => render( - - - - - - )); - - // wait for 2 seconds - await sleep(2000); - - // the word unauthorised should show - const unauthorised = screen.getByText("Unauthorised"); - expect(unauthorised).not.toBeNull(); - + await act(() => + render( + + + + + + ) + ); + + // wait for 2 seconds + await sleep(2000); + + // the word unauthorised should show + const unauthorised = screen.getByText("Unauthorised"); + expect(unauthorised).not.toBeNull(); }); -// test that users not verified their emails cannot access this page +// test that users not verified their emails cannot access this page test("Unverified user", async () => { - - await act(() => render( - - - - - - )); - - // wait for 2 seconds - await sleep(2000); - - const unverified = screen.getByText("Please click here to verify your email."); - expect(unverified).not.toBeNull(); - + await act(() => + render( + + + + + + ) + ); + + // wait for 2 seconds + await sleep(2000); + + const unverified = screen.getByText( + "Please click here to verify your email." + ); + expect(unverified).not.toBeNull(); }); - -// test that the correct added job is displayed +// test that the correct added job is displayed test("View pending job", async () => { - const userRef = await setDoc(doc(db, "users", "user123@gmail.com"), { - admin_granted_on: "0", - is_admin: true, - last_posted_on: "0" - }); - - await addDoc(collection(db, "postings"), { - user_id: "123", - company_name: "testCompany", - programme_name: "testProgramme", - url: "https://google.com", - status: PostedJobStatus.Pending, - posted_on: Date.now(), - sector: Sector.Technology, - level: Level.Graduate - }); - - await addDoc(collection(db, "postings"), { - user_id: "12345", - company_name: "otherCompany", - programme_name: "otherProgramme", - url: "https://google.com", - status: PostedJobStatus.Pending, - posted_on: Date.now(), - sector: Sector.Technology, - level: Level.Graduate - }); - - render( - - - - - - ); - - // the page should show a single job displayed - const singleJob = await screen.findByText("testProgramme"); - expect(singleJob).toBeInTheDocument(); - - // the other job should not be displayed - const otherJob = await screen.queryByText("otherProgramme"); - expect(otherJob).not.toBeInTheDocument(); - + const userRef = await setDoc(doc(db, "users", "123"), { + email: "user123@gmail.com", + admin_granted_on: "0", + is_admin: true, + last_posted_on: "0", + }); + + await addDoc(collection(db, "postings"), { + user_id: "123", + company_name: "testCompany", + programme_name: "testProgramme", + url: "https://google.com", + status: PostedJobStatus.Pending, + posted_on: Date.now(), + sector: Sector.Technology, + level: Level.Graduate, + }); + + await addDoc(collection(db, "postings"), { + user_id: "12345", + company_name: "otherCompany", + programme_name: "otherProgramme", + url: "https://google.com", + status: PostedJobStatus.Pending, + posted_on: Date.now(), + sector: Sector.Technology, + level: Level.Graduate, + }); + + render( + + + + + + ); + + // the page should show a single job displayed + const singleJob = await screen.findByText("testProgramme"); + expect(singleJob).toBeInTheDocument(); + + // the other job should not be displayed + const otherJob = await screen.queryByText("otherProgramme"); + expect(otherJob).not.toBeInTheDocument(); }); // test that the correct rejection message is displayed test("Show rejection message", async () => { - const userRef = await setDoc(doc(db, "users", "user123@gmail.com"), { - admin_granted_on: "0", - is_admin: true, - last_posted_on: "0" - }); - - await addDoc(collection(db, "postings"), { - user_id: "123", - company_name: "testCompany", - programme_name: "testProgramme", - url: "https://google.com", - status: PostedJobStatus.Pending, - posted_on: Date.now(), - sector: Sector.Technology, - level: Level.Graduate - }); - - await addDoc(collection(db, "postings"), { - user_id: "123", - company_name: "otherCompany", - programme_name: "rejectedProgramme", - url: "https://google.com", - status: PostedJobStatus.Rejected, - posted_on: Date.now(), - sector: Sector.Technology, - level: Level.Graduate, - rejection_message: "TestRejectionMessage" - }); - - render( - - - - - - ); - - // click on the rejection info button - const infos = await screen.findAllByTestId("rejection-info-button"); - expect(infos.length).toBe(1); - - // click - await act(() => userEvent.click(infos[0])); - - // the message should show - const rejectionMessage = screen.getByText("TestRejectionMessage"); - expect(rejectionMessage).toBeInTheDocument(); + const userRef = await setDoc(doc(db, "users", "123"), { + email: "user123@gmail.com", + admin_granted_on: "0", + is_admin: true, + last_posted_on: "0", + }); + + await addDoc(collection(db, "postings"), { + user_id: "123", + company_name: "testCompany", + programme_name: "testProgramme", + url: "https://google.com", + status: PostedJobStatus.Pending, + posted_on: Date.now(), + sector: Sector.Technology, + level: Level.Graduate, + }); + + await addDoc(collection(db, "postings"), { + user_id: "123", + company_name: "otherCompany", + programme_name: "rejectedProgramme", + url: "https://google.com", + status: PostedJobStatus.Rejected, + posted_on: Date.now(), + sector: Sector.Technology, + level: Level.Graduate, + rejection_message: "TestRejectionMessage", + }); + + render( + + + + + + ); + + // click on the rejection info button + const infos = await screen.findAllByTestId("rejection-info-button"); + expect(infos.length).toBe(1); + + // click + await act(() => userEvent.click(infos[0])); + + // the message should show + const rejectionMessage = screen.getByText("TestRejectionMessage"); + expect(rejectionMessage).toBeInTheDocument(); }); -// test that no message is shown when rejection message is not provided +// test that no message is shown when rejection message is not provided test("No rejection message", async () => { - const userRef = await setDoc(doc(db, "users", "user123@gmail.com"), { - admin_granted_on: "0", - is_admin: true, - last_posted_on: "0" - }); - - await addDoc(collection(db, "postings"), { - user_id: "123", - company_name: "otherCompany", - programme_name: "rejectedProgramme", - url: "https://google.com", - status: PostedJobStatus.Rejected, - posted_on: Date.now(), - sector: Sector.Technology, - level: Level.Graduate, - rejection_message: "" - }); - - render( - - - - - - ); - - // click on the rejection info button - const infos = await screen.findAllByTestId("rejection-info-button"); - expect(infos.length).toBe(1); - - // click - await act(() => userEvent.click(infos[0])); - - // the message should show - const rejectionMessage = screen.getByText("(No message provided)"); - expect(rejectionMessage).toBeInTheDocument(); + const userRef = await setDoc(doc(db, "users", "123"), { + email: "user123@gmail.com", + admin_granted_on: "0", + is_admin: true, + last_posted_on: "0", + }); + + await addDoc(collection(db, "postings"), { + user_id: "123", + company_name: "otherCompany", + programme_name: "rejectedProgramme", + url: "https://google.com", + status: PostedJobStatus.Rejected, + posted_on: Date.now(), + sector: Sector.Technology, + level: Level.Graduate, + rejection_message: "", + }); + + render( + + + + + + ); + + // click on the rejection info button + const infos = await screen.findAllByTestId("rejection-info-button"); + expect(infos.length).toBe(1); + + // click + await act(() => userEvent.click(infos[0])); + + // the message should show + const rejectionMessage = screen.getByText("(No message provided)"); + expect(rejectionMessage).toBeInTheDocument(); }); - -// test that the correct job can be deleted +// test that the correct job can be deleted test("Delete job", async () => { - const userRef = await setDoc(doc(db, "users", "user123@gmail.com"), { - admin_granted_on: "0", - is_admin: true, - last_posted_on: "0" - }); - - await addDoc(collection(db, "postings"), { - user_id: "123", - company_name: "testCompany", - programme_name: "testProgramme", - url: "https://google.com", - status: PostedJobStatus.Pending, - posted_on: Date.now() - 10000, - sector: Sector.Technology, - level: Level.Graduate - }); - - const deletedJob = await addDoc(collection(db, "postings"), { // this appear on top - user_id: "123", - company_name: "otherCompany", - programme_name: "otherProgramme", - url: "https://google.com", - status: PostedJobStatus.Pending, - posted_on: Date.now(), - sector: Sector.Technology, - level: Level.Graduate, - }); - - render( - - - - - - ); - - // click on the first delete button - const infos = await screen.findAllByTestId("delete-button"); - await act(() => userEvent.click(infos[0])); - await sleep(500); - - // confirm - const confirmBtn = await screen.findByRole("button", { name: "Delete" }); - await act(() => userEvent.click(confirmBtn)); - await sleep(1000); - - // the other programme not visible on screen anymore - const deletedJobText = await screen.queryByText("otherProgramme"); - expect(deletedJobText).not.toBeInTheDocument(); - - // the other programme no longer exists in the db - // console.log(deletedJob.id) - // const q2 = doc(db, "postings", deletedJob.id); - // const postingSnapshot = await getDoc(q2); - // expect(postingSnapshot).toBeNull(); - await waitFor(async () => { - const q2 = doc(db, "postings", deletedJob.id); - const postingSnapshot = await getDoc(q2); - expect(postingSnapshot.exists()).toBeFalsy(); - }); + const userRef = await setDoc(doc(db, "users", "123"), { + email: "user123@gmail.com", + admin_granted_on: "0", + is_admin: true, + last_posted_on: "0", + }); + + await addDoc(collection(db, "postings"), { + user_id: "123", + company_name: "testCompany", + programme_name: "testProgramme", + url: "https://google.com", + status: PostedJobStatus.Pending, + posted_on: Date.now() - 10000, + sector: Sector.Technology, + level: Level.Graduate, + }); + + const deletedJob = await addDoc(collection(db, "postings"), { + // this appear on top + user_id: "123", + company_name: "otherCompany", + programme_name: "otherProgramme", + url: "https://google.com", + status: PostedJobStatus.Pending, + posted_on: Date.now(), + sector: Sector.Technology, + level: Level.Graduate, + }); + + render( + + + + + + ); + + // click on the first delete button + const infos = await screen.findAllByTestId("delete-button"); + await act(() => userEvent.click(infos[0])); + await sleep(500); + + // confirm + const confirmBtn = await screen.findByRole("button", { name: "Delete" }); + await act(() => userEvent.click(confirmBtn)); + await sleep(1000); + + // the other programme not visible on screen anymore + const deletedJobText = await screen.queryByText("otherProgramme"); + expect(deletedJobText).not.toBeInTheDocument(); + + // the other programme no longer exists in the db + // console.log(deletedJob.id) + // const q2 = doc(db, "postings", deletedJob.id); + // const postingSnapshot = await getDoc(q2); + // expect(postingSnapshot).toBeNull(); + await waitFor(async () => { + const q2 = doc(db, "postings", deletedJob.id); + const postingSnapshot = await getDoc(q2); + expect(postingSnapshot.exists()).toBeFalsy(); + }); }); - test("Can only delete pending jobs", async () => { - const userRef = await setDoc(doc(db, "users", "user123@gmail.com"), { - admin_granted_on: "0", - is_admin: true, - last_posted_on: "0" - }); - - await addDoc(collection(db, "postings"), { - user_id: "123", - company_name: "testCompany", - programme_name: "testProgramme", - url: "https://google.com", - status: PostedJobStatus.Rejected, - posted_on: Date.now() - 10000, - sector: Sector.Technology, - level: Level.Graduate - }); - - render( - - - - - - ); - - // click on the first delete button - const infos = await screen.findAllByTestId("delete-button"); - - // the delete button should be disabled - expect(infos[0]).toBeDisabled(); -}); \ No newline at end of file + const userRef = await setDoc(doc(db, "users", "123"), { + email: "user123@gmail.com", + admin_granted_on: "0", + is_admin: true, + last_posted_on: "0", + }); + + await addDoc(collection(db, "postings"), { + user_id: "123", + company_name: "testCompany", + programme_name: "testProgramme", + url: "https://google.com", + status: PostedJobStatus.Rejected, + posted_on: Date.now() - 10000, + sector: Sector.Technology, + level: Level.Graduate, + }); + + render( + + + + + + ); + + // click on the first delete button + const infos = await screen.findAllByTestId("delete-button"); + + // the delete button should be disabled + expect(infos[0]).toBeDisabled(); +}); diff --git a/src/pages/Placements.tsx b/src/pages/Placements.tsx index 86545bb..622cfb0 100644 --- a/src/pages/Placements.tsx +++ b/src/pages/Placements.tsx @@ -27,8 +27,8 @@ export default function Placements({ sector }: { sector: string }) { setShowLoading(true); }, 2000); - if (user && user.email) { - checkAdmin(db, user.email).then((result) => { + if (user && user.uid) { + checkAdmin(db, user.uid).then((result) => { setIsAdmin(result); }); } else { diff --git a/src/pages/Profile.test.tsx b/src/pages/Profile.test.tsx index 798350b..28ff00f 100644 --- a/src/pages/Profile.test.tsx +++ b/src/pages/Profile.test.tsx @@ -1,12 +1,25 @@ import { AuthContext } from "../contexts/AuthContext"; import { render, screen, waitFor } from "@testing-library/react"; -import { createUserWithEmailAndPassword, type Auth, type User } from "firebase/auth"; -import { addDoc, collection, doc, getDocs, query, getDoc, setDoc, where } from "firebase/firestore"; +import { + createUserWithEmailAndPassword, + type Auth, + type User, +} from "firebase/auth"; +import { + addDoc, + collection, + doc, + getDocs, + query, + getDoc, + setDoc, + where, +} from "firebase/firestore"; import type { Firestore } from "firebase/firestore"; import { terminate } from "firebase/firestore"; import { initializeFirebase } from "../config/firebaseUtilityTest"; import { clearFirestore } from "../config/firebaseUtilityTest"; -import '@testing-library/jest-dom' +import "@testing-library/jest-dom"; import { act } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { MemoryRouter, Router } from "react-router-dom"; @@ -17,108 +30,118 @@ let db: Firestore; let auth: Auth; let createdUser: User | null = null; - beforeAll(() => { - ({ auth, db } = initializeFirebase()); + ({ auth, db } = initializeFirebase()); }); beforeEach(async () => { - await clearFirestore(db); + await clearFirestore(db); }); afterEach(async () => { - if (createdUser !== null) { - await createdUser.delete(); - } - await auth.currentUser?.delete(); + if (createdUser !== null) { + await createdUser.delete(); + } + await auth.currentUser?.delete(); }); afterAll(async () => { - await terminate(db); + await terminate(db); }); - // test that users not signed in cannot access this test("Users not signed in", async () => { - - await act(() => render( - - - - - - )); - - // wait for 2 seconds - await sleep(2000); - - // the word unauthorised should show - const unauthorised = screen.getByText("Unauthorised"); - expect(unauthorised).not.toBeNull(); - + await act(() => + render( + + + + + + ) + ); + + // wait for 2 seconds + await sleep(2000); + + // the word unauthorised should show + const unauthorised = screen.getByText("Unauthorised"); + expect(unauthorised).not.toBeNull(); }); // test that the user email is displayed test("Display email", async () => { - // create and sign in user - const user = await createUserWithEmailAndPassword(auth, "test12345@gmail.com", "28y8h2hU*9"); - createdUser = user.user; - const userRef = await setDoc(doc(db, "users", "test12345@gmail.com"), { - admin_granted_on: "0", - is_admin: false, - last_posted_on: "0" - }); - - await act(() => render( - - - - - - )); - - // wait for 2 seconds - await sleep(2000); - - // the email should show - const email = screen.getByText("Currently logged in as: test12345@gmail.com"); - expect(email).not.toBeNull(); - + // create and sign in user + const user = await createUserWithEmailAndPassword( + auth, + "user123@gmail.com", + "28y8h2hU*9" + ); + createdUser = user.user; + const userRef = await setDoc(doc(db, "users", createdUser.uid), { + email: "user123@gmail.com", + admin_granted_on: "0", + is_admin: false, + last_posted_on: "0", + }); + + await act(() => + render( + + + + + + ) + ); + + // wait for 2 seconds + await sleep(2000); + + // the email should show + const email = screen.getByText("Currently logged in as: user123@gmail.com"); + expect(email).not.toBeNull(); }); - -// test that users can log out +// test that users can log out test("Display email", async () => { - // create and sign in user - const user = await createUserWithEmailAndPassword(auth, "test12345@gmail.com", "28y8h2hU*9"); - createdUser = user.user; - const userRef = await setDoc(doc(db, "users", "test12345@gmail.com"), { - admin_granted_on: "0", - is_admin: false, - last_posted_on: "0" - }); - - await act(() => render( - - - - - - )); - - // wait for 1 second - await sleep(1000); - - // click the logout button - const logoutBtn = screen.getByRole("button", { name: "Logout" }); - await act(() => userEvent.click(logoutBtn)); - await sleep(1000); - - // click the logout button on the dialog - const logoutBtn2 = screen.getAllByRole("button", { name: "Logout" }); - await act(() => userEvent.click(logoutBtn2[0])); - - // wait for 2 seconds - // user is now logged out - expect(auth.currentUser).toBeNull(); + // create and sign in user + const user = await createUserWithEmailAndPassword( + auth, + "user123@gmail.com", + "28y8h2hU*9" + ); + createdUser = user.user; + const userRef = await setDoc(doc(db, "users", createdUser.uid), { + email: "user123@gmail.com", + admin_granted_on: "0", + is_admin: false, + last_posted_on: "0", + }); + + await act(() => + render( + + + + + + ) + ); + + // wait for 1 second + await sleep(1000); + + // click the logout button + const logoutBtn = screen.getByRole("button", { name: "Logout" }); + await act(() => userEvent.click(logoutBtn)); + await sleep(1000); + + // click the logout button on the dialog + const logoutBtn2 = screen.getAllByRole("button", { name: "Logout" }); + await act(() => userEvent.click(logoutBtn2[0])); + + // wait for 2 seconds + // user is now logged out + expect(auth.currentUser).toBeNull(); }); diff --git a/src/pages/Register.test.tsx b/src/pages/Register.test.tsx index 24964bf..2ea362b 100644 --- a/src/pages/Register.test.tsx +++ b/src/pages/Register.test.tsx @@ -1,12 +1,25 @@ import { AuthContext } from "../contexts/AuthContext"; import { render, screen, waitFor } from "@testing-library/react"; -import { createUserWithEmailAndPassword, type Auth, type User } from "firebase/auth"; -import { addDoc, collection, doc, getDocs, query, getDoc, setDoc, where } from "firebase/firestore"; +import { + createUserWithEmailAndPassword, + type Auth, + type User, +} from "firebase/auth"; +import { + addDoc, + collection, + doc, + getDocs, + query, + getDoc, + setDoc, + where, +} from "firebase/firestore"; import type { Firestore } from "firebase/firestore"; import { terminate } from "firebase/firestore"; import { initializeFirebase } from "../config/firebaseUtilityTest"; import { clearFirestore } from "../config/firebaseUtilityTest"; -import '@testing-library/jest-dom' +import "@testing-library/jest-dom"; import { act } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import Register from "./Register"; @@ -19,116 +32,127 @@ let auth: Auth; let createdUser: User | null = null; beforeAll(() => { - ({ auth, db } = initializeFirebase()); + ({ auth, db } = initializeFirebase()); }); beforeEach(async () => { - await clearFirestore(db); + await clearFirestore(db); }); afterEach(async () => { - if (createdUser !== null) { - await createdUser.delete(); - } - await auth.currentUser?.delete(); -}) + if (createdUser !== null) { + await createdUser.delete(); + } + await auth.currentUser?.delete(); +}); afterAll(async () => { - await terminate(db); + await terminate(db); }); -// test that a user can successfully register +// test that a user can successfully register test("Can register", async () => { - // render screen - await act(() => render( - - - - - )); - - // set email input - const emailTextField = await screen.findByTestId('email-textfield') - const inputEmail = emailTextField.querySelector('input'); - await act(() => userEvent.type(inputEmail!, "test12345@gmail.com")); - - // set password input - const passwordTextField = await screen.findByTestId('password-textfield') - const inputPw = passwordTextField.querySelector('input'); - await act(() => userEvent.type(inputPw!, "Wuidiw289*")); - - const passwordConfirmTextField = await screen.findByTestId('password-confirm-textfield') - const inputPw2 = passwordConfirmTextField.querySelector('input'); - await act(() => userEvent.type(inputPw2!, "Wuidiw289*")); - - // click register button - const registerBtn = await screen.getByRole("button", { name: "Register" }) - await act(() => userEvent.click(registerBtn)); - - await new Promise((resolve) => { - const unsubscribe = onAuthStateChanged(auth, (user) => { - if (user) { - unsubscribe(); // remove listener - resolve(); // exit the promise - } - }); - - setTimeout(() => { - unsubscribe(); - resolve(); - }, 2000); + // render screen + await act(() => + render( + + + + ) + ); + + // set email input + const emailTextField = await screen.findByTestId("email-textfield"); + const inputEmail = emailTextField.querySelector("input"); + await act(() => userEvent.type(inputEmail!, "user123@gmail.com")); + + // set password input + const passwordTextField = await screen.findByTestId("password-textfield"); + const inputPw = passwordTextField.querySelector("input"); + await act(() => userEvent.type(inputPw!, "Wuidiw289*")); + + const passwordConfirmTextField = await screen.findByTestId( + "password-confirm-textfield" + ); + const inputPw2 = passwordConfirmTextField.querySelector("input"); + await act(() => userEvent.type(inputPw2!, "Wuidiw289*")); + + // click register button + const registerBtn = await screen.getByRole("button", { name: "Register" }); + await act(() => userEvent.click(registerBtn)); + + await new Promise((resolve) => { + const unsubscribe = onAuthStateChanged(auth, (user) => { + if (user) { + unsubscribe(); // remove listener + resolve(); // exit the promise + } }); - // check that user is logged in in auth - expect(auth.currentUser?.email).toBe("test12345@gmail.com"); + setTimeout(() => { + unsubscribe(); + resolve(); + }, 2000); + }); - // check that user is added to db with the correct attributes - const snapshot = await getDoc(doc(db, "users", "test12345@gmail.com")); - expect(snapshot.data()).not.toBeNull(); + // check that user is logged in in auth + expect(auth.currentUser?.email).toBe("user123@gmail.com"); + // check that user is added to db with the correct attributes + const snapshot = await getDoc(doc(db, "users", "user123@gmail.com")); + expect(snapshot.data()).not.toBeNull(); }); // test that user cannot register accounts already exist (in the test, it exist in both auth and db) test("Cannot register duplicate accounts", async () => { - // initially add the user to auth and db - const user = await createUserWithEmailAndPassword(auth, "test12345@gmail.com", "28y8h2hU*9"); - createdUser = user.user; - const userRef = await setDoc(doc(db, "users", "test12345@gmail.com"), { - admin_granted_on: "0", - is_admin: false, - last_posted_on: "0" - }); - auth?.signOut(); - - // render screen - await act(() => render( - - - - - )); - - // set email input - const emailTextField = await screen.findByTestId('email-textfield') - const inputEmail = emailTextField.querySelector('input'); - await act(() => userEvent.type(inputEmail!, "test12345@gmail.com")); - - // set password input - const passwordTextField = await screen.findByTestId('password-textfield') - const inputPw = passwordTextField.querySelector('input'); - await act(() => userEvent.type(inputPw!, "Wuidiw289*")); - - const passwordConfirmTextField = await screen.findByTestId('password-confirm-textfield') - const inputPw2 = passwordConfirmTextField.querySelector('input'); - await act(() => userEvent.type(inputPw2!, "Wuidiw289*")); - - // click register button - const registerBtn = await screen.getByRole("button", { name: "Register" }) - await act(() => userEvent.click(registerBtn)); - await sleep(2000); // necessary to wait for Firebase to process the credentials - - // check that an error message is shown - const errorMsg = await screen.getByText("Email address is already registered, please login"); - expect(errorMsg).not.toBeNull(); -}) - + // initially add the user to auth and db + const user = await createUserWithEmailAndPassword( + auth, + "user123@gmail.com", + "28y8h2hU*9" + ); + createdUser = user.user; + const userRef = await setDoc(doc(db, "users", createdUser.uid), { + email: "user123@gmail.com", + admin_granted_on: "0", + is_admin: false, + last_posted_on: "0", + }); + auth?.signOut(); + + // render screen + await act(() => + render( + + + + ) + ); + + // set email input + const emailTextField = await screen.findByTestId("email-textfield"); + const inputEmail = emailTextField.querySelector("input"); + await act(() => userEvent.type(inputEmail!, "user123@gmail.com")); + + // set password input + const passwordTextField = await screen.findByTestId("password-textfield"); + const inputPw = passwordTextField.querySelector("input"); + await act(() => userEvent.type(inputPw!, "Wuidiw289*")); + + const passwordConfirmTextField = await screen.findByTestId( + "password-confirm-textfield" + ); + const inputPw2 = passwordConfirmTextField.querySelector("input"); + await act(() => userEvent.type(inputPw2!, "Wuidiw289*")); + + // click register button + const registerBtn = await screen.getByRole("button", { name: "Register" }); + await act(() => userEvent.click(registerBtn)); + await sleep(2000); // necessary to wait for Firebase to process the credentials + + // check that an error message is shown + const errorMsg = await screen.getByText( + "Email address is already registered, please login" + ); + expect(errorMsg).not.toBeNull(); +}); diff --git a/src/pages/Register.tsx b/src/pages/Register.tsx index e8d1ab2..d70de98 100644 --- a/src/pages/Register.tsx +++ b/src/pages/Register.tsx @@ -3,7 +3,11 @@ import { useContext, useState } from "react"; import { validateRegisterPassword } from "../utils/validate.ts"; import { auth, db } from "../config/firebase.ts"; import { doc, setDoc } from "firebase/firestore"; -import { createUserWithEmailAndPassword, type User } from "firebase/auth"; +import { + createUserWithEmailAndPassword, + type User, + type UserCredential, +} from "firebase/auth"; import { AuthContext } from "../contexts/AuthContext.tsx"; import { useNavigate } from "react-router-dom"; import UnauthorisedLoggedIn from "./UnauthorisedLoggedIn.tsx"; @@ -41,10 +45,15 @@ export default function Register() { // if validation passes, log in the user try { // register user in firebase auth - await createUserWithEmailAndPassword(auth, emailTrimmed, password); + const userCredential: UserCredential = + await createUserWithEmailAndPassword(auth, emailTrimmed, password); + + // obtain the user id + const uid: string = userCredential.user.uid; // add user details of created_on, last_posted_on, is_admin - await setDoc(doc(db, "users", emailTrimmed), { + await setDoc(doc(db, "users", uid), { + email: emailTrimmed, created_on: Date.now(), last_posted_on: 0, is_admin: false,