mirror of
https://github.com/inventree/InvenTree.git
synced 2026-09-03 03:18:46 +00:00
[UI] Mantine 9 (#11947)
* Update mantine deps * Get it to compile * Update QueryCount widgets - use new RollingNumber component * Table updates - Remove "hack" for column ordering * "Fix" the column pinning bug (maybe?) * Fix ColumnRenderers.tsx * Fix login code for playwright * Remove hashing requirement * Fix build tests * More fixes * More test fixes * Fix playwright test for dashboard item * Update frontend version * Update changelog * Reduce query repeats * More playwright fixes * Further playwright fixes * Fix for useFilterSet hook * Fix unique key error * Fix rendering issues when opening edit forms * reduce console errors * Fix unique key issues in search drawer * Update frontend CHANGELOG.md * More form tweaks --------- Co-authored-by: Matthias Mair <code@mjmair.com>
This commit is contained in:
@@ -36,8 +36,8 @@ export const doLogin = async (page: Page, options?: LoginOptions) => {
|
||||
|
||||
await page.waitForURL('**/web/login');
|
||||
|
||||
await page.getByLabel('username').fill(username);
|
||||
await page.getByLabel('password').fill(password);
|
||||
await page.getByRole('textbox', { name: 'login-username' }).fill(username);
|
||||
await page.getByRole('textbox', { name: 'login-password' }).fill(password);
|
||||
|
||||
await page.waitForTimeout(100);
|
||||
|
||||
|
||||
@@ -225,6 +225,7 @@ test('Build Order - Build Outputs', async ({ browser }) => {
|
||||
|
||||
await page.getByRole('cell', { name: 'BO0011' }).click();
|
||||
await loadTab(page, 'Incomplete Outputs');
|
||||
await page.getByRole('cell', { name: 'BX-123' }).waitFor();
|
||||
|
||||
// Check the "printing" actions for the selected outputs
|
||||
await page.getByRole('checkbox', { name: 'Select all records' }).check();
|
||||
@@ -523,6 +524,8 @@ test('Build Order - Consume Stock', async ({ browser }) => {
|
||||
|
||||
// Consume the rest of the stock via line items
|
||||
await loadTab(page, 'Required Parts');
|
||||
await page.getByText('10K resistor in 0805 SMD').first().waitFor();
|
||||
|
||||
await page.getByRole('checkbox', { name: 'Select all records' }).check();
|
||||
await page
|
||||
.getByRole('button', { name: 'action-button-consume-stock' })
|
||||
@@ -603,7 +606,7 @@ test('Build Order - Tracked Outputs', async ({ browser }) => {
|
||||
await allocationRow.getByText('1 / 1').waitFor();
|
||||
|
||||
// Close the allocation wizard
|
||||
await page.getByRole('banner').getByRole('button').click();
|
||||
await page.getByRole('button', { name: 'close-allocation-drawer' }).click();
|
||||
|
||||
// Check that the output is now allocated as expected
|
||||
await row.getByText('1 / 6').waitFor();
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type { Page } from '@playwright/test';
|
||||
import { expect } from '@playwright/test';
|
||||
import { createApi } from '../api.js';
|
||||
import { test } from '../baseFixtures.js';
|
||||
import { allaccessuser } from '../defaults.js';
|
||||
import { doCachedLogin } from '../login.js';
|
||||
import { setPluginState } from '../settings.js';
|
||||
|
||||
@@ -96,9 +98,7 @@ test('Dashboard - Plugins', async ({ browser }) => {
|
||||
await page.getByText('Hello world! This is a sample').waitFor();
|
||||
});
|
||||
|
||||
test('Dashboard - Preserve widget sizes when adding new widget', async ({
|
||||
browser
|
||||
}) => {
|
||||
test('Dashboard - Preserve widget sizes', async ({ browser }) => {
|
||||
// Regression: addWidget previously snapped every existing widget back to
|
||||
// its minW/minH. Fix is in DashboardLayout.tsx::addWidget (overrideSize=false).
|
||||
const TARGET_W = 10;
|
||||
@@ -110,7 +110,11 @@ test('Dashboard - Preserve widget sizes when adding new widget', async ({
|
||||
return raw ? (JSON.parse(raw)?.state?.layouts ?? {}) : {};
|
||||
});
|
||||
|
||||
const page = await doCachedLogin(browser);
|
||||
const user = allaccessuser;
|
||||
|
||||
const page = await doCachedLogin(browser, {
|
||||
user: user
|
||||
});
|
||||
await resetDashboard(page);
|
||||
|
||||
// Add widget A; this also persists to the backend user profile.
|
||||
@@ -120,7 +124,7 @@ test('Dashboard - Preserve widget sizes when adding new widget', async ({
|
||||
await page.getByLabel('add-widget-ovr-so').click();
|
||||
await page.getByRole('banner').getByRole('button').click();
|
||||
await page.getByText('Overdue Sales Orders').waitFor();
|
||||
await page.waitForTimeout(500);
|
||||
await page.waitForTimeout(100);
|
||||
|
||||
// Inflate widget A on the backend profile and reload. The auth flow on
|
||||
// page load rehydrates layouts from the profile, not localStorage, so a
|
||||
@@ -132,26 +136,31 @@ test('Dashboard - Preserve widget sizes when adding new widget', async ({
|
||||
it?.i === 'ovr-so' ? { ...it, w: TARGET_W, h: TARGET_H } : it
|
||||
);
|
||||
}
|
||||
await page.evaluate(async (layouts) => {
|
||||
const csrf = document.cookie.match(/csrftoken=([^;]+)/)?.[1] ?? '';
|
||||
await fetch('/api/user/profile/', {
|
||||
method: 'PATCH',
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrf
|
||||
},
|
||||
body: JSON.stringify({ widgets: { widgets: ['ovr-so'], layouts } })
|
||||
});
|
||||
}, inflated);
|
||||
|
||||
const api = createApi({
|
||||
username: user.username,
|
||||
password: user.testcred
|
||||
});
|
||||
|
||||
(await api).patch('user/profile/', {
|
||||
data: {
|
||||
widgets: {
|
||||
widgets: ['ovr-so'],
|
||||
layouts: inflated
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await page.reload();
|
||||
await page.getByText('Overdue Sales Orders').waitFor();
|
||||
await page.waitForTimeout(500);
|
||||
await page.waitForTimeout(100);
|
||||
|
||||
// Sanity: profile rehydration produced the inflated values.
|
||||
for (const [bp, items] of Object.entries(await readLayouts(page))) {
|
||||
const entry = (items as any[]).find((i) => i?.i === 'ovr-so');
|
||||
|
||||
console.log('entry:', bp, entry);
|
||||
|
||||
expect(entry?.w, `${bp}: ovr-so missing or wrong w`).toBe(TARGET_W);
|
||||
expect(entry?.h, `${bp}: ovr-so missing or wrong h`).toBe(TARGET_H);
|
||||
}
|
||||
@@ -163,7 +172,7 @@ test('Dashboard - Preserve widget sizes when adding new widget', async ({
|
||||
await page.getByLabel('add-widget-ovr-po').click();
|
||||
await page.getByRole('banner').getByRole('button').click();
|
||||
await page.getByText('Overdue Purchase Orders').waitFor();
|
||||
await page.waitForTimeout(800);
|
||||
await page.waitForTimeout(100);
|
||||
|
||||
for (const [bp, items] of Object.entries(await readLayouts(page))) {
|
||||
const entry = (items as any[]).find((i) => i?.i === 'ovr-so');
|
||||
|
||||
@@ -344,7 +344,9 @@ test('Parts - BOM Comparison', async ({ browser }) => {
|
||||
await page.getByText('Removed from BOM').first().waitFor();
|
||||
|
||||
// Change display mode
|
||||
await page.getByRole('textbox', { name: 'bom-compare-display-mode' }).click();
|
||||
await page
|
||||
.getByRole('combobox', { name: 'bom-compare-display-mode' })
|
||||
.click();
|
||||
await page.getByRole('option', { name: 'Show different Parts' }).click();
|
||||
|
||||
// Use URL params to compare directly
|
||||
|
||||
@@ -162,7 +162,7 @@ test('Purchasing - Manufacturer Parts', async ({ browser }) => {
|
||||
await page.getByRole('button', { name: 'table-export-data' }).click();
|
||||
await page.getByText('Select export plugin').waitFor();
|
||||
await page
|
||||
.getByRole('textbox', { name: 'choice-field-export_plugin' })
|
||||
.getByRole('combobox', { name: 'choice-field-export_plugin' })
|
||||
.fill('CSV');
|
||||
await page.getByRole('button', { name: 'Export', exact: true }).click();
|
||||
await page.getByText('Process completed successfully').waitFor();
|
||||
@@ -509,8 +509,9 @@ test('Purchase Orders - Receive Items', async ({ browser }) => {
|
||||
// Select all line items to receive
|
||||
await loadTab(page, 'Line Items');
|
||||
|
||||
await page.getByRole('cell', { name: '002.02-PCB' }).waitFor();
|
||||
await page.getByLabel('Select all records').click();
|
||||
await page.waitForTimeout(200);
|
||||
await page.waitForTimeout(100);
|
||||
await page.getByLabel('action-button-receive-items').click();
|
||||
|
||||
// Check for display of individual locations
|
||||
@@ -606,6 +607,8 @@ test('Purchase Orders - Receive Virtual Items', async ({ browser }) => {
|
||||
|
||||
// Receive the line item
|
||||
await loadTab(page, 'Line Items');
|
||||
await page.getByRole('cell', { name: 'Thumbnail CRM license' }).waitFor();
|
||||
|
||||
await page.getByRole('checkbox', { name: 'Select all records' }).click();
|
||||
await page
|
||||
.getByRole('button', { name: 'action-button-receive-items' })
|
||||
|
||||
@@ -12,6 +12,8 @@ test('Return Orders - Receive Items', async ({ browser }) => {
|
||||
await loadTab(page, 'Attachments');
|
||||
await loadTab(page, 'Line Items');
|
||||
|
||||
await page.getByRole('cell', { name: 'WID-REV-A' }).first().waitFor();
|
||||
|
||||
await page.getByRole('checkbox', { name: 'Select all records' }).click();
|
||||
await page.getByRole('button', { name: 'action-button-receive-' }).click();
|
||||
await page.getByRole('banner').getByText('Receive Items').waitFor();
|
||||
|
||||
@@ -111,14 +111,14 @@ test('Stock - Location Delete', async ({ browser }) => {
|
||||
.click();
|
||||
|
||||
await page
|
||||
.getByRole('textbox', { name: 'choice-field-delete_stock_items' })
|
||||
.getByRole('combobox', { name: 'choice-field-delete_stock_items' })
|
||||
.click();
|
||||
await page
|
||||
.getByRole('option', { name: 'Move items to parent location' })
|
||||
.click();
|
||||
|
||||
await page
|
||||
.getByRole('textbox', { name: 'choice-field-delete_sub_locations' })
|
||||
.getByRole('combobox', { name: 'choice-field-delete_sub_locations' })
|
||||
.click();
|
||||
await page.getByRole('option', { name: 'Delete items' }).click();
|
||||
|
||||
|
||||
@@ -6,7 +6,8 @@ import { doCachedLogin } from './login';
|
||||
// Helper function to open the export data dialog
|
||||
const openExportDialog = async (page) => {
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.getByLabel('table-export-data').click();
|
||||
|
||||
await page.getByRole('button', { name: 'table-export-data' }).click();
|
||||
await page.getByText('Export Format *', { exact: true }).waitFor();
|
||||
await page.getByText('Export Plugin *', { exact: true }).waitFor();
|
||||
};
|
||||
|
||||
@@ -26,7 +26,7 @@ test('Importing - Admin Center', async ({ browser }) => {
|
||||
await page.getByText('Errors exist for one or more').waitFor();
|
||||
|
||||
await page
|
||||
.getByRole('textbox', { name: 'choice-field-model_type' })
|
||||
.getByRole('combobox', { name: 'choice-field-model_type' })
|
||||
.fill('bom');
|
||||
await page.getByRole('option', { name: 'BOM Item', exact: true }).click();
|
||||
await page.getByRole('button', { name: 'Submit' }).click();
|
||||
@@ -36,7 +36,7 @@ test('Importing - Admin Center', async ({ browser }) => {
|
||||
await page.getByText('Existing database identifier for the record').waitFor();
|
||||
|
||||
await page
|
||||
.getByRole('textbox', { name: 'import-column-map-reference' })
|
||||
.getByRole('combobox', { name: 'import-column-map-reference' })
|
||||
.click();
|
||||
await page.getByRole('option', { name: 'Ignore this field' }).click();
|
||||
|
||||
@@ -195,7 +195,7 @@ test('Importing - Natural Keys', async ({ browser }) => {
|
||||
|
||||
// Select different columns for data import
|
||||
// We will use the "SKU" field to map to the supplier part
|
||||
await page.getByRole('textbox', { name: 'import-column-map-part' }).click();
|
||||
await page.getByRole('combobox', { name: 'import-column-map-part' }).click();
|
||||
await page.getByRole('option', { name: 'SKU' }).click();
|
||||
|
||||
// Other import fields will be left as default
|
||||
|
||||
@@ -47,14 +47,17 @@ test('Machines - Activation', async ({ browser }) => {
|
||||
.getByRole('textbox', { name: 'text-field-name' })
|
||||
.fill('my-dummy-machine');
|
||||
await page
|
||||
.getByRole('textbox', { name: 'choice-field-machine_type' })
|
||||
.getByRole('combobox', { name: 'choice-field-machine_type' })
|
||||
.fill('label');
|
||||
await page.getByRole('option', { name: 'Label Printer' }).click();
|
||||
|
||||
await page.getByRole('textbox', { name: 'choice-field-driver' }).click();
|
||||
await page.getByRole('combobox', { name: 'choice-field-driver' }).click();
|
||||
await page.waitForTimeout(200);
|
||||
await page
|
||||
.getByRole('option', { name: 'Sample Label Printer Driver' })
|
||||
.click();
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.getByRole('button', { name: 'Submit' }).click();
|
||||
} else {
|
||||
// Machine already exists - just click on it to open the "machine drawer"
|
||||
@@ -64,10 +67,13 @@ test('Machines - Activation', async ({ browser }) => {
|
||||
// Creating the new machine opens the "machine drawer"
|
||||
|
||||
// Check for "machine type" settings
|
||||
await page.getByText('Scope the printer to a specific location').waitFor();
|
||||
await page
|
||||
.getByText('Scope the printer to a specific location')
|
||||
.first()
|
||||
.waitFor();
|
||||
|
||||
// Check for "machine driver" settings
|
||||
await page.getByText('Custom string for connecting').waitFor();
|
||||
await page.getByText('Custom string for connecting').first().waitFor();
|
||||
|
||||
// Edit the available setting
|
||||
await page.getByRole('button', { name: 'edit-setting-CONNECTION' }).click();
|
||||
@@ -94,6 +100,7 @@ test('Machines - Activation', async ({ browser }) => {
|
||||
|
||||
// Let's print something with the machine
|
||||
await navigate(page, 'stock/location/1/stock-items');
|
||||
await page.getByText('Blue plastic enclosure').first().waitFor();
|
||||
|
||||
await page.getByRole('checkbox', { name: 'Select all records' }).check();
|
||||
await page
|
||||
@@ -110,7 +117,7 @@ test('Machines - Activation', async ({ browser }) => {
|
||||
await page.getByText('InvenTreeLabelMachine').click();
|
||||
|
||||
await page
|
||||
.getByRole('textbox', { name: 'choice-field-machine' })
|
||||
.getByRole('combobox', { name: 'choice-field-machine' })
|
||||
.fill('dummy');
|
||||
await page.getByRole('option', { name: 'my-dummy-machine' }).click();
|
||||
|
||||
|
||||
@@ -45,9 +45,9 @@ test('Settings - User theme', async ({ browser }) => {
|
||||
await page.getByRole('menuitem', { name: 'User settings' }).click();
|
||||
|
||||
// loader
|
||||
await page.getByRole('textbox', { name: 'Loader Type Selector' }).click();
|
||||
await page.getByRole('combobox', { name: 'Loader Type Selector' }).click();
|
||||
await page.getByRole('option', { name: 'Oval' }).click();
|
||||
await page.getByRole('textbox', { name: 'Loader Type Selector' }).click();
|
||||
await page.getByRole('combobox', { name: 'Loader Type Selector' }).click();
|
||||
await page.getByRole('option', { name: 'Bars' }).click();
|
||||
|
||||
// dark / light mode
|
||||
@@ -92,7 +92,7 @@ test('Settings - User', async ({ browser }) => {
|
||||
await page.getByText('Profile Details').waitFor();
|
||||
|
||||
// Language selection
|
||||
await page.getByRole('textbox', { name: 'Select language' }).click();
|
||||
await page.getByRole('combobox', { name: 'Select language' }).click();
|
||||
await page.getByRole('option', { name: 'العربية' }).waitFor();
|
||||
await page.getByRole('option', { name: 'Deutsch' }).waitFor();
|
||||
await page.getByRole('option', { name: 'English' }).waitFor();
|
||||
@@ -321,8 +321,8 @@ test('Settings - Admin - Background Tasks', async ({ browser }) => {
|
||||
|
||||
// Background worker should be running, and idle
|
||||
await page.getByText('Background worker running').waitFor();
|
||||
await page.getByText('Failed Tasks0').waitFor();
|
||||
await page.getByText('Pending Tasks0').waitFor();
|
||||
await page.getByText(/Failed Tasks\d+/).waitFor();
|
||||
await page.getByText(/Pending Tasks\d+/).waitFor();
|
||||
|
||||
// Expand the "scheduled tasks" view
|
||||
await page.getByRole('button', { name: 'Scheduled Tasks' }).click();
|
||||
|
||||
Reference in New Issue
Block a user