test(260924-i8v): RED fuer Titel und Serverauswahl der Proxmox-Kachel
- Auswahl-Bauteil: Reihenfolge nach position, Produktwort, Aufraeumen geloeschter Kennungen - Einstellungsformular: Laden, Fehler, Leer, onChange fuer Titel und Auswahl - Einstellungsbereich: Zweig proxmox mit Titel-Zusatz in der Kopfzeile - Kachel im Bearbeitungsmodus: entprellter Titel, Auswahl statt Liste, schliesst beim Verlassen Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
import { cleanup, fireEvent, render as rtlRender, screen, waitFor } from '@testing-library/react';
|
||||
import { NextIntlClientProvider } from 'next-intl';
|
||||
import type { ReactElement } from 'react';
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import type { ProxmoxServer } from '@/lib/proxmox-api';
|
||||
import de from '@/messages/de.json';
|
||||
|
||||
/**
|
||||
* Einstellungsformular der Proxmox-Kachel (quick-260924-i8v) fuer
|
||||
* Einstellungen > Dashboard: Titel und Serverauswahl.
|
||||
*/
|
||||
function render(ui: ReactElement) {
|
||||
return rtlRender(
|
||||
<NextIntlClientProvider locale="de" messages={de} timeZone="Europe/Berlin">
|
||||
{ui}
|
||||
</NextIntlClientProvider>,
|
||||
);
|
||||
}
|
||||
|
||||
// T-I8V-02: `pollServer` nur als Spion — das Formular darf ihn nie rufen.
|
||||
const mockListServers = vi.fn();
|
||||
const mockPollServer = vi.fn();
|
||||
vi.mock('@/lib/proxmox-api', () => ({
|
||||
listServers: (...args: unknown[]) => mockListServers(...args),
|
||||
pollServer: (...args: unknown[]) => mockPollServer(...args),
|
||||
}));
|
||||
|
||||
import { ProxmoxWidgetConfigForm } from './proxmox-widget-config-form';
|
||||
|
||||
function server(id: string, position: number): ProxmoxServer {
|
||||
return {
|
||||
id,
|
||||
tenantId: 't1',
|
||||
name: id,
|
||||
productType: 'pve',
|
||||
baseUrl: 'https://x.intern',
|
||||
authMethod: 'token',
|
||||
tokenId: 'a@pam!b',
|
||||
username: null,
|
||||
tlsRejectUnauthorized: true,
|
||||
isActive: true,
|
||||
pollIntervalMin: 5,
|
||||
position,
|
||||
createdAt: '2026-01-01T00:00:00.000Z',
|
||||
updatedAt: '2026-01-01T00:00:00.000Z',
|
||||
status: null,
|
||||
};
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('ProxmoxWidgetConfigForm (quick-260924-i8v)', () => {
|
||||
it('laedt die Server einmal und zeigt die Auswahl, vorbelegt aus der Konfiguration', async () => {
|
||||
mockListServers.mockResolvedValue([server('pve-a', 0), server('pve-b', 1)]);
|
||||
render(
|
||||
<ProxmoxWidgetConfigForm
|
||||
config={{ title: 'Rechenzentrum', serverIds: ['pve-b'] }}
|
||||
onChange={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByText('Lade Serverliste...')).toBeInTheDocument();
|
||||
expect(await screen.findByRole('checkbox', { name: /pve-b/ })).toBeChecked();
|
||||
expect(screen.getByRole('checkbox', { name: /pve-a/ })).not.toBeChecked();
|
||||
expect((screen.getByLabelText('Titel') as HTMLInputElement).value).toBe('Rechenzentrum');
|
||||
expect(mockListServers).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('Fehlersatz, wenn die Liste nicht geladen werden kann', async () => {
|
||||
mockListServers.mockRejectedValue(new Error('500'));
|
||||
render(<ProxmoxWidgetConfigForm config={{}} onChange={vi.fn()} />);
|
||||
|
||||
expect(
|
||||
await screen.findByText('Die Serverliste konnte nicht geladen werden.'),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Leersatz ohne Server', async () => {
|
||||
mockListServers.mockResolvedValue([]);
|
||||
render(<ProxmoxWidgetConfigForm config={{}} onChange={vi.fn()} />);
|
||||
|
||||
expect(await screen.findByText('Noch kein Proxmox-Server eingetragen.')).toBeInTheDocument();
|
||||
expect(screen.queryByRole('checkbox')).toBeNull();
|
||||
});
|
||||
|
||||
it('Titelaenderung ruft onChange({ title }) mit dem rohen Tippwert', async () => {
|
||||
mockListServers.mockResolvedValue([server('pve-a', 0)]);
|
||||
const onChange = vi.fn();
|
||||
render(<ProxmoxWidgetConfigForm config={{}} onChange={onChange} />);
|
||||
await screen.findByRole('checkbox');
|
||||
|
||||
fireEvent.change(screen.getByLabelText('Titel'), { target: { value: 'Keller ' } });
|
||||
|
||||
expect(onChange).toHaveBeenCalledWith({ title: 'Keller ' });
|
||||
});
|
||||
|
||||
it('Auswahlaenderung ruft onChange({ serverIds })', async () => {
|
||||
mockListServers.mockResolvedValue([server('pve-a', 0), server('pve-b', 1)]);
|
||||
const onChange = vi.fn();
|
||||
render(<ProxmoxWidgetConfigForm config={{ serverIds: ['pve-a'] }} onChange={onChange} />);
|
||||
|
||||
fireEvent.click(await screen.findByRole('checkbox', { name: /pve-b/ }));
|
||||
|
||||
expect(onChange).toHaveBeenCalledWith({ serverIds: ['pve-a', 'pve-b'] });
|
||||
});
|
||||
|
||||
it('ruft nie die manuelle Abfrage auf (T-I8V-02)', async () => {
|
||||
mockListServers.mockResolvedValue([server('pve-a', 0)]);
|
||||
render(<ProxmoxWidgetConfigForm config={{}} onChange={vi.fn()} />);
|
||||
|
||||
await screen.findByRole('checkbox');
|
||||
await waitFor(() => expect(mockListServers).toHaveBeenCalledTimes(1));
|
||||
expect(mockPollServer).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -40,6 +40,15 @@ vi.mock('@/components/settings/search-provider-form', () => ({
|
||||
SearchProviderForm: () => null,
|
||||
}));
|
||||
|
||||
// quick-260924-i8v: das Proxmox-Formular laedt die Serverliste; `pollServer`
|
||||
// nur als Spion (T-I8V-02).
|
||||
const mockListServers = vi.fn();
|
||||
const mockPollServer = vi.fn();
|
||||
vi.mock('@/lib/proxmox-api', () => ({
|
||||
listServers: (...args: unknown[]) => mockListServers(...args),
|
||||
pollServer: (...args: unknown[]) => mockPollServer(...args),
|
||||
}));
|
||||
|
||||
const clockTexts = (de as { widgets: { clock: Record<string, string> } }).widgets.clock;
|
||||
const cal = (de as { widgets: { calendar: Record<string, string> } }).widgets.calendar;
|
||||
|
||||
@@ -285,3 +294,42 @@ describe('WidgetSettingsPanel — Bilderrahmen-Kopfzeile (Kosmetik nach quick-26
|
||||
expect(screen.getByRole('button', { name: /Bilderrahmen #3/ })).not.toHaveTextContent('—');
|
||||
});
|
||||
});
|
||||
|
||||
describe('WidgetSettingsPanel — Proxmox (quick-260924-i8v)', () => {
|
||||
it('Test PX1: der Zweig rendert das Proxmox-Formular, Kopfzeile zeigt "— Titel", Auswahl ruft updateWidgetConfig/onWidgetUpdate', async () => {
|
||||
mockListServers.mockResolvedValue([
|
||||
{
|
||||
id: 'srv-a',
|
||||
name: 'pve-a',
|
||||
productType: 'pve',
|
||||
position: 0,
|
||||
isActive: true,
|
||||
status: null,
|
||||
},
|
||||
]);
|
||||
const onWidgetUpdate = vi.fn();
|
||||
const widget = { id: 'px1', widgetType: 'proxmox', config: { title: 'Rechenzentrum' } };
|
||||
render(<WidgetSettingsPanel widgets={[widget]} onWidgetUpdate={onWidgetUpdate} />);
|
||||
|
||||
const header = screen.getByRole('button', { name: /Proxmox #1/ });
|
||||
expect(header).toHaveTextContent('— Rechenzentrum');
|
||||
fireEvent.click(header);
|
||||
|
||||
expect((screen.getByLabelText('Titel') as HTMLInputElement).value).toBe('Rechenzentrum');
|
||||
fireEvent.click(await screen.findByRole('checkbox', { name: /pve-a/ }));
|
||||
|
||||
await vi.waitFor(() =>
|
||||
expect(onWidgetUpdate).toHaveBeenCalledWith('px1', { serverIds: ['srv-a'] }),
|
||||
);
|
||||
expect(updateWidgetConfig).toHaveBeenCalledWith('px1', { serverIds: ['srv-a'] });
|
||||
expect(mockPollServer).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Test PX2: ohne Titel kein Zusatz in der Kopfzeile', () => {
|
||||
mockListServers.mockResolvedValue([]);
|
||||
const widget = { id: 'px2', widgetType: 'proxmox', config: {} };
|
||||
render(<WidgetSettingsPanel widgets={[widget]} onWidgetUpdate={vi.fn()} />);
|
||||
|
||||
expect(screen.getByRole('button', { name: /Proxmox #1/ })).not.toHaveTextContent('—');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user