Skip to content
This repository has been archived by the owner on Sep 18, 2023. It is now read-only.

Commit

Permalink
refactor(views/SignIn): split component and hooks (#114)
Browse files Browse the repository at this point in the history
  • Loading branch information
sbolel authored Aug 8, 2023
1 parent 0030d88 commit 6045294
Show file tree
Hide file tree
Showing 16 changed files with 787 additions and 384 deletions.
5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"dependencies": {
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@hookform/resolvers": "^3.1.1",
"@hookform/resolvers": "^3.2.0",
"@mui/icons-material": "^5.14.1",
"@mui/lab": "^5.0.0-alpha.137",
"@mui/material": "^5.14.1",
Expand All @@ -57,7 +57,7 @@
"react-cookie": "^4.1.1",
"react-dom": "^18.2.0",
"react-dropzone": "^14.2.3",
"react-hook-form": "^7.45.2",
"react-hook-form": "^7.45.4",
"react-popper": "^2.3.0",
"react-router-dom": "^6.14.2",
"uuid": "^9.0.0",
Expand Down Expand Up @@ -97,7 +97,6 @@
"@types/testing-library__dom": "^7.5.0",
"@types/testing-library__react": "^10.2.0",
"@types/uuid": "^9.0.2",
"@types/yup": "^0.32.0",
"@typescript-eslint/eslint-plugin": "^6.1.0",
"@typescript-eslint/parser": "^6.1.0",
"@vitejs/plugin-react-swc": "^3.3.2",
Expand Down
8 changes: 7 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,13 @@ import theme from '@/theme/theme'
* @returns {JSX.Element}
*/
const App = (): JSX.Element => (
<main data-testid="main">
<main
data-testid="main"
style={{
height: '100%',
width: '100%',
}}
>
<ThemeProvider theme={theme}>
<AuthProvider>
<AlertProvider>
Expand Down
83 changes: 83 additions & 0 deletions src/components/PasswordVisibilityToggle.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// PasswordVisibilityToggle.test.tsx
import { act, render, fireEvent, waitFor } from '@testing-library/react'
import PasswordVisibilityToggle from './PasswordVisibilityToggle'

describe('PasswordVisibilityToggle', () => {
let setShowPassword: jest.Mock

beforeEach(() => {
jest.resetAllMocks()
setShowPassword = jest.fn()
})

it('renders without crashing', () => {
const { getByRole } = render(
<PasswordVisibilityToggle
showPassword={false}
setShowPassword={setShowPassword}
/>
)
expect(getByRole('button')).toBeInTheDocument()
})

it('changes icon when clicked', async () => {
const { getByRole, rerender } = render(
<PasswordVisibilityToggle
showPassword={false}
setShowPassword={setShowPassword}
/>
)
const button = getByRole('button')
await act(async () => {
fireEvent.click(button)
})
waitFor(() => {
expect(setShowPassword).toHaveBeenCalledWith(true)
})
rerender(
<PasswordVisibilityToggle
showPassword={true}
setShowPassword={setShowPassword}
/>
)
await act(async () => {
fireEvent.click(button)
})
waitFor(() => {
expect(setShowPassword).toHaveBeenCalledWith(false)
})
})

it('calls setShowPassword with correct argument when clicked', async () => {
const { getByRole } = render(
<PasswordVisibilityToggle
showPassword={false}
setShowPassword={setShowPassword}
/>
)
const button = getByRole('button')
await act(async () => {
fireEvent.click(button)
})
waitFor(() => {
expect(setShowPassword).toHaveBeenCalledWith(true)
})
})

it('prevents default on mouse down', async () => {
const { getByRole } = render(
<PasswordVisibilityToggle
showPassword={false}
setShowPassword={setShowPassword}
/>
)
const button = getByRole('button')
const preventDefault = jest.fn()
await act(async () => {
fireEvent.mouseDown(button, { preventDefault })
})
waitFor(() => {
expect(preventDefault).toHaveBeenCalled()
})
})
})
38 changes: 38 additions & 0 deletions src/components/PasswordVisibilityToggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* @module sbom-harbor-ui/components/PasswordVisibilityToggle.tsx
*/
import React from 'react'
import IconButton from '@mui/material/IconButton'
import InputAdornment from '@mui/material/InputAdornment'
import VisibilityOutline from '@mui/icons-material/Visibility'
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff'

interface PasswordVisibilityToggleProps {
showPassword: boolean
setShowPassword: (value: boolean) => void
}

const PasswordVisibilityToggle: React.FC<PasswordVisibilityToggleProps> = ({
showPassword,
setShowPassword,
}) => {
const handleClick = React.useCallback(
() => setShowPassword(!showPassword),
[showPassword, setShowPassword]
)

return (
<InputAdornment position="end">
<IconButton
edge="end"
onMouseDown={(e) => e.preventDefault()}
onClick={handleClick}
aria-label={showPassword ? 'Hide password' : 'Show password'}
>
{showPassword ? <VisibilityOutline /> : <VisibilityOffIcon />}
</IconButton>
</InputAdornment>
)
}

export default PasswordVisibilityToggle
83 changes: 83 additions & 0 deletions src/components/forms/InputFormControl.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { Control, Controller, FieldValues, Path } from 'react-hook-form'
import FormControl, { FormControlProps } from '@mui/material/FormControl'
import FormHelperText, {
FormHelperTextProps,
} from '@mui/material/FormHelperText'
import InputLabel, { InputLabelProps } from '@mui/material/InputLabel'
import OutlinedInput, { OutlinedInputProps } from '@mui/material/OutlinedInput'
import toTitleCase from '@/utils/toTitleCase'

type InputFormControlProps<TFieldValues extends FieldValues = FieldValues> = {
control: Control<TFieldValues, unknown>
name: Path<TFieldValues>
label?: string
FormControlProps?: FormControlProps
FormHelperTextProps?: FormHelperTextProps
InputLabelProps?: InputLabelProps
InputProps?: OutlinedInputProps
}

const InputFormControl = <TFieldValues extends FieldValues>({
control,
name,
label = toTitleCase(name),
FormControlProps,
FormHelperTextProps,
InputLabelProps,
InputProps,
}: InputFormControlProps<TFieldValues>) => {
const {
slotProps: { input: inputSlotProps = {}, root: rootSlotProps = {} } = {},
} = InputProps || {}

return (
<FormControl fullWidth {...FormControlProps}>
<Controller
control={control}
name={name}
render={({
field: { value, onBlur, onChange },
fieldState: { error },
}) => (
<>
<InputLabel
htmlFor={`${name}-input`}
id={`${name}-label`}
{...InputLabelProps}
error={!!error}
>
{label}
</InputLabel>
<OutlinedInput
aria-describedby={error && `${name}-error`}
id={`${name}-input`}
{...InputProps}
error={!!error}
label={label}
value={value}
onBlur={onBlur}
onChange={onChange}
slotProps={{
root: rootSlotProps,
input: {
'aria-invalid': !!error,
'aria-labelledby': `${name}-label`,
...inputSlotProps,
},
}}
/>
<FormHelperText
id={`${name}-error`}
{...FormHelperTextProps}
error={!!error}
>
{error?.message || ' '}
</FormHelperText>
</>
)}
/>
</FormControl>
)
}

export default InputFormControl
12 changes: 8 additions & 4 deletions src/components/forms/SubmitButton.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { render, screen } from '@testing-library/react'
import SubmitButton from './SubmitButton'
import SubmitButton from '@/components/forms/SubmitButton'

describe('SubmitButton', () => {
it('renders without crashing', () => {
Expand All @@ -14,15 +14,19 @@ describe('SubmitButton', () => {
expect(buttonElement).toHaveAttribute('type', 'submit')
expect(buttonElement).toHaveClass('MuiButton-contained')
expect(buttonElement).toHaveClass('MuiButton-containedPrimary')
expect(buttonElement.textContent).toBe('Save')
expect(buttonElement.textContent).toBe('Submit')
})

it('applies passed props correctly', () => {
render(<SubmitButton color="secondary" variant="outlined" label="Submit" />)
render(
<SubmitButton color="secondary" variant="outlined">
Save
</SubmitButton>
)
const buttonElement = screen.getByRole('button')
expect(buttonElement).toHaveClass('MuiButton-outlined')
expect(buttonElement).toHaveClass('MuiButton-outlinedSecondary')
expect(buttonElement.textContent).toBe('Submit')
expect(buttonElement.textContent).toBe('Save')
})

it('is disabled when disabled prop is passed', () => {
Expand Down
14 changes: 7 additions & 7 deletions src/components/forms/SubmitButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,21 @@
* @module sbom-harbor-ui/components/forms/SubmitButton
*/
import Button, { ButtonProps } from '@mui/material/Button'
import { PropsWithChildren } from 'react'

type InputProps = {
disabled?: boolean
label?: string
} & ButtonProps

const SubmitButton = ({ label, ...props }: InputProps) => (
<Button {...props} sx={{ mt: 3, ml: 1, ...props.sx }}>
{label}
</Button>
)
const SubmitButton = ({
children = 'Submit',
...props
}: PropsWithChildren<InputProps>) => <Button {...props}>{children}</Button>

SubmitButton.defaultProps = {
color: 'primary',
label: 'Save',
label: 'Submit',
size: 'large',
type: 'submit',
variant: 'contained',
}
Expand Down
5 changes: 5 additions & 0 deletions src/theme/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1091,6 +1091,10 @@ theme = createTheme(theme, {
'& .MuiTypography-root': {
marginBottom: 0,
},

'& .MuiFormControlLabel-label': {
color: theme.palette.text.primary,
},
},
},
},
Expand All @@ -1113,6 +1117,7 @@ theme = createTheme(theme, {
lineHeight: '1.125rem',
fontWeight: theme.typography.fontWeightRegular,
color: theme.palette.text.secondary,

'&.Mui-error': {
color: 'rgba(255, 77, 73, 1)',
},
Expand Down
6 changes: 5 additions & 1 deletion src/views/Dashboard/Team/TeamForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,11 @@ const TeamForm = () => {
>
Cancel
</Button>
<SubmitButton disabled={isSubmitting} />
<SubmitButton
disabled={isSubmitting}
label="Save"
sx={{ mt: 3, ml: 1 }}
/>
</Box>
</Grid>
</Grid>
Expand Down
Loading

0 comments on commit 6045294

Please sign in to comment.