mirror of
https://github.com/s-frick/effigenix.git
synced 2026-03-28 12:29:36 +01:00
phase 0
This commit is contained in:
parent
c84629cc4e
commit
6a672705c2
4 changed files with 285 additions and 6 deletions
|
|
@ -0,0 +1,76 @@
|
||||||
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||||
|
import { render } from 'ink-testing-library';
|
||||||
|
import React from 'react';
|
||||||
|
import { CategoryCreateScreen } from '../../../components/masterdata/categories/CategoryCreateScreen.js';
|
||||||
|
import { useCategories } from '../../../hooks/useCategories.js';
|
||||||
|
|
||||||
|
vi.mock('../../../state/navigation-context.js', () => ({
|
||||||
|
useNavigation: () => ({
|
||||||
|
replace: vi.fn(),
|
||||||
|
back: vi.fn(),
|
||||||
|
navigate: vi.fn(),
|
||||||
|
current: 'category-create',
|
||||||
|
params: {},
|
||||||
|
canGoBack: true,
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('../../../hooks/useCategories.js', () => ({
|
||||||
|
useCategories: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const createCategoryMock = vi.fn();
|
||||||
|
|
||||||
|
const defaultState = {
|
||||||
|
categories: [],
|
||||||
|
loading: false,
|
||||||
|
error: null,
|
||||||
|
fetchCategories: vi.fn(),
|
||||||
|
createCategory: createCategoryMock,
|
||||||
|
updateCategory: vi.fn(),
|
||||||
|
deleteCategory: vi.fn(),
|
||||||
|
clearError: vi.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('TC-CAT: Produktkategorie-Formular', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
vi.mocked(useCategories).mockReturnValue(defaultState);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('TC-CAT-01: Rendert Formular korrekt (Happy-Path-Vorbedingung)', () => {
|
||||||
|
const { lastFrame } = render(React.createElement(CategoryCreateScreen));
|
||||||
|
expect(lastFrame()).toContain('Neue Produktkategorie');
|
||||||
|
expect(lastFrame()).toContain('Name *');
|
||||||
|
expect(lastFrame()).toContain('Beschreibung');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('TC-CAT-04: Doppelter Name → API-Fehler wird angezeigt', () => {
|
||||||
|
vi.mocked(useCategories).mockReturnValue({
|
||||||
|
...defaultState,
|
||||||
|
error: 'Kategorie mit diesem Namen existiert bereits.',
|
||||||
|
});
|
||||||
|
const { lastFrame } = render(React.createElement(CategoryCreateScreen));
|
||||||
|
expect(lastFrame()).toContain('Kategorie mit diesem Namen existiert bereits.');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('TC-CAT-06: Leerer Name → Fehlermeldung, kein API-Call', async () => {
|
||||||
|
createCategoryMock.mockResolvedValue(null);
|
||||||
|
vi.mocked(useCategories).mockReturnValue({ ...defaultState, createCategory: createCategoryMock });
|
||||||
|
|
||||||
|
const { stdin, lastFrame } = render(React.createElement(CategoryCreateScreen));
|
||||||
|
// Warten bis useInput-Effects (ink 5, readable-Listener) registriert sind
|
||||||
|
await new Promise((r) => setTimeout(r, 20));
|
||||||
|
|
||||||
|
// Enter auf Feld 0 (name) → springt zu Feld 1 (description)
|
||||||
|
stdin.write('\r');
|
||||||
|
// Warten bis React re-rendert (description bekommt focus=true)
|
||||||
|
await new Promise((r) => setTimeout(r, 30));
|
||||||
|
// Enter auf Feld 1 (description, letztes Feld) → löst handleSubmit aus
|
||||||
|
stdin.write('\r');
|
||||||
|
await new Promise((r) => setTimeout(r, 50));
|
||||||
|
|
||||||
|
expect(lastFrame()).toContain('Name ist erforderlich');
|
||||||
|
expect(createCategoryMock).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||||
|
import { render } from 'ink-testing-library';
|
||||||
|
import React from 'react';
|
||||||
|
import { SupplierCreateScreen } from '../../../components/masterdata/suppliers/SupplierCreateScreen.js';
|
||||||
|
import { useSuppliers } from '../../../hooks/useSuppliers.js';
|
||||||
|
|
||||||
|
vi.mock('../../../state/navigation-context.js', () => ({
|
||||||
|
useNavigation: () => ({
|
||||||
|
replace: vi.fn(),
|
||||||
|
back: vi.fn(),
|
||||||
|
navigate: vi.fn(),
|
||||||
|
current: 'supplier-create',
|
||||||
|
params: {},
|
||||||
|
canGoBack: true,
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('../../../hooks/useSuppliers.js', () => ({
|
||||||
|
useSuppliers: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('../../../utils/api-client.js', () => ({
|
||||||
|
client: {
|
||||||
|
countries: {
|
||||||
|
search: vi.fn().mockResolvedValue([]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const createSupplierMock = vi.fn();
|
||||||
|
|
||||||
|
const defaultState = {
|
||||||
|
suppliers: [],
|
||||||
|
loading: false,
|
||||||
|
error: null,
|
||||||
|
fetchSuppliers: vi.fn(),
|
||||||
|
createSupplier: createSupplierMock,
|
||||||
|
updateSupplier: vi.fn(),
|
||||||
|
activateSupplier: vi.fn(),
|
||||||
|
deactivateSupplier: vi.fn(),
|
||||||
|
rateSupplier: vi.fn(),
|
||||||
|
addCertificate: vi.fn(),
|
||||||
|
removeCertificate: vi.fn(),
|
||||||
|
clearError: vi.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('TC-SUP: Lieferanten-Formular', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
vi.mocked(useSuppliers).mockReturnValue(defaultState);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('TC-SUP-01: Rendert Formular mit Pflichtfeld-Labels', () => {
|
||||||
|
const { lastFrame } = render(React.createElement(SupplierCreateScreen));
|
||||||
|
expect(lastFrame()).toContain('Neuer Lieferant');
|
||||||
|
expect(lastFrame()).toContain('Name *');
|
||||||
|
expect(lastFrame()).toContain('Telefon *');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('TC-SUP-02: API-Fehler wird im Formular angezeigt', () => {
|
||||||
|
vi.mocked(useSuppliers).mockReturnValue({
|
||||||
|
...defaultState,
|
||||||
|
error: 'Lieferant mit diesem Namen existiert bereits.',
|
||||||
|
});
|
||||||
|
const { lastFrame } = render(React.createElement(SupplierCreateScreen));
|
||||||
|
expect(lastFrame()).toContain('Lieferant mit diesem Namen existiert bereits.');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('TC-SUP-03: Leerer Name → Fehlermeldung, kein API-Call', async () => {
|
||||||
|
createSupplierMock.mockResolvedValue(null);
|
||||||
|
vi.mocked(useSuppliers).mockReturnValue({ ...defaultState, createSupplier: createSupplierMock });
|
||||||
|
|
||||||
|
const { stdin, lastFrame } = render(React.createElement(SupplierCreateScreen));
|
||||||
|
// Warten bis useInput-Effects (ink 5, readable-Listener) registriert sind
|
||||||
|
await new Promise((r) => setTimeout(r, 20));
|
||||||
|
|
||||||
|
// Tab durch alle 9 Feldübergänge bis zum letzten Feld (paymentDueDays)
|
||||||
|
for (let i = 0; i < 9; i++) {
|
||||||
|
stdin.write('\t');
|
||||||
|
}
|
||||||
|
// Warten bis React re-rendert und das fokussierte Feld aktualisiert
|
||||||
|
await new Promise((r) => setTimeout(r, 30));
|
||||||
|
// Enter auf letztem Feld (paymentDueDays) löst handleSubmit aus
|
||||||
|
stdin.write('\r');
|
||||||
|
await new Promise((r) => setTimeout(r, 50));
|
||||||
|
|
||||||
|
expect(lastFrame()).toContain('Name ist erforderlich');
|
||||||
|
expect(createSupplierMock).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,113 @@
|
||||||
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||||
|
import { render } from 'ink-testing-library';
|
||||||
|
import React from 'react';
|
||||||
|
import { SupplierListScreen } from '../../../components/masterdata/suppliers/SupplierListScreen.js';
|
||||||
|
import { useSuppliers } from '../../../hooks/useSuppliers.js';
|
||||||
|
import type { SupplierDTO } from '@effigenix/api-client';
|
||||||
|
|
||||||
|
vi.mock('../../../state/navigation-context.js', () => ({
|
||||||
|
useNavigation: () => ({
|
||||||
|
replace: vi.fn(),
|
||||||
|
back: vi.fn(),
|
||||||
|
navigate: vi.fn(),
|
||||||
|
current: 'supplier-list',
|
||||||
|
params: {},
|
||||||
|
canGoBack: true,
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('../../../hooks/useSuppliers.js', () => ({
|
||||||
|
useSuppliers: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const mockSuppliers: SupplierDTO[] = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
name: 'Frisch AG',
|
||||||
|
status: 'ACTIVE',
|
||||||
|
rating: null,
|
||||||
|
certificates: [],
|
||||||
|
} as unknown as SupplierDTO,
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
name: 'Alt GmbH',
|
||||||
|
status: 'INACTIVE',
|
||||||
|
rating: null,
|
||||||
|
certificates: [],
|
||||||
|
} as unknown as SupplierDTO,
|
||||||
|
];
|
||||||
|
|
||||||
|
const defaultState = {
|
||||||
|
suppliers: mockSuppliers,
|
||||||
|
loading: false,
|
||||||
|
error: null,
|
||||||
|
fetchSuppliers: vi.fn(),
|
||||||
|
createSupplier: vi.fn(),
|
||||||
|
updateSupplier: vi.fn(),
|
||||||
|
activateSupplier: vi.fn(),
|
||||||
|
deactivateSupplier: vi.fn(),
|
||||||
|
rateSupplier: vi.fn(),
|
||||||
|
addCertificate: vi.fn(),
|
||||||
|
removeCertificate: vi.fn(),
|
||||||
|
clearError: vi.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('TC-SUP-06: Lieferantenliste – Filter', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
vi.mocked(useSuppliers).mockReturnValue(defaultState);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Zeigt initial alle Lieferanten (Filter: Alle)', () => {
|
||||||
|
const { lastFrame } = render(React.createElement(SupplierListScreen));
|
||||||
|
expect(lastFrame()).toContain('Lieferanten');
|
||||||
|
expect(lastFrame()).toContain('Alle');
|
||||||
|
expect(lastFrame()).toContain('Frisch AG');
|
||||||
|
expect(lastFrame()).toContain('Alt GmbH');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('[A] filtert auf aktive Lieferanten', async () => {
|
||||||
|
const { stdin, lastFrame } = render(React.createElement(SupplierListScreen));
|
||||||
|
await new Promise((r) => setTimeout(r, 20));
|
||||||
|
|
||||||
|
stdin.write('A');
|
||||||
|
await new Promise((r) => setTimeout(r, 30));
|
||||||
|
|
||||||
|
expect(lastFrame()).toContain('Aktiv');
|
||||||
|
expect(lastFrame()).toContain('Frisch AG');
|
||||||
|
expect(lastFrame()).not.toContain('Alt GmbH');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('[I] filtert auf inaktive Lieferanten', async () => {
|
||||||
|
const { stdin, lastFrame } = render(React.createElement(SupplierListScreen));
|
||||||
|
await new Promise((r) => setTimeout(r, 20));
|
||||||
|
|
||||||
|
stdin.write('I');
|
||||||
|
await new Promise((r) => setTimeout(r, 30));
|
||||||
|
|
||||||
|
expect(lastFrame()).toContain('Inaktiv');
|
||||||
|
expect(lastFrame()).toContain('Alt GmbH');
|
||||||
|
expect(lastFrame()).not.toContain('Frisch AG');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('[a] setzt Filter zurück auf Alle', async () => {
|
||||||
|
const { stdin, lastFrame } = render(React.createElement(SupplierListScreen));
|
||||||
|
await new Promise((r) => setTimeout(r, 20));
|
||||||
|
|
||||||
|
// Erst auf Aktiv filtern, dann zurücksetzen
|
||||||
|
stdin.write('A');
|
||||||
|
await new Promise((r) => setTimeout(r, 30));
|
||||||
|
stdin.write('a');
|
||||||
|
await new Promise((r) => setTimeout(r, 30));
|
||||||
|
|
||||||
|
expect(lastFrame()).toContain('Alle');
|
||||||
|
expect(lastFrame()).toContain('Frisch AG');
|
||||||
|
expect(lastFrame()).toContain('Alt GmbH');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Zeigt Lade-Spinner wenn loading=true', () => {
|
||||||
|
vi.mocked(useSuppliers).mockReturnValue({ ...defaultState, loading: true, suppliers: [] });
|
||||||
|
const { lastFrame } = render(React.createElement(SupplierListScreen));
|
||||||
|
expect(lastFrame()).toContain('Lade Lieferanten');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -12,12 +12,12 @@ Konzept: [`docs/ui-testing-automation.md`](./docs/ui-testing-automation.md)
|
||||||
> Sofort umsetzbar, kein neues Setup nötig. Nutzt `ink-testing-library` + `vitest`.
|
> Sofort umsetzbar, kein neues Setup nötig. Nutzt `ink-testing-library` + `vitest`.
|
||||||
> Ort: `frontend/apps/cli/src/__tests__/screens/`
|
> Ort: `frontend/apps/cli/src/__tests__/screens/`
|
||||||
|
|
||||||
- [ ] `__tests__/screens/` Verzeichnisstruktur anlegen (`masterdata/`, `production/`)
|
- [x] `__tests__/screens/` Verzeichnisstruktur anlegen (`masterdata/`, `production/`)
|
||||||
- [ ] `SupplierForm.test.tsx` – TC-SUP-01 (Pflichtfelder), TC-SUP-02, TC-SUP-03 (leerer Name)
|
- [x] `SupplierForm.test.tsx` – TC-SUP-01 (Pflichtfelder), TC-SUP-02, TC-SUP-03 (leerer Name)
|
||||||
- [ ] `CategoryForm.test.tsx` – TC-CAT-01 (Happy Path), TC-CAT-04 (Duplikat), TC-CAT-06 (leerer Name)
|
- [x] `CategoryForm.test.tsx` – TC-CAT-01 (Happy Path), TC-CAT-04 (Duplikat), TC-CAT-06 (leerer Name)
|
||||||
- [ ] `SupplierList.test.tsx` – TC-SUP-06 (Filter/Suche)
|
- [x] `SupplierList.test.tsx` – TC-SUP-06 (Filter/Suche)
|
||||||
- [ ] Bestehende 4 Tests (`ConfirmDialog`, `ErrorDisplay`, `SuccessDisplay`, `useRoles`) als Vorlage prüfen
|
- [x] Bestehende 4 Tests (`ConfirmDialog`, `ErrorDisplay`, `SuccessDisplay`, `useRoles`) als Vorlage prüfen
|
||||||
- [ ] Alle neuen TUI-Tests laufen durch (`pnpm --filter @effigenix/cli test`)
|
- [x] Alle neuen TUI-Tests laufen durch (`pnpm --filter @effigenix/cli test`)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue