1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2025-02-26 20:54:21 +01:00

(test) improved frontend tests

This commit is contained in:
Sylvain 2022-11-29 10:48:23 +01:00
parent 9d2dde8257
commit b30701ba6f
9 changed files with 61 additions and 8 deletions

View File

@ -25,6 +25,7 @@ interface FabTextEditorProps {
placeholder?: string, placeholder?: string,
error?: string, error?: string,
disabled?: boolean disabled?: boolean
editorId?: string,
} }
export interface FabTextEditorRef { export interface FabTextEditorRef {
@ -34,7 +35,7 @@ export interface FabTextEditorRef {
/** /**
* This component is a WYSIWYG text editor * This component is a WYSIWYG text editor
*/ */
const FabTextEditor: React.ForwardRefRenderFunction<FabTextEditorRef, FabTextEditorProps> = ({ heading, bulletList, blockquote, content, limit = 400, video, image, link, onChange, placeholder, error, disabled = false }, ref: RefObject<FabTextEditorRef>) => { const FabTextEditor: React.ForwardRefRenderFunction<FabTextEditorRef, FabTextEditorProps> = ({ heading, bulletList, blockquote, content, limit = 400, video, image, link, onChange, placeholder, error, disabled = false, editorId }, ref: RefObject<FabTextEditorRef>) => {
const { t } = useTranslation('shared'); const { t } = useTranslation('shared');
const placeholderText = placeholder || t('app.shared.text_editor.fab_text_editor.text_placeholder'); const placeholderText = placeholder || t('app.shared.text_editor.fab_text_editor.text_placeholder');
// TODO: Add ctrl+click on link to visit // TODO: Add ctrl+click on link to visit
@ -74,6 +75,11 @@ const FabTextEditor: React.ForwardRefRenderFunction<FabTextEditorRef, FabTextEdi
} }
}) })
], ],
editorProps: {
attributes: {
id: editorId
}
},
content, content,
onUpdate: ({ editor }) => { onUpdate: ({ editor }) => {
if (editor.isEmpty) { if (editor.isEmpty) {

View File

@ -8,6 +8,7 @@ export interface AbstractFormItemProps<TFieldValues> extends PropsWithChildren<A
id: string, id: string,
label?: string|ReactNode, label?: string|ReactNode,
ariaLabel?: string, ariaLabel?: string,
ariaLabelledBy?: string,
tooltip?: ReactNode, tooltip?: ReactNode,
className?: string, className?: string,
disabled?: boolean|((id: string) => boolean), disabled?: boolean|((id: string) => boolean),
@ -20,7 +21,7 @@ export interface AbstractFormItemProps<TFieldValues> extends PropsWithChildren<A
* This abstract component should not be used directly. * This abstract component should not be used directly.
* Other forms components that are intended to be used with react-hook-form must extend this component. * Other forms components that are intended to be used with react-hook-form must extend this component.
*/ */
export const AbstractFormItem = <TFieldValues extends FieldValues>({ id, label, ariaLabel, tooltip, className, disabled, error, warning, rules, formState, onLabelClick, inLine, containerType, children }: AbstractFormItemProps<TFieldValues>) => { export const AbstractFormItem = <TFieldValues extends FieldValues>({ id, label, ariaLabel, ariaLabelledBy, tooltip, className, disabled, error, warning, rules, formState, onLabelClick, inLine, containerType, children }: AbstractFormItemProps<TFieldValues>) => {
const [isDirty, setIsDirty] = useState<boolean>(false); const [isDirty, setIsDirty] = useState<boolean>(false);
const [fieldError, setFieldError] = useState<{ message: string }>(error); const [fieldError, setFieldError] = useState<{ message: string }>(error);
const [isDisabled, setIsDisabled] = useState<boolean>(false); const [isDisabled, setIsDisabled] = useState<boolean>(false);
@ -71,7 +72,7 @@ export const AbstractFormItem = <TFieldValues extends FieldValues>({ id, label,
</div>} </div>}
</div>} </div>}
<div className='form-item-field' aria-label={ariaLabel}> <div className='form-item-field' aria-label={ariaLabel} aria-labelledby={ariaLabelledBy}>
{inLine && <div className='form-item-header'><p>{label}</p> {inLine && <div className='form-item-header'><p>{label}</p>
{tooltip && <div className="fab-tooltip"> {tooltip && <div className="fab-tooltip">
<span className="trigger"><i className="fa fa-question-circle" /></span> <span className="trigger"><i className="fa fa-question-circle" /></span>

View File

@ -47,6 +47,7 @@ export const FormRichText = <TFieldValues extends FieldValues, TContext extends
return ( return (
<AbstractFormItem id={id} label={label} tooltip={tooltip} <AbstractFormItem id={id} label={label} tooltip={tooltip}
ariaLabel={label as string} ariaLabel={label as string}
ariaLabelledBy={id}
containerType={'div'} containerType={'div'}
className={`form-rich-text ${className || ''}`} className={`form-rich-text ${className || ''}`}
error={error} warning={warning} rules={rules} error={error} warning={warning} rules={rules}
@ -66,7 +67,8 @@ export const FormRichText = <TFieldValues extends FieldValues, TContext extends
image={image} image={image}
link={link} link={link}
disabled={isDisabled} disabled={isDisabled}
ref={textEditorRef} /> ref={textEditorRef}
editorId={id} />
} /> } />
</AbstractFormItem> </AbstractFormItem>
); );

View File

@ -32,12 +32,13 @@ interface PlanFormProps {
plan?: Plan, plan?: Plan,
onError: (message: string) => void, onError: (message: string) => void,
onSuccess: (message: string) => void, onSuccess: (message: string) => void,
beforeSubmit?: (data: Plan) => void,
} }
/** /**
* Form to edit or create subscription plans * Form to edit or create subscription plans
*/ */
export const PlanForm: React.FC<PlanFormProps> = ({ action, plan, onError, onSuccess }) => { export const PlanForm: React.FC<PlanFormProps> = ({ action, plan, onError, onSuccess, beforeSubmit }) => {
const { handleSubmit, register, control, formState, setValue } = useForm<Plan>({ defaultValues: { ...plan } }); const { handleSubmit, register, control, formState, setValue } = useForm<Plan>({ defaultValues: { ...plan } });
const output = useWatch<Plan>({ control }); // eslint-disable-line const output = useWatch<Plan>({ control }); // eslint-disable-line
const { t } = useTranslation('admin'); const { t } = useTranslation('admin');
@ -64,6 +65,7 @@ export const PlanForm: React.FC<PlanFormProps> = ({ action, plan, onError, onSuc
* Callback triggered when the user validates the plan form: handle create or update * Callback triggered when the user validates the plan form: handle create or update
*/ */
const onSubmit: SubmitHandler<Plan> = (data: Plan) => { const onSubmit: SubmitHandler<Plan> = (data: Plan) => {
if (typeof beforeSubmit === 'function') beforeSubmit(data);
PlanAPI[action](data).then(() => { PlanAPI[action](data).then(() => {
onSuccess(t(`app.admin.plan_form.${action}_success`)); onSuccess(t(`app.admin.plan_form.${action}_success`));
window.location.href = '/#!/admin/pricing'; window.location.href = '/#!/admin/pricing';

View File

@ -24,6 +24,7 @@
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.4", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.4",
"@testing-library/jest-dom": "^5.16.5", "@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "12", "@testing-library/react": "12",
"@testing-library/user-event": "^14.4.3",
"@typescript-eslint/eslint-plugin": "^5.17.0", "@typescript-eslint/eslint-plugin": "^5.17.0",
"@typescript-eslint/parser": "^5.17.0", "@typescript-eslint/parser": "^5.17.0",
"babel-jest": "^29.3.1", "babel-jest": "^29.3.1",

View File

@ -3,7 +3,7 @@ import { rest } from 'msw';
import groups from '../__fixtures__/groups'; import groups from '../__fixtures__/groups';
import plans from '../__fixtures__/plans'; import plans from '../__fixtures__/plans';
import planCategories from '../__fixtures__/plan_categories'; import planCategories from '../__fixtures__/plan_categories';
import { partners } from '../__fixtures__/users'; import { partners, managers, users } from '../__fixtures__/users';
import { settings } from '../__fixtures__/settings'; import { settings } from '../__fixtures__/settings';
export const server = setupServer( export const server = setupServer(
@ -14,11 +14,21 @@ export const server = setupServer(
return res(ctx.json(planCategories)); return res(ctx.json(planCategories));
}), }),
rest.get('/api/users', (req, res, ctx) => { rest.get('/api/users', (req, res, ctx) => {
switch (new URLSearchParams(req.url.search).get('role')) {
case 'partner':
return res(ctx.json(partners)); return res(ctx.json(partners));
case 'manager':
return res(ctx.json(managers));
default:
return res(ctx.json(users));
}
}), }),
rest.get('/api/plans', (req, res, ctx) => { rest.get('/api/plans', (req, res, ctx) => {
return res(ctx.json(plans)); return res(ctx.json(plans));
}), }),
rest.post('/api/plans', (req, res, ctx) => {
return res(ctx.json(req.body));
}),
rest.post('/api/users', (req, res, ctx) => { rest.post('/api/users', (req, res, ctx) => {
/* eslint-disable camelcase */ /* eslint-disable camelcase */
const { user: { first_name, last_name, email } } = req.body; const { user: { first_name, last_name, email } } = req.body;

View File

@ -15,5 +15,9 @@ describe('AccountingCodesSettings', () => {
expect(screen.getAllByLabelText(/app.admin.accounting_codes_settings.code/)).toHaveLength(13); expect(screen.getAllByLabelText(/app.admin.accounting_codes_settings.code/)).toHaveLength(13);
expect(screen.getAllByLabelText(/app.admin.accounting_codes_settings.label/)).toHaveLength(13); expect(screen.getAllByLabelText(/app.admin.accounting_codes_settings.label/)).toHaveLength(13);
expect(screen.getByRole('button', { name: /app.admin.accounting_codes_settings.save/ })).toBeInTheDocument(); expect(screen.getByRole('button', { name: /app.admin.accounting_codes_settings.save/ })).toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: /app.admin.accounting_codes_settings.save/ }));
await waitFor(() => {
expect(onSuccess).toHaveBeenCalledWith('app.admin.accounting_codes_settings.update_success');
});
}); });
}); });

View File

@ -2,15 +2,19 @@ import React from 'react';
import { render, fireEvent, waitFor, screen } from '@testing-library/react'; import { render, fireEvent, waitFor, screen } from '@testing-library/react';
import '@testing-library/jest-dom'; import '@testing-library/jest-dom';
import { PlanForm } from 'components/plans/plan-form'; import { PlanForm } from 'components/plans/plan-form';
import selectEvent from 'react-select-event';
import userEvent from '@testing-library/user-event';
import plans from '../../__fixtures__/plans'; import plans from '../../__fixtures__/plans';
describe('PlanForm', () => { describe('PlanForm', () => {
const onError = jest.fn(); const onError = jest.fn();
const onSuccess = jest.fn(); const onSuccess = jest.fn();
const beforeSubmit = jest.fn();
test('render create PlanForm', async () => { test('render create PlanForm', async () => {
render(<PlanForm action="create" onError={onError} onSuccess={onSuccess} />); render(<PlanForm action="create" onError={onError} onSuccess={onSuccess} beforeSubmit={beforeSubmit} />);
await waitFor(() => screen.getByRole('combobox', { name: /app.admin.plan_form.group/ })); await waitFor(() => screen.getByRole('combobox', { name: /app.admin.plan_form.group/ }));
// check inputs
expect(screen.getByLabelText(/app.admin.plan_form.name/)).toBeInTheDocument(); expect(screen.getByLabelText(/app.admin.plan_form.name/)).toBeInTheDocument();
expect(screen.getByLabelText(/app.admin.plan_form.transversal/)).toBeInTheDocument(); expect(screen.getByLabelText(/app.admin.plan_form.transversal/)).toBeInTheDocument();
expect(screen.getByLabelText(/app.admin.plan_form.group/)).toBeInTheDocument(); expect(screen.getByLabelText(/app.admin.plan_form.group/)).toBeInTheDocument();
@ -27,6 +31,24 @@ describe('PlanForm', () => {
expect(screen.getByLabelText(/app.admin.plan_form.partner_plan/)).toBeInTheDocument(); expect(screen.getByLabelText(/app.admin.plan_form.partner_plan/)).toBeInTheDocument();
expect(screen.queryByTestId('plan-pricing-form')).toBeNull(); expect(screen.queryByTestId('plan-pricing-form')).toBeNull();
expect(screen.getByRole('button', { name: /app.admin.plan_form.ACTION_plan/ })).toBeInTheDocument(); expect(screen.getByRole('button', { name: /app.admin.plan_form.ACTION_plan/ })).toBeInTheDocument();
// input values
const user = userEvent.setup();
fireEvent.change(screen.getByLabelText(/app.admin.plan_form.name/), { target: { value: 'Test Plan' } });
await selectEvent.select(screen.getByLabelText(/app.admin.plan_form.group/), 'Standard');
await selectEvent.select(screen.getByLabelText(/app.admin.plan_form.category/), 'beginners');
fireEvent.change(screen.getByLabelText(/app.admin.plan_form.subscription_price/), { target: { value: 25.21 } });
fireEvent.change(screen.getByLabelText(/app.admin.plan_form.visual_prominence/), { target: { value: 10 } });
fireEvent.change(screen.getByLabelText(/app.admin.plan_form.rolling_subscription/), { target: { value: true } });
fireEvent.change(screen.getByLabelText(/app.admin.plan_form.monthly_payment/), { target: { value: true } });
await user.click(screen.getByLabelText(/app.admin.plan_form.description/));
await user.keyboard('Lorem ipsum dolor sit amet');
fireEvent.change(screen.getByLabelText(/app.admin.plan_form.number_of_periods/), { target: { value: 6 } });
await selectEvent.select(screen.getByLabelText(/app.admin.plan_form.period/), 'app.admin.plan_form.month');
// send the form
fireEvent.click(screen.getByRole('button', { name: /app.admin.plan_form.ACTION_plan/ }));
await waitFor(() => {
expect(beforeSubmit).toHaveBeenCalled();
});
}); });
test('render update PlanForm with partner', async () => { test('render update PlanForm with partner', async () => {

View File

@ -2914,6 +2914,11 @@
"@testing-library/dom" "^8.0.0" "@testing-library/dom" "^8.0.0"
"@types/react-dom" "<18.0.0" "@types/react-dom" "<18.0.0"
"@testing-library/user-event@^14.4.3":
version "14.4.3"
resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.4.3.tgz#af975e367743fa91989cd666666aec31a8f50591"
integrity sha512-kCUc5MEwaEMakkO5x7aoD+DLi02ehmEM2QCGWvNqAS1dV/fAvORWEjnjsEIvml59M7Y5kCkWN6fCCyPOe8OL6Q==
"@tiptap/core@^2.0.0-beta.204": "@tiptap/core@^2.0.0-beta.204":
version "2.0.0-beta.204" version "2.0.0-beta.204"
resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.0.0-beta.204.tgz#ec37e333718ed21b399e394cea06b7ab4653bbd3" resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.0.0-beta.204.tgz#ec37e333718ed21b399e394cea06b7ab4653bbd3"