mirror of
https://github.com/inventree/InvenTree.git
synced 2026-08-10 15:36:17 +00:00
[UI] Keep Add Parts button visible in Parametric View (#12392)
* [UI] Keep Add Parts button visible in Parametric View * [UI] Add PartCreationMenu component for shared part creation actions * [UI] Fix unused imports and import ordering in PartTable and ParametricDataTable * [UI] Fix ambiguous text locator in parametric view test * [UI] Fix import formatting in PartTable --------- Co-authored-by: Hana <hana@MacBook-Air-M1.local> Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
This commit is contained in:
@@ -0,0 +1,109 @@
|
|||||||
|
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||||
|
import { ModelType } from '@lib/enums/ModelType';
|
||||||
|
import { UserRoles } from '@lib/enums/Roles';
|
||||||
|
import { t } from '@lingui/core/macro';
|
||||||
|
import {
|
||||||
|
IconFileUpload,
|
||||||
|
IconPackageImport,
|
||||||
|
IconPlus
|
||||||
|
} from '@tabler/icons-react';
|
||||||
|
import { type RefObject, useMemo } from 'react';
|
||||||
|
import { dataImporterSessionFields } from '../../forms/ImporterForms';
|
||||||
|
import { usePartFields } from '../../forms/PartForms';
|
||||||
|
import { useCreateApiFormModal } from '../../hooks/UseForm';
|
||||||
|
import { usePluginsWithMixin } from '../../hooks/UsePlugins';
|
||||||
|
import { openGlobalImporter } from '../../states/ImporterState';
|
||||||
|
import { useUserState } from '../../states/UserState';
|
||||||
|
import ImportPartWizard from '../wizards/ImportPartWizard';
|
||||||
|
import { ActionDropdown } from './ActionDropdown';
|
||||||
|
|
||||||
|
export function PartCreationMenu({
|
||||||
|
categoryId,
|
||||||
|
initialData,
|
||||||
|
basePartInstance,
|
||||||
|
enableImport = true,
|
||||||
|
refreshRef
|
||||||
|
}: Readonly<{
|
||||||
|
categoryId?: any;
|
||||||
|
initialData?: Record<string, any>;
|
||||||
|
basePartInstance?: any;
|
||||||
|
enableImport?: boolean;
|
||||||
|
refreshRef?: RefObject<() => void>;
|
||||||
|
}>) {
|
||||||
|
const user = useUserState();
|
||||||
|
|
||||||
|
const importSessionFields = useMemo(() => {
|
||||||
|
const fields = dataImporterSessionFields({ modelType: ModelType.part });
|
||||||
|
fields.field_defaults.value = initialData ?? { category: categoryId };
|
||||||
|
return fields;
|
||||||
|
}, [categoryId, initialData]);
|
||||||
|
|
||||||
|
const importParts = useCreateApiFormModal({
|
||||||
|
url: ApiEndpoints.import_session_list,
|
||||||
|
title: t`Import Parts`,
|
||||||
|
fields: importSessionFields,
|
||||||
|
onFormSuccess: (response: any) => {
|
||||||
|
openGlobalImporter(response.pk, { onClose: refreshRef?.current });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const partInitialData = useMemo(
|
||||||
|
() => initialData ?? { category: categoryId },
|
||||||
|
[categoryId, initialData]
|
||||||
|
);
|
||||||
|
|
||||||
|
const newPartFields = usePartFields({
|
||||||
|
create: true,
|
||||||
|
duplicatePartInstance: basePartInstance
|
||||||
|
});
|
||||||
|
|
||||||
|
const newPart = useCreateApiFormModal({
|
||||||
|
url: ApiEndpoints.part_list,
|
||||||
|
title: t`Add Part`,
|
||||||
|
fields: newPartFields,
|
||||||
|
initialData: partInitialData,
|
||||||
|
follow: true,
|
||||||
|
modelType: ModelType.part,
|
||||||
|
keepOpenOption: true
|
||||||
|
});
|
||||||
|
|
||||||
|
const supplierPlugins = usePluginsWithMixin('supplier');
|
||||||
|
const importPartWizard = ImportPartWizard({ categoryId });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{newPart.modal}
|
||||||
|
{importParts.modal}
|
||||||
|
{importPartWizard.wizard}
|
||||||
|
<ActionDropdown
|
||||||
|
key='add-parts-actions'
|
||||||
|
tooltip={t`Add Parts`}
|
||||||
|
position='bottom-start'
|
||||||
|
icon={<IconPlus />}
|
||||||
|
hidden={!user.hasAddRole(UserRoles.part)}
|
||||||
|
actions={[
|
||||||
|
{
|
||||||
|
name: t`Create Part`,
|
||||||
|
icon: <IconPlus />,
|
||||||
|
tooltip: t`Create a new part`,
|
||||||
|
onClick: () => newPart.open()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: t`Import from File`,
|
||||||
|
icon: <IconFileUpload />,
|
||||||
|
tooltip: t`Import parts from a file`,
|
||||||
|
onClick: () => importParts.open(),
|
||||||
|
hidden: !enableImport
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: t`Import from Supplier`,
|
||||||
|
icon: <IconPackageImport />,
|
||||||
|
tooltip: t`Import parts from a supplier plugin`,
|
||||||
|
hidden: !enableImport || supplierPlugins.length === 0,
|
||||||
|
onClick: () => importPartWizard.openWizard()
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -21,7 +21,14 @@ import { Divider, Group, Text } from '@mantine/core';
|
|||||||
import { useHover } from '@mantine/hooks';
|
import { useHover } from '@mantine/hooks';
|
||||||
import { IconCirclePlus } from '@tabler/icons-react';
|
import { IconCirclePlus } from '@tabler/icons-react';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { type ReactNode, useCallback, useMemo, useState } from 'react';
|
import {
|
||||||
|
type ReactNode,
|
||||||
|
type RefObject,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useState
|
||||||
|
} from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { InvenTreeTable } from '../../components/tables/InvenTreeTable';
|
import { InvenTreeTable } from '../../components/tables/InvenTreeTable';
|
||||||
import { TableHoverCard } from '../../components/tables/TableHoverCard';
|
import { TableHoverCard } from '../../components/tables/TableHoverCard';
|
||||||
@@ -143,7 +150,9 @@ export default function ParametricDataTable({
|
|||||||
endpoint,
|
endpoint,
|
||||||
queryParams,
|
queryParams,
|
||||||
customFilters,
|
customFilters,
|
||||||
customColumns
|
customColumns,
|
||||||
|
customActions,
|
||||||
|
refreshRef
|
||||||
}: {
|
}: {
|
||||||
modelType: ModelType;
|
modelType: ModelType;
|
||||||
modelId?: number;
|
modelId?: number;
|
||||||
@@ -153,12 +162,20 @@ export default function ParametricDataTable({
|
|||||||
queryParams?: Record<string, any>;
|
queryParams?: Record<string, any>;
|
||||||
customFilters?: TableFilter[];
|
customFilters?: TableFilter[];
|
||||||
customColumns?: TableColumn[];
|
customColumns?: TableColumn[];
|
||||||
|
customActions?: ReactNode[];
|
||||||
|
refreshRef?: RefObject<() => void>;
|
||||||
}) {
|
}) {
|
||||||
const api = useApi();
|
const api = useApi();
|
||||||
const table = useTable(`parametric-data-${modelType}`);
|
const table = useTable(`parametric-data-${modelType}`);
|
||||||
const user = useUserState();
|
const user = useUserState();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (refreshRef) {
|
||||||
|
refreshRef.current = table.refreshTable;
|
||||||
|
}
|
||||||
|
}, [table.refreshTable]);
|
||||||
|
|
||||||
// Fetch all active parameter templates for the given model type
|
// Fetch all active parameter templates for the given model type
|
||||||
const parameterTemplates = useQuery({
|
const parameterTemplates = useQuery({
|
||||||
queryKey: ['parameter-templates', modelType],
|
queryKey: ['parameter-templates', modelType],
|
||||||
@@ -461,6 +478,7 @@ export default function ParametricDataTable({
|
|||||||
props={{
|
props={{
|
||||||
enableDownload: true,
|
enableDownload: true,
|
||||||
rowActions: rowActions,
|
rowActions: rowActions,
|
||||||
|
tableActions: customActions,
|
||||||
tableFilters: tableFilters,
|
tableFilters: tableFilters,
|
||||||
params: {
|
params: {
|
||||||
...queryParams,
|
...queryParams,
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
|||||||
import { ModelType } from '@lib/enums/ModelType';
|
import { ModelType } from '@lib/enums/ModelType';
|
||||||
import type { TableFilter } from '@lib/types/Filters';
|
import type { TableFilter } from '@lib/types/Filters';
|
||||||
import type { TableColumn } from '@lib/types/Tables';
|
import type { TableColumn } from '@lib/types/Tables';
|
||||||
import { useMemo } from 'react';
|
import { useMemo, useRef } from 'react';
|
||||||
|
import { PartCreationMenu } from '../../components/items/PartCreationMenu';
|
||||||
import {
|
import {
|
||||||
DescriptionColumn,
|
DescriptionColumn,
|
||||||
PartColumn
|
PartColumn
|
||||||
@@ -12,10 +13,14 @@ import ParametricDataTable from '../general/ParametricDataTable';
|
|||||||
import { PartTableFilters } from './PartTableFilters';
|
import { PartTableFilters } from './PartTableFilters';
|
||||||
|
|
||||||
export default function ParametricPartTable({
|
export default function ParametricPartTable({
|
||||||
categoryId
|
categoryId,
|
||||||
|
enableImport = true
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
categoryId?: any;
|
categoryId?: any;
|
||||||
|
enableImport?: boolean;
|
||||||
}>) {
|
}>) {
|
||||||
|
const tableRefreshRef = useRef<() => void>(null!);
|
||||||
|
|
||||||
const customFilters: TableFilter[] = useMemo(() => PartTableFilters(), []);
|
const customFilters: TableFilter[] = useMemo(() => PartTableFilters(), []);
|
||||||
|
|
||||||
const customColumns: TableColumn[] = useMemo(() => {
|
const customColumns: TableColumn[] = useMemo(() => {
|
||||||
@@ -40,6 +45,18 @@ export default function ParametricPartTable({
|
|||||||
];
|
];
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const tableActions = useMemo(
|
||||||
|
() => [
|
||||||
|
<PartCreationMenu
|
||||||
|
key='part-creation-menu'
|
||||||
|
categoryId={categoryId}
|
||||||
|
enableImport={enableImport}
|
||||||
|
refreshRef={tableRefreshRef}
|
||||||
|
/>
|
||||||
|
],
|
||||||
|
[categoryId, enableImport]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ParametricDataTable
|
<ParametricDataTable
|
||||||
modelType={ModelType.part}
|
modelType={ModelType.part}
|
||||||
@@ -48,6 +65,8 @@ export default function ParametricPartTable({
|
|||||||
endpoint={ApiEndpoints.part_list}
|
endpoint={ApiEndpoints.part_list}
|
||||||
customColumns={customColumns}
|
customColumns={customColumns}
|
||||||
customFilters={customFilters}
|
customFilters={customFilters}
|
||||||
|
customActions={tableActions}
|
||||||
|
refreshRef={tableRefreshRef}
|
||||||
queryParams={{
|
queryParams={{
|
||||||
category: categoryId,
|
category: categoryId,
|
||||||
cascade: true,
|
cascade: true,
|
||||||
|
|||||||
@@ -12,14 +12,10 @@ import type { ApiFormFieldSet } from '@lib/types/Forms';
|
|||||||
import type { TableColumn } from '@lib/types/Tables';
|
import type { TableColumn } from '@lib/types/Tables';
|
||||||
import type { InvenTreeTableProps } from '@lib/types/Tables';
|
import type { InvenTreeTableProps } from '@lib/types/Tables';
|
||||||
import { t } from '@lingui/core/macro';
|
import { t } from '@lingui/core/macro';
|
||||||
import {
|
import { IconShoppingCart } from '@tabler/icons-react';
|
||||||
IconFileUpload,
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
IconPackageImport,
|
|
||||||
IconPlus,
|
|
||||||
IconShoppingCart
|
|
||||||
} from '@tabler/icons-react';
|
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
|
||||||
import { ActionDropdown } from '../../components/items/ActionDropdown';
|
import { ActionDropdown } from '../../components/items/ActionDropdown';
|
||||||
|
import { PartCreationMenu } from '../../components/items/PartCreationMenu';
|
||||||
import {
|
import {
|
||||||
BooleanColumn,
|
BooleanColumn,
|
||||||
CategoryColumn,
|
CategoryColumn,
|
||||||
@@ -32,11 +28,9 @@ import {
|
|||||||
} from '../../components/tables/ColumnRenderers';
|
} from '../../components/tables/ColumnRenderers';
|
||||||
import { InvenTreeTable } from '../../components/tables/InvenTreeTable';
|
import { InvenTreeTable } from '../../components/tables/InvenTreeTable';
|
||||||
import { renderPartStockCell } from '../../components/tables/PartStockCell';
|
import { renderPartStockCell } from '../../components/tables/PartStockCell';
|
||||||
import ImportPartWizard from '../../components/wizards/ImportPartWizard';
|
|
||||||
import OrderPartsWizard from '../../components/wizards/OrderPartsWizard';
|
import OrderPartsWizard from '../../components/wizards/OrderPartsWizard';
|
||||||
import { formatPriceRange } from '../../defaults/formatters';
|
import { formatPriceRange } from '../../defaults/formatters';
|
||||||
import { DuplicateField } from '../../forms/CommonFields';
|
import { DuplicateField } from '../../forms/CommonFields';
|
||||||
import { dataImporterSessionFields } from '../../forms/ImporterForms';
|
|
||||||
import { usePartFields } from '../../forms/PartForms';
|
import { usePartFields } from '../../forms/PartForms';
|
||||||
import { InvenTreeIcon } from '../../functions/icons';
|
import { InvenTreeIcon } from '../../functions/icons';
|
||||||
import {
|
import {
|
||||||
@@ -44,8 +38,6 @@ import {
|
|||||||
useCreateApiFormModal,
|
useCreateApiFormModal,
|
||||||
useEditApiFormModal
|
useEditApiFormModal
|
||||||
} from '../../hooks/UseForm';
|
} from '../../hooks/UseForm';
|
||||||
import { usePluginsWithMixin } from '../../hooks/UsePlugins';
|
|
||||||
import { useImporterState } from '../../states/ImporterState';
|
|
||||||
import { useGlobalSettingsState } from '../../states/SettingsStates';
|
import { useGlobalSettingsState } from '../../states/SettingsStates';
|
||||||
import { useUserState } from '../../states/UserState';
|
import { useUserState } from '../../states/UserState';
|
||||||
import { PartTableFilters } from './PartTableFilters';
|
import { PartTableFilters } from './PartTableFilters';
|
||||||
@@ -151,52 +143,16 @@ export function PartListTable({
|
|||||||
});
|
});
|
||||||
const user = useUserState();
|
const user = useUserState();
|
||||||
const globalSettings = useGlobalSettingsState();
|
const globalSettings = useGlobalSettingsState();
|
||||||
const openImporter = useImporterState((state) => state.openImporter);
|
const refreshRef = useRef<() => void>(null!);
|
||||||
|
|
||||||
const importSessionFields = useMemo(() => {
|
useEffect(() => {
|
||||||
const fields = dataImporterSessionFields({
|
refreshRef.current = table.refreshTable;
|
||||||
modelType: ModelType.part
|
}, [table.refreshTable]);
|
||||||
});
|
|
||||||
|
|
||||||
// Override default field values with provided fields
|
|
||||||
fields.field_defaults.value = {
|
|
||||||
...props?.params,
|
|
||||||
...defaultPartData
|
|
||||||
};
|
|
||||||
|
|
||||||
return fields;
|
|
||||||
}, [defaultPartData, props?.params]);
|
|
||||||
|
|
||||||
const importParts = useCreateApiFormModal({
|
|
||||||
url: ApiEndpoints.import_session_list,
|
|
||||||
title: t`Import Parts`,
|
|
||||||
fields: importSessionFields,
|
|
||||||
onFormSuccess: (response: any) => {
|
|
||||||
openImporter(response.pk, {
|
|
||||||
onClose: table.refreshTable
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const initialPartData = useMemo(() => {
|
const initialPartData = useMemo(() => {
|
||||||
return defaultPartData ?? props?.params ?? {};
|
return defaultPartData ?? props?.params ?? {};
|
||||||
}, [defaultPartData, props?.params]);
|
}, [defaultPartData, props?.params]);
|
||||||
|
|
||||||
const newPartFields = usePartFields({
|
|
||||||
create: true,
|
|
||||||
duplicatePartInstance: basePartInstance
|
|
||||||
});
|
|
||||||
|
|
||||||
const newPart = useCreateApiFormModal({
|
|
||||||
url: ApiEndpoints.part_list,
|
|
||||||
title: t`Add Part`,
|
|
||||||
fields: newPartFields,
|
|
||||||
initialData: initialPartData,
|
|
||||||
follow: true,
|
|
||||||
modelType: ModelType.part,
|
|
||||||
keepOpenOption: true
|
|
||||||
});
|
|
||||||
|
|
||||||
const [selectedPart, setSelectedPart] = useState<any>({});
|
const [selectedPart, setSelectedPart] = useState<any>({});
|
||||||
|
|
||||||
const editPart = useEditApiFormModal({
|
const editPart = useEditApiFormModal({
|
||||||
@@ -264,11 +220,6 @@ export function PartListTable({
|
|||||||
|
|
||||||
const orderPartsWizard = OrderPartsWizard({ parts: table.selectedRecords });
|
const orderPartsWizard = OrderPartsWizard({ parts: table.selectedRecords });
|
||||||
|
|
||||||
const supplierPlugins = usePluginsWithMixin('supplier');
|
|
||||||
const importPartWizard = ImportPartWizard({
|
|
||||||
categoryId: initialPartData.category
|
|
||||||
});
|
|
||||||
|
|
||||||
const rowActions = useCallback(
|
const rowActions = useCallback(
|
||||||
(record: any): RowAction[] => {
|
(record: any): RowAction[] => {
|
||||||
const can_edit = user.hasChangePermission(ModelType.part);
|
const can_edit = user.hasChangePermission(ModelType.part);
|
||||||
@@ -323,47 +274,22 @@ export function PartListTable({
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
/>,
|
/>,
|
||||||
<ActionDropdown
|
<PartCreationMenu
|
||||||
key='add-parts-actions'
|
key='part-creation-menu'
|
||||||
tooltip={t`Add Parts`}
|
initialData={initialPartData}
|
||||||
position='bottom-start'
|
basePartInstance={basePartInstance}
|
||||||
icon={<IconPlus />}
|
enableImport={enableImport}
|
||||||
hidden={!user.hasAddRole(UserRoles.part)}
|
refreshRef={refreshRef}
|
||||||
actions={[
|
|
||||||
{
|
|
||||||
name: t`Create Part`,
|
|
||||||
icon: <IconPlus />,
|
|
||||||
tooltip: t`Create a new part`,
|
|
||||||
onClick: () => newPart.open()
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: t`Import from File`,
|
|
||||||
icon: <IconFileUpload />,
|
|
||||||
tooltip: t`Import parts from a file`,
|
|
||||||
onClick: () => importParts.open(),
|
|
||||||
hidden: !enableImport
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: t`Import from Supplier`,
|
|
||||||
icon: <IconPackageImport />,
|
|
||||||
tooltip: t`Import parts from a supplier plugin`,
|
|
||||||
hidden: !enableImport || supplierPlugins.length === 0,
|
|
||||||
onClick: () => importPartWizard.openWizard()
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
];
|
];
|
||||||
}, [user, enableImport, table.hasSelectedRecords, supplierPlugins]);
|
}, [user, enableImport, table.hasSelectedRecords]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{newPart.modal}
|
|
||||||
{duplicatePart.modal}
|
{duplicatePart.modal}
|
||||||
{editPart.modal}
|
{editPart.modal}
|
||||||
{setCategory.modal}
|
{setCategory.modal}
|
||||||
{importParts.modal}
|
|
||||||
{orderPartsWizard.wizard}
|
{orderPartsWizard.wizard}
|
||||||
{importPartWizard.wizard}
|
|
||||||
<InvenTreeTable
|
<InvenTreeTable
|
||||||
url={apiUrl(ApiEndpoints.part_list)}
|
url={apiUrl(ApiEndpoints.part_list)}
|
||||||
tableState={table}
|
tableState={table}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { expect } from '@playwright/test';
|
import { expect } from '@playwright/test';
|
||||||
import { test } from '../baseFixtures';
|
import { test } from '../baseFixtures';
|
||||||
|
import { readeruser } from '../defaults';
|
||||||
import {
|
import {
|
||||||
clearTableFilters,
|
clearTableFilters,
|
||||||
clickOnParamFilter,
|
clickOnParamFilter,
|
||||||
@@ -1217,3 +1218,75 @@ test('Parts - Import supplier part', async ({ browser }) => {
|
|||||||
await deletePart('BOLT-Steel-M5-5');
|
await deletePart('BOLT-Steel-M5-5');
|
||||||
await deletePart('BOLT-M5-5');
|
await deletePart('BOLT-M5-5');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Parts - Add button visible in Parametric View (admin)', async ({
|
||||||
|
browser
|
||||||
|
}) => {
|
||||||
|
const page = await doCachedLogin(browser, { url: 'part/category/4/parts' });
|
||||||
|
|
||||||
|
await showParametricView(page);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'action-menu-add-parts' })
|
||||||
|
).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Parts - Add button opens Create Part form in Parametric View', async ({
|
||||||
|
browser
|
||||||
|
}) => {
|
||||||
|
const page = await doCachedLogin(browser, { url: 'part/category/4/parts' });
|
||||||
|
|
||||||
|
await showParametricView(page);
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'action-menu-add-parts' }).click();
|
||||||
|
await page
|
||||||
|
.getByRole('menuitem', { name: 'action-menu-add-parts-create-part' })
|
||||||
|
.click();
|
||||||
|
|
||||||
|
await expect(page.getByText('Add Part', { exact: true })).toBeVisible();
|
||||||
|
await page.getByRole('button', { name: 'Cancel' }).click();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Parts - Add button hidden in Parametric View (reader)', async ({
|
||||||
|
browser
|
||||||
|
}) => {
|
||||||
|
const page = await doCachedLogin(browser, {
|
||||||
|
url: 'part/category/4/parts',
|
||||||
|
user: readeruser
|
||||||
|
});
|
||||||
|
|
||||||
|
await showParametricView(page);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'action-menu-add-parts' })
|
||||||
|
).toHaveCount(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Parts - Create part via Parametric View submits to API', async ({
|
||||||
|
browser
|
||||||
|
}) => {
|
||||||
|
const testPartName = 'TEST-PARAMETRIC-ADD-PART';
|
||||||
|
|
||||||
|
await deletePart(testPartName);
|
||||||
|
|
||||||
|
const page = await doCachedLogin(browser, { url: 'part/category/4/parts' });
|
||||||
|
|
||||||
|
await showParametricView(page);
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'action-menu-add-parts' }).click();
|
||||||
|
await page
|
||||||
|
.getByRole('menuitem', { name: 'action-menu-add-parts-create-part' })
|
||||||
|
.click();
|
||||||
|
|
||||||
|
await page.getByLabel('text-field-name', { exact: true }).fill(testPartName);
|
||||||
|
await page
|
||||||
|
.getByLabel('text-field-description', { exact: true })
|
||||||
|
.fill('Created from Parametric View integration test');
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Submit' }).click();
|
||||||
|
await page.waitForLoadState('networkidle');
|
||||||
|
|
||||||
|
await page.getByText(testPartName).first().waitFor();
|
||||||
|
|
||||||
|
await deletePart(testPartName);
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user