mirror of
https://github.com/inventree/InvenTree.git
synced 2026-07-17 20:23:50 +00:00
* Implement API endpoint for stock disassembly * Add basic frontend implementation * Adjust required user permission * Add preFormContent * Read-only if serialized * Adjust location and status of each subcomponent * Handle null value * display installed items in frontend form * More unit tests * Traceability * Add docs * Exclude virtual / consumable stock * Additional docs * Added bundled items docs * More docs * more docs tweaks * Updated part docs * Add playwright tests * Robustify test * suppress certain warnings in schema * Adjust playwright tests * bug fix * Tweak playwright tests
318 lines
11 KiB
TypeScript
318 lines
11 KiB
TypeScript
import { createApi } from './api';
|
|
/** Unit tests for form validation, rendering, etc */
|
|
import { expect, test } from './baseFixtures';
|
|
import { stevenuser } from './defaults';
|
|
import { clickOnRowMenu, loadTab, navigate, openDetailAction } from './helpers';
|
|
import { doCachedLogin } from './login';
|
|
|
|
// Test hover form action in related fields
|
|
test('Forms - Hover', async ({ browser }) => {
|
|
const page = await doCachedLogin(browser, {
|
|
user: stevenuser,
|
|
url: 'purchasing/index/purchaseorders'
|
|
});
|
|
|
|
// Patch user settings to ensure we can see "extra model info" on hover
|
|
const api = await createApi({
|
|
username: stevenuser.username,
|
|
password: stevenuser.testcred
|
|
});
|
|
|
|
const response = await api.patch('settings/user/SHOW_EXTRA_MODEL_INFO/', {
|
|
data: {
|
|
value: 'true'
|
|
}
|
|
});
|
|
|
|
expect(response.status()).toBe(200);
|
|
|
|
await page
|
|
.getByRole('button', { name: 'action-button-add-purchase-' })
|
|
.click();
|
|
await page.getByLabel('related-field-supplier').fill('mou');
|
|
await page.waitForLoadState('networkidle');
|
|
await page.waitForTimeout(250);
|
|
await page.getByText('Mouser Electronics').first().hover();
|
|
|
|
// Check for hover info
|
|
await page.getByText('Company[ID: 2]').waitFor();
|
|
await page.getByRole('link', { name: 'View details' }).waitFor();
|
|
});
|
|
|
|
test('Forms - Stock Item Validation', async ({ browser }) => {
|
|
const page = await doCachedLogin(browser, {
|
|
user: stevenuser,
|
|
url: 'stock/location/index/stock-items'
|
|
});
|
|
|
|
await page.waitForURL('**/web/stock/location/**');
|
|
|
|
// Create new stock item form
|
|
await page.getByLabel('action-button-add-stock-item').click();
|
|
await page.getByRole('button', { name: 'Submit' }).click();
|
|
|
|
// Check for validation errors
|
|
await page.getByText('Form Error').waitFor();
|
|
await page.getByText('Errors exist for one or more form fields').waitFor();
|
|
await page.getByText('Valid part must be supplied').waitFor();
|
|
|
|
// Adjust other field - the errors should persist
|
|
await page.getByLabel('text-field-batch', { exact: true }).fill('BATCH-123');
|
|
await page.waitForTimeout(250);
|
|
|
|
await page.getByText('Valid part must be supplied').waitFor();
|
|
|
|
// Fill out fields
|
|
await page.getByLabel('number-field-quantity').fill('-1');
|
|
await page.getByLabel('related-field-part').click();
|
|
await page.getByRole('option', { name: /1551AGY/ }).click();
|
|
await page.getByRole('button', { name: 'Submit' }).click();
|
|
|
|
// Check for validation errors
|
|
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/)
|
|
.first()
|
|
.waitFor();
|
|
|
|
// Set location
|
|
await page.getByLabel('tree-field-location').fill('production');
|
|
await page.getByText('Electronics production facility').click();
|
|
|
|
// Create the stock item
|
|
await page.getByLabel('number-field-quantity').fill('123');
|
|
await page.getByRole('button', { name: 'Submit' }).click();
|
|
|
|
// Edit the resulting stock item
|
|
await openDetailAction(page, 'stock-item', 'edit');
|
|
|
|
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', { exact: true }).fill('a box');
|
|
await page.waitForTimeout(250);
|
|
await page
|
|
.getByText('Ensure this value is greater than or equal to 0')
|
|
.waitFor();
|
|
|
|
// Correct the price
|
|
await page.getByLabel('number-field-purchase_price').fill('1.2345');
|
|
await page.getByRole('button', { name: 'Submit' }).click();
|
|
await page.getByText('Item Updated').waitFor();
|
|
|
|
// Ensure the stock item has been updated correctly
|
|
await page.getByText('$151.8435').waitFor();
|
|
await page.getByText('$151.8435').waitFor();
|
|
await page.getByText('a box').waitFor();
|
|
await page.getByRole('cell', { name: 'Electronics Lab' }).waitFor();
|
|
});
|
|
|
|
test('Forms - Supplier Validation', async ({ browser }) => {
|
|
const page = await doCachedLogin(browser, {
|
|
user: stevenuser,
|
|
url: 'purchasing/index/suppliers'
|
|
});
|
|
await page.waitForURL('**/purchasing/index/**');
|
|
|
|
await page.getByLabel('action-button-add-company').click();
|
|
await page
|
|
.getByLabel('text-field-website', { exact: true })
|
|
.fill('not-a-website');
|
|
|
|
await page.getByRole('button', { name: 'Submit' }).click();
|
|
|
|
// Check for validation errors
|
|
await page.getByText('Form Error').waitFor();
|
|
await page.getByText('Errors exist for one or more').waitFor();
|
|
await page.getByText('This field is required').waitFor();
|
|
await page.getByText('Enter a valid URL.').waitFor();
|
|
|
|
// Fill out another field, expect that the errors persist
|
|
await page
|
|
.getByLabel('text-field-description', { exact: true })
|
|
.fill('A description');
|
|
await page.waitForTimeout(250);
|
|
await page.getByText('This field is required').waitFor();
|
|
await page.getByText('Enter a valid URL.').waitFor();
|
|
|
|
// Generate a unique supplier name
|
|
const supplierName = `Supplier ${new Date().getTime()}`;
|
|
|
|
// Fill with good data
|
|
await page
|
|
.getByLabel('text-field-website', { exact: true })
|
|
.fill('https://www.test-website.co.uk');
|
|
await page.getByLabel('text-field-name', { exact: true }).fill(supplierName);
|
|
await page.getByRole('button', { name: 'Submit' }).click();
|
|
|
|
await page.getByText('A description').first().waitFor();
|
|
await page
|
|
.getByRole('link', { name: 'https://www.test-website.co.uk' })
|
|
.waitFor();
|
|
|
|
// Now, try to create another new supplier with the same name
|
|
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', { exact: true }).fill(supplierName);
|
|
await page.getByRole('button', { name: 'Submit' }).click();
|
|
|
|
// Is prevented, due to uniqueness requirements
|
|
await page.getByText('Form Error').waitFor();
|
|
await page.getByRole('button', { name: 'Cancel' }).click();
|
|
});
|
|
|
|
test('Forms - Keep form open option', async ({ browser }) => {
|
|
const page = await doCachedLogin(browser, {
|
|
user: stevenuser,
|
|
url: 'stock/location/index/sublocations'
|
|
});
|
|
await page.waitForURL('**/stock/location/index/**');
|
|
|
|
await page.getByLabel('action-button-add-stock-location').click();
|
|
|
|
// Generate unique location name
|
|
const locationName = `New Sublocation ${new Date().getTime()}`;
|
|
|
|
await page.getByLabel('text-field-name', { exact: true }).fill(locationName);
|
|
|
|
// Check keep form open switch and submit
|
|
await page.getByRole('switch', { name: 'Keep form open' }).click();
|
|
await page.getByRole('button', { name: 'Submit' }).click();
|
|
|
|
// Location should be created, form should remain opened
|
|
await page.getByText('Item Created').waitFor();
|
|
await expect(page.getByRole('dialog')).toBeVisible();
|
|
|
|
// Create another location and uncheck this option
|
|
await page
|
|
.getByLabel('text-field-name', { exact: true })
|
|
.fill(`Another ${locationName}`);
|
|
await page.getByRole('switch', { name: 'Keep form open' }).click();
|
|
await page.getByRole('button', { name: 'Submit' }).click();
|
|
|
|
// Location should be created, and the form (modal) should disappear
|
|
await page.getByText('Item Created').waitFor();
|
|
await expect(page.getByRole('dialog')).toBeHidden();
|
|
});
|
|
|
|
test('Forms - DateTime Field', async ({ browser }) => {
|
|
// The "started_datetime" / "finished_datetime" test-result fields are only
|
|
// shown when the "TEST_STATION_DATA" global setting is enabled
|
|
const api = await createApi({});
|
|
const settingUrl = 'settings/global/TEST_STATION_DATA/';
|
|
|
|
const enableResponse = await api.patch(settingUrl, {
|
|
data: { value: 'true' }
|
|
});
|
|
expect(enableResponse.status()).toBe(200);
|
|
|
|
const page = await doCachedLogin(browser, {
|
|
user: stevenuser,
|
|
url: 'stock/item/1194/test_results'
|
|
});
|
|
await page.waitForURL('**/web/stock/**');
|
|
await loadTab(page, 'Test Results');
|
|
|
|
const cell = page.getByText('395c6d5586e5fb656901d047be27e1f7');
|
|
await clickOnRowMenu(cell);
|
|
await page.getByRole('menuitem', { name: 'Edit' }).click();
|
|
|
|
// Set a value via the Mantine DateTimePicker popup - a button which opens a
|
|
// calendar (for the date) plus separate hour/minute/second spinbuttons
|
|
const setDateTime = async (
|
|
fieldLabel: string,
|
|
day: string,
|
|
hour: string,
|
|
minute: string
|
|
) => {
|
|
const field = page.getByLabel(fieldLabel).first();
|
|
await expect(field).toBeVisible();
|
|
await field.click();
|
|
await page.getByRole('button', { name: day }).first().click();
|
|
|
|
const spinbuttons = page.getByRole('spinbutton');
|
|
await spinbuttons.nth(0).fill(hour);
|
|
await spinbuttons.nth(1).fill(minute);
|
|
await spinbuttons.nth(2).fill('00');
|
|
|
|
await page.keyboard.press('Escape');
|
|
};
|
|
|
|
await setDateTime(
|
|
'date-time-field-started_datetime',
|
|
'10 March 2024',
|
|
'10',
|
|
'30'
|
|
);
|
|
await setDateTime(
|
|
'date-time-field-finished_datetime',
|
|
'11 March 2024',
|
|
'11',
|
|
'45'
|
|
);
|
|
|
|
await expect(page.getByLabel('date-time-field-started_datetime')).toHaveText(
|
|
'2024-03-10 10:30:00'
|
|
);
|
|
await expect(page.getByLabel('date-time-field-finished_datetime')).toHaveText(
|
|
'2024-03-11 11:45:00'
|
|
);
|
|
|
|
await page.getByRole('button', { name: 'Submit' }).click();
|
|
await page.getByText('Test result updated').waitFor();
|
|
|
|
// Restore the global setting to its default value
|
|
const disableResponse = await api.patch(settingUrl, {
|
|
data: { value: 'false' }
|
|
});
|
|
expect(disableResponse.status()).toBe(200);
|
|
});
|
|
|
|
// Test the "nested object" field type, via the "Initial Supplier" section
|
|
// of the "Create Part" form
|
|
test('Forms - Nested Object Field', async ({ browser }) => {
|
|
const page = await doCachedLogin(browser, {
|
|
user: stevenuser,
|
|
url: 'part/category/index/parts'
|
|
});
|
|
await page.waitForURL('**/part/category/index/**');
|
|
|
|
await page.getByRole('button', { name: 'action-menu-add-parts' }).click();
|
|
await page
|
|
.getByRole('menuitem', { name: 'action-menu-add-parts-create-part' })
|
|
.click();
|
|
|
|
// Generate a unique part name
|
|
const partName = `Test Part ${new Date().getTime()}`;
|
|
await page.getByLabel('text-field-name', { exact: true }).fill(partName);
|
|
|
|
// The "Initial Supplier" nested-object field should be visible and expanded by default
|
|
await page.getByText('Supplier Information').waitFor();
|
|
|
|
const supplierField = page.getByLabel(
|
|
'related-field-initial_supplier.supplier'
|
|
);
|
|
await expect(supplierField).toBeVisible();
|
|
await supplierField.fill('Mouser');
|
|
await page.getByText('Mouser Electronics').first().click();
|
|
|
|
const skuValue = `SKU-${new Date().getTime()}`;
|
|
await page
|
|
.getByLabel('text-field-initial_supplier.sku', { exact: true })
|
|
.fill(skuValue);
|
|
|
|
await page.getByRole('button', { name: 'Submit' }).click();
|
|
await page.getByText('Item Created').waitFor();
|
|
|
|
// Confirm the part was created with the expected name
|
|
await page.getByText(partName).first().waitFor();
|
|
});
|