-
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.
* initial Toggle * initial ToggleInput; SettingsForm * docs * ToggleInput * tests * tests * tests * remove delay util * tests
- Loading branch information
Showing
15 changed files
with
629 additions
and
43 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,5 @@ | ||
import { Settings } from 'common/models/settings'; | ||
|
||
export const settingsFixture: Settings = { | ||
allowNotifications: 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,49 @@ | ||
import { afterEach, describe, expect, it, vi } from 'vitest'; | ||
|
||
import { renderHook, waitFor } from 'test/test-utils'; | ||
import { settingsFixture } from '__fixtures__/settings'; | ||
import storage from 'common/utils/storage'; | ||
import { DEFAULT_SETTINGS } from 'common/utils/constants'; | ||
|
||
import { useGetSettings } from '../useGetSettings'; | ||
|
||
describe('useGetUser', () => { | ||
afterEach(() => { | ||
vi.restoreAllMocks(); | ||
}); | ||
|
||
it('should get settings', async () => { | ||
// ARRANGE | ||
const { result } = renderHook(() => useGetSettings()); | ||
await waitFor(() => expect(result.current.isSuccess).toBe(true)); | ||
|
||
// ASSERT | ||
expect(result.current.isSuccess).toBe(true); | ||
expect(result.current.data).toEqual(DEFAULT_SETTINGS); | ||
}); | ||
|
||
it('should get settings from storage', async () => { | ||
// ARRANGE | ||
const getItemSpy = vi.spyOn(storage, 'getItem'); | ||
getItemSpy.mockReturnValue(JSON.stringify(settingsFixture)); | ||
const { result } = renderHook(() => useGetSettings()); | ||
await waitFor(() => expect(result.current.isSuccess).toBe(true)); | ||
|
||
// ASSERT | ||
expect(result.current.isSuccess).toBe(true); | ||
expect(result.current.data).toEqual({ ...DEFAULT_SETTINGS, ...settingsFixture }); | ||
}); | ||
|
||
it('should return error', async () => { | ||
// ARRANGE | ||
const getItemSpy = vi.spyOn(storage, 'getItem'); | ||
getItemSpy.mockImplementation(() => { | ||
throw new Error('test'); | ||
}); | ||
const { result } = renderHook(() => useGetSettings()); | ||
await waitFor(() => expect(result.current.isError).toBe(true)); | ||
|
||
// ASSERT | ||
expect(result.current.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,64 @@ | ||
import { afterEach, describe, expect, it, vi } from 'vitest'; | ||
|
||
import { renderHook, waitFor } from 'test/test-utils'; | ||
import { settingsFixture } from '__fixtures__/settings'; | ||
import storage from 'common/utils/storage'; | ||
|
||
import { useUpdateSettings } from '../useUpdateSettings'; | ||
|
||
describe('useUpdateSettings', () => { | ||
afterEach(() => { | ||
vi.restoreAllMocks(); | ||
}); | ||
|
||
it('should update settings', async () => { | ||
// ARRANGE | ||
let isSuccess = false; | ||
const { result } = renderHook(() => useUpdateSettings()); | ||
await waitFor(() => expect(result.current).not.toBeNull()); | ||
|
||
// ACT | ||
result.current.mutate( | ||
{ settings: settingsFixture }, | ||
{ | ||
onSuccess: () => { | ||
isSuccess = true; | ||
}, | ||
}, | ||
); | ||
await waitFor(() => expect(result.current.isSuccess).toBe(true)); | ||
|
||
// ASSERT | ||
expect(isSuccess).toBe(true); | ||
}); | ||
|
||
it('should error when update fails', async () => { | ||
// ARRANGE | ||
const setItemSpy = vi.spyOn(storage, 'setItem'); | ||
setItemSpy.mockImplementation(() => { | ||
throw new Error('test'); | ||
}); | ||
let isError = false; | ||
let isSuccess = false; | ||
const { result } = renderHook(() => useUpdateSettings()); | ||
await waitFor(() => expect(result.current).not.toBeNull()); | ||
|
||
// ACT | ||
result.current.mutate( | ||
{ settings: settingsFixture }, | ||
{ | ||
onSuccess: () => { | ||
isSuccess = true; | ||
}, | ||
onError: () => { | ||
isError = true; | ||
}, | ||
}, | ||
); | ||
await waitFor(() => expect(result.current.isError).toBe(true)); | ||
|
||
// ASSERT | ||
expect(isError).toBe(true); | ||
expect(isSuccess).toBe(false); | ||
}); | ||
}); |
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,36 @@ | ||
import { useQuery } from '@tanstack/react-query'; | ||
|
||
import { Settings } from 'common/models/settings'; | ||
import { DEFAULT_SETTINGS, QueryKey, StorageKey } from 'common/utils/constants'; | ||
import storage from 'common/utils/storage'; | ||
|
||
/** | ||
* A query hook to fetch the user `Settings` values. | ||
* @returns Returns a `UserQueryResult` with `Settings` data. | ||
*/ | ||
export const useGetSettings = () => { | ||
/** | ||
* Fetches the [user] `Settings`. | ||
* @returns {Promise<Settings>} A Promise which resolves to the `Settings` object. | ||
*/ | ||
const getSettings = async (): Promise<Settings> => { | ||
return new Promise((resolve, reject) => { | ||
try { | ||
const storedSettings = storage.getItem(StorageKey.Settings); | ||
if (storedSettings) { | ||
const settings = JSON.parse(storedSettings) as Settings; | ||
return resolve({ ...DEFAULT_SETTINGS, ...settings }); | ||
} else { | ||
return resolve(DEFAULT_SETTINGS); | ||
} | ||
} catch (error) { | ||
return reject(error); | ||
} | ||
}); | ||
}; | ||
|
||
return useQuery({ | ||
queryKey: [QueryKey.Settings], | ||
queryFn: () => getSettings(), | ||
}); | ||
}; |
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,52 @@ | ||
import { useMutation, useQueryClient } from '@tanstack/react-query'; | ||
|
||
import { Settings } from 'common/models/settings'; | ||
import { DEFAULT_SETTINGS, QueryKey, StorageKey } from 'common/utils/constants'; | ||
import storage from 'common/utils/storage'; | ||
|
||
/** | ||
* The `useUpdateSettings` mutation function variables. | ||
* @param {Partial<Settings>} settings - The updated `Settings` attributes. | ||
*/ | ||
export type UpdateSettingsVariables = { | ||
settings: Partial<Settings>; | ||
}; | ||
|
||
/** | ||
* A mutation hook which updates the user `Settings`. Returns a `UseMutationResult` | ||
* object whose `mutate` attribute is a function to update the `Settings`. | ||
* | ||
* When successful, the hook updates the cached `Settings` query data. | ||
* @returns Returns a `UseMutationResult`. | ||
*/ | ||
export const useUpdateSettings = () => { | ||
const queryClient = useQueryClient(); | ||
|
||
/** | ||
* Updates the [user] `Settings`. | ||
* @param {UpdateSettingsVariables} variables - The mutation function variables. | ||
* @returns {Promise<Settings>} A Promise which resolves to the updated `Settings`. | ||
*/ | ||
const updateSettings = async ({ settings }: UpdateSettingsVariables): Promise<Settings> => { | ||
return new Promise((resolve, reject) => { | ||
try { | ||
const storedSettings: Settings = JSON.parse(storage.getItem(StorageKey.Settings) ?? '{}'); | ||
const updatedSettings: Settings = { ...DEFAULT_SETTINGS, ...storedSettings, ...settings }; | ||
storage.setItem(StorageKey.Settings, JSON.stringify(updatedSettings)); | ||
return resolve(updatedSettings); | ||
} catch (error) { | ||
return reject(error); | ||
} | ||
}); | ||
}; | ||
|
||
return useMutation({ | ||
mutationFn: updateSettings, | ||
onSuccess: (data) => { | ||
// update cached query data | ||
queryClient.setQueryData<Settings>([QueryKey.Settings], data); | ||
// you may [also|instead] choose to invalidate certain cached queries, triggering refetch | ||
// queryClient.invalidateQueries({ queryKey: [QueryKey.Settings] }); | ||
}, | ||
}); | ||
}; |
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,51 @@ | ||
import { IonToggle, ToggleChangeEventDetail, ToggleCustomEvent } from '@ionic/react'; | ||
import { ComponentPropsWithoutRef } from 'react'; | ||
import { useField } from 'formik'; | ||
import classNames from 'classnames'; | ||
|
||
import { PropsWithTestId } from '../types'; | ||
|
||
/** | ||
* Properties for the `ToggleInput` component. | ||
* @param {string} name - The field `name` attribute value. | ||
* @see {@link PropsWithTestId} | ||
* @see {@link IonToggle} | ||
*/ | ||
interface ToggleInputProps extends PropsWithTestId, ComponentPropsWithoutRef<typeof IonToggle> { | ||
name: string; | ||
} | ||
|
||
/** | ||
* The `ToggleInput` component renders a standardized `IonToggle` which is | ||
* integrated with Formik. | ||
* | ||
* @param {ToggleInputProps} props - Component properties. | ||
* @returns {JSX.Element} JSX | ||
*/ | ||
const ToggleInput = ({ | ||
className, | ||
name, | ||
onIonChange, | ||
testid = 'input-toggle', | ||
...toggleProps | ||
}: ToggleInputProps): JSX.Element => { | ||
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */ | ||
const [field, meta, helpers] = useField<boolean>(name); | ||
|
||
const onChange = async (e: ToggleCustomEvent<ToggleChangeEventDetail>) => { | ||
await helpers.setValue(e.detail.checked); | ||
onIonChange?.(e); | ||
}; | ||
|
||
return ( | ||
<IonToggle | ||
className={classNames('input-toggle', className)} | ||
checked={field.value} | ||
onIonChange={onChange} | ||
data-testid={testid} | ||
{...toggleProps} | ||
/> | ||
); | ||
}; | ||
|
||
export default ToggleInput; |
51 changes: 51 additions & 0 deletions
51
src/common/components/Input/__tests__/ToggleInput.test.tsx
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,51 @@ | ||
import { describe, expect, it } from 'vitest'; | ||
import userEvent from '@testing-library/user-event'; | ||
import { Form, Formik } from 'formik'; | ||
|
||
import { render, screen } from 'test/test-utils'; | ||
|
||
import ToggleInput from '../ToggleInput'; | ||
|
||
describe('ToggleInput', () => { | ||
it('should render successfully', async () => { | ||
// ARRANGE | ||
render( | ||
<Formik initialValues={{ testField: '' }} onSubmit={() => {}}> | ||
<Form> | ||
<ToggleInput name="test-field" /> | ||
</Form> | ||
</Formik>, | ||
); | ||
await screen.findByTestId('input-toggle'); | ||
|
||
// ASSERT | ||
expect(screen.getByTestId('input-toggle')).toBeDefined(); | ||
}); | ||
|
||
it('should change value', async () => { | ||
// ARRANGE | ||
let value = false; | ||
render( | ||
<Formik<{ testField: boolean }> | ||
initialValues={{ testField: value }} | ||
onSubmit={(values) => { | ||
value = values.testField; | ||
}} | ||
> | ||
{(formikProps) => ( | ||
<Form> | ||
<ToggleInput name="testField" onIonChange={() => formikProps.submitForm()} /> | ||
</Form> | ||
)} | ||
</Formik>, | ||
); | ||
await screen.findByTestId('input-toggle'); | ||
|
||
// ACT | ||
await userEvent.click(screen.getByTestId('input-toggle')); | ||
|
||
// ASSERT | ||
expect(screen.getByTestId('input-toggle')).toBeDefined(); | ||
expect(value).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,18 @@ | ||
import { describe, expect, it } from 'vitest'; | ||
|
||
import { renderHook, waitFor } from 'test/test-utils'; | ||
|
||
import { usePlatform } from '../usePlatform'; | ||
|
||
describe('usePlatform', () => { | ||
it('should return platform details', async () => { | ||
// ARRANGE | ||
const { result } = renderHook(() => usePlatform()); | ||
await waitFor(() => expect(result.current).not.toBeNull()); | ||
|
||
// ASSERT | ||
expect(result.current).toBeDefined(); | ||
expect(result.current.isNativePlatform).toBe(false); | ||
expect(result.current.platforms.length).toBeGreaterThan(0); | ||
}); | ||
}); |
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,35 @@ | ||
import { Capacitor } from '@capacitor/core'; | ||
import { getPlatforms } from '@ionic/react'; | ||
|
||
/** | ||
* The `Platform` type has attributes which describe the platform on which the | ||
* application is running. | ||
* @param {boolean} isNativePlatform - Returns `true` if the application is | ||
* running as a native mobile application; otherwise returns `false`. | ||
* @param {string[]} platforms - An array of platforms describing the runtime | ||
* environment. | ||
* @see {@link https://ionicframework.com/docs/react/platform#platforms} | ||
*/ | ||
type Platform = { | ||
isNativePlatform: boolean; | ||
platforms: string[]; | ||
}; | ||
|
||
/** | ||
* The `usePlatform` hook provides details about the `Platform` on which the | ||
* application is running. | ||
* | ||
* @see {@link https://ionicframework.com/docs/react/platform} | ||
* @returns {Platform} A `Platform` object. | ||
*/ | ||
export const usePlatform = (): Platform => { | ||
const isNativePlatform = Capacitor.isNativePlatform(); | ||
console.log(`usePlatform::isNativePlatform::${isNativePlatform}`); | ||
const platforms = getPlatforms(); | ||
console.log(`usePlatform::platforms::${platforms}`); | ||
|
||
return { | ||
isNativePlatform, | ||
platforms, | ||
}; | ||
}; |
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,6 @@ | ||
/** | ||
* The [user] `Settings` type. | ||
*/ | ||
export type Settings = { | ||
allowNotifications: boolean; | ||
}; |
Oops, something went wrong.