1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2024-11-28 09:24:24 +01:00

(test) ProductForm

This commit is contained in:
Sylvain 2022-12-05 17:09:42 +01:00
parent d547660170
commit e246480049
12 changed files with 286 additions and 9 deletions

View File

@ -16,13 +16,14 @@ interface FormRichTextProps<TFieldValues, TContext extends object> extends FormC
blockquote?: boolean,
link?: boolean,
video?: boolean,
image?: boolean
image?: boolean,
ariaLabel?: string,
}
/**
* This component is a rich-text editor to use with react-hook-form.
*/
export const FormRichText = <TFieldValues extends FieldValues, TContext extends object>({ id, label, tooltip, className, control, valueDefault, error, warning, rules, disabled = false, formState, limit, heading, bulletList, blockquote, video, image, link }: FormRichTextProps<TFieldValues, TContext>) => {
export const FormRichText = <TFieldValues extends FieldValues, TContext extends object>({ id, label, tooltip, className, control, valueDefault, error, warning, rules, disabled = false, formState, limit, heading, bulletList, blockquote, video, image, link, ariaLabel }: FormRichTextProps<TFieldValues, TContext>) => {
const textEditorRef = React.useRef<FabTextEditorRef>();
const [isDisabled, setIsDisabled] = React.useState<boolean>(false);
@ -66,7 +67,7 @@ export const FormRichText = <TFieldValues extends FieldValues, TContext extends
link={link}
disabled={isDisabled}
ref={textEditorRef}
ariaLabel={label as string}/>
ariaLabel={ariaLabel || label as string}/>
} />
</AbstractFormItem>
);

View File

@ -269,7 +269,8 @@ export const ProductForm: React.FC<ProductFormProps> = ({ product, title, onSucc
blockquote
link
limit={6000}
id="description" />
id="description"
ariaLabel={t('app.admin.store.product_form.product_description')} />
</div>
<hr />

View File

@ -265,8 +265,8 @@ export const ProductStockForm = <TContext extends object> ({ currentFormValues,
/>
</div>
</div>
{stockMovements?.data?.map(movement => <div className="stock-history" key={movement.id}>
<div className="stock-item">
{stockMovements?.data?.map(movement => <div className="stock-history" key={movement.id} role="list">
<div className="stock-item" role="listitem">
<p className='title'>{currentFormValues.name}</p>
<p>{FormatLib.date(movement.date)}</p>
<div className="group">

View File

@ -14,7 +14,7 @@ export interface Machine {
spec?: string,
disabled: boolean,
slug: string,
machine_image_attributes: FileType,
machine_image_attributes?: FileType,
machine_files_attributes?: Array<FileType>,
trainings?: Array<{
id: number,

View File

@ -87,8 +87,8 @@ module.exports = {
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
moduleNameMapper: {
'\\.(jpg|jpeg|png|gif|tiff|ico|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
'<rootDir>/__mocks__/fileMock.js',
'\\.(css|less)$': '<rootDir>/__mocks__/styleMock.js'
'<rootDir>/test/frontend/__mocks__/fileMock.js',
'\\.(css|less)$': '<rootDir>/test/frontend/__mocks__/styleMock.js'
},
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader

View File

@ -0,0 +1,22 @@
import { Machine } from '../../../app/frontend/src/javascript/models/machine';
const machines: Array<Machine> = [
{
id: 1,
name: 'Laser cutter',
description: 'EPILOG Legend 36EXT',
spec: 'Power: 40W, Working area: 914 x 609mm, Maximum material thickness: 305mm',
slug: 'laser-cutter',
disabled: false
},
{
id: 2,
name: '3D printer',
description: 'Ultimaker 2',
spec: 'Maximum working area: 210 x 210 x 220mm, Mechanical resolution: 0.02mm',
slug: '3d-printer',
disabled: false
}
];
export default machines;

View File

@ -0,0 +1,7 @@
import { ProductCategory } from '../../../app/frontend/src/javascript/models/product-category';
const categories: Array<ProductCategory> = [
{ id: 1, name: 'Consumables', slug: 'consumables', products_count: 1, parent_id: null, position: 1 }
];
export default categories;

View File

@ -0,0 +1,51 @@
import { ProductStockMovement } from '../../../app/frontend/src/javascript/models/product';
const movements: Array<ProductStockMovement> = [
{
id: 1,
product_id: 1,
quantity: 10,
reason: 'inward_stock',
stock_type: 'internal',
remaining_stock: 10,
date: '2022-12-05T15:24:00Z'
},
{
id: 2,
product_id: 1,
quantity: 85,
reason: 'inward_stock',
stock_type: 'external',
remaining_stock: 85,
date: '2022-12-05T15:24:00Z'
},
{
id: 3,
product_id: 2,
quantity: 2,
reason: 'inward_stock',
stock_type: 'internal',
remaining_stock: 2,
date: '2022-12-05T15:24:00Z'
},
{
id: 4,
product_id: 2,
quantity: 12,
reason: 'inward_stock',
stock_type: 'external',
remaining_stock: 15,
date: '2022-12-05T15:24:00Z'
},
{
id: 5,
product_id: 2,
quantity: 3,
reason: 'sold',
stock_type: 'external',
remaining_stock: 12,
date: '2022-12-05T15:24:00Z'
}
];
export default movements;

View File

@ -0,0 +1,49 @@
import { Product } from '../../../app/frontend/src/javascript/models/product';
import movements from './product_stock_movements';
const products: Array<Product> = [
{
id: 1,
name: 'MDF panel',
slug: 'mdf-panel',
sku: '3-8612',
description: 'Medium Density Fiberboard (MDF) is a composite panel product typically consisting of cellulosic fibers combined with a synthetic resin',
is_active: true,
amount: 47.12,
quantity_min: 1,
stock: {
internal: 10,
external: 85
},
low_stock_alert: true,
low_stock_threshold: 20,
machine_ids: [],
created_at: '2022-12-05T16:15:00Z',
product_category_id: 1,
product_files_attributes: [],
product_images_attributes: [],
product_stock_movements_attributes: movements.filter(m => m.product_id === 1)
},
{
id: 2,
name: 'Particleboard',
slug: 'particleboard',
sku: '3-7421',
description: 'Particleboard is a composite panel product consisting of cellulosic particles of various sizes that are bonded together with a synthetic resin or binder under heat and pressure',
is_active: false,
stock: {
internal: 2,
external: 12
},
low_stock_alert: true,
low_stock_threshold: 5,
machine_ids: [],
created_at: '2022-12-05T17:04:00Z',
product_category_id: 1,
product_files_attributes: [],
product_images_attributes: [],
product_stock_movements_attributes: movements.filter(m => m.product_id === 2)
}
];
export default products;

View File

@ -0,0 +1,29 @@
import { UIRouter } from '@uirouter/angularjs';
export const uiRouter = {
$id: 0,
_disposed: false,
_disposables: [],
_plugins: [],
locationService: {},
locationConfig: {},
trace: {},
globals: {
current: { name: '' }
},
viewService: {},
transitionService: {
onBefore: () => jest.fn()
},
urlMatcherFactory: {},
urlRouter: {},
urlRouterProvider: {},
urlService: {},
stateRegistry: {},
stateService: {},
stateProvider: {},
disposable: jest.fn(),
dispose: jest.fn(),
plugin: jest.fn(),
getPlugin: jest.fn()
} as unknown as UIRouter;

View File

@ -5,6 +5,10 @@ import plans from '../__fixtures__/plans';
import planCategories from '../__fixtures__/plan_categories';
import { partners, managers, users } from '../__fixtures__/users';
import { settings } from '../__fixtures__/settings';
import products from '../__fixtures__/products';
import productCategories from '../__fixtures__/product_categories';
import productStockMovements from '../__fixtures__/product_stock_movements';
import machines from '../__fixtures__/machines';
export const server = setupServer(
rest.get('/api/groups', (req, res, ctx) => {
@ -50,6 +54,25 @@ export const server = setupServer(
}),
rest.patch('/api/settings/bulk_update', (req, res, ctx) => {
return res(ctx.json(req.body));
}),
rest.get('/api/product_categories', (req, res, ctx) => {
return res(ctx.json(productCategories));
}),
rest.get('/api/products', (req, res, ctx) => {
return res(ctx.json(products));
}),
rest.get('/api/products/:id/stock_movements', (req, res, ctx) => {
const { id } = req.params;
return res(ctx.json({
page: 1,
total_pages: Math.ceil(productStockMovements.length / 10),
page_size: 10,
total_count: productStockMovements.length,
data: productStockMovements.filter(m => String(m.product_id) === id)
}));
}),
rest.get('/api/machines', (req, res, ctx) => {
return res(ctx.json(machines));
})
);

View File

@ -0,0 +1,94 @@
import { ProductForm } from '../../../../app/frontend/src/javascript/components/store/product-form';
import { render, fireEvent, waitFor, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import products from '../../__fixtures__/products';
import machines from '../../__fixtures__/machines';
import { uiRouter } from '../../__lib__/ui-router';
describe('ProductForm', () => {
const onError = jest.fn();
const onSuccess = jest.fn();
test('render ProductForm', async () => {
render(<ProductForm product={products[0]}
title={'Update a product'}
onSuccess={onSuccess}
onError={onError}
uiRouter={uiRouter} />);
await waitFor(() => screen.getByRole('combobox', { name: /app.admin.store.product_form.linking_product_to_category/ }));
expect(screen.getByLabelText(/app.admin.store.product_form.name/)).toBeInTheDocument();
expect(screen.getByLabelText(/app.admin.store.product_form.sku/)).toBeInTheDocument();
expect(screen.getByLabelText(/app.admin.store.product_form.slug/)).toBeInTheDocument();
expect(screen.getByLabelText(/app.admin.store.product_form.is_show_in_store/)).toBeInTheDocument();
expect(screen.getByLabelText(/app.admin.store.product_form.is_active_price/)).toBeInTheDocument();
expect(screen.getByLabelText(/app.admin.store.product_form.price/)).toBeInTheDocument();
expect(screen.getByLabelText(/app.admin.store.product_form.quantity_min/)).toBeInTheDocument();
expect(screen.getByRole('button', { name: /app.admin.store.product_form.add_product_image/ })).toBeInTheDocument();
expect(screen.getByLabelText(/app.admin.store.product_form.linking_product_to_category/)).toBeInTheDocument();
expect(screen.getByRole('heading', { name: /app.admin.store.product_form.assigning_machines/ })).toBeInTheDocument();
for (const machine of machines) {
expect(screen.getByLabelText(machine.name)).toBeInTheDocument();
}
expect(screen.getByLabelText(/app.admin.store.product_form.product_description/)).toBeInTheDocument();
expect(screen.getByRole('button', { name: /app.admin.store.product_form.add_product_file/ })).toBeInTheDocument();
expect(screen.getByLabelText(/app.admin.advanced_accounting_form.code/)).toBeInTheDocument();
expect(screen.getByLabelText(/app.admin.advanced_accounting_form.analytical_section/)).toBeInTheDocument();
fireEvent.click(screen.getByRole('tab', { name: /app.admin.store.product_form.stock_management/ }));
await waitFor(() =>
expect(screen.getByRole('heading', { name: /app.admin.store.product_stock_form.stock_up_to_date/ })).toBeInTheDocument()
);
expect(screen.getByLabelText(/app.admin.store.product_stock_form.stock_threshold_toggle/)).toBeInTheDocument();
});
test('toggle off the price', async () => {
render(<ProductForm product={products[0]}
title={'Update a product'}
onSuccess={onSuccess}
onError={onError}
uiRouter={uiRouter} />);
await waitFor(() => screen.getByRole('combobox', { name: /app.admin.store.product_form.linking_product_to_category/ }));
expect(screen.getByLabelText(/app.admin.store.product_form.price/)).toBeInTheDocument();
expect(screen.getByLabelText(/app.admin.store.product_form.quantity_min/)).toBeInTheDocument();
expect(screen.getByLabelText(/app.admin.store.product_form.is_show_in_store/)).toBeChecked();
fireEvent.click(screen.getByLabelText(/app.admin.store.product_form.is_active_price/));
expect(screen.queryByLabelText(/app.admin.store.product_form.price/)).toBeNull();
expect(screen.queryByLabelText(/app.admin.store.product_form.quantity_min/)).toBeNull();
expect(screen.getByLabelText(/app.admin.store.product_form.is_show_in_store/)).not.toBeChecked();
});
test('toggle off the visibility', async () => {
render(<ProductForm product={products[0]}
title={'Update a product'}
onSuccess={onSuccess}
onError={onError}
uiRouter={uiRouter} />);
await waitFor(() => screen.getByRole('combobox', { name: /app.admin.store.product_form.linking_product_to_category/ }));
expect(screen.getByLabelText(/app.admin.store.product_form.is_show_in_store/)).toBeChecked();
expect(screen.getByLabelText(/app.admin.store.product_form.is_active_price/)).toBeChecked();
expect(screen.getByLabelText(/app.admin.store.product_form.price/)).toBeInTheDocument();
expect(screen.getByLabelText(/app.admin.store.product_form.quantity_min/)).toBeInTheDocument();
fireEvent.click(screen.getByLabelText(/app.admin.store.product_form.is_show_in_store/));
expect(screen.getByLabelText(/app.admin.store.product_form.is_show_in_store/)).not.toBeChecked();
expect(screen.getByLabelText(/app.admin.store.product_form.is_active_price/)).toBeChecked();
expect(screen.getByLabelText(/app.admin.store.product_form.price/)).toBeInTheDocument();
expect(screen.getByLabelText(/app.admin.store.product_form.quantity_min/)).toBeInTheDocument();
});
test('toggle on the visibility', async () => {
render(<ProductForm product={products[1]}
title={'Update a product'}
onSuccess={onSuccess}
onError={onError}
uiRouter={uiRouter} />);
await waitFor(() => screen.getByRole('combobox', { name: /app.admin.store.product_form.linking_product_to_category/ }));
expect(screen.getByLabelText(/app.admin.store.product_form.is_show_in_store/)).not.toBeChecked();
expect(screen.getByLabelText(/app.admin.store.product_form.is_active_price/)).not.toBeChecked();
expect(screen.queryByLabelText(/app.admin.store.product_form.price/)).toBeNull();
expect(screen.queryByLabelText(/app.admin.store.product_form.quantity_min/)).toBeNull();
fireEvent.click(screen.getByLabelText(/app.admin.store.product_form.is_show_in_store/));
expect(screen.getByLabelText(/app.admin.store.product_form.is_show_in_store/)).toBeChecked();
expect(screen.getByLabelText(/app.admin.store.product_form.is_active_price/)).toBeChecked();
expect(screen.getByLabelText(/app.admin.store.product_form.price/)).toBeInTheDocument();
expect(screen.getByLabelText(/app.admin.store.product_form.quantity_min/)).toBeInTheDocument();
});
});