Updated playwright tests
This commit is contained in:
parent
4df12736dc
commit
2f16471128
|
|
@ -5,6 +5,7 @@ import { useCallback, useEffect, useMemo } from 'react';
|
|||
import { type Control, type FieldValues, useController } from 'react-hook-form';
|
||||
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { ModelType } from '@lib/enums/ModelType';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import type { ApiFormFieldSet, ApiFormFieldType } from '@lib/types/Forms';
|
||||
import { IconFileUpload } from '@tabler/icons-react';
|
||||
|
|
@ -131,6 +132,8 @@ export function ApiFormField({
|
|||
fieldName={fieldName}
|
||||
endpoint={ApiEndpoints.stock_location_tree}
|
||||
childIdentifier='sublocations'
|
||||
model={ModelType.stocklocation}
|
||||
navigate={navigate}
|
||||
/>
|
||||
);
|
||||
case 'category':
|
||||
|
|
@ -141,6 +144,8 @@ export function ApiFormField({
|
|||
fieldName={fieldName}
|
||||
endpoint={ApiEndpoints.category_tree}
|
||||
childIdentifier='subcategories'
|
||||
model={ModelType.partcategory}
|
||||
navigate={navigate}
|
||||
/>
|
||||
);
|
||||
case 'related field':
|
||||
|
|
@ -155,6 +160,8 @@ export function ApiFormField({
|
|||
fieldName={fieldName}
|
||||
endpoint={ApiEndpoints.stock_location_tree}
|
||||
childIdentifier='sublocations'
|
||||
model={ModelType.stocklocation}
|
||||
navigate={navigate}
|
||||
/>
|
||||
);
|
||||
} else if (
|
||||
|
|
@ -167,6 +174,8 @@ export function ApiFormField({
|
|||
fieldName={fieldName}
|
||||
endpoint={ApiEndpoints.category_tree}
|
||||
childIdentifier='subcategories'
|
||||
model={ModelType.partcategory}
|
||||
navigate={navigate}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,37 +1,68 @@
|
|||
import { t } from '@lingui/core/macro';
|
||||
import { Group, Text, type TreeNodeData, TreeSelect } from '@mantine/core';
|
||||
import { useDebouncedValue } from '@mantine/hooks';
|
||||
import { IconChevronDown, IconChevronRight } from '@tabler/icons-react';
|
||||
import {
|
||||
ActionIcon,
|
||||
Group,
|
||||
Input,
|
||||
Text,
|
||||
type TreeNodeData,
|
||||
TreeSelect
|
||||
} from '@mantine/core';
|
||||
import { useDebouncedValue, useId } from '@mantine/hooks';
|
||||
import {
|
||||
IconChevronDown,
|
||||
IconChevronRight,
|
||||
IconLink,
|
||||
IconX
|
||||
} from '@tabler/icons-react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import type { FieldValues, UseControllerReturn } from 'react-hook-form';
|
||||
import type { NavigateFunction } from 'react-router-dom';
|
||||
|
||||
import type { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { ModelInformationDict } from '@lib/enums/ModelInformation';
|
||||
import type { ModelType } from '@lib/enums/ModelType';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import { cancelEvent } from '@lib/functions/Events';
|
||||
import { getDetailUrl, navigateToLink } from '@lib/index';
|
||||
import type { ApiFormFieldType } from '@lib/types/Forms';
|
||||
import { useApi } from '../../../contexts/ApiContext';
|
||||
import {
|
||||
useGlobalSettingsState,
|
||||
useUserSettingsState
|
||||
} from '../../../states/SettingsStates';
|
||||
import { ScanButton } from '../../buttons/ScanButton';
|
||||
import { ApiIcon } from '../../items/ApiIcon';
|
||||
import Expand from '../../items/Expand';
|
||||
import { ModelHoverCard } from '../../render/ModelHoverCard';
|
||||
|
||||
/**
|
||||
* A form field that renders a hierarchical tree selector backed by a tree API endpoint.
|
||||
* Supports server-side search: when the user types, the API is queried with the search term
|
||||
* and the backend returns matching nodes plus their ancestors for context.
|
||||
* A form field that renders a hierarchical tree selector backed by a tree API
|
||||
* endpoint. Supports server-side search, lazy child loading, and (when a model
|
||||
* type is provided) barcode scanning and a hover-card navigate link.
|
||||
*/
|
||||
export function TreeField({
|
||||
controller,
|
||||
definition,
|
||||
fieldName,
|
||||
endpoint,
|
||||
childIdentifier
|
||||
childIdentifier,
|
||||
model,
|
||||
navigate
|
||||
}: Readonly<{
|
||||
controller: UseControllerReturn<FieldValues, any>;
|
||||
definition: ApiFormFieldType;
|
||||
fieldName: string;
|
||||
endpoint: ApiEndpoints;
|
||||
childIdentifier: string;
|
||||
model?: ModelType;
|
||||
navigate?: NavigateFunction | null;
|
||||
}>) {
|
||||
const api = useApi();
|
||||
const inputId = useId();
|
||||
const globalSettings = useGlobalSettingsState();
|
||||
const userSettings = useUserSettingsState();
|
||||
|
||||
const {
|
||||
field,
|
||||
fieldState: { error }
|
||||
|
|
@ -249,102 +280,208 @@ export function TreeField({
|
|||
[refreshChildren]
|
||||
);
|
||||
|
||||
// --- Navigate / hovercard ---
|
||||
|
||||
const detailUrl = useMemo(() => {
|
||||
if (!model || !selectedValue) return '';
|
||||
return getDetailUrl(model, selectedValue, true);
|
||||
}, [model, selectedValue]);
|
||||
|
||||
const handleNavigate = useCallback(
|
||||
(e: any) => {
|
||||
if (navigate && detailUrl) navigateToLink(detailUrl, navigate, e);
|
||||
},
|
||||
[navigate, detailUrl]
|
||||
);
|
||||
|
||||
// When a navigate model is present and a value is selected, swap out
|
||||
// Mantine's built-in clear button for a custom right section that holds
|
||||
// both a clear button and a link to the model detail page (with tooltip).
|
||||
const showNavigateSection = Boolean(model && selectedValue);
|
||||
|
||||
const navigateRightSection = showNavigateSection ? (
|
||||
<Group gap={2} wrap='nowrap' style={{ paddingRight: 4 }}>
|
||||
{!definition.required && selectValue && (
|
||||
<ActionIcon
|
||||
variant='transparent'
|
||||
size='xs'
|
||||
color='dimmed'
|
||||
aria-label={t`Clear`}
|
||||
onClick={(e: any) => {
|
||||
cancelEvent(e);
|
||||
onChange(null);
|
||||
}}
|
||||
>
|
||||
<IconX size={12} />
|
||||
</ActionIcon>
|
||||
)}
|
||||
<ModelHoverCard model={model} pk={selectedValue} navigate={navigate}>
|
||||
<ActionIcon
|
||||
variant='transparent'
|
||||
size='xs'
|
||||
component='a'
|
||||
href={detailUrl || '#'}
|
||||
target='_blank'
|
||||
aria-label={t`View details`}
|
||||
onClick={handleNavigate}
|
||||
>
|
||||
<IconLink size={12} />
|
||||
</ActionIcon>
|
||||
</ModelHoverCard>
|
||||
</Group>
|
||||
) : undefined;
|
||||
|
||||
// --- Barcode scanning ---
|
||||
|
||||
const modelInfo = useMemo(
|
||||
() => (model ? ModelInformationDict[model] : null),
|
||||
[model]
|
||||
);
|
||||
|
||||
const addBarcodeField = useMemo(() => {
|
||||
if (!modelInfo?.supports_barcode) return false;
|
||||
if (!globalSettings.isSet('BARCODE_ENABLE')) return false;
|
||||
if (!userSettings.isSet('BARCODE_IN_FORM_FIELDS')) return false;
|
||||
return true;
|
||||
}, [modelInfo, globalSettings, userSettings]);
|
||||
|
||||
const onBarcodeScan = useCallback(
|
||||
(_barcode: string, response: any) => {
|
||||
if (!model) return;
|
||||
const modelData = response?.[model] ?? null;
|
||||
if (modelData?.pk) {
|
||||
field.onChange(modelData.pk);
|
||||
definition.onValueChange?.(modelData.pk, modelData);
|
||||
}
|
||||
},
|
||||
[model, field, definition]
|
||||
);
|
||||
|
||||
// --- Render ---
|
||||
|
||||
return (
|
||||
<TreeSelect
|
||||
data={treeData}
|
||||
aria-label={`tree-field-${fieldName}`}
|
||||
value={selectValue}
|
||||
searchValue={inputSearchValue}
|
||||
onChange={onChange}
|
||||
onSearchChange={(val) => {
|
||||
if (dropdownOpen.current) setSearchValue(val);
|
||||
}}
|
||||
searchable
|
||||
filter={() => true}
|
||||
clearable={!definition.required}
|
||||
expandedValues={expandedValues}
|
||||
onExpandedChange={setExpandedValues}
|
||||
onDropdownOpen={() => {
|
||||
dropdownOpen.current = true;
|
||||
setIsDropdownOpen(true);
|
||||
}}
|
||||
onDropdownClose={() => {
|
||||
dropdownOpen.current = false;
|
||||
setIsDropdownOpen(false);
|
||||
setSearchValue('');
|
||||
}}
|
||||
<Input.Wrapper
|
||||
label={definition.label}
|
||||
description={definition.description}
|
||||
placeholder={definition.placeholder ?? t`Select...`}
|
||||
required={definition.required}
|
||||
disabled={definition.disabled}
|
||||
error={definition.error ?? error?.message}
|
||||
comboboxProps={{ withinPortal: true }}
|
||||
maxDropdownHeight={300}
|
||||
nothingFoundMessage={
|
||||
query.isFetching ? t`Loading...` : t`No results found`
|
||||
}
|
||||
renderNode={({ node, expanded, hasChildren, selected }) => {
|
||||
const raw = nodeMap[node.value];
|
||||
return (
|
||||
<Group
|
||||
justify='space-between'
|
||||
gap='xs'
|
||||
wrap='nowrap'
|
||||
style={{ flex: 1 }}
|
||||
>
|
||||
<Group gap={4} wrap='nowrap'>
|
||||
{/* Chevron rendered manually so renderNode can coexist with expand behavior.
|
||||
stopPropagation prevents the Combobox.Option from selecting the node
|
||||
when the user clicks the expand toggle. */}
|
||||
<span
|
||||
style={{
|
||||
display: 'inline-flex',
|
||||
width: 16,
|
||||
alignItems: 'center',
|
||||
flexShrink: 0,
|
||||
cursor: hasChildren ? 'pointer' : 'default'
|
||||
}}
|
||||
aria-label={expanded ? t`Collapse` : t`Expand`}
|
||||
onClick={
|
||||
hasChildren
|
||||
? (event: any) => {
|
||||
cancelEvent(event);
|
||||
toggleExpanded(node.value);
|
||||
id={inputId}
|
||||
>
|
||||
<Group wrap='nowrap' gap={3}>
|
||||
<Expand>
|
||||
<TreeSelect
|
||||
id={inputId}
|
||||
data={treeData}
|
||||
aria-label={`tree-field-${fieldName}`}
|
||||
value={selectValue}
|
||||
searchValue={inputSearchValue}
|
||||
onChange={onChange}
|
||||
onSearchChange={(val) => {
|
||||
if (dropdownOpen.current) setSearchValue(val);
|
||||
}}
|
||||
searchable
|
||||
filter={() => true}
|
||||
clearable={showNavigateSection ? false : !definition.required}
|
||||
rightSection={navigateRightSection}
|
||||
rightSectionPointerEvents={showNavigateSection ? 'all' : undefined}
|
||||
rightSectionWidth={
|
||||
showNavigateSection ? (definition.required ? 28 : 52) : undefined
|
||||
}
|
||||
expandedValues={expandedValues}
|
||||
onExpandedChange={setExpandedValues}
|
||||
onDropdownOpen={() => {
|
||||
dropdownOpen.current = true;
|
||||
setIsDropdownOpen(true);
|
||||
}}
|
||||
onDropdownClose={() => {
|
||||
dropdownOpen.current = false;
|
||||
setIsDropdownOpen(false);
|
||||
setSearchValue('');
|
||||
}}
|
||||
placeholder={definition.placeholder ?? t`Select...`}
|
||||
disabled={definition.disabled}
|
||||
comboboxProps={{ withinPortal: true }}
|
||||
maxDropdownHeight={300}
|
||||
nothingFoundMessage={
|
||||
query.isFetching ? t`Loading...` : t`No results found`
|
||||
}
|
||||
renderNode={({ node, expanded, hasChildren, selected }) => {
|
||||
const raw = nodeMap[node.value];
|
||||
return (
|
||||
<Group
|
||||
justify='space-between'
|
||||
gap='xs'
|
||||
wrap='nowrap'
|
||||
style={{ flex: 1 }}
|
||||
>
|
||||
<Group gap={4} wrap='nowrap'>
|
||||
{/* Chevron rendered manually so renderNode can coexist with
|
||||
expand behavior. stopPropagation prevents the
|
||||
Combobox.Option from selecting the node when the user
|
||||
clicks the expand toggle. */}
|
||||
<span
|
||||
role={hasChildren ? 'button' : undefined}
|
||||
tabIndex={hasChildren ? 0 : undefined}
|
||||
style={{
|
||||
display: 'inline-flex',
|
||||
width: 16,
|
||||
alignItems: 'center',
|
||||
flexShrink: 0,
|
||||
cursor: hasChildren ? 'pointer' : 'default'
|
||||
}}
|
||||
aria-label={expanded ? t`Collapse` : t`Expand`}
|
||||
onClick={
|
||||
hasChildren
|
||||
? (event: any) => {
|
||||
cancelEvent(event);
|
||||
toggleExpanded(node.value);
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
onKeyDown={
|
||||
hasChildren
|
||||
? (event: any) => {
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
cancelEvent(event);
|
||||
toggleExpanded(node.value);
|
||||
}
|
||||
onKeyDown={
|
||||
hasChildren
|
||||
? (event: any) => {
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
cancelEvent(event);
|
||||
toggleExpanded(node.value);
|
||||
}
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{hasChildren &&
|
||||
(expanded ? (
|
||||
<IconChevronDown size={14} />
|
||||
) : (
|
||||
<IconChevronRight size={14} />
|
||||
))}
|
||||
</span>
|
||||
{raw?.icon && <ApiIcon name={raw.icon} size={14} />}
|
||||
<Text size='sm' fw={selected ? 600 : undefined}>
|
||||
{raw?.name ?? String(node.label)}
|
||||
</Text>
|
||||
</Group>
|
||||
{raw?.description && (
|
||||
<Text size='xs' c='dimmed' ta='right' truncate flex={1} maw='50%'>
|
||||
{raw.description}
|
||||
</Text>
|
||||
)}
|
||||
</Group>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
>
|
||||
{hasChildren &&
|
||||
(expanded ? (
|
||||
<IconChevronDown size={14} />
|
||||
) : (
|
||||
<IconChevronRight size={14} />
|
||||
))}
|
||||
</span>
|
||||
{raw?.icon && <ApiIcon name={raw.icon} size={14} />}
|
||||
<Text size='sm' fw={selected ? 600 : undefined}>
|
||||
{raw?.name ?? String(node.label)}
|
||||
</Text>
|
||||
</Group>
|
||||
{raw?.description && (
|
||||
<Text
|
||||
size='xs'
|
||||
c='dimmed'
|
||||
ta='right'
|
||||
truncate
|
||||
flex={1}
|
||||
maw='50%'
|
||||
>
|
||||
{raw.description}
|
||||
</Text>
|
||||
)}
|
||||
</Group>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Expand>
|
||||
{addBarcodeField && (
|
||||
<ScanButton modelType={model} onScanSuccess={onBarcodeScan} />
|
||||
)}
|
||||
</Group>
|
||||
</Input.Wrapper>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,81 @@
|
|||
import { t } from '@lingui/core/macro';
|
||||
import {
|
||||
ActionIcon,
|
||||
Anchor,
|
||||
Group,
|
||||
HoverCard,
|
||||
Stack,
|
||||
Text
|
||||
} from '@mantine/core';
|
||||
import type { ReactNode } from 'react';
|
||||
import type { NavigateFunction } from 'react-router-dom';
|
||||
|
||||
import { ModelInformationDict } from '@lib/enums/ModelInformation';
|
||||
import type { ModelType } from '@lib/enums/ModelType';
|
||||
import { getDetailUrl, navigateToLink } from '@lib/index';
|
||||
import { IconLink } from '@tabler/icons-react';
|
||||
|
||||
/**
|
||||
* Wraps children in a HoverCard showing the model label, pk, and a "View
|
||||
* details" link for the given instance. Renders children directly (no card)
|
||||
* when model or pk is absent.
|
||||
*/
|
||||
export function ModelHoverCard({
|
||||
children,
|
||||
model,
|
||||
pk,
|
||||
navigate
|
||||
}: {
|
||||
children: ReactNode;
|
||||
model: ModelType | undefined;
|
||||
pk: number | null | undefined;
|
||||
navigate?: NavigateFunction | null;
|
||||
}) {
|
||||
const modelInfo = model ? ModelInformationDict[model] : undefined;
|
||||
|
||||
if (!modelInfo || !pk) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
const detailUrl = getDetailUrl(model!, pk, true);
|
||||
|
||||
return (
|
||||
<HoverCard
|
||||
position='top-end'
|
||||
withinPortal
|
||||
openDelay={500}
|
||||
closeDelay={100}
|
||||
zIndex={99999}
|
||||
>
|
||||
<HoverCard.Target>
|
||||
<span>{children}</span>
|
||||
</HoverCard.Target>
|
||||
<HoverCard.Dropdown>
|
||||
<Stack gap='xs'>
|
||||
<Group justify='space-between'>
|
||||
<Text size='sm' fw='bold'>
|
||||
{modelInfo.label()}
|
||||
</Text>
|
||||
<Text size='xs'>{`[${t`ID`}: ${pk}]`}</Text>
|
||||
</Group>
|
||||
{detailUrl && (
|
||||
<Anchor
|
||||
href={detailUrl}
|
||||
target='_blank'
|
||||
onClick={(event) => {
|
||||
if (navigate) navigateToLink(detailUrl, navigate, event);
|
||||
}}
|
||||
>
|
||||
<Group gap='xs' wrap='nowrap'>
|
||||
<ActionIcon variant='transparent' size='xs'>
|
||||
<IconLink />
|
||||
</ActionIcon>
|
||||
<Text size='sm'>{t`View details`}</Text>
|
||||
</Group>
|
||||
</Anchor>
|
||||
)}
|
||||
</Stack>
|
||||
</HoverCard.Dropdown>
|
||||
</HoverCard>
|
||||
);
|
||||
}
|
||||
|
|
@ -227,8 +227,10 @@ test('Build Order - Calendar', async ({ browser }) => {
|
|||
await page
|
||||
.getByRole('option', { name: 'Part Category', exact: true })
|
||||
.click();
|
||||
await page.getByLabel('related-field-filter-category').click();
|
||||
await page.getByText('Part category, level 1').waitFor();
|
||||
await page.getByLabel('tree-field-filter-category').click();
|
||||
await page.getByText('Part category, level 1').click();
|
||||
await page.getByText('Filter by part category').waitFor();
|
||||
await page.getByText('Category 0').first().waitFor();
|
||||
|
||||
// Required because we downloaded a file
|
||||
await page.context().close();
|
||||
|
|
@ -324,9 +326,9 @@ test('Build Order - Build Outputs', async ({ browser }) => {
|
|||
.getByRole('img', { name: 'field-batch_code-accept-placeholder' })
|
||||
.click();
|
||||
|
||||
await page.getByLabel('related-field-location').click();
|
||||
await page.getByLabel('related-field-location').fill('Reel');
|
||||
await page.getByText('- Electronics Lab/Reel Storage').click();
|
||||
await page.getByLabel('tree-field-location').click();
|
||||
await page.getByLabel('tree-field-location').fill('Reel');
|
||||
await page.getByText('Storage for component reels').click();
|
||||
await page.getByRole('button', { name: 'Submit' }).click();
|
||||
|
||||
// Should be an error as the number of serial numbers doesn't match the quantity
|
||||
|
|
@ -361,9 +363,10 @@ test('Build Order - Build Outputs', async ({ browser }) => {
|
|||
const row2 = await getRowFromCell(cell2);
|
||||
await row2.getByLabel(/row-action-menu-/i).click();
|
||||
await page.getByRole('menuitem', { name: 'Complete' }).click();
|
||||
await page.getByLabel('related-field-location').click();
|
||||
await page.getByLabel('tree-field-location').click();
|
||||
await page.getByLabel('tree-field-location').fill('Mechanical');
|
||||
await page.getByText('Mechanical Lab').click();
|
||||
await page.waitForTimeout(250);
|
||||
await page.waitForTimeout(100);
|
||||
await page.getByRole('button', { name: 'Submit' }).click();
|
||||
await page.getByText('Build outputs have been completed').waitFor();
|
||||
|
||||
|
|
@ -385,10 +388,8 @@ test('Build Order - Build Outputs', async ({ browser }) => {
|
|||
|
||||
// Next, adjust the "location" field - and check that the "quantity" field does not change
|
||||
// Ref: https://github.com/inventree/InvenTree/pull/12081
|
||||
await page
|
||||
.getByRole('combobox', { name: 'related-field-location' })
|
||||
.fill('factory');
|
||||
await page.getByTitle('Factory/Mechanical Lab').click();
|
||||
await page.getByLabel('tree-field-location').fill('mechanical');
|
||||
await page.getByText('Mechanical Lab').click();
|
||||
await page.waitForTimeout(250);
|
||||
|
||||
// Check the 'quantity' value again - it should not have changed
|
||||
|
|
@ -539,11 +540,7 @@ test('Build Order - Auto Allocate Tracked', async ({ browser }) => {
|
|||
.click();
|
||||
|
||||
// Wait for auto-filled form field
|
||||
await page
|
||||
.locator('div')
|
||||
.filter({ hasText: /^Factory$/ })
|
||||
.first()
|
||||
.waitFor();
|
||||
await page.getByText('Factory').waitFor();
|
||||
await page.getByRole('button', { name: 'Submit' }).click();
|
||||
|
||||
// Wait for one of the required parts to be allocated
|
||||
|
|
|
|||
|
|
@ -973,7 +973,7 @@ test('Parts - Notes', async ({ browser }) => {
|
|||
await page.keyboard.press('Control+E');
|
||||
await page.getByLabel('text-field-name', { exact: true }).waitFor();
|
||||
await page.getByLabel('text-field-description', { exact: true }).waitFor();
|
||||
await page.getByLabel('related-field-category').waitFor();
|
||||
await page.getByLabel('tree-field-category').waitFor();
|
||||
await page.getByRole('button', { name: 'Cancel' }).click();
|
||||
|
||||
// Enable notes editing
|
||||
|
|
@ -1024,10 +1024,8 @@ test('Parts - Bulk Edit', async ({ browser }) => {
|
|||
|
||||
await openDetailAction(page, 'part', 'set-category');
|
||||
|
||||
await page.getByLabel('related-field-category').fill('rnitu');
|
||||
await page.waitForTimeout(250);
|
||||
|
||||
await page.getByRole('option', { name: '- Furniture/Chairs' }).click();
|
||||
await page.getByLabel('tree-field-category').fill('rnitu');
|
||||
await page.getByText('Furniture and associated').click();
|
||||
await page.getByRole('button', { name: 'Update' }).click();
|
||||
await page.getByText('Items Updated').waitFor();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -512,15 +512,8 @@ test('Purchase Orders - Receive Items', async ({ browser }) => {
|
|||
await page.getByLabel('action-button-receive-items').click();
|
||||
|
||||
// Check for display of individual locations
|
||||
await page
|
||||
.getByRole('cell', { name: /Choose Location/ })
|
||||
.getByText('Parts Bins')
|
||||
.waitFor();
|
||||
await page
|
||||
.getByRole('cell', { name: /Choose Location/ })
|
||||
.getByText('Room 101')
|
||||
.waitFor();
|
||||
|
||||
await page.getByText('Parts Bins').first().waitFor();
|
||||
await page.getByText('Room 101').first().waitFor();
|
||||
await page.getByText('Mechanical Lab').first().waitFor();
|
||||
|
||||
await page.getByRole('button', { name: 'Cancel' }).click();
|
||||
|
|
@ -549,8 +542,8 @@ test('Purchase Orders - Receive Items', async ({ browser }) => {
|
|||
await page.getByRole('menuitem', { name: 'Receive line item' }).click();
|
||||
|
||||
// Select destination location
|
||||
await page.getByLabel('related-field-location').click();
|
||||
await page.getByRole('option', { name: 'Factory', exact: true }).click();
|
||||
await page.getByLabel('tree-field-location').fill('factory');
|
||||
await page.getByText('Factory', { exact: true }).click();
|
||||
|
||||
// Receive only a *single* item
|
||||
await page.getByLabel('number-field-quantity').fill('1');
|
||||
|
|
@ -611,10 +604,8 @@ test('Purchase Orders - Receive Virtual Items', async ({ browser }) => {
|
|||
.getByRole('button', { name: 'action-button-receive-items' })
|
||||
.click();
|
||||
|
||||
await page
|
||||
.getByRole('combobox', { name: 'related-field-location' })
|
||||
.fill('factory');
|
||||
await page.getByText('Factory/Storage Room A').click();
|
||||
await page.getByLabel('tree-field-location').fill('factory');
|
||||
await page.getByText('Factory', { exact: true }).click();
|
||||
|
||||
await page.getByRole('button', { name: 'Submit' }).click();
|
||||
|
||||
|
|
|
|||
|
|
@ -177,6 +177,7 @@ test('Barcode Scanning - Forms', async ({ browser }) => {
|
|||
await page
|
||||
.getByRole('button', { name: 'barcode-scan-button-stocklocation' })
|
||||
.click();
|
||||
|
||||
await page
|
||||
.getByRole('textbox', { name: 'barcode-scan-keyboard-input' })
|
||||
.fill('INV-SL37');
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ test('Forms - Stock Item Validation', async ({ browser }) => {
|
|||
.waitFor();
|
||||
|
||||
// Set location
|
||||
await page.getByLabel('related-field-location').click();
|
||||
await page.getByLabel('tree-field-location').fill('production');
|
||||
await page.getByText('Electronics production facility').click();
|
||||
|
||||
// Create the stock item
|
||||
|
|
|
|||
Loading…
Reference in New Issue