diff --git a/src/__tests__/LoginPage/SignIn.test.js b/src/__tests__/LoginPage/SignIn.test.js index 015706d2..7052c078 100644 --- a/src/__tests__/LoginPage/SignIn.test.js +++ b/src/__tests__/LoginPage/SignIn.test.js @@ -24,14 +24,14 @@ afterEach(() => { describe('Signin component automatic navigation', () => { it('navigates to homepage when user is already logged in', async () => { - render( {}} isLoggedIn={true} setIsLoggedIn={() => {}} />); + render( {}} />); await expect(mockedUsedNavigate).toHaveBeenCalledWith('/home'); }); it('navigates to homepage when auth is disabled', async () => { // mock request to check auth jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { http: {} } }); - render( {}} isLoggedIn={false} setIsLoggedIn={() => {}} />); + render( {}} />); await waitFor(() => { expect(mockedUsedNavigate).toHaveBeenCalledWith('/home'); }); @@ -48,7 +48,7 @@ describe('Sign in form', () => { }); it('should change username and password values on user input', async () => { - render( {}} isLoggedIn={false} setIsLoggedIn={() => {}} />); + render( {}} />); const usernameInput = await screen.findByLabelText(/^Username/i); const passwordInput = await screen.findByLabelText(/^Enter Password/i); fireEvent.change(usernameInput, { target: { value: 'test' } }); @@ -59,7 +59,7 @@ describe('Sign in form', () => { }); it('should display error if username and password values are empty after change', async () => { - render( {}} isLoggedIn={false} setIsLoggedIn={() => {}} />); + render( {}} />); const usernameInput = await screen.findByLabelText(/^Username/i); const passwordInput = await screen.findByLabelText(/^Enter Password/i); userEvent.click(usernameInput); @@ -74,24 +74,154 @@ describe('Sign in form', () => { await waitFor(() => expect(passwordError).toBeInTheDocument()); }); - it('should log in the user and navigate to homepage if login is successful', async () => { - render( {}} isLoggedIn={false} setIsLoggedIn={() => {}} />); - const submitButton = await screen.findByText('Continue'); + it('should log in the user and navigate to homepage if login is successful using button', async () => { + render( {}} />); + + const usernameInput = await screen.findByLabelText(/^Username/i); + const passwordInput = await screen.findByLabelText(/^Enter Password/i); + userEvent.type(usernameInput, 'test'); + userEvent.type(passwordInput, 'test'); + jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: {} } }); + const submitButton = await screen.findByText('Continue'); + fireEvent.click(submitButton); + await waitFor(() => { + expect(mockedUsedNavigate).toHaveBeenCalledWith('/home'); + }); + }); + + it('should display an error if username is blank and login is attempted using button', async () => { + render( {}} />); + + const passwordInput = await screen.findByLabelText(/^Enter Password/i); + userEvent.type(passwordInput, 'test'); + const submitButton = await screen.findByTestId('basic-auth-submit-btn'); + fireEvent.click(submitButton); + + await waitFor(() => expect(screen.queryByText(/enter a username/i)).toBeInTheDocument()); + await waitFor(() => expect(screen.queryByText(/enter a password/i)).not.toBeInTheDocument()); + await waitFor(() => { + expect(mockedUsedNavigate).not.toHaveBeenCalled(); + }); + }); + + it('should display an error if password is blank and login is attempted using button', async () => { + render( {}} />); + + const usernameInput = await screen.findByLabelText(/^Username/i); + userEvent.type(usernameInput, 'test'); + const submitButton = await screen.findByTestId('basic-auth-submit-btn'); + fireEvent.click(submitButton); + + await waitFor(() => expect(screen.queryByText(/enter a username/i)).not.toBeInTheDocument()); + await waitFor(() => expect(screen.queryByText(/enter a password/i)).toBeInTheDocument()); + await waitFor(() => { + expect(mockedUsedNavigate).not.toHaveBeenCalled(); + }); + }); + + it('should display an error if username and password are both blank and login is attempted using button', async () => { + render( {}} />); + + const submitButton = await screen.findByTestId('basic-auth-submit-btn'); fireEvent.click(submitButton); + + await waitFor(() => expect(screen.queryByText(/enter a username/i)).toBeInTheDocument()); + await waitFor(() => expect(screen.queryByText(/enter a password/i)).toBeInTheDocument()); + await waitFor(() => { + expect(mockedUsedNavigate).not.toHaveBeenCalled(); + }); + }); + + it('should log in the user and navigate to homepage if login is successful using enter key on username field', async () => { + render( {}} />); + + const usernameInput = await screen.findByLabelText(/^Username/i); + const passwordInput = await screen.findByLabelText(/^Enter Password/i); + userEvent.type(usernameInput, 'test'); + userEvent.type(passwordInput, 'test'); + + jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: {} } }); + userEvent.type(usernameInput, '{enter}'); + await waitFor(() => { + expect(mockedUsedNavigate).toHaveBeenCalledWith('/home'); + }); + }); + + it('should log in the user and navigate to homepage if login is successful using enter key on password field', async () => { + render( {}} />); + + const usernameInput = await screen.findByLabelText(/^Username/i); + const passwordInput = await screen.findByLabelText(/^Enter Password/i); + userEvent.type(usernameInput, 'test'); + userEvent.type(passwordInput, 'test'); + + jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: {} } }); + userEvent.type(passwordInput, '{enter}'); await waitFor(() => { expect(mockedUsedNavigate).toHaveBeenCalledWith('/home'); }); }); + it('should display an error if username is blank and login is attempted using enter key', async () => { + render( {}} />); + + const passwordInput = await screen.findByLabelText(/^Enter Password/i); + userEvent.type(passwordInput, 'test'); + userEvent.type(passwordInput, '{enter}'); + + await waitFor(() => expect(screen.queryByText(/enter a username/i)).toBeInTheDocument()); + await waitFor(() => expect(screen.queryByText(/enter a password/i)).not.toBeInTheDocument()); + await waitFor(() => { + expect(mockedUsedNavigate).not.toHaveBeenCalled(); + }); + }); + + it('should display an error if password is blank and login is attempted using enter key', async () => { + render( {}} />); + + const usernameInput = await screen.findByLabelText(/^Username/i); + userEvent.type(usernameInput, 'test'); + userEvent.type(usernameInput, '{enter}'); + + await waitFor(() => expect(screen.queryByText(/enter a username/i)).not.toBeInTheDocument()); + await waitFor(() => expect(screen.queryByText(/enter a password/i)).toBeInTheDocument()); + await waitFor(() => { + expect(mockedUsedNavigate).not.toHaveBeenCalled(); + }); + }); + + it('should display an error if username and password are both blank and login is attempted using enter key', async () => { + render( {}} />); + + const passwordInput = await screen.findByLabelText(/^Enter Password/i); + userEvent.type(passwordInput, '{enter}'); + + await waitFor(() => expect(screen.queryByText(/enter a username/i)).toBeInTheDocument()); + await waitFor(() => expect(screen.queryByText(/enter a password/i)).toBeInTheDocument()); + await waitFor(() => { + expect(mockedUsedNavigate).not.toHaveBeenCalled(); + }); + }); + it('should should display login error if login not successful', async () => { - render( {}} isLoggedIn={false} setIsLoggedIn={() => {}} />); - const submitButton = await screen.findByText('Continue'); + render( {}} />); + + const usernameInput = await screen.findByLabelText(/^Username/i); + const passwordInput = await screen.findByLabelText(/^Enter Password/i); + userEvent.type(usernameInput, 'test'); + userEvent.type(passwordInput, 'test'); + jest.spyOn(api, 'get').mockRejectedValue({ status: 401, data: {} }); + + const submitButton = await screen.findByText('Continue'); fireEvent.click(submitButton); - const errorDisplay = await screen.findByText(/Authentication Failed/i); + + await waitFor(() => { + expect(screen.queryByText(/Authentication Failed/i)).toBeInTheDocument(); + }); await waitFor(() => { - expect(errorDisplay).toBeInTheDocument(); + expect(mockedUsedNavigate).not.toHaveBeenCalled(); }); }); }); diff --git a/src/components/Login/SignIn.jsx b/src/components/Login/SignIn.jsx index 78b92156..761ed8c1 100644 --- a/src/components/Login/SignIn.jsx +++ b/src/components/Login/SignIn.jsx @@ -149,8 +149,8 @@ const useStyles = makeStyles(() => ({ export default function SignIn({ isLoggedIn, setIsLoggedIn, wrapperSetLoading = () => {} }) { const [usernameError, setUsernameError] = useState(null); const [passwordError, setPasswordError] = useState(null); - const [username, setUsername] = useState(null); - const [password, setPassword] = useState(null); + const [username, setUsername] = useState(''); + const [password, setPassword] = useState(''); const [requestProcessing, setRequestProcessing] = useState(false); const [requestError, setRequestError] = useState(false); const [isLoading, setIsLoading] = useState(true); @@ -228,13 +228,20 @@ export default function SignIn({ isLoggedIn, setIsLoggedIn, wrapperSetLoading = }); }; - const handleClick = (event) => { - event.preventDefault(); - if (Object.keys(authMethods).includes('htpasswd')) { + const handleBasicAuthSubmit = () => { + setRequestError(false); + const isUsernameValid = handleUsernameValidation(username); + const isPasswordValid = handlePasswordValidation(password); + if (Object.keys(authMethods).includes('htpasswd') && isUsernameValid && isPasswordValid) { handleBasicAuth(); } }; + const handleClick = (event) => { + event.preventDefault(); + handleBasicAuthSubmit(); + }; + const handleGuestClick = () => { setRequestProcessing(false); setRequestError(false); @@ -251,35 +258,55 @@ export default function SignIn({ isLoggedIn, setIsLoggedIn, wrapperSetLoading = ); }; + const handleUsernameValidation = (username) => { + let isValid = true; + if (username === '') { + setUsernameError('Please enter a username'); + isValid = false; + } else { + setUsernameError(null); + } + return isValid; + }; + + const handlePasswordValidation = (password) => { + let isValid = true; + if (password === '') { + setPasswordError('Please enter a password'); + isValid = false; + } else { + setPasswordError(null); + } + return isValid; + }; + const handleChange = (event, type) => { event.preventDefault(); setRequestError(false); const val = event.target?.value; - const isEmpty = val === ''; switch (type) { case 'username': setUsername(val); - if (isEmpty) { - setUsernameError('Please enter a username'); - } else { - setUsernameError(null); - } + handleUsernameValidation(val); break; case 'password': setPassword(val); - if (isEmpty) { - setPasswordError('Please enter a password'); - } else { - setPasswordError(null); - } + handlePasswordValidation(val); break; default: break; } }; + const handleLoginInputFieldKeyDown = (event) => { + const keyPressed = event.key; + if (keyPressed === 'Enter') { + handleBasicAuthSubmit(); + } + }; + const renderThirdPartyLoginMethods = () => { let isGoogle = isObject(authMethods.openid?.providers?.google); // let isGitlab = isObject(authMethods.openid?.providers?.gitlab); @@ -315,7 +342,7 @@ export default function SignIn({ isLoggedIn, setIsLoggedIn, wrapperSetLoading = {Object.keys(authMethods).length > 1 && Object.keys(authMethods).includes('openid') && Object.keys(authMethods.openid.providers).length > 0 && ( - + or )} @@ -334,6 +361,7 @@ export default function SignIn({ isLoggedIn, setIsLoggedIn, wrapperSetLoading = onInput={(e) => handleChange(e, 'username')} error={usernameError != null} helperText={usernameError} + onKeyDown={(e) => handleLoginInputFieldKeyDown(e)} /> handleChange(e, 'password')} error={passwordError != null} helperText={passwordError} + onKeyDown={(e) => handleLoginInputFieldKeyDown(e)} /> {requestProcessing && } {requestError && ( @@ -357,7 +386,13 @@ export default function SignIn({ isLoggedIn, setIsLoggedIn, wrapperSetLoading = )}
-