feat(T06): Verkaufsmodul + Rechtsdokumente + DATEV-Export + Sales UI
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import { DatevExport } from '@/components/sales/DatevExport';
|
||||
|
||||
// Mock the datev lib
|
||||
vi.mock('@/lib/datev', () => ({
|
||||
createDatevExport: vi.fn(),
|
||||
listDatevExports: vi.fn(),
|
||||
getDatevDownloadUrl: vi.fn().mockReturnValue('http://test/datev.csv'),
|
||||
}));
|
||||
|
||||
import { listDatevExports, createDatevExport } from '@/lib/datev';
|
||||
|
||||
describe('DatevExport', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders DATEV export page with title', async () => {
|
||||
vi.mocked(listDatevExports).mockResolvedValue({
|
||||
items: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
page_size: 20,
|
||||
});
|
||||
|
||||
render(<DatevExport />);
|
||||
expect(screen.getByText('DATEV Export')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('datev-export')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('has date range inputs', async () => {
|
||||
vi.mocked(listDatevExports).mockResolvedValue({
|
||||
items: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
page_size: 20,
|
||||
});
|
||||
|
||||
render(<DatevExport />);
|
||||
expect(screen.getByTestId('datev-start-date')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('datev-end-date')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('has create export button', async () => {
|
||||
vi.mocked(listDatevExports).mockResolvedValue({
|
||||
items: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
page_size: 20,
|
||||
});
|
||||
|
||||
render(<DatevExport />);
|
||||
expect(screen.getByTestId('datev-create-btn')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows empty state when no exports', async () => {
|
||||
vi.mocked(listDatevExports).mockResolvedValue({
|
||||
items: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
page_size: 20,
|
||||
});
|
||||
|
||||
render(<DatevExport />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('datev-empty')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('displays exports in table', async () => {
|
||||
const mockExport = {
|
||||
id: 'test-export-id',
|
||||
start_date: '2025-01-01',
|
||||
end_date: '2025-01-31',
|
||||
total_amount: 95000,
|
||||
created_at: '2025-02-01T10:00:00Z',
|
||||
};
|
||||
vi.mocked(listDatevExports).mockResolvedValue({
|
||||
items: [mockExport],
|
||||
total: 1,
|
||||
page: 1,
|
||||
page_size: 20,
|
||||
});
|
||||
|
||||
render(<DatevExport />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('datev-download-test-export-id')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,152 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
|
||||
import { SaleList } from '@/components/sales/SaleList';
|
||||
import { SaleForm } from '@/components/sales/SaleForm';
|
||||
import { ContractPreview } from '@/components/sales/ContractPreview';
|
||||
|
||||
// Mock the sales lib
|
||||
vi.mock('@/lib/sales', () => ({
|
||||
listSales: vi.fn(),
|
||||
createSale: vi.fn(),
|
||||
getSale: vi.fn(),
|
||||
deleteSale: vi.fn(),
|
||||
regenerateContract: vi.fn(),
|
||||
verifyUstId: vi.fn(),
|
||||
getContractDownloadUrl: vi.fn().mockReturnValue('http://test/contract'),
|
||||
}));
|
||||
|
||||
vi.mock('@/lib/vehicles', () => ({
|
||||
listVehicles: vi.fn().mockResolvedValue({ items: [], total: 0, page: 1, page_size: 20 }),
|
||||
}));
|
||||
|
||||
import { listSales, createSale, regenerateContract } from '@/lib/sales';
|
||||
|
||||
describe('SaleList', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders sale list with title', async () => {
|
||||
vi.mocked(listSales).mockResolvedValue({
|
||||
items: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
page_size: 20,
|
||||
});
|
||||
|
||||
render(<SaleList />);
|
||||
expect(screen.getByText('Verkäufe')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('sale-list')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('displays sales in table', async () => {
|
||||
const mockSale = {
|
||||
id: 'test-sale-id',
|
||||
vehicle_id: 'v1',
|
||||
buyer_contact_id: 'b1',
|
||||
sale_price: 45000,
|
||||
sale_date: '2025-01-15',
|
||||
status: 'completed',
|
||||
is_gwg: false,
|
||||
vehicle: { id: 'v1', make: 'Mercedes', model: 'Actros', fin: 'WDB123', vehicle_type: 'lkw', availability: 'sold', price: 45000 },
|
||||
buyer: { id: 'b1', company_name: 'Test Buyer', address_country: 'DE' },
|
||||
};
|
||||
vi.mocked(listSales).mockResolvedValue({
|
||||
items: [mockSale],
|
||||
total: 1,
|
||||
page: 1,
|
||||
page_size: 20,
|
||||
});
|
||||
|
||||
render(<SaleList />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Mercedes Actros')).toBeInTheDocument();
|
||||
expect(screen.getByText('Test Buyer')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('shows create button', async () => {
|
||||
vi.mocked(listSales).mockResolvedValue({
|
||||
items: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
page_size: 20,
|
||||
});
|
||||
|
||||
render(<SaleList />);
|
||||
expect(screen.getByTestId('sale-create-btn')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('has status filter', async () => {
|
||||
vi.mocked(listSales).mockResolvedValue({
|
||||
items: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
page_size: 20,
|
||||
});
|
||||
|
||||
render(<SaleList />);
|
||||
expect(screen.getByTestId('sale-status-filter')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('has date range filters', async () => {
|
||||
vi.mocked(listSales).mockResolvedValue({
|
||||
items: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
page_size: 20,
|
||||
});
|
||||
|
||||
render(<SaleList />);
|
||||
expect(screen.getByTestId('sale-date-from')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('sale-date-to')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('SaleForm', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders form with required fields', async () => {
|
||||
render(<SaleForm />);
|
||||
expect(screen.getByTestId('sale-form')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('sale-vehicle-select')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('sale-buyer-input')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('sale-price-input')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('sale-gwg-toggle')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('has GwG toggle checkbox', async () => {
|
||||
render(<SaleForm />);
|
||||
const gwgToggle = screen.getByTestId('sale-gwg-toggle') as HTMLInputElement;
|
||||
expect(gwgToggle.type).toBe('checkbox');
|
||||
expect(gwgToggle.checked).toBe(false);
|
||||
});
|
||||
|
||||
it('has submit button', async () => {
|
||||
render(<SaleForm />);
|
||||
expect(screen.getByTestId('sale-submit-btn')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('ContractPreview', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('shows empty state when no PDF path', async () => {
|
||||
render(<ContractPreview saleId="test-id" contractPdfPath={null} />);
|
||||
expect(screen.getByTestId('contract-empty')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows regenerate button', async () => {
|
||||
render(<ContractPreview saleId="test-id" contractPdfPath={null} />);
|
||||
expect(screen.getByTestId('contract-regenerate-btn')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows iframe when PDF path exists', async () => {
|
||||
render(<ContractPreview saleId="test-id" contractPdfPath="/tmp/contract.pdf" />);
|
||||
expect(screen.getByTestId('contract-iframe')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user