-
Notifications
You must be signed in to change notification settings - Fork 8
VIDSOL-105: Background Replacement Support for Web VERA - Custom Images #201
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
3841fd7
55af7e2
506acb5
8f359b0
45020d0
f60b121
0b5e785
d40aa17
3a51e0e
21077e5
7570983
ceffe18
a9ba4d2
310abf1
cfff835
d36d700
2639977
965c1fe
b7c582b
835c2b5
714c562
3d1af88
574ac90
9f5e3f4
8092cdc
75f145e
3afadd5
651cff2
6b7031f
8f9211b
54081f6
05b808e
cf7502d
e4b58ec
72bf9ac
f5e4392
baa4ce2
9287c09
dfa5506
8650c99
973d82d
6fc8c7e
03cf685
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import { render, screen, fireEvent, waitFor } from '@testing-library/react'; | ||
import { vi, describe, it, expect } from 'vitest'; | ||
import AddBackgroundEffectLayout from './AddBackgroundEffectLayout'; | ||
|
||
vi.mock('../../../../utils/useImageStorage/useImageStorage', () => ({ | ||
useImageStorage: () => ({ | ||
storageError: '', | ||
handleImageFromFile: vi.fn(async () => ({ | ||
dataUrl: 'data:image/png;base64,MOCKED', | ||
})), | ||
handleImageFromLink: vi.fn(async () => ({ | ||
dataUrl: 'data:image/png;base64,MOCKED_LINK', | ||
})), | ||
}), | ||
})); | ||
|
||
describe('AddBackgroundEffectLayout', () => { | ||
it('should render without crashing', () => { | ||
render(<AddBackgroundEffectLayout customBackgroundImageChange={vi.fn()} />); | ||
expect(screen.getByText(/Drag and Drop, or click here to upload Image/i)).toBeInTheDocument(); | ||
expect(screen.getByPlaceholderText(/Link from the web/i)).toBeInTheDocument(); | ||
expect(screen.getByTestId('background-effect-link-submit-button')).toBeInTheDocument(); | ||
}); | ||
|
||
it('shows error for invalid file type', async () => { | ||
render(<AddBackgroundEffectLayout customBackgroundImageChange={vi.fn()} />); | ||
const input = screen.getByLabelText(/upload/i); | ||
const file = new File(['dummy'], 'test.txt', { type: 'text/plain' }); | ||
fireEvent.change(input, { target: { files: [file] } }); | ||
expect( | ||
await screen.findByText(/Only JPG, PNG, or WebP images are allowed/i) | ||
).toBeInTheDocument(); | ||
}); | ||
|
||
it('shows error for file size too large', async () => { | ||
render(<AddBackgroundEffectLayout customBackgroundImageChange={vi.fn()} />); | ||
const input = screen.getByLabelText(/upload/i); | ||
const file = new File(['x'.repeat(3 * 1024 * 1024)], 'big.png', { type: 'image/png' }); | ||
Object.defineProperty(file, 'size', { value: 3 * 1024 * 1024 }); | ||
fireEvent.change(input, { target: { files: [file] } }); | ||
expect(await screen.findByText(/Image must be less than 2MB/i)).toBeInTheDocument(); | ||
}); | ||
|
||
it('calls customBackgroundImageChange on valid file upload', async () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: I think the name of the test is too literal. could we change it to something more generic? |
||
const cb = vi.fn(); | ||
render(<AddBackgroundEffectLayout customBackgroundImageChange={cb} />); | ||
const input = screen.getByLabelText(/upload/i); | ||
const file = new File(['dummy'], 'test.png', { type: 'image/png' }); | ||
fireEvent.change(input, { target: { files: [file] } }); | ||
await waitFor(() => expect(cb).toHaveBeenCalledWith('data:image/png;base64,MOCKED')); | ||
}); | ||
|
||
it('calls customBackgroundImageChange on valid link submit', async () => { | ||
const cb = vi.fn(); | ||
render(<AddBackgroundEffectLayout customBackgroundImageChange={cb} />); | ||
const input = screen.getByPlaceholderText(/Link from the web/i); | ||
fireEvent.change(input, { target: { value: 'https://example.com/image.png' } }); | ||
const button = screen.getByTestId('background-effect-link-submit-button'); | ||
fireEvent.click(button); | ||
await waitFor(() => expect(cb).toHaveBeenCalledWith('data:image/png;base64,MOCKED_LINK')); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
import { | ||
Box, | ||
Button, | ||
CircularProgress, | ||
InputAdornment, | ||
TextField, | ||
Typography, | ||
} from '@mui/material'; | ||
import { ChangeEvent, ReactElement, useState } from 'react'; | ||
import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; | ||
import LinkIcon from '@mui/icons-material/Link'; | ||
import { useImageStorage } from '../../../../utils/useImageStorage/useImageStorage'; | ||
import FileUploader from '../../FileUploader/FileUploader'; | ||
|
||
export type AddBackgroundEffectLayoutProps = { | ||
customBackgroundImageChange: (param1: string) => void; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: can we change the name |
||
}; | ||
|
||
/** | ||
* AddBackgroundEffectLayout Component | ||
* | ||
* This component manages the UI for adding background effects. | ||
* @param {AddBackgroundEffectLayoutProps} props - The props for the component. | ||
* @property {Function} customBackgroundImageChange - Callback function to handle background image change. | ||
* @returns {ReactElement} The add background effect layout component. | ||
*/ | ||
const AddBackgroundEffectLayout = ({ | ||
customBackgroundImageChange, | ||
}: AddBackgroundEffectLayoutProps): ReactElement => { | ||
const MAX_SIZE_MB = 2; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could this be moved to the |
||
const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/webp']; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same here. can this be moved to the contants file? |
||
|
||
const [fileError, setFileError] = useState(''); | ||
const [imageLink, setImageLink] = useState(''); | ||
const [linkLoading, setLinkLoading] = useState(false); | ||
Comment on lines
+33
to
+35
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could you specify the types i.e |
||
const { storageError, handleImageFromFile, handleImageFromLink } = useImageStorage(); | ||
|
||
type HandleFileChangeType = ChangeEvent<HTMLInputElement> | { target: { files: FileList } }; | ||
|
||
const handleFileChange = async (e: HandleFileChangeType) => { | ||
const { files } = e.target; | ||
if (!files || files.length === 0) { | ||
return; | ||
} | ||
|
||
const file = files[0]; | ||
if (!file) { | ||
return; | ||
} | ||
|
||
if (!ALLOWED_TYPES.includes(file.type)) { | ||
setFileError('Only JPG, PNG, or WebP images are allowed.'); | ||
return; | ||
} | ||
|
||
if (file.size > MAX_SIZE_MB * 1024 * 1024) { | ||
setFileError('Image must be less than 2MB.'); | ||
return; | ||
} | ||
|
||
try { | ||
const newImage = await handleImageFromFile(file); | ||
if (newImage) { | ||
setFileError(''); | ||
customBackgroundImageChange(newImage.dataUrl); | ||
} | ||
} catch { | ||
setFileError('Failed to process uploaded image.'); | ||
} | ||
}; | ||
|
||
const handleLinkSubmit = async () => { | ||
setFileError(''); | ||
setLinkLoading(true); | ||
try { | ||
const newImage = await handleImageFromLink(imageLink); | ||
if (newImage) { | ||
setFileError(''); | ||
customBackgroundImageChange(newImage.dataUrl); | ||
} else { | ||
setFileError('Failed to store image.'); | ||
} | ||
} catch { | ||
// error handled in hook | ||
} finally { | ||
setLinkLoading(false); | ||
} | ||
}; | ||
|
||
return ( | ||
<Box> | ||
<FileUploader handleFileChange={handleFileChange} /> | ||
|
||
{(fileError || storageError) && ( | ||
<Typography color="error" mt={1} fontSize={14}> | ||
{fileError || storageError} | ||
</Typography> | ||
)} | ||
|
||
<Box mt={2} display="flex" alignItems="center" gap={1}> | ||
<TextField | ||
fullWidth | ||
size="small" | ||
placeholder="Link from the web" | ||
className="add-background-effect-input" | ||
value={imageLink} | ||
onChange={(e) => setImageLink(e.target.value)} | ||
InputProps={{ | ||
startAdornment: ( | ||
<InputAdornment position="start"> | ||
{linkLoading ? <CircularProgress size={24} /> : <LinkIcon sx={{ fontSize: 24 }} />} | ||
</InputAdornment> | ||
), | ||
}} | ||
/> | ||
|
||
<Button | ||
data-testid="background-effect-link-submit-button" | ||
variant="contained" | ||
color="primary" | ||
onClick={handleLinkSubmit} | ||
disabled={linkLoading} | ||
style={{ minWidth: 0, padding: '8px 12px' }} | ||
> | ||
{linkLoading ? ( | ||
<CircularProgress size={24} color="inherit" /> | ||
) : ( | ||
<ArrowForwardIcon sx={{ fontSize: 24 }} /> | ||
)} | ||
</Button> | ||
</Box> | ||
</Box> | ||
); | ||
}; | ||
|
||
export default AddBackgroundEffectLayout; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import AddBackgroundEffectLayout from './AddBackgroundEffectLayout'; | ||
|
||
export default AddBackgroundEffectLayout; |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import { render, screen } from '@testing-library/react'; | ||
import userEvent from '@testing-library/user-event'; | ||
import { vi, describe, it, expect } from 'vitest'; | ||
import BackgroundEffectTabs from './BackgroundEffectTabs'; | ||
|
||
describe('BackgroundEffectTabs', () => { | ||
const setTabSelected = vi.fn(); | ||
const setBackgroundSelected = vi.fn(); | ||
const cleanBackgroundReplacementIfSelectedAndDeleted = vi.fn(); | ||
const customBackgroundImageChange = vi.fn(); | ||
|
||
it('renders tabs and defaults to Backgrounds tab', () => { | ||
render( | ||
<BackgroundEffectTabs | ||
tabSelected={0} | ||
setTabSelected={setTabSelected} | ||
backgroundSelected="" | ||
setBackgroundSelected={setBackgroundSelected} | ||
cleanBackgroundReplacementIfSelectedAndDeletedFunction={ | ||
cleanBackgroundReplacementIfSelectedAndDeleted | ||
} | ||
customBackgroundImageChange={customBackgroundImageChange} | ||
/> | ||
); | ||
expect(screen.getByRole('tab', { name: /Backgrounds/i })).toBeInTheDocument(); | ||
expect(screen.getByRole('tab', { name: /Add Background/i })).toBeInTheDocument(); | ||
}); | ||
|
||
it('switches to Add Background tab when clicked', async () => { | ||
render( | ||
<BackgroundEffectTabs | ||
tabSelected={0} | ||
setTabSelected={setTabSelected} | ||
backgroundSelected="" | ||
setBackgroundSelected={setBackgroundSelected} | ||
cleanBackgroundReplacementIfSelectedAndDeletedFunction={ | ||
cleanBackgroundReplacementIfSelectedAndDeleted | ||
} | ||
customBackgroundImageChange={customBackgroundImageChange} | ||
/> | ||
); | ||
const addTab = screen.getByRole('tab', { name: /Add Background/i }); | ||
await userEvent.click(addTab); | ||
expect(setTabSelected).toHaveBeenCalledWith(1); | ||
}); | ||
|
||
it('renders AddBackgroundEffectLayout when Add Background tab is selected', () => { | ||
render( | ||
<BackgroundEffectTabs | ||
tabSelected={1} | ||
setTabSelected={setTabSelected} | ||
backgroundSelected="" | ||
setBackgroundSelected={setBackgroundSelected} | ||
cleanBackgroundReplacementIfSelectedAndDeletedFunction={ | ||
cleanBackgroundReplacementIfSelectedAndDeleted | ||
} | ||
customBackgroundImageChange={customBackgroundImageChange} | ||
/> | ||
); | ||
expect(screen.getByText(/upload/i)).toBeInTheDocument(); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: I think we can just say
should render