test(products): add regression tests for GROUP_CONCAT NULL bug and reload path
- 'renders summary table without crash when product has empty colors and locations': mocks getProductSummary returning a product with colors/locations='', verifying the page does not crash and calls the API (reproduces the previously-broken scenario). - 'closes dialog after successful product creation (reload branch reached)': verifies that a successful form submission calls createProduct and the ModalForm closes, confirming onFinish returned true — the same code branch that triggers summaryRef.reload().
This commit is contained in:
parent
70532dda3a
commit
0db2be28d8
@ -1,11 +1,151 @@
|
||||
import { describe, vi } from 'vitest';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { render, screen, waitFor, act, within } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
vi.mock('@umijs/max');
|
||||
vi.mock('@/services/api');
|
||||
|
||||
import { smokeTest } from '../../../../test/smoke';
|
||||
import Page from './index';
|
||||
import { createProduct, getProductSummary } from '@/services/api';
|
||||
|
||||
describe('Inventory / Products page', () => {
|
||||
smokeTest(Page);
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
smokeTest(Page);
|
||||
|
||||
it('renders 新增产品 toolbar button', async () => {
|
||||
render(<Page />);
|
||||
await act(async () => {
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
});
|
||||
expect(screen.getByRole('button', { name: /新增产品/ })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('opens create dialog when 新增产品 is clicked', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<Page />);
|
||||
await act(async () => {
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
await user.click(screen.getByRole('button', { name: /新增产品/ }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('dialog')).toBeInTheDocument();
|
||||
});
|
||||
expect(within(screen.getByRole('dialog')).getByText('新增产品')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls createProduct after filling form and submitting', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<Page />);
|
||||
await act(async () => {
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
await user.click(screen.getByRole('button', { name: /新增产品/ }));
|
||||
await waitFor(() => screen.getByRole('dialog'));
|
||||
|
||||
const dialog = screen.getByRole('dialog');
|
||||
const nameInput = within(dialog).getByRole('textbox', { name: /产品名称/ });
|
||||
await user.type(nameInput, '测试产品');
|
||||
|
||||
// Click the submit/OK button – find the non-close button in the dialog footer
|
||||
const buttons = within(dialog).getAllByRole('button');
|
||||
const submitBtn = buttons.find((b) => {
|
||||
const text = b.textContent?.trim() ?? '';
|
||||
return text && !text.includes('×') && !text.includes('✕') && !/取/.test(text) && !/重/.test(text) && !/Cancel/i.test(text);
|
||||
});
|
||||
if (submitBtn) await user.click(submitBtn);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(vi.mocked(createProduct)).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders Excel export button', async () => {
|
||||
render(<Page />);
|
||||
await act(async () => {
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
});
|
||||
expect(screen.getByRole('button', { name: /Excel导出/ })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Regression: when a product has no pans the backend returns empty strings for
|
||||
// colors/locations (via COALESCE). Previously GROUP_CONCAT returned NULL which
|
||||
// crashed the Go scanner and made the entire summary return an error, leaving the
|
||||
// table empty. This test ensures the frontend handles the fixed response correctly.
|
||||
it('renders summary table without crash when product has empty colors and locations', async () => {
|
||||
vi.mocked(getProductSummary).mockResolvedValueOnce({
|
||||
code: 0,
|
||||
msg: 'ok',
|
||||
data: {
|
||||
list: [
|
||||
{
|
||||
productName: '无盘测试布料',
|
||||
spec: '100cm',
|
||||
colorCount: 1,
|
||||
totalPanCount: 0,
|
||||
totalBoltCount: 0,
|
||||
totalLengthM: '0.00',
|
||||
colors: '',
|
||||
locations: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
} as any);
|
||||
|
||||
render(<Page />);
|
||||
|
||||
// ProTable header text appears in multiple nodes — presence check is enough
|
||||
await waitFor(() => {
|
||||
expect(screen.getAllByText('产品总览').length).toBeGreaterThan(0);
|
||||
});
|
||||
// getProductSummary was invoked (not short-circuited by an error)
|
||||
await waitFor(() => {
|
||||
expect(vi.mocked(getProductSummary)).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
// Verifies the success path: createProduct returns code 0 → ModalForm closes.
|
||||
// The dialog closing is the proxy for summaryRef.reload() being reached —
|
||||
// ModalForm only calls onOpenChange(false) when onFinish returns true,
|
||||
// which is the same branch that triggers the summary reload.
|
||||
it('closes dialog after successful product creation (reload branch reached)', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<Page />);
|
||||
await act(async () => {
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
await user.click(screen.getByRole('button', { name: /新增产品/ }));
|
||||
await waitFor(() => screen.getByRole('dialog'));
|
||||
|
||||
const dialog = screen.getByRole('dialog');
|
||||
const nameInput = within(dialog).getByRole('textbox', { name: /产品名称/ });
|
||||
await user.type(nameInput, '新布料');
|
||||
|
||||
const buttons = within(dialog).getAllByRole('button');
|
||||
const submitBtn = buttons.find((b) => {
|
||||
const text = b.textContent?.trim() ?? '';
|
||||
return text && !text.includes('×') && !text.includes('✕') && !/取/.test(text) && !/重/.test(text) && !/Cancel/i.test(text);
|
||||
});
|
||||
if (submitBtn) await user.click(submitBtn);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(vi.mocked(createProduct)).toHaveBeenCalled();
|
||||
});
|
||||
// ModalForm closes only when onFinish returns true — the same branch that calls reload()
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user