Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
644 changes: 238 additions & 406 deletions package-lock.json

Large diffs are not rendered by default.

30 changes: 15 additions & 15 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@
"@fortawesome/fontawesome-svg-core": "6.6.0",
"@fortawesome/free-solid-svg-icons": "6.6.0",
"@fortawesome/react-fontawesome": "0.2.2",
"@ionic/react": "8.2.7",
"@ionic/react-router": "8.2.7",
"@tanstack/react-query": "5.55.0",
"@tanstack/react-query-devtools": "5.55.0",
"@ionic/react": "8.3.1",
"@ionic/react-router": "8.3.1",
"@tanstack/react-query": "5.56.2",
"@tanstack/react-query-devtools": "5.56.2",
"@types/react-router": "5.1.20",
"@types/react-router-dom": "5.3.3",
"axios": "1.7.7",
Expand All @@ -52,25 +52,25 @@
"@testing-library/react": "16.0.1",
"@testing-library/user-event": "14.5.2",
"@types/lodash": "4.17.7",
"@types/react": "18.3.5",
"@types/react": "18.3.8",
"@types/react-dom": "18.3.0",
"@types/uuid": "10.0.0",
"@typescript-eslint/eslint-plugin": "8.4.0",
"@typescript-eslint/parser": "8.4.0",
"@typescript-eslint/eslint-plugin": "8.6.0",
"@typescript-eslint/parser": "8.6.0",
"@vitejs/plugin-legacy": "5.4.2",
"@vitejs/plugin-react": "4.3.1",
"@vitest/coverage-v8": "2.0.5",
"@vitest/coverage-v8": "2.1.1",
"cypress": "13.14.2",
"eslint": "8.57.0",
"eslint-plugin-react": "7.35.2",
"eslint-plugin-react": "7.36.1",
"eslint-plugin-react-hooks": "4.6.2",
"eslint-plugin-react-refresh": "0.4.11",
"eslint-plugin-react-refresh": "0.4.12",
"jsdom": "25.0.0",
"msw": "2.4.2",
"sass": "1.78.0",
"terser": "5.31.6",
"msw": "2.4.9",
"sass": "1.79.2",
"terser": "5.33.0",
"typescript": "5.5.4",
"vite": "5.4.3",
"vitest": "2.0.5"
"vite": "5.4.6",
"vitest": "2.1.1"
}
}
1 change: 1 addition & 0 deletions src/__fixtures__/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ import { Settings } from 'common/models/settings';
export const settingsFixture: Settings = {
allowNotifications: true,
brightness: 0,
fontSize: 'default',
language: 'en',
};
2 changes: 1 addition & 1 deletion src/common/components/Input/CheckboxInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ interface CheckboxInputProps
* do not use the `value` prop.
*
* To create a `string[]` field, use one to many `CheckboxInput` within a form
* with the same `name` and a unique `value` property.s
* with the same `name` and a unique `value` property.
*
* @param {CheckboxInputProps} props - Component properties.
* @returns {JSX.Element} JSX
Expand Down
10 changes: 10 additions & 0 deletions src/common/components/Input/RadioGroupInput.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.ls-radiogroup-wrapper {
width: 100%;

.ls-radiogroup-error {
display: block;
margin-bottom: 0.5rem;

font-size: 0.75rem;
}
}
66 changes: 66 additions & 0 deletions src/common/components/Input/RadioGroupInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { ComponentPropsWithoutRef } from 'react';
import { IonRadioGroup, IonText, RadioGroupCustomEvent } from '@ionic/react';
import { useField } from 'formik';
import classNames from 'classnames';

import './RadioGroupInput.scss';
import { PropsWithTestId } from '../types';

/**
* Properties for the `RadioGroupInput` component.
* @see {@link PropsWithTestId}
* @see {@link IonRadioGroup}
*/
interface RadioGroupInputProps
extends PropsWithTestId,
Omit<ComponentPropsWithoutRef<typeof IonRadioGroup>, 'name'>,
Required<Pick<ComponentPropsWithoutRef<typeof IonRadioGroup>, 'name'>> {}

/**
* The `RadioGroupInput` component renders a standardized `IonRadioGroup` which
* is integrated with Formik.
*
* Use one to many `IonRadio` components as the `children` to specify the
* available options.
*
* @param {RadioGroupInputProps} props - Component properties.
* @returns {JSX.Element} JSX
*/
const RadioGroupInput = ({
className,
name,
onIonChange,
testid = 'input-radiogroup',
...radioGroupProps
}: RadioGroupInputProps): JSX.Element => {
const [field, meta, helpers] = useField({ name });

/**
* Handles changes to the field value as a result of user action.
* @param {RadioGroupCustomEvent} event - The event
*/
const onChange = async (event: RadioGroupCustomEvent): Promise<void> => {
await helpers.setValue(event.detail.value);
await helpers.setTouched(true);
onIonChange?.(event);
};

return (
<div className="ls-radiogroup-wrapper" data-testid={`${testid}-wrapper`}>
<IonRadioGroup
className={classNames('ls-radiogroup-input', className)}
data-testid={testid}
onIonChange={onChange}
{...field}
{...radioGroupProps}
/>
{!!meta.error && (
<IonText className="ls-radiogroup-error" color="danger" data-testid={`${testid}-error`}>
{meta.error}
</IonText>
)}
</div>
);
};

export default RadioGroupInput;
113 changes: 113 additions & 0 deletions src/common/components/Input/__tests__/RadioGroupInput.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { describe, expect, it, vi } from 'vitest';
import userEvent from '@testing-library/user-event';
import { IonRadio } from '@ionic/react';
import { Form, Formik } from 'formik';
import { object, string } from 'yup';

import { render, screen, waitFor } from 'test/test-utils';

import RadioGroupInput from '../RadioGroupInput';

describe('RadioGroupInput', () => {
it('should render successfully', async () => {
// ARRANGE
render(
<Formik initialValues={{ testField: '' }} onSubmit={() => {}}>
<Form>
<RadioGroupInput name="testField" testid="input">
<IonRadio value="one">One</IonRadio>
<IonRadio value="two">Two</IonRadio>
</RadioGroupInput>
</Form>
</Formik>,
);
await screen.findByTestId('input');

// ASSERT
expect(screen.getByTestId('input')).toBeDefined();
});

it('should should be selected', async () => {
// ARRANGE
render(
<Formik initialValues={{ testField: 'one' }} onSubmit={() => {}}>
<Form>
<RadioGroupInput name="testField" testid="input">
<IonRadio value="one">One</IonRadio>
<IonRadio value="two">Two</IonRadio>
</RadioGroupInput>
</Form>
</Formik>,
);
await screen.findAllByRole('radio');

// ASSERT
expect(screen.getByTestId('input')).toHaveAttribute('value', 'one');
expect(screen.getByText(/One/i)).toHaveClass('radio-checked');
expect(screen.getByText(/One/i)).toBeChecked();
expect(screen.getByText(/Two/i)).not.toHaveClass('radio-checked');
expect(screen.getByText(/Two/i)).not.toBeChecked();
});

it('should should change value', async () => {
// ARRANGE
const user = userEvent.setup();
const mockOnChange = vi.fn();
render(
<Formik initialValues={{ testField: 'two' }} onSubmit={() => {}}>
<Form>
<RadioGroupInput name="testField" onIonChange={mockOnChange} testid="input">
<IonRadio value="one">One</IonRadio>
<IonRadio value="two">Two</IonRadio>
</RadioGroupInput>
</Form>
</Formik>,
);
await screen.findAllByRole('radio');
expect(screen.getByText(/One/i)).not.toHaveClass('radio-checked');

// ACT
await user.click(screen.getByText(/One/i));
await waitFor(() => expect(screen.getByText(/One/i)).toHaveClass('radio-checked'));

// ASSERT
expect(screen.getByTestId('input')).toHaveAttribute('value', 'one');
expect(screen.getByText(/One/i)).toHaveClass('radio-checked');
expect(screen.getByText(/One/i)).toBeChecked();
expect(screen.getByText(/Two/i)).not.toHaveClass('radio-checked');
expect(screen.getByText(/Two/i)).not.toBeChecked();
expect(mockOnChange).toHaveBeenCalled();
});

it('should should display error', async () => {
// ARRANGE
const user = userEvent.setup();
const validationSchema = object({
testField: string().oneOf(['three'], 'invalid'),
});
render(
<Formik
initialValues={{ testField: '' }}
onSubmit={() => {}}
validationSchema={validationSchema}
>
{({ submitForm }) => (
<Form>
<RadioGroupInput onIonChange={() => submitForm()} name="testField" testid="input">
<IonRadio value="one">One</IonRadio>
<IonRadio value="two">Two</IonRadio>
</RadioGroupInput>
</Form>
)}
</Formik>,
);
await screen.findAllByRole('radio');

// ACT
await user.click(screen.getByText(/One/i));

// ASSERT
expect(screen.getByTestId('input-error')).toBeDefined();
expect(screen.getByText(/invalid/i)).toBeDefined();
});
});
1 change: 1 addition & 0 deletions src/common/models/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
export type Settings = {
allowNotifications: boolean;
brightness: number;
fontSize?: string;
language: string;
};
1 change: 1 addition & 0 deletions src/common/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export enum StorageKey {
export const DEFAULT_SETTINGS: Settings = {
allowNotifications: true,
brightness: 50,
fontSize: 'default',
language: 'en',
};

Expand Down
10 changes: 5 additions & 5 deletions src/pages/Account/AccountPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,14 @@ const AccountPage = ({ testid = 'page-account' }: PropsWithTestId): JSX.Element
};

return (
<IonPage className="page-account" data-testid={testid}>
<IonPage className="ls-page-account" data-testid={testid}>
<ProgressProvider>
<Header title="My Account" />

<IonContent>
<IonGrid fixed>
<IonRow>
<IonCol size="12" sizeLg="6">
<IonCol size="12" sizeLg="6" className="order-1-lg">
<List>
<IonListHeader>
<IonLabel>Account</IonLabel>
Expand All @@ -67,11 +67,11 @@ const AccountPage = ({ testid = 'page-account' }: PropsWithTestId): JSX.Element
</List>
</IonCol>

<IonCol size="12" sizeLg="6">
<IonCol size="12" sizeLg="6" className="order-3-lg">
<SettingsForm />
</IonCol>

<IonCol size="12" sizeLg="6">
<IonCol size="12" sizeLg="6" className="order-2-lg">
<List>
<IonListHeader>
<IonLabel>Legal</IonLabel>
Expand All @@ -86,7 +86,7 @@ const AccountPage = ({ testid = 'page-account' }: PropsWithTestId): JSX.Element
</List>
</IonCol>

<IonCol size="12" sizeLg="6">
<IonCol size="12" sizeLg="6" className="order-4-lg">
<List>
<IonListHeader>
<IonLabel>About</IonLabel>
Expand Down
7 changes: 7 additions & 0 deletions src/pages/Account/components/Settings/SettingsForm.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.ls-form-settings {
.ls-field-fontSize {
ion-radio {
min-height: 3rem;
}
}
}
Loading