1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2024-11-28 09:24:24 +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,
error?: string,
disabled?: boolean
editorId?: string,
}
export interface FabTextEditorRef {
@ -34,7 +35,7 @@ export interface FabTextEditorRef {
/**
* 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 placeholderText = placeholder || t('app.shared.text_editor.fab_text_editor.text_placeholder');
// TODO: Add ctrl+click on link to visit
@ -74,6 +75,11 @@ const FabTextEditor: React.ForwardRefRenderFunction<FabTextEditorRef, FabTextEdi
}
})
],
editorProps: {
attributes: {
id: editorId
}
},
content,
onUpdate: ({ editor }) => {
if (editor.isEmpty) {

View File

@ -8,6 +8,7 @@ export interface AbstractFormItemProps<TFieldValues> extends PropsWithChildren<A
id: string,
label?: string|ReactNode,
ariaLabel?: string,
ariaLabelledBy?: string,
tooltip?: ReactNode,
className?: string,
disabled?: boolean|((id: string) => boolean),
@ -20,7 +21,7 @@ export interface AbstractFormItemProps<TFieldValues> extends PropsWithChildren<A
* 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.
*/
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 [fieldError, setFieldError] = useState<{ message: string }>(error);
const [isDisabled, setIsDisabled] = useState<boolean>(false);
@ -71,7 +72,7 @@ export const AbstractFormItem = <TFieldValues extends FieldValues>({ id, label,
</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>
{tooltip && <div className="fab-tooltip">
<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 (
<AbstractFormItem id={id} label={label} tooltip={tooltip}
ariaLabel={label as string}
ariaLabelledBy={id}
containerType={'div'}
className={`form-rich-text ${className || ''}`}
error={error} warning={warning} rules={rules}
@ -66,7 +67,8 @@ export const FormRichText = <TFieldValues extends FieldValues, TContext extends
image={image}
link={link}
disabled={isDisabled}
ref={textEditorRef} />
ref={textEditorRef}
editorId={id} />
} />
</AbstractFormItem>
);

View File

@ -32,12 +32,13 @@ interface PlanFormProps {
plan?: Plan,
onError: (message: string) => void,
onSuccess: (message: string) => void,
beforeSubmit?: (data: Plan) => void,
}
/**
* 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 output = useWatch<Plan>({ control }); // eslint-disable-line
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
*/
const onSubmit: SubmitHandler<Plan> = (data: Plan) => {
if (typeof beforeSubmit === 'function') beforeSubmit(data);
PlanAPI[action](data).then(() => {
onSuccess(t(`app.admin.plan_form.${action}_success`));
window.location.href = '/#!/admin/pricing';

View File

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

View File

@ -3,7 +3,7 @@ import { rest } from 'msw';
import groups from '../__fixtures__/groups';
import plans from '../__fixtures__/plans';
import planCategories from '../__fixtures__/plan_categories';
import { partners } from '../__fixtures__/users';
import { partners, managers, users } from '../__fixtures__/users';
import { settings } from '../__fixtures__/settings';
export const server = setupServer(
@ -14,11 +14,21 @@ export const server = setupServer(
return res(ctx.json(planCategories));
}),
rest.get('/api/users', (req, res, ctx) => {
return res(ctx.json(partners));
switch (new URLSearchParams(req.url.search).get('role')) {
case 'partner':
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) => {
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) => {
/* eslint-disable camelcase */
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.label/)).toHaveLength(13);
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 '@testing-library/jest-dom';
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';
describe('PlanForm', () => {
const onError = jest.fn();
const onSuccess = jest.fn();
const beforeSubmit = jest.fn();
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/ }));
// check inputs
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.group/)).toBeInTheDocument();
@ -27,6 +31,24 @@ describe('PlanForm', () => {
expect(screen.getByLabelText(/app.admin.plan_form.partner_plan/)).toBeInTheDocument();
expect(screen.queryByTestId('plan-pricing-form')).toBeNull();
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 () => {

View File

@ -2914,6 +2914,11 @@
"@testing-library/dom" "^8.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":
version "2.0.0-beta.204"
resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.0.0-beta.204.tgz#ec37e333718ed21b399e394cea06b7ab4653bbd3"