diff --git a/src/backend/InvenTree/build/serializers.py b/src/backend/InvenTree/build/serializers.py index e1ac881471..7c2c6def2b 100644 --- a/src/backend/InvenTree/build/serializers.py +++ b/src/backend/InvenTree/build/serializers.py @@ -33,7 +33,6 @@ from generic.states.fields import InvenTreeCustomStatusSerializerMixin from InvenTree.mixins import DataImportExportSerializerMixin from InvenTree.serializers import ( FilterableCharField, - FilterableIntegerField, FilterableSerializerMixin, InvenTreeDecimalField, InvenTreeModelSerializer, @@ -164,17 +163,6 @@ class BuildSerializer( filter_name='project_code_detail', ) - project_code = enable_filter( - FilterableIntegerField( - allow_null=True, - required=False, - label=_('Project Code'), - help_text=_('Project code for this build order'), - ), - True, - filter_name='project_code_detail', - ) - @staticmethod def annotate_queryset(queryset): """Add custom annotations to the BuildSerializer queryset, performing database queries as efficiently as possible. diff --git a/src/frontend/lib/types/Forms.tsx b/src/frontend/lib/types/Forms.tsx index 3393ec361a..364dc38e6f 100644 --- a/src/frontend/lib/types/Forms.tsx +++ b/src/frontend/lib/types/Forms.tsx @@ -53,9 +53,12 @@ export type ApiFormFieldHeader = { * @param error : Optional error message to display * @param exclude : Whether to exclude the field from the submitted data * @param placeholder : The placeholder text to display + * @param placeholderAutofill: Whether to allow auto-filling of the placeholder value * @param description : The description to display for the field * @param preFieldContent : Content to render before the field * @param postFieldContent : Content to render after the field + * @param leftSection : Content to render in the left section of the field + * @param rightSection : Content to render in the right section of the field * @param autoFill: Whether to automatically fill the field with data from the API * @param autoFillFilters: Optional filters to apply when auto-filling the field * @param onValueChange : Callback function to call when the field value changes @@ -103,9 +106,12 @@ export type ApiFormFieldType = { exclude?: boolean; read_only?: boolean; placeholder?: string; + placeholderAutofill?: boolean; description?: string; preFieldContent?: JSX.Element; postFieldContent?: JSX.Element; + leftSection?: JSX.Element; + rightSection?: JSX.Element; autoFill?: boolean; autoFillFilters?: any; adjustValue?: (value: any) => any; diff --git a/src/frontend/src/components/forms/fields/ApiFormField.tsx b/src/frontend/src/components/forms/fields/ApiFormField.tsx index a1de487236..e5d56bbdd4 100644 --- a/src/frontend/src/components/forms/fields/ApiFormField.tsx +++ b/src/frontend/src/components/forms/fields/ApiFormField.tsx @@ -74,6 +74,7 @@ export function ApiFormField({ return { ...fieldDefinition, autoFill: undefined, + placeholderAutofill: undefined, autoFillFilters: undefined, onValueChange: undefined, adjustFilters: undefined, @@ -146,6 +147,7 @@ export function ApiFormField({ return ( diff --git a/src/frontend/src/components/forms/fields/TextField.tsx b/src/frontend/src/components/forms/fields/TextField.tsx index 98761716db..8e95011b4b 100644 --- a/src/frontend/src/components/forms/fields/TextField.tsx +++ b/src/frontend/src/components/forms/fields/TextField.tsx @@ -1,7 +1,15 @@ -import { TextInput } from '@mantine/core'; +import { t } from '@lingui/core/macro'; +import { TextInput, Tooltip } from '@mantine/core'; import { useDebouncedValue } from '@mantine/hooks'; -import { IconX } from '@tabler/icons-react'; -import { useCallback, useEffect, useId, useState } from 'react'; +import { IconCopyCheck, IconX } from '@tabler/icons-react'; +import { + type ReactNode, + useCallback, + useEffect, + useId, + useMemo, + useState +} from 'react'; import type { FieldValues, UseControllerReturn } from 'react-hook-form'; /* @@ -13,12 +21,14 @@ export default function TextField({ controller, fieldName, definition, + placeholderAutofill, onChange, onKeyDown }: Readonly<{ controller: UseControllerReturn; definition: any; fieldName: string; + placeholderAutofill?: boolean; onChange: (value: any) => void; onKeyDown: (value: any) => void; }>) { @@ -28,7 +38,7 @@ export default function TextField({ fieldState: { error } } = controller; - const { value } = field; + const { value } = useMemo(() => field, [field]); const [rawText, setRawText] = useState(value || ''); @@ -48,6 +58,44 @@ export default function TextField({ } }, [debouncedText]); + // Construct a "right section" for the text field + const textFieldRightSection: ReactNode = useMemo(() => { + if (definition.rightSection) { + // Use the specified override value + return definition.rightSection; + } else if (value) { + if (!definition.required && !definition.disabled) { + // Render a button to clear the text field + return ( + + onTextChange('')} + /> + + ); + } + } else if ( + !value && + definition.placeholder && + placeholderAutofill && + !definition.disabled + ) { + return ( + + onTextChange(definition.placeholder)} + /> + + ); + } + }, [placeholderAutofill, definition, value]); + return ( onTextChange('')} /> - ) : null - } + rightSection={textFieldRightSection} /> ); } diff --git a/src/frontend/src/forms/BuildForms.tsx b/src/frontend/src/forms/BuildForms.tsx index b975e0054f..d751028166 100644 --- a/src/frontend/src/forms/BuildForms.tsx +++ b/src/frontend/src/forms/BuildForms.tsx @@ -107,9 +107,8 @@ export function useBuildOrderFields({ icon: }, batch: { - placeholder: - batchGenerator.result && - `${t`Next batch code`}: ${batchGenerator.result}`, + placeholder: batchGenerator.result, + placeholderAutofill: true, value: batchCode, onValueChange: (value: any) => setBatchCode(value) }, @@ -207,14 +206,12 @@ export function useBuildOrderOutputFields({ }, serial_numbers: { hidden: !trackable, - placeholder: - serialGenerator.result && - `${t`Next serial number`}: ${serialGenerator.result}` + placeholder: serialGenerator.result && `${serialGenerator.result}+`, + placeholderAutofill: true }, batch_code: { - placeholder: - batchGenerator.result && - `${t`Next batch code`}: ${batchGenerator.result}` + placeholder: batchGenerator.result, + placeholderAutofill: true }, location: { value: location, diff --git a/src/frontend/src/forms/StockForms.tsx b/src/frontend/src/forms/StockForms.tsx index b1a700a318..ae3fd0d475 100644 --- a/src/frontend/src/forms/StockForms.tsx +++ b/src/frontend/src/forms/StockForms.tsx @@ -212,23 +212,21 @@ export function useStockFields({ description: t`Enter serial numbers for new stock (or leave blank)`, required: false, hidden: !create, - placeholder: - serialGenerator.result && - `${t`Next serial number`}: ${serialGenerator.result}` + placeholderAutofill: true, + placeholder: serialGenerator.result && `${serialGenerator.result}+` }, serial: { - placeholder: - serialGenerator.result && - `${t`Next serial number`}: ${serialGenerator.result}`, + placeholderAutofill: true, + placeholder: serialGenerator.result, hidden: create || partInstance.trackable == false || (stockItem?.quantity != undefined && stockItem?.quantity != 1) }, batch: { - placeholder: - batchGenerator.result && - `${t`Next batch code`}: ${batchGenerator.result}` + default: '', + placeholderAutofill: true, + placeholder: batchGenerator.result }, status_custom_key: { label: t`Stock Status` @@ -272,6 +270,10 @@ export function useStockFields({ delete fields.expiry_date; } + if (!create) { + delete fields.serial_numbers; + } + return fields; }, [ stockItem, @@ -400,9 +402,8 @@ export function useStockItemSerializeFields({ return { quantity: {}, serial_numbers: { - placeholder: - serialGenerator.result && - `${t`Next serial number`}: ${serialGenerator.result}` + placeholder: serialGenerator.result && `${serialGenerator.result}+`, + placeholderAutofill: true }, destination: {} }; diff --git a/src/frontend/tests/pages/pui_build.spec.ts b/src/frontend/tests/pages/pui_build.spec.ts index 28c9091223..8817487e1f 100644 --- a/src/frontend/tests/pages/pui_build.spec.ts +++ b/src/frontend/tests/pages/pui_build.spec.ts @@ -34,7 +34,7 @@ test('Build Order - Basic Tests', async ({ browser }) => { // Edit the build order (via keyboard shortcut) await page.keyboard.press('Control+E'); - await page.getByLabel('text-field-title').waitFor(); + await page.getByLabel('text-field-title', { exact: true }).waitFor(); await page.getByLabel('related-field-project_code').waitFor(); await page.getByRole('button', { name: 'Cancel' }).click(); @@ -85,7 +85,9 @@ test('Build Order - Basic Tests', async ({ browser }) => { .getByLabel('add-test-result'); await button.click(); - await page.getByRole('textbox', { name: 'text-field-value' }).waitFor(); + await page + .getByRole('textbox', { name: 'text-field-value', exact: true }) + .waitFor(); await page.getByRole('button', { name: 'Cancel' }).click(); // Click through to the "parent" build @@ -189,22 +191,27 @@ test('Build Order - Build Outputs', async ({ browser }) => { await page.getByLabel('action-button-add-build-output').click(); await page.getByLabel('number-field-quantity').fill('5'); - const placeholder = await page - .getByLabel('text-field-serial_numbers') - .getAttribute('placeholder'); + const placeholder: string = + (await page + .getByLabel('text-field-serial_numbers', { exact: true }) + .getAttribute('placeholder')) || ''; - expect(placeholder).toContain('Next serial number'); + expect(placeholder).toContain('+'); let sn = 1; - if (!!placeholder && placeholder.includes('Next serial number')) { - sn = Number.parseInt(placeholder.split(':')[1].trim()); - } + sn = Number.parseInt(placeholder.split('+')[0].trim()); // Generate some new serial numbers - await page.getByLabel('text-field-serial_numbers').fill(`${sn}, ${sn + 1}`); + await page + .getByLabel('text-field-serial_numbers', { exact: true }) + .fill(`${sn}, ${sn + 1}`); + + // Accept the suggested batch code + await page + .getByRole('img', { name: 'text-field-batch_code-accept-placeholder' }) + .click(); - await page.getByLabel('text-field-batch_code').fill('BATCH12345'); await page.getByLabel('related-field-location').click(); await page.getByLabel('related-field-location').fill('Reel'); await page.getByText('- Electronics Lab/Reel Storage').click(); @@ -397,7 +404,7 @@ test('Build Order - Consume Stock', async ({ browser }) => { await page.getByLabel('Consume Stock').getByText('5 / 35').waitFor(); await page.getByLabel('Consume Stock').getByText('5 / 40').waitFor(); await page - .getByRole('textbox', { name: 'text-field-notes' }) + .getByRole('textbox', { name: 'text-field-notes', exact: true }) .fill('some notes here...'); await page.getByRole('button', { name: 'Cancel' }).click(); @@ -426,7 +433,7 @@ test('Build Order - Tracked Outputs', async ({ browser }) => { const cancelBuildOutput = async (cell) => { await clickOnRowMenu(cell); await page.getByRole('menuitem', { name: 'Cancel' }).click(); - await page.getByRole('button', { name: 'Submit' }).click(); + await page.getByRole('button', { name: 'Submit', exact: true }).click(); await page.getByText('Build outputs have been cancelled').waitFor(); }; @@ -444,7 +451,9 @@ test('Build Order - Tracked Outputs', async ({ browser }) => { .getByRole('button', { name: 'action-button-add-build-output' }) .click(); await page.getByLabel('number-field-quantity').fill('1'); - await page.getByLabel('text-field-serial_numbers').fill('15'); + await page + .getByLabel('text-field-serial_numbers', { exact: true }) + .fill('15'); await page.getByRole('button', { name: 'Submit' }).click(); await page.getByText('Build output created').waitFor(); @@ -499,7 +508,9 @@ test('Build Order - Tracked Outputs', async ({ browser }) => { .getByRole('button', { name: 'action-button-add-build-output' }) .click(); await page.getByLabel('number-field-quantity').fill('1'); - await page.getByLabel('text-field-serial_numbers').fill('16'); + await page + .getByLabel('text-field-serial_numbers', { exact: true }) + .fill('16'); await page .locator('label') .filter({ hasText: 'Auto Allocate Serial' }) @@ -560,7 +571,9 @@ test('Build Order - Duplicate', async ({ browser }) => { await page.getByLabel('action-menu-build-order-actions-duplicate').click(); // Ensure a new reference is suggested - await expect(page.getByLabel('text-field-reference')).not.toBeEmpty(); + await expect( + page.getByLabel('text-field-reference', { exact: true }) + ).not.toBeEmpty(); // Submit the duplicate request and ensure it completes await page.getByRole('button', { name: 'Submit' }).isEnabled(); diff --git a/src/frontend/tests/pages/pui_company.spec.ts b/src/frontend/tests/pages/pui_company.spec.ts index 07139768b2..2fbfa00780 100644 --- a/src/frontend/tests/pages/pui_company.spec.ts +++ b/src/frontend/tests/pages/pui_company.spec.ts @@ -30,8 +30,10 @@ test('Company', async ({ browser }) => { await page.getByLabel('action-menu-company-actions').click(); await page.getByLabel('action-menu-company-actions-edit').click(); - await page.getByLabel('text-field-name').fill(''); - await page.getByLabel('text-field-website').fill('invalid-website'); + await page.getByLabel('text-field-name', { exact: true }).fill(''); + await page + .getByLabel('text-field-website', { exact: true }) + .fill('invalid-website'); await page.getByRole('button', { name: 'Submit' }).click(); await page.getByText('This field may not be blank.').waitFor(); diff --git a/src/frontend/tests/pages/pui_part.spec.ts b/src/frontend/tests/pages/pui_part.spec.ts index f5c3679e05..d8d6cc53e0 100644 --- a/src/frontend/tests/pages/pui_part.spec.ts +++ b/src/frontend/tests/pages/pui_part.spec.ts @@ -142,14 +142,16 @@ test('Part - Editing', async ({ browser }) => { // Open part edit dialog await page.keyboard.press('Control+E'); - const keywords = await page.getByLabel('text-field-keywords').inputValue(); + const keywords = await page + .getByLabel('text-field-keywords', { exact: true }) + .inputValue(); await page - .getByLabel('text-field-keywords') + .getByLabel('text-field-keywords', { exact: true }) .fill(keywords ? '' : 'table furniture'); // Test URL validation await page - .getByRole('textbox', { name: 'text-field-link' }) + .getByRole('textbox', { name: 'text-field-link', exact: true }) .fill('htxp-??QQQ++'); await page.waitForTimeout(200); await page.getByRole('button', { name: 'Submit' }).click(); @@ -157,11 +159,15 @@ test('Part - Editing', async ({ browser }) => { // Fill with an empty URL const description = await page - .getByLabel('text-field-description') + .getByLabel('text-field-description', { exact: true }) .inputValue(); - await page.getByRole('textbox', { name: 'text-field-link' }).fill(''); - await page.getByLabel('text-field-description').fill(`${description}+`); + await page + .getByRole('textbox', { name: 'text-field-link', exact: true }) + .fill(''); + await page + .getByLabel('text-field-description', { exact: true }) + .fill(`${description}+`); await page.waitForTimeout(200); await page.getByRole('button', { name: 'Submit' }).click(); await page.getByText('Item Updated').waitFor(); @@ -462,8 +468,12 @@ test('Parts - Attachments', async ({ browser }) => { // Submit a new external link await page.getByLabel('action-button-add-external-').click(); - await page.getByLabel('text-field-link').fill('https://www.google.com'); - await page.getByLabel('text-field-comment').fill('a sample comment'); + await page + .getByLabel('text-field-link', { exact: true }) + .fill('https://www.google.com'); + await page + .getByLabel('text-field-comment', { exact: true }) + .fill('a sample comment'); // Note: Text field values are debounced for 250ms await page.waitForTimeout(300); @@ -473,7 +483,9 @@ test('Parts - Attachments', async ({ browser }) => { // Launch dialog to upload a file await page.getByLabel('action-button-add-attachment').click(); - await page.getByLabel('text-field-comment').fill('some comment'); + await page + .getByLabel('text-field-comment', { exact: true }) + .fill('some comment'); await page.getByRole('button', { name: 'Cancel' }).click(); }); @@ -489,7 +501,9 @@ test('Parts - Parameters', async ({ browser }) => { await page.getByLabel('choice-field-data').click(); await page.getByRole('option', { name: 'Green' }).click(); - await page.getByLabel('text-field-note').fill('A custom note field'); + await page + .getByLabel('text-field-note', { exact: true }) + .fill('A custom note field'); // Select the "polarized" parameter template (should create a "checkbox" field) await page.getByLabel('related-field-template').fill('Polarized'); @@ -577,8 +591,8 @@ test('Parts - Notes', async ({ browser }) => { // Use keyboard shortcut to "edit" the part await page.keyboard.press('Control+E'); - await page.getByLabel('text-field-name').waitFor(); - await page.getByLabel('text-field-description').waitFor(); + 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.getByRole('button', { name: 'Cancel' }).click(); diff --git a/src/frontend/tests/pages/pui_purchase_order.spec.ts b/src/frontend/tests/pages/pui_purchase_order.spec.ts index a0a86cb406..2b71c6383d 100644 --- a/src/frontend/tests/pages/pui_purchase_order.spec.ts +++ b/src/frontend/tests/pages/pui_purchase_order.spec.ts @@ -169,13 +169,15 @@ test('Purchase Orders - General', async ({ browser }) => { .click(); await page.getByRole('menuitem', { name: 'Edit' }).click(); - await page.getByLabel('text-field-title').waitFor(); - await page.getByLabel('text-field-line2').waitFor(); + await page.getByLabel('text-field-title', { exact: true }).waitFor(); + await page.getByLabel('text-field-line2', { exact: true }).waitFor(); // Read the current value of the cell, to ensure we always *change* it! - const value = await page.getByLabel('text-field-line2').inputValue(); + const value = await page + .getByLabel('text-field-line2', { exact: true }) + .inputValue(); await page - .getByLabel('text-field-line2') + .getByLabel('text-field-line2', { exact: true }) .fill(value == 'old' ? 'new' : 'old'); await page.getByRole('button', { name: 'Submit' }).isEnabled(); @@ -344,9 +346,13 @@ test('Purchase Orders - Receive Items', async ({ browser }) => { await page.getByLabel('action-button-change-status').click(); await page.getByLabel('action-button-add-note').click(); - await page.getByLabel('text-field-batch_code').fill('my-batch-code'); - await page.getByLabel('text-field-packaging').fill('bucket'); - await page.getByLabel('text-field-note').fill('The quick brown fox'); + await page + .getByLabel('text-field-batch_code', { exact: true }) + .fill('my-batch-code'); + await page.getByLabel('text-field-packaging', { exact: true }).fill('bucket'); + await page + .getByLabel('text-field-note', { exact: true }) + .fill('The quick brown fox'); await page.getByLabel('choice-field-status').click(); await page.getByRole('option', { name: 'Destroyed' }).click(); @@ -371,7 +377,9 @@ test('Purchase Orders - Duplicate', async ({ browser }) => { await page.getByLabel('action-menu-order-actions-duplicate').click(); // Ensure a new reference is suggested - await expect(page.getByLabel('text-field-reference')).not.toBeEmpty(); + await expect( + page.getByLabel('text-field-reference', { exact: true }) + ).not.toBeEmpty(); // Submit the duplicate request and ensure it completes await page.getByRole('button', { name: 'Submit' }).isEnabled(); diff --git a/src/frontend/tests/pages/pui_sales_order.spec.ts b/src/frontend/tests/pages/pui_sales_order.spec.ts index b84b8adb9c..2a9546f958 100644 --- a/src/frontend/tests/pages/pui_sales_order.spec.ts +++ b/src/frontend/tests/pages/pui_sales_order.spec.ts @@ -120,8 +120,12 @@ test('Sales Orders - Shipments', async ({ browser }) => { // Create a new shipment await page.getByLabel('action-button-add-shipment').click(); - await page.getByLabel('text-field-tracking_number').fill('1234567890'); - await page.getByLabel('text-field-invoice_number').fill('9876543210'); + await page + .getByLabel('text-field-tracking_number', { exact: true }) + .fill('1234567890'); + await page + .getByLabel('text-field-invoice_number', { exact: true }) + .fill('9876543210'); await page.getByRole('button', { name: 'Submit' }).click(); // Expected field error @@ -140,7 +144,7 @@ test('Sales Orders - Shipments', async ({ browser }) => { await page.waitForLoadState('networkidle'); let tracking_number = await page - .getByLabel('text-field-tracking_number') + .getByLabel('text-field-tracking_number', { exact: true }) .inputValue(); if (!tracking_number) { @@ -154,7 +158,9 @@ test('Sales Orders - Shipments', async ({ browser }) => { } // Change the tracking number - await page.getByLabel('text-field-tracking_number').fill(tracking_number); + await page + .getByLabel('text-field-tracking_number', { exact: true }) + .fill(tracking_number); await page.waitForTimeout(250); await page.getByRole('button', { name: 'Submit' }).click(); @@ -217,7 +223,9 @@ test('Sales Orders - Duplicate', async ({ browser }) => { await page.getByLabel('action-menu-order-actions-duplicate').click(); // Ensure a new reference is suggested - await expect(page.getByLabel('text-field-reference')).not.toBeEmpty(); + await expect( + page.getByLabel('text-field-reference', { exact: true }) + ).not.toBeEmpty(); // Submit the duplicate request and ensure it completes await page.getByRole('button', { name: 'Submit' }).isEnabled(); diff --git a/src/frontend/tests/pages/pui_stock.spec.ts b/src/frontend/tests/pages/pui_stock.spec.ts index 7b0131fab2..1dbb650aaa 100644 --- a/src/frontend/tests/pages/pui_stock.spec.ts +++ b/src/frontend/tests/pages/pui_stock.spec.ts @@ -130,7 +130,9 @@ test('Stock - Serial Numbers', async ({ browser }) => { await page.getByLabel('action-button-add-stock-item').click(); // Initially fill with invalid serial/quantity combinations - await page.getByLabel('text-field-serial_numbers').fill('200-250'); + await page + .getByLabel('text-field-serial_numbers', { exact: true }) + .fill('200-250'); await page.getByLabel('number-field-quantity').fill('10'); // Add delay to account to field debounce @@ -171,7 +173,7 @@ test('Stock - Serial Navigation', async ({ browser }) => { await page.getByLabel('action-menu-stock-actions').click(); await page.getByLabel('action-menu-stock-actions-search').click(); - await page.getByLabel('text-field-serial').fill('359'); + await page.getByLabel('text-field-serial', { exact: true }).fill('359'); await page.getByRole('button', { name: 'Submit' }).click(); // Start at serial 359 @@ -183,7 +185,7 @@ test('Stock - Serial Navigation', async ({ browser }) => { await page.getByText('358', { exact: true }).first().waitFor(); await page.getByLabel('action-button-find-serial').click(); - await page.getByLabel('text-field-serial').fill('200'); + await page.getByLabel('text-field-serial', { exact: true }).fill('200'); await page.getByRole('button', { name: 'Submit' }).click(); await page.getByText('Serial Number: 200').waitFor(); @@ -201,10 +203,15 @@ test('Stock - Serialize', async ({ browser }) => { // Check for expected placeholder value await expect( - page.getByRole('textbox', { name: 'text-field-serial_numbers' }) - ).toHaveAttribute('placeholder', 'Next serial number: 365'); + page.getByRole('textbox', { + name: 'text-field-serial_numbers', + exact: true + }) + ).toHaveAttribute('placeholder', '365+'); - await page.getByLabel('text-field-serial_numbers').fill('200-250'); + await page + .getByLabel('text-field-serial_numbers', { exact: true }) + .fill('200-250'); await page.getByRole('button', { name: 'Submit' }).click(); @@ -212,7 +219,9 @@ test('Stock - Serialize', async ({ browser }) => { .getByText('Number of unique serial numbers (51) must match quantity (100)') .waitFor(); - await page.getByLabel('text-field-serial_numbers').fill('1, 2, 3'); + await page + .getByLabel('text-field-serial_numbers', { exact: true }) + .fill('1, 2, 3'); await page.waitForTimeout(250); await page.getByRole('button', { name: 'Submit' }).click(); diff --git a/src/frontend/tests/pui_forms.spec.ts b/src/frontend/tests/pui_forms.spec.ts index 18a1a655f6..a04d7a0e5d 100644 --- a/src/frontend/tests/pui_forms.spec.ts +++ b/src/frontend/tests/pui_forms.spec.ts @@ -22,7 +22,7 @@ test('Forms - Stock Item Validation', async ({ browser }) => { await page.getByText('Valid part must be supplied').waitFor(); // Adjust other field - the errors should persist - await page.getByLabel('text-field-batch').fill('BATCH-123'); + await page.getByLabel('text-field-batch', { exact: true }).fill('BATCH-123'); await page.waitForTimeout(250); await page.getByText('Valid part must be supplied').waitFor(); @@ -53,14 +53,16 @@ test('Forms - Stock Item Validation', async ({ browser }) => { await page.getByLabel('action-menu-stock-item-actions-edit').click(); await page.getByLabel('number-field-purchase_price').fill('-1'); + await page.getByRole('button', { name: 'Submit' }).click(); + await page.getByText('Errors exist for one or more form fields').waitFor(); await page .getByText('Ensure this value is greater than or equal to 0') .waitFor(); // Check the error message still persists after editing a different field - await page.getByLabel('text-field-packaging').fill('a box'); + await page.getByLabel('text-field-packaging', { exact: true }).fill('a box'); await page.waitForTimeout(250); await page .getByText('Ensure this value is greater than or equal to 0') @@ -87,7 +89,9 @@ test('Forms - Supplier Validation', async ({ browser }) => { await page.waitForURL('**/purchasing/index/**'); await page.getByLabel('action-button-add-company').click(); - await page.getByLabel('text-field-website').fill('not-a-website'); + await page + .getByLabel('text-field-website', { exact: true }) + .fill('not-a-website'); await page.getByRole('button', { name: 'Submit' }).click(); @@ -98,7 +102,9 @@ test('Forms - Supplier Validation', async ({ browser }) => { await page.getByText('Enter a valid URL.').waitFor(); // Fill out another field, expect that the errors persist - await page.getByLabel('text-field-description').fill('A description'); + await page + .getByLabel('text-field-description', { exact: true }) + .fill('A description'); await page.waitForTimeout(250); await page.getByText('This field may not be blank.').waitFor(); await page.getByText('Enter a valid URL.').waitFor(); @@ -108,9 +114,9 @@ test('Forms - Supplier Validation', async ({ browser }) => { // Fill with good data await page - .getByLabel('text-field-website') + .getByLabel('text-field-website', { exact: true }) .fill('https://www.test-website.co.uk'); - await page.getByLabel('text-field-name').fill(supplierName); + await page.getByLabel('text-field-name', { exact: true }).fill(supplierName); await page.getByRole('button', { name: 'Submit' }).click(); await page.getByText('A description').first().waitFor(); @@ -122,7 +128,7 @@ test('Forms - Supplier Validation', async ({ browser }) => { await navigate(page, 'purchasing/index/suppliers'); await page.waitForURL('**/purchasing/index/**'); await page.getByLabel('action-button-add-company').click(); - await page.getByLabel('text-field-name').fill(supplierName); + await page.getByLabel('text-field-name', { exact: true }).fill(supplierName); await page.getByRole('button', { name: 'Submit' }).click(); // Is prevented, due to uniqueness requirements diff --git a/src/frontend/tests/pui_settings.spec.ts b/src/frontend/tests/pui_settings.spec.ts index aac3c4e7d0..330b1eb3a3 100644 --- a/src/frontend/tests/pui_settings.spec.ts +++ b/src/frontend/tests/pui_settings.spec.ts @@ -270,16 +270,20 @@ test('Settings - Admin', async ({ browser }) => { await roomRow.getByLabel(/row-action-menu-/i).click(); await page.getByRole('menuitem', { name: 'Edit' }).click(); - await expect(page.getByLabel('text-field-name')).toHaveValue('Room'); + await expect(page.getByLabel('text-field-name', { exact: true })).toHaveValue( + 'Room' + ); // Toggle the "description" field const oldDescription = await page - .getByLabel('text-field-description') + .getByLabel('text-field-description', { exact: true }) .inputValue(); const newDescription = `${oldDescription} (edited)`; - await page.getByLabel('text-field-description').fill(newDescription); + await page + .getByLabel('text-field-description', { exact: true }) + .fill(newDescription); await page.waitForTimeout(500); await page.getByRole('button', { name: 'Submit' }).click(); @@ -293,19 +297,21 @@ test('Settings - Admin', async ({ browser }) => { await boxRow.getByLabel(/row-action-menu-/i).click(); await page.getByRole('menuitem', { name: 'Edit' }).click(); - await expect(page.getByLabel('text-field-name')).toHaveValue('Box (Large)'); - await expect(page.getByLabel('text-field-description')).toHaveValue( - 'Large cardboard box' + await expect(page.getByLabel('text-field-name', { exact: true })).toHaveValue( + 'Box (Large)' ); + await expect( + page.getByLabel('text-field-description', { exact: true }) + ).toHaveValue('Large cardboard box'); await page.getByRole('button', { name: 'Cancel' }).click(); // Edit first item again (revert values) await roomRow.getByLabel(/row-action-menu-/i).click(); await page.getByRole('menuitem', { name: 'Edit' }).click(); - await page.getByLabel('text-field-name').fill('Room'); + await page.getByLabel('text-field-name', { exact: true }).fill('Room'); await page.waitForTimeout(500); await page - .getByLabel('text-field-description') + .getByLabel('text-field-description', { exact: true }) .fill(newDescription.replaceAll(' (edited)', '')); await page.waitForTimeout(500); await page.getByRole('button', { name: 'Submit' }).click(); diff --git a/src/frontend/tests/pui_tables.spec.ts b/src/frontend/tests/pui_tables.spec.ts index f2d61ab62c..00c10a0dc8 100644 --- a/src/frontend/tests/pui_tables.spec.ts +++ b/src/frontend/tests/pui_tables.spec.ts @@ -50,25 +50,25 @@ test('Tables - Pagination', async ({ browser }) => { // Expected pagination size is 25 // Note: Due to other tests, there may be more than 25 items in the list - await page.getByText(/1 - 25 \/ 2[2|8]/).waitFor(); + await page.getByText(/1 - 25 \/ \d+/).waitFor(); await page.getByRole('button', { name: 'Next page' }).click(); - await page.getByText(/26 - 2[7|8] \/ 2[7|8]/).waitFor(); + await page.getByText(/26 - \d+ \/ \d+/).waitFor(); // Set page size to 10 await page.getByRole('button', { name: '25' }).click(); await page.getByRole('menuitem', { name: '10', exact: true }).click(); - await page.getByText(/1 - 10 \/ 2[7|8]/).waitFor(); + await page.getByText(/1 - 10 \/ \d+/).waitFor(); await page.getByRole('button', { name: '3' }).click(); - await page.getByText(/21 - 2[7|8] \/ 2[7|8]/).waitFor(); + await page.getByText(/21 - \d+ \/ \d+/).waitFor(); await page.getByRole('button', { name: 'Previous page' }).click(); - await page.getByText(/11 - 20 \/ 2[7|8]/).waitFor(); + await page.getByText(/11 - 20 \/ \d+/).waitFor(); // Set page size back to 25 await page.getByRole('button', { name: '10' }).click(); await page.getByRole('menuitem', { name: '25', exact: true }).click(); - await page.getByText(/1 - 25 \/ 2[7|8]/).waitFor(); + await page.getByText(/1 - 25 \/ \d+/).waitFor(); }); test('Tables - Columns', async ({ browser }) => {