[UI] Tree select in form fields (#12217)

* Add TreeField component

* Add chevrons in dropdown menu

* Custom node rendering

* Add childIdentifier

* Ensure full value gets displayed

* Fix for search results

* Refactor old renderers

* Override field types

* use definition filters

* Better location validation messages

* Tweaks

* Fix unit test

* Include icon

* Fix playwright test

* Fix call to onValueChange

* Updated playwright tests

* Use pathstring in selectedLabel (if available)

* Mark structural nodes

* Retain selected value when dropdown open

* Add better placeholder values

* Simplify field selection
This commit is contained in:
Oliver
2026-06-30 22:59:04 +10:00
committed by GitHub
parent 7a81aa216f
commit 09f85aeae9
13 changed files with 688 additions and 62 deletions
+13 -16
View File
@@ -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
+3 -5
View File
@@ -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');
+2 -1
View File
@@ -499,7 +499,8 @@ test('Stock - Tracking', async ({ browser }) => {
// Navigate to the "stock tracking" tab
await loadTab(page, 'Stock Tracking');
await page.getByText('- - Factory/Office Block/Room').first().waitFor();
await page.getByText('Factory/Office Block/Room').first().waitFor();
await page.getByRole('link', { name: 'Widget Assembly' }).waitFor();
await page.getByRole('cell', { name: 'Installed into assembly' }).waitFor();