Skip to content

Commit 017bf15

Browse files
committed
tests: profile pages
1 parent 8bdf634 commit 017bf15

File tree

5 files changed

+332
-0
lines changed

5 files changed

+332
-0
lines changed
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { cleanup, render, screen, waitFor } from '@testing-library/react';
2+
import userEvent from '@testing-library/user-event';
3+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
4+
import EditPublicProfile from '@components/account/profile/EditPublicProfile';
5+
import { PublicProfileInfo } from '@typedefs/general';
6+
7+
const apiMocks = vi.hoisted(() => ({
8+
updatePublicProfileInfo: vi.fn(),
9+
}));
10+
11+
vi.mock('@lib/api/backend', () => ({
12+
updatePublicProfileInfo: apiMocks.updatePublicProfileInfo,
13+
}));
14+
15+
vi.mock('react-hot-toast', () => ({
16+
default: {
17+
success: vi.fn(),
18+
error: vi.fn(),
19+
},
20+
}));
21+
22+
describe('EditPublicProfile', () => {
23+
const profileInfo: PublicProfileInfo = {
24+
name: 'Alice',
25+
description: 'Builder',
26+
links: {
27+
Linkedin: 'https://linkedin.com/in/alice',
28+
},
29+
};
30+
31+
beforeEach(() => {
32+
apiMocks.updatePublicProfileInfo.mockResolvedValue({});
33+
});
34+
35+
afterEach(() => {
36+
cleanup();
37+
vi.clearAllMocks();
38+
});
39+
40+
it('submits updated values', async () => {
41+
const user = userEvent.setup();
42+
const onEdit = vi.fn();
43+
44+
render(
45+
<EditPublicProfile
46+
profileInfo={profileInfo}
47+
brandingPlatforms={['Linkedin']}
48+
setEditing={vi.fn()}
49+
onEdit={onEdit}
50+
/>,
51+
);
52+
53+
const nameInput = screen.getByDisplayValue('Alice');
54+
await user.clear(nameInput);
55+
await user.type(nameInput, 'Alice Updated');
56+
57+
const descriptionInput = screen.getByDisplayValue('Builder');
58+
await user.clear(descriptionInput);
59+
await user.type(descriptionInput, 'New bio');
60+
61+
const linkedinInput = screen.getByPlaceholderText('LinkedIn');
62+
await user.clear(linkedinInput);
63+
await user.type(linkedinInput, 'https://linkedin.com/in/new');
64+
65+
await user.click(screen.getByRole('button', { name: /update profile/i }));
66+
67+
await waitFor(() => {
68+
expect(apiMocks.updatePublicProfileInfo).toHaveBeenCalledWith({
69+
name: 'Alice Updated',
70+
description: 'New bio',
71+
links: {
72+
Linkedin: 'https://linkedin.com/in/new',
73+
},
74+
});
75+
});
76+
77+
expect(onEdit).toHaveBeenCalled();
78+
});
79+
80+
it('cancels editing', async () => {
81+
const user = userEvent.setup();
82+
const setEditing = vi.fn();
83+
84+
render(
85+
<EditPublicProfile
86+
profileInfo={profileInfo}
87+
brandingPlatforms={['Linkedin']}
88+
setEditing={setEditing}
89+
onEdit={vi.fn()}
90+
/>,
91+
);
92+
93+
await user.click(screen.getByRole('button', { name: /cancel/i }));
94+
expect(setEditing).toHaveBeenCalledWith(false);
95+
});
96+
});
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { cleanup, fireEvent, render, waitFor } from '@testing-library/react';
2+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3+
import ImageUpload from '@components/account/profile/ImageUpload';
4+
5+
const apiMocks = vi.hoisted(() => ({
6+
uploadProfileImage: vi.fn(),
7+
}));
8+
9+
const utilsMocks = vi.hoisted(() => ({
10+
resizeImage: vi.fn(),
11+
}));
12+
13+
const toastMocks = vi.hoisted(() => ({
14+
error: vi.fn(),
15+
success: vi.fn(),
16+
}));
17+
18+
vi.mock('@lib/api/backend', () => ({
19+
uploadProfileImage: apiMocks.uploadProfileImage,
20+
}));
21+
22+
vi.mock('@lib/utils', () => ({
23+
resizeImage: utilsMocks.resizeImage,
24+
}));
25+
26+
vi.mock('react-hot-toast', () => ({
27+
default: toastMocks,
28+
}));
29+
30+
describe('ImageUpload', () => {
31+
beforeEach(() => {
32+
apiMocks.uploadProfileImage.mockResolvedValue({});
33+
utilsMocks.resizeImage.mockResolvedValue(new Blob(['resized'], { type: 'image/jpeg' }));
34+
});
35+
36+
afterEach(() => {
37+
cleanup();
38+
vi.clearAllMocks();
39+
});
40+
41+
it('rejects unsupported file types', async () => {
42+
const setImageLoading = vi.fn();
43+
const onSuccessfulUpload = vi.fn();
44+
45+
const { container } = render(
46+
<ImageUpload onSuccessfulUpload={onSuccessfulUpload} setImageLoading={setImageLoading} />,
47+
);
48+
49+
const input = container.querySelector('input[type="file"]') as HTMLInputElement;
50+
expect(input).toBeTruthy();
51+
52+
const file = new File(['gif'], 'avatar.gif', { type: 'image/gif' });
53+
fireEvent.change(input, { target: { files: [file] } });
54+
55+
expect(setImageLoading).toHaveBeenCalledWith(true);
56+
expect(setImageLoading).toHaveBeenCalledWith(false);
57+
expect(apiMocks.uploadProfileImage).not.toHaveBeenCalled();
58+
expect(toastMocks.error).toHaveBeenCalledWith('Only .jpg, .jpeg, and .png images are allowed.');
59+
expect(input.value).toBe('');
60+
});
61+
62+
it('uploads valid images and calls the success callback', async () => {
63+
const setImageLoading = vi.fn();
64+
const onSuccessfulUpload = vi.fn();
65+
66+
const { container } = render(
67+
<ImageUpload onSuccessfulUpload={onSuccessfulUpload} setImageLoading={setImageLoading} />,
68+
);
69+
70+
const input = container.querySelector('input[type="file"]') as HTMLInputElement;
71+
expect(input).toBeTruthy();
72+
73+
const file = new File(['png'], 'avatar.png', { type: 'image/png' });
74+
fireEvent.change(input, { target: { files: [file] } });
75+
76+
await waitFor(() => {
77+
expect(apiMocks.uploadProfileImage).toHaveBeenCalledWith(expect.any(File));
78+
});
79+
80+
expect(onSuccessfulUpload).toHaveBeenCalled();
81+
expect(setImageLoading).toHaveBeenCalledWith(true);
82+
});
83+
});
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { cleanup, render, screen } from '@testing-library/react';
2+
import { afterEach, describe, expect, it, vi } from 'vitest';
3+
import Profile from '@components/account/profile/Profile';
4+
5+
vi.mock('@components/account/profile/PublicProfile', () => ({
6+
default: () => <div>PublicProfileContent</div>,
7+
}));
8+
9+
vi.mock('@components/account/profile/WalletInformation', () => ({
10+
default: () => <div>WalletInformationContent</div>,
11+
}));
12+
13+
describe('Profile', () => {
14+
afterEach(() => {
15+
cleanup();
16+
});
17+
18+
it('renders public profile and wallet sections', () => {
19+
render(<Profile />);
20+
21+
expect(screen.getByText('Public Profile')).toBeInTheDocument();
22+
expect(screen.getByText('Wallet')).toBeInTheDocument();
23+
expect(screen.getByText('PublicProfileContent')).toBeInTheDocument();
24+
expect(screen.getByText('WalletInformationContent')).toBeInTheDocument();
25+
});
26+
});
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
2+
import { cleanup, render, screen } from '@testing-library/react';
3+
import userEvent from '@testing-library/user-event';
4+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
5+
import PublicProfile from '@components/account/profile/PublicProfile';
6+
7+
const apiMocks = vi.hoisted(() => ({
8+
getBrandingPlatforms: vi.fn(),
9+
getPublicProfileInfo: vi.fn(),
10+
uploadProfileImage: vi.fn(),
11+
updatePublicProfileInfo: vi.fn(),
12+
}));
13+
14+
const wagmiMocks = vi.hoisted(() => ({
15+
useAccount: vi.fn(),
16+
}));
17+
18+
vi.mock('@lib/api/backend', () => ({
19+
getBrandingPlatforms: apiMocks.getBrandingPlatforms,
20+
getPublicProfileInfo: apiMocks.getPublicProfileInfo,
21+
uploadProfileImage: apiMocks.uploadProfileImage,
22+
updatePublicProfileInfo: apiMocks.updatePublicProfileInfo,
23+
}));
24+
25+
vi.mock('wagmi', () => ({
26+
useAccount: wagmiMocks.useAccount,
27+
}));
28+
29+
const renderWithQueryClient = () => {
30+
const queryClient = new QueryClient({
31+
defaultOptions: {
32+
queries: {
33+
retry: false,
34+
},
35+
},
36+
});
37+
38+
return render(
39+
<QueryClientProvider client={queryClient}>
40+
<PublicProfile />
41+
</QueryClientProvider>,
42+
);
43+
};
44+
45+
describe('PublicProfile', () => {
46+
beforeEach(() => {
47+
wagmiMocks.useAccount.mockReturnValue({ address: '0xabc' });
48+
apiMocks.getBrandingPlatforms.mockResolvedValue(['Linkedin', 'Website']);
49+
apiMocks.getPublicProfileInfo.mockResolvedValue({
50+
brands: [
51+
{
52+
name: 'Alice',
53+
description: 'Builder',
54+
links: {
55+
Linkedin: 'https://linkedin.com/in/alice',
56+
Website: '',
57+
},
58+
},
59+
],
60+
});
61+
});
62+
63+
afterEach(() => {
64+
cleanup();
65+
vi.clearAllMocks();
66+
});
67+
68+
it('renders profile info and link values', async () => {
69+
renderWithQueryClient();
70+
71+
expect(await screen.findByText('Alice')).toBeInTheDocument();
72+
expect(screen.getByText('Builder')).toBeInTheDocument();
73+
expect(screen.getByText('LinkedIn')).toBeInTheDocument();
74+
expect(screen.getByText('https://linkedin.com/in/alice')).toBeInTheDocument();
75+
76+
expect(apiMocks.getBrandingPlatforms).toHaveBeenCalled();
77+
expect(apiMocks.getPublicProfileInfo).toHaveBeenCalledWith('0xabc');
78+
});
79+
80+
it('switches to edit mode', async () => {
81+
const user = userEvent.setup();
82+
renderWithQueryClient();
83+
84+
await screen.findByText('Alice');
85+
await user.click(screen.getByRole('button', { name: /edit profile/i }));
86+
87+
expect(screen.getByRole('button', { name: /update profile/i })).toBeInTheDocument();
88+
expect(screen.getByRole('button', { name: /cancel/i })).toBeInTheDocument();
89+
});
90+
});
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { cleanup, render, screen, waitFor } from '@testing-library/react';
2+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3+
import WalletInformation from '@components/account/profile/WalletInformation';
4+
import { config } from '@lib/config';
5+
import { fBI } from '@lib/utils';
6+
7+
const blockchainMocks = vi.hoisted(() => ({
8+
fetchErc20Balance: vi.fn(),
9+
}));
10+
11+
vi.mock('@lib/contexts/blockchain', () => ({
12+
useBlockchainContext: () => ({
13+
fetchErc20Balance: blockchainMocks.fetchErc20Balance,
14+
}),
15+
}));
16+
17+
describe('WalletInformation', () => {
18+
beforeEach(() => {
19+
blockchainMocks.fetchErc20Balance.mockResolvedValue(1_234_567n);
20+
});
21+
22+
afterEach(() => {
23+
cleanup();
24+
vi.clearAllMocks();
25+
});
26+
27+
it('fetches and displays the USDC balance', async () => {
28+
render(<WalletInformation />);
29+
30+
await waitFor(() => {
31+
expect(blockchainMocks.fetchErc20Balance).toHaveBeenCalledWith(config.usdcContractAddress);
32+
});
33+
34+
const expected = String(fBI(1_234_567n, 6));
35+
expect(screen.getByText(expected)).toBeInTheDocument();
36+
});
37+
});

0 commit comments

Comments
 (0)