-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* ProfilePage and ProfileForm components * input ref; profile form autofocus * profilepage error and loading states * styles * tests * tests * tests * fixes * buttonrow component
- Loading branch information
Showing
15 changed files
with
650 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
.button-row { | ||
&.button-row-block { | ||
ion-button { | ||
flex-grow: 1; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { IonRow } from '@ionic/react'; | ||
import { ComponentPropsWithoutRef } from 'react'; | ||
import classNames from 'classnames'; | ||
|
||
import './ButtonRow.scss'; | ||
import { BaseComponentProps } from '../types'; | ||
|
||
/** | ||
* Properties for the `ButtonRow` component. | ||
* @see {@link BaseComponentProps} | ||
* @see {@link IonRow} | ||
*/ | ||
interface ButtonRowProps extends BaseComponentProps, ComponentPropsWithoutRef<typeof IonRow> { | ||
expand?: 'block'; | ||
} | ||
|
||
/** | ||
* The `ButtonRow` component renders an `IonRow` for the display of one or more | ||
* `IonButton` components in a horizontal row. | ||
* | ||
* Use the `expand` property control how buttons are displayed within the row. | ||
* @param {ButtonRowProps} props - Component properties. | ||
* @returns {JSX.Element} JSX | ||
*/ | ||
const ButtonRow = ({ | ||
className, | ||
expand, | ||
testid = 'row-button', | ||
...rowProps | ||
}: ButtonRowProps): JSX.Element => { | ||
return ( | ||
<IonRow | ||
className={classNames('button-row', { 'button-row-block': expand === 'block' }, className)} | ||
data-testid={testid} | ||
{...rowProps} | ||
/> | ||
); | ||
}; | ||
|
||
export default ButtonRow; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { describe, expect } from 'vitest'; | ||
|
||
import { render, screen } from 'test/test-utils'; | ||
|
||
import ButtonRow from '../ButtonRow'; | ||
|
||
describe('ButtonRow', () => { | ||
it('should render successfully', async () => { | ||
// ARRANGE | ||
render( | ||
<ButtonRow> | ||
<div data-testid="children"></div> | ||
</ButtonRow>, | ||
); | ||
await screen.findByTestId('row-button'); | ||
|
||
// ASSERT | ||
expect(screen.getByTestId('row-button')).toBeDefined(); | ||
expect(screen.getByTestId('children')).toBeDefined(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
import { afterAll, describe, expect, it, vi } from 'vitest'; | ||
|
||
import { renderHook, waitFor } from 'test/test-utils'; | ||
import { userFixture1 } from '__fixtures__/users'; | ||
import storage from 'common/utils/storage'; | ||
import { StorageKey } from 'common/utils/constants'; | ||
|
||
import { useUpdateProfile } from '../useUpdateProfile'; | ||
|
||
describe('useUpdateProfile', () => { | ||
afterEach(() => { | ||
vi.restoreAllMocks(); | ||
}); | ||
|
||
afterAll(() => { | ||
storage.removeItem(StorageKey.User); | ||
}); | ||
|
||
it('should update profile', async () => { | ||
// ARRANGE | ||
let isSuccess = false; | ||
storage.setItem(StorageKey.User, JSON.stringify(userFixture1)); | ||
const { result } = renderHook(() => useUpdateProfile()); | ||
await waitFor(() => expect(result.current).not.toBeNull()); | ||
|
||
// ACT | ||
result.current.mutate( | ||
{ profile: userFixture1 }, | ||
{ | ||
onSuccess: () => { | ||
isSuccess = true; | ||
}, | ||
}, | ||
); | ||
await waitFor(() => expect(result.current.isSuccess).toBe(true)); | ||
|
||
// ASSERT | ||
expect(isSuccess).toBe(true); | ||
}); | ||
|
||
it('should error when no stored profile', async () => { | ||
// ARRANGE | ||
let isSuccess = false; | ||
let isError = false; | ||
storage.removeItem(StorageKey.User); | ||
const { result } = renderHook(() => useUpdateProfile()); | ||
await waitFor(() => expect(result.current).not.toBeNull()); | ||
|
||
// ACT | ||
result.current.mutate( | ||
{ profile: userFixture1 }, | ||
{ | ||
onSuccess: () => { | ||
isSuccess = true; | ||
}, | ||
onError: () => { | ||
isError = true; | ||
}, | ||
}, | ||
); | ||
await waitFor(() => expect(result.current.isError).toBe(true)); | ||
|
||
// ASSERT | ||
expect(isSuccess).toBe(false); | ||
expect(isError).toBe(true); | ||
}); | ||
|
||
it('should error when an error is caught in mutation function', async () => { | ||
// ARRANGE | ||
let isSuccess = false; | ||
let isError = false; | ||
const getItemSpy = vi.spyOn(storage, 'getItem'); | ||
getItemSpy.mockImplementation(() => { | ||
throw new Error('test'); | ||
}); | ||
const { result } = renderHook(() => useUpdateProfile()); | ||
await waitFor(() => expect(result.current).not.toBeNull()); | ||
|
||
// ACT | ||
result.current.mutate( | ||
{ profile: userFixture1 }, | ||
{ | ||
onSuccess: () => { | ||
isSuccess = true; | ||
}, | ||
onError: () => { | ||
isError = true; | ||
}, | ||
}, | ||
); | ||
await waitFor(() => expect(result.current.isError).toBe(true)); | ||
|
||
// ASSERT | ||
expect(isSuccess).toBe(false); | ||
expect(isError).toBe(true); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import { useMutation, useQueryClient } from '@tanstack/react-query'; | ||
|
||
import { User } from 'common/models/user'; | ||
import { QueryKey, StorageKey } from 'common/utils/constants'; | ||
import storage from 'common/utils/storage'; | ||
|
||
/** | ||
* The `Profile` object. This is a contrived type for demonstration purposes. | ||
*/ | ||
export type Profile = Pick<User, 'email' | 'name' | 'phone' | 'username' | 'website'>; | ||
|
||
/** | ||
* The `useUpdateProfile` mutatation function variables. | ||
* @param {Profile} profile - The updated `Profile` object. | ||
*/ | ||
export type UpdateProfileVariables = { | ||
profile: Profile; | ||
}; | ||
|
||
/** | ||
* An API hook which updates a single `Profile`. Returns a `UseMutationResult` | ||
* object whose `mutate` attribute is a function to update a `Profile`. | ||
* | ||
* When successful, the hook updates the cached `Profile` query data. | ||
* @returns Returns a `UseMutationResult`. | ||
*/ | ||
export const useUpdateProfile = () => { | ||
const queryClient = useQueryClient(); | ||
|
||
const updateProfile = ({ profile }: UpdateProfileVariables): Promise<User> => { | ||
return new Promise((resolve, reject) => { | ||
try { | ||
const storedProfile = storage.getItem(StorageKey.User); | ||
if (storedProfile) { | ||
const currentProfile: User = JSON.parse(storedProfile); | ||
const updatedProfile: User = { ...currentProfile, ...profile }; | ||
storage.setItem(StorageKey.User, JSON.stringify(updatedProfile)); | ||
return resolve(updatedProfile); | ||
} else { | ||
return reject(new Error('Profile not found.')); | ||
} | ||
} catch (err) { | ||
return reject(err); | ||
} | ||
}); | ||
}; | ||
|
||
return useMutation({ | ||
mutationFn: updateProfile, | ||
onSuccess: (data) => { | ||
// update cached query data | ||
queryClient.setQueryData<User>([QueryKey.Users, 'current'], data); | ||
// you may [also|instead] choose to invalidate certain cached queries, triggering refetch | ||
// queryClient.invalidateQueries({ queryKey: [QueryKey.Users, 'current'] }); | ||
}, | ||
}); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
.form-profile { | ||
ion-input { | ||
margin-bottom: 0.5rem; | ||
} | ||
} |
Oops, something went wrong.