mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-02-26 20:54:21 +01:00
(test) ProductForm
This commit is contained in:
parent
d547660170
commit
e246480049
@ -16,13 +16,14 @@ interface FormRichTextProps<TFieldValues, TContext extends object> extends FormC
|
|||||||
blockquote?: boolean,
|
blockquote?: boolean,
|
||||||
link?: boolean,
|
link?: boolean,
|
||||||
video?: boolean,
|
video?: boolean,
|
||||||
image?: boolean
|
image?: boolean,
|
||||||
|
ariaLabel?: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component is a rich-text editor to use with react-hook-form.
|
* 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 textEditorRef = React.useRef<FabTextEditorRef>();
|
||||||
const [isDisabled, setIsDisabled] = React.useState<boolean>(false);
|
const [isDisabled, setIsDisabled] = React.useState<boolean>(false);
|
||||||
|
|
||||||
@ -66,7 +67,7 @@ export const FormRichText = <TFieldValues extends FieldValues, TContext extends
|
|||||||
link={link}
|
link={link}
|
||||||
disabled={isDisabled}
|
disabled={isDisabled}
|
||||||
ref={textEditorRef}
|
ref={textEditorRef}
|
||||||
ariaLabel={label as string}/>
|
ariaLabel={ariaLabel || label as string}/>
|
||||||
} />
|
} />
|
||||||
</AbstractFormItem>
|
</AbstractFormItem>
|
||||||
);
|
);
|
||||||
|
@ -269,7 +269,8 @@ export const ProductForm: React.FC<ProductFormProps> = ({ product, title, onSucc
|
|||||||
blockquote
|
blockquote
|
||||||
link
|
link
|
||||||
limit={6000}
|
limit={6000}
|
||||||
id="description" />
|
id="description"
|
||||||
|
ariaLabel={t('app.admin.store.product_form.product_description')} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
@ -265,8 +265,8 @@ export const ProductStockForm = <TContext extends object> ({ currentFormValues,
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{stockMovements?.data?.map(movement => <div className="stock-history" key={movement.id}>
|
{stockMovements?.data?.map(movement => <div className="stock-history" key={movement.id} role="list">
|
||||||
<div className="stock-item">
|
<div className="stock-item" role="listitem">
|
||||||
<p className='title'>{currentFormValues.name}</p>
|
<p className='title'>{currentFormValues.name}</p>
|
||||||
<p>{FormatLib.date(movement.date)}</p>
|
<p>{FormatLib.date(movement.date)}</p>
|
||||||
<div className="group">
|
<div className="group">
|
||||||
|
@ -14,7 +14,7 @@ export interface Machine {
|
|||||||
spec?: string,
|
spec?: string,
|
||||||
disabled: boolean,
|
disabled: boolean,
|
||||||
slug: string,
|
slug: string,
|
||||||
machine_image_attributes: FileType,
|
machine_image_attributes?: FileType,
|
||||||
machine_files_attributes?: Array<FileType>,
|
machine_files_attributes?: Array<FileType>,
|
||||||
trainings?: Array<{
|
trainings?: Array<{
|
||||||
id: number,
|
id: number,
|
||||||
|
@ -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
|
// 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: {
|
moduleNameMapper: {
|
||||||
'\\.(jpg|jpeg|png|gif|tiff|ico|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
|
'\\.(jpg|jpeg|png|gif|tiff|ico|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
|
||||||
'<rootDir>/__mocks__/fileMock.js',
|
'<rootDir>/test/frontend/__mocks__/fileMock.js',
|
||||||
'\\.(css|less)$': '<rootDir>/__mocks__/styleMock.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
|
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
|
||||||
|
22
test/frontend/__fixtures__/machines.ts
Normal file
22
test/frontend/__fixtures__/machines.ts
Normal 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;
|
7
test/frontend/__fixtures__/product_categories.ts
Normal file
7
test/frontend/__fixtures__/product_categories.ts
Normal 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;
|
51
test/frontend/__fixtures__/product_stock_movements.ts
Normal file
51
test/frontend/__fixtures__/product_stock_movements.ts
Normal 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;
|
49
test/frontend/__fixtures__/products.ts
Normal file
49
test/frontend/__fixtures__/products.ts
Normal 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;
|
29
test/frontend/__lib__/ui-router.ts
Normal file
29
test/frontend/__lib__/ui-router.ts
Normal 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;
|
@ -5,6 +5,10 @@ import plans from '../__fixtures__/plans';
|
|||||||
import planCategories from '../__fixtures__/plan_categories';
|
import planCategories from '../__fixtures__/plan_categories';
|
||||||
import { partners, managers, users } from '../__fixtures__/users';
|
import { partners, managers, users } from '../__fixtures__/users';
|
||||||
import { settings } from '../__fixtures__/settings';
|
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(
|
export const server = setupServer(
|
||||||
rest.get('/api/groups', (req, res, ctx) => {
|
rest.get('/api/groups', (req, res, ctx) => {
|
||||||
@ -50,6 +54,25 @@ export const server = setupServer(
|
|||||||
}),
|
}),
|
||||||
rest.patch('/api/settings/bulk_update', (req, res, ctx) => {
|
rest.patch('/api/settings/bulk_update', (req, res, ctx) => {
|
||||||
return res(ctx.json(req.body));
|
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));
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
94
test/frontend/components/store/product-form.test.tsx
Normal file
94
test/frontend/components/store/product-form.test.tsx
Normal 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();
|
||||||
|
});
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user