1+ /**
2+ * Login Page Integration Tests
3+ *
4+ * Integration tests for the login page and authentication flow.
5+ */
6+
7+ import React from 'react' ;
8+ import { render , screen , fireEvent } from '@testing-library/react' ;
9+ import LoginPage from '@/app/login/page' ;
10+ import { useAuth } from '@/components/auth/AuthProvider' ;
11+
12+ // Mock the auth context
13+ jest . mock ( '@/components/auth/AuthProvider' , ( ) => ( {
14+ useAuth : jest . fn ( ) ,
15+ } ) ) ;
16+
17+ // Mock Next.js navigation
18+ jest . mock ( 'next/navigation' , ( ) => ( {
19+ useRouter : ( ) => ( {
20+ push : jest . fn ( ) ,
21+ } ) ,
22+ useSearchParams : ( ) => new URLSearchParams ( ) ,
23+ } ) ) ;
24+
25+ const mockUseAuth = useAuth as jest . MockedFunction < typeof useAuth > ;
26+
27+ describe ( 'Login Page' , ( ) => {
28+ beforeEach ( ( ) => {
29+ jest . clearAllMocks ( ) ;
30+ mockUseAuth . mockReturnValue ( {
31+ user : null ,
32+ accessToken : null ,
33+ isAuthenticated : false ,
34+ isLoading : false ,
35+ error : null ,
36+ login : jest . fn ( ) ,
37+ logout : jest . fn ( ) ,
38+ refreshToken : jest . fn ( ) ,
39+ setError : jest . fn ( ) ,
40+ } ) ;
41+ } ) ;
42+
43+ describe ( 'Page Rendering' , ( ) => {
44+ it ( 'should render login page with SmartQuery branding' , ( ) => {
45+ render ( < LoginPage /> ) ;
46+
47+ expect ( screen . getByText ( 'Welcome to SmartQuery' ) ) . toBeInTheDocument ( ) ;
48+ expect ( screen . getByText ( 'Sign in to access your data analysis dashboard' ) ) . toBeInTheDocument ( ) ;
49+ } ) ;
50+
51+ it ( 'should render Google login buttons' , ( ) => {
52+ render ( < LoginPage /> ) ;
53+
54+ expect ( screen . getByText ( 'Continue with Google' ) ) . toBeInTheDocument ( ) ;
55+ expect ( screen . getByText ( 'Sign in with Google' ) ) . toBeInTheDocument ( ) ;
56+ } ) ;
57+
58+ it ( 'should render features preview section' , ( ) => {
59+ render ( < LoginPage /> ) ;
60+
61+ expect ( screen . getByText ( 'What you can do with SmartQuery' ) ) . toBeInTheDocument ( ) ;
62+ expect ( screen . getByText ( / U p l o a d a n d a n a l y z e C S V f i l e s / ) ) . toBeInTheDocument ( ) ;
63+ expect ( screen . getByText ( / G e n e r a t e i n t e r a c t i v e c h a r t s / ) ) . toBeInTheDocument ( ) ;
64+ expect ( screen . getByText ( / G e t i n s t a n t i n s i g h t s / ) ) . toBeInTheDocument ( ) ;
65+ } ) ;
66+
67+ it ( 'should render terms and privacy links' , ( ) => {
68+ render ( < LoginPage /> ) ;
69+
70+ expect ( screen . getByText ( 'Terms of Service' ) ) . toBeInTheDocument ( ) ;
71+ expect ( screen . getByText ( 'Privacy Policy' ) ) . toBeInTheDocument ( ) ;
72+ } ) ;
73+ } ) ;
74+
75+ describe ( 'Authentication States' , ( ) => {
76+ it ( 'should redirect to dashboard when already authenticated' , ( ) => {
77+ const mockPush = jest . fn ( ) ;
78+ jest . doMock ( 'next/navigation' , ( ) => ( {
79+ useRouter : ( ) => ( { push : mockPush } ) ,
80+ useSearchParams : ( ) => new URLSearchParams ( ) ,
81+ } ) ) ;
82+
83+ mockUseAuth . mockReturnValue ( {
84+ user : { id : '1' , name : 'Test User' , email : 'test@example.com' } ,
85+ accessToken : 'token' ,
86+ isAuthenticated : true ,
87+ isLoading : false ,
88+ error : null ,
89+ login : jest . fn ( ) ,
90+ logout : jest . fn ( ) ,
91+ refreshToken : jest . fn ( ) ,
92+ setError : jest . fn ( ) ,
93+ } ) ;
94+
95+ render ( < LoginPage /> ) ;
96+
97+ expect ( mockPush ) . toHaveBeenCalledWith ( '/dashboard' ) ;
98+ } ) ;
99+
100+ it ( 'should show error message when authentication fails' , ( ) => {
101+ const mockSetError = jest . fn ( ) ;
102+ mockUseAuth . mockReturnValue ( {
103+ user : null ,
104+ accessToken : null ,
105+ isAuthenticated : false ,
106+ isLoading : false ,
107+ error : 'Authentication failed' ,
108+ login : jest . fn ( ) ,
109+ logout : jest . fn ( ) ,
110+ refreshToken : jest . fn ( ) ,
111+ setError : mockSetError ,
112+ } ) ;
113+
114+ render ( < LoginPage /> ) ;
115+
116+ expect ( screen . getByText ( 'Authentication Error' ) ) . toBeInTheDocument ( ) ;
117+ expect ( screen . getByText ( 'Authentication failed' ) ) . toBeInTheDocument ( ) ;
118+ } ) ;
119+
120+ it ( 'should handle OAuth errors from URL parameters' , ( ) => {
121+ const mockSetError = jest . fn ( ) ;
122+ mockUseAuth . mockReturnValue ( {
123+ user : null ,
124+ accessToken : null ,
125+ isAuthenticated : false ,
126+ isLoading : false ,
127+ error : null ,
128+ login : jest . fn ( ) ,
129+ logout : jest . fn ( ) ,
130+ refreshToken : jest . fn ( ) ,
131+ setError : mockSetError ,
132+ } ) ;
133+
134+ // Mock useSearchParams to return an error
135+ jest . doMock ( 'next/navigation' , ( ) => ( {
136+ useRouter : ( ) => ( { push : jest . fn ( ) } ) ,
137+ useSearchParams : ( ) => new URLSearchParams ( '?error=access_denied' ) ,
138+ } ) ) ;
139+
140+ render ( < LoginPage /> ) ;
141+
142+ expect ( mockSetError ) . toHaveBeenCalledWith ( 'Login failed: access_denied' ) ;
143+ } ) ;
144+ } ) ;
145+
146+ describe ( 'Button Interactions' , ( ) => {
147+ it ( 'should handle Google login button clicks' , ( ) => {
148+ const originalLocation = window . location ;
149+ delete ( window as any ) . location ;
150+ window . location = { href : '' } as any ;
151+
152+ render ( < LoginPage /> ) ;
153+
154+ const googleButton = screen . getByText ( 'Continue with Google' ) ;
155+ fireEvent . click ( googleButton ) ;
156+
157+ expect ( window . location . href ) . toBe ( 'http://localhost:8000/auth/google' ) ;
158+
159+ window . location = originalLocation ;
160+ } ) ;
161+
162+ it ( 'should handle alternative login button clicks' , ( ) => {
163+ const originalLocation = window . location ;
164+ delete ( window as any ) . location ;
165+ window . location = { href : '' } as any ;
166+
167+ render ( < LoginPage /> ) ;
168+
169+ const altButton = screen . getByText ( 'Sign in with Google' ) ;
170+ fireEvent . click ( altButton ) ;
171+
172+ expect ( window . location . href ) . toBe ( 'http://localhost:8000/auth/google' ) ;
173+
174+ window . location = originalLocation ;
175+ } ) ;
176+ } ) ;
177+
178+ describe ( 'Page Layout' , ( ) => {
179+ it ( 'should have proper responsive layout' , ( ) => {
180+ render ( < LoginPage /> ) ;
181+
182+ const container = screen . getByText ( 'Welcome to SmartQuery' ) . closest ( 'div' ) ;
183+ expect ( container ) . toHaveClass ( 'min-h-screen' , 'bg-gradient-to-br' , 'from-blue-50' , 'to-indigo-100' ) ;
184+ } ) ;
185+
186+ it ( 'should have proper card styling' , ( ) => {
187+ render ( < LoginPage /> ) ;
188+
189+ const loginCard = screen . getByText ( 'Continue with Google' ) . closest ( 'div' ) ;
190+ expect ( loginCard ) . toHaveClass ( 'bg-white' , 'py-8' , 'px-6' , 'shadow-xl' , 'rounded-lg' ) ;
191+ } ) ;
192+
193+ it ( 'should have proper button styling' , ( ) => {
194+ render ( < LoginPage /> ) ;
195+
196+ const googleButton = screen . getByText ( 'Continue with Google' ) ;
197+ expect ( googleButton ) . toHaveClass ( 'w-full' , 'max-w-sm' , 'mx-auto' , 'bg-white' , 'text-gray-700' ) ;
198+ } ) ;
199+ } ) ;
200+
201+ describe ( 'Accessibility' , ( ) => {
202+ it ( 'should have proper button roles' , ( ) => {
203+ render ( < LoginPage /> ) ;
204+
205+ const buttons = screen . getAllByRole ( 'button' ) ;
206+ expect ( buttons ) . toHaveLength ( 2 ) ; // Two login buttons
207+ } ) ;
208+
209+ it ( 'should have proper heading structure' , ( ) => {
210+ render ( < LoginPage /> ) ;
211+
212+ const mainHeading = screen . getByRole ( 'heading' , { level : 1 } ) ;
213+ expect ( mainHeading ) . toHaveTextContent ( 'Welcome to SmartQuery' ) ;
214+
215+ const subHeading = screen . getByRole ( 'heading' , { level : 3 } ) ;
216+ expect ( subHeading ) . toHaveTextContent ( 'What you can do with SmartQuery' ) ;
217+ } ) ;
218+
219+ it ( 'should have proper link elements' , ( ) => {
220+ render ( < LoginPage /> ) ;
221+
222+ const links = screen . getAllByRole ( 'link' ) ;
223+ expect ( links ) . toHaveLength ( 2 ) ; // Terms and Privacy links
224+ } ) ;
225+ } ) ;
226+ } ) ;
0 commit comments