1+ import { render , screen , fireEvent , waitFor } from "@testing-library/react" ;
2+ import { BrowserRouter } from "react-router-dom" ;
3+ import { beforeEach , describe , vi , Mock , afterEach , it , expect , MockedFunction } from "vitest" ;
4+ import TimetableBuilder from "../src/pages/TimetableBuilder/TimetableBuilder" ;
5+ import { useForm , UseFormWatch } from "react-hook-form" ;
6+ import { z } from "zod" ;
7+ import React from "react" ;
8+
9+ vi . mock ( "react-hook-form" , ( ) => ( {
10+ useForm : vi . fn ( ) ,
11+ } ) ) ;
12+
13+ vi . mock ( "@/api/coursesApiSlice" , ( ) => ( {
14+ useGetCoursesQuery : vi . fn ( ( ) => ( { data : [ ] , isLoading : false } ) ) ,
15+ } ) ) ;
16+
17+ vi . mock ( "@/api/timetableApiSlice" , ( ) => ( {
18+ useGetTimetablesQuery : vi . fn ( ( ) => ( { data : [ ] } ) ) ,
19+ } ) ) ;
20+
21+ vi . mock ( "@/api/eventsApiSlice" , ( ) => ( {
22+ useGetEventsQuery : vi . fn ( ( ) => ( { data : null } ) ) ,
23+ } ) ) ;
24+
25+ vi . mock ( "@/api/offeringsApiSlice" , ( ) => ( {
26+ useGetOfferingsQuery : vi . fn ( ( ) => ( { data : [ ] } ) ) ,
27+ } ) ) ;
28+
29+ vi . mock ( "@/api/restrictionsApiSlice" , ( ) => ( {
30+ useGetRestrictionsQuery : vi . fn ( ( ) => ( { data : [ ] } ) ) ,
31+ } ) ) ;
32+
33+ vi . mock ( "@/utils/useDebounce" , ( ) => ( {
34+ useDebounceValue : ( value : string ) => value ,
35+ } ) ) ;
36+
37+ describe ( "TimetableBuilder" , ( ) => {
38+ const mockSetValue = vi . fn ( ) ;
39+ const mockHandleSubmit = vi . fn ( ( fn ) => fn ) ;
40+
41+ beforeEach ( ( ) => {
42+ ( useForm as Mock ) . mockReturnValue ( {
43+ control : { } ,
44+ handleSubmit : mockHandleSubmit ,
45+ setValue : mockSetValue ,
46+ watch : vi . fn ( ( ) => {
47+ const result : { unsubscribe : ( ) => void } & any [ ] = [ ] ;
48+ result . unsubscribe = ( ) => { } ;
49+ return result ;
50+ } ) as unknown as UseFormWatch < any > ,
51+ reset : vi . fn ( ) ,
52+ getValues : vi . fn ( ( ) => [ ] ) ,
53+ } ) ;
54+ } ) ;
55+
56+ afterEach ( ( ) => {
57+ vi . clearAllMocks ( ) ;
58+ } ) ;
59+
60+ it ( "renders the TimetableBuilder component" , ( ) => {
61+ render (
62+ < BrowserRouter >
63+ < TimetableBuilder />
64+ </ BrowserRouter >
65+ ) ;
66+
67+ expect ( screen . getByText ( / N e w T i m e t a b l e / i) ) . toBeInTheDocument ( ) ;
68+ expect ( screen . getByText ( / P i c k a f e w c o u r s e s y o u ' d l i k e t o t a k e / i) ) . toBeInTheDocument ( ) ;
69+ } ) ;
70+
71+ it ( "calls the reset function when the Reset button is clicked" , ( ) => {
72+ const mockReset = vi . fn ( ) ;
73+ ( useForm as MockedFunction < typeof useForm > ) . mockReturnValue ( {
74+ reset : mockReset ,
75+ handleSubmit : mockHandleSubmit ,
76+ setValue : mockSetValue ,
77+ watch : vi . fn ( ( ) => {
78+ const result : unknown = [ ] ;
79+ result . unsubscribe = ( ) => { } ;
80+ return result ;
81+ } ) ,
82+ getValues : vi . fn ( ( ) => [ ] ) ,
83+ } ) ;
84+
85+ render (
86+ < BrowserRouter >
87+ < TimetableBuilder />
88+ </ BrowserRouter >
89+ ) ;
90+
91+ const resetButton = screen . getByText ( / R e s e t / i) ;
92+ fireEvent . click ( resetButton ) ;
93+
94+ expect ( mockReset ) . toHaveBeenCalled ( ) ;
95+ } ) ;
96+
97+ it ( "opens the custom settings modal when the Add new button is clicked" , ( ) => {
98+ render (
99+ < BrowserRouter >
100+ < TimetableBuilder />
101+ </ BrowserRouter >
102+ ) ;
103+
104+ const addNewButton = screen . getByText ( / \+ A d d n e w / i) ;
105+ fireEvent . click ( addNewButton ) ;
106+
107+ expect ( screen . getByText ( / C u s t o m S e t t i n g s / i) ) . toBeInTheDocument ( ) ;
108+ } ) ;
109+
110+ it ( "displays selected courses when courses are added" , async ( ) => {
111+ const mockWatch = vi . fn ( ( ) => [
112+ { id : 1 , code : "CS101" , name : "Introduction to Computer Science" } ,
113+ ] ) ;
114+ ( useForm as vi . Mock ) . mockReturnValue ( {
115+ watch : mockWatch ,
116+ handleSubmit : mockHandleSubmit ,
117+ setValue : mockSetValue ,
118+ reset : vi . fn ( ) ,
119+ getValues : vi . fn ( ( ) => [ ] ) ,
120+ } ) ;
121+
122+ render (
123+ < BrowserRouter >
124+ < TimetableBuilder />
125+ </ BrowserRouter >
126+ ) ;
127+
128+ expect ( screen . getByText ( / S e l e c t e d c o u r s e s : 1 / i) ) . toBeInTheDocument ( ) ;
129+ expect ( screen . getByText ( / C S 1 0 1 / i) ) . toBeInTheDocument ( ) ;
130+ } ) ;
131+
132+ it ( "removes a course when the remove button is clicked" , async ( ) => {
133+ const mockWatch = vi . fn ( ( ) => [
134+ { id : 1 , code : "CS101" , name : "Introduction to Computer Science" } ,
135+ ] ) ;
136+ const mockSetValue = vi . fn ( ) ;
137+ ( useForm as vi . Mock ) . mockReturnValue ( {
138+ watch : mockWatch ,
139+ handleSubmit : mockHandleSubmit ,
140+ setValue : mockSetValue ,
141+ reset : vi . fn ( ) ,
142+ getValues : vi . fn ( ( ) => [
143+ { id : 1 , code : "CS101" , name : "Introduction to Computer Science" } ,
144+ ] ) ,
145+ } ) ;
146+
147+ render (
148+ < BrowserRouter >
149+ < TimetableBuilder />
150+ </ BrowserRouter >
151+ ) ;
152+
153+ const removeButton = screen . getByRole ( "button" , { name : / R e m o v e / i } ) ;
154+ fireEvent . click ( removeButton ) ;
155+
156+ await waitFor ( ( ) => {
157+ expect ( mockSetValue ) . toHaveBeenCalledWith ( "courses" , [ ] ) ;
158+ } ) ;
159+ } ) ;
160+
161+ it ( "submits the form when the Generate button is clicked" , ( ) => {
162+ const mockSubmit = vi . fn ( ) ;
163+ ( useForm as vi . Mock ) . mockReturnValue ( {
164+ handleSubmit : vi . fn ( ( fn ) => fn ) ,
165+ setValue : mockSetValue ,
166+ watch : vi . fn ( ( ) => [ ] ) ,
167+ reset : vi . fn ( ) ,
168+ getValues : vi . fn ( ( ) => [ ] ) ,
169+ } ) ;
170+
171+ render (
172+ < BrowserRouter >
173+ < TimetableBuilder />
174+ </ BrowserRouter >
175+ ) ;
176+
177+ const generateButton = screen . getByText ( / G e n e r a t e / i) ;
178+ fireEvent . click ( generateButton ) ;
179+
180+ expect ( mockHandleSubmit ) . toHaveBeenCalled ( ) ;
181+ } ) ;
182+ } ) ;
0 commit comments