feat(nextcloud-status): Clouds verwalten, Logos, stündliche Prüfung und Sortierung
- Ändern, Entfernen, Logo-Upload und -Abruf, Sammelprüfung nur mit Verwalten - Stündlicher Auftrag beim Start registriert, Prüfung je Cloud an ihren Mandanten gebunden - Sortierung nach Kundenname, Status, Version und Support-Ende, je Benutzer gemerkt - Formular zum Anlegen und Bearbeiten, Zugriffsinventar angepasst Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,11 @@
|
||||
import { cleanup, render as rtlRender, screen, waitFor, within } from '@testing-library/react';
|
||||
import {
|
||||
cleanup,
|
||||
fireEvent,
|
||||
render as rtlRender,
|
||||
screen,
|
||||
waitFor,
|
||||
within,
|
||||
} from '@testing-library/react';
|
||||
import { NextIntlClientProvider } from 'next-intl';
|
||||
import type { ReactElement } from 'react';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
@@ -15,12 +22,29 @@ function render(ui: ReactElement) {
|
||||
}
|
||||
|
||||
const mockListInstances = vi.fn();
|
||||
const mockCheckAll = vi.fn();
|
||||
const mockCheckOne = vi.fn();
|
||||
|
||||
vi.mock('@/lib/nextcloud-status-api', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@/lib/nextcloud-status-api')>();
|
||||
return { ...actual, listInstances: (...args: unknown[]) => mockListInstances(...args) };
|
||||
return {
|
||||
...actual,
|
||||
listInstances: (...args: unknown[]) => mockListInstances(...args),
|
||||
checkAll: (...args: unknown[]) => mockCheckAll(...args),
|
||||
checkOne: (...args: unknown[]) => mockCheckOne(...args),
|
||||
};
|
||||
});
|
||||
|
||||
let mockCanManage: boolean | null = false;
|
||||
vi.mock('@/lib/use-module-capability', () => ({
|
||||
useCanManageModule: () => mockCanManage,
|
||||
}));
|
||||
|
||||
vi.mock('@/lib/stores/auth-store', () => ({
|
||||
useAuthStore: (selector: (state: { user: { id: string } }) => unknown) =>
|
||||
selector({ user: { id: 'u1' } }),
|
||||
}));
|
||||
|
||||
function makeInstance(
|
||||
over: Partial<NextcloudInstance> & { id: string; customerName: string },
|
||||
): NextcloudInstance {
|
||||
@@ -101,6 +125,10 @@ const LIST: NextcloudList = {
|
||||
describe('NextcloudStatusPage', () => {
|
||||
beforeEach(() => {
|
||||
mockListInstances.mockReset();
|
||||
mockCheckAll.mockReset();
|
||||
mockCheckOne.mockReset();
|
||||
mockCanManage = false;
|
||||
window.localStorage.clear();
|
||||
});
|
||||
afterEach(() => cleanup());
|
||||
|
||||
@@ -167,4 +195,94 @@ describe('NextcloudStatusPage', () => {
|
||||
const tile = await screen.findByTestId('cloud-tile');
|
||||
expect(within(tile).getByText('KG')).toBeInTheDocument();
|
||||
});
|
||||
describe('Rechte und Sortierung', () => {
|
||||
const tileNames = () =>
|
||||
screen.getAllByTestId('cloud-tile').map((t) => t.getAttribute('aria-label')?.split(':')[0]);
|
||||
|
||||
it('Benutzen-Ebene: Kacheln und Sortierung ja, Verwalten-Knöpfe nein', async () => {
|
||||
mockCanManage = false;
|
||||
mockListInstances.mockResolvedValue(LIST);
|
||||
render(<NextcloudStatusPage />);
|
||||
await screen.findAllByTestId('cloud-tile');
|
||||
expect(screen.getByLabelText('Sortieren nach')).toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: 'Cloud hinzufügen' })).toBeNull();
|
||||
expect(screen.queryByRole('button', { name: 'Jetzt prüfen' })).toBeNull();
|
||||
expect(screen.queryByRole('button', { name: 'Diese Cloud jetzt prüfen' })).toBeNull();
|
||||
expect(screen.queryByRole('button', { name: 'Cloud bearbeiten' })).toBeNull();
|
||||
});
|
||||
|
||||
it('Verwalter sehen alle Knöpfe', async () => {
|
||||
mockCanManage = true;
|
||||
mockListInstances.mockResolvedValue(LIST);
|
||||
render(<NextcloudStatusPage />);
|
||||
await screen.findAllByTestId('cloud-tile');
|
||||
expect(screen.getByRole('button', { name: 'Cloud hinzufügen' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Jetzt prüfen' })).toBeInTheDocument();
|
||||
expect(screen.getAllByRole('button', { name: 'Diese Cloud jetzt prüfen' })).toHaveLength(4);
|
||||
expect(screen.getAllByRole('button', { name: 'Cloud bearbeiten' })).toHaveLength(4);
|
||||
});
|
||||
|
||||
it('Status-Sortierung stellt Rot nach vorn und merkt die Auswahl', async () => {
|
||||
mockListInstances.mockResolvedValue(LIST);
|
||||
render(<NextcloudStatusPage />);
|
||||
await screen.findAllByTestId('cloud-tile');
|
||||
expect(tileNames()).toEqual(['Kunde Gelb', 'Kunde Grau', 'Kunde Grün', 'Kunde Rot']);
|
||||
|
||||
fireEvent.change(screen.getByLabelText('Sortieren nach'), { target: { value: 'status' } });
|
||||
expect(tileNames()).toEqual(['Kunde Rot', 'Kunde Gelb', 'Kunde Grau', 'Kunde Grün']);
|
||||
expect(window.localStorage.getItem('tessera:nextcloud-status:sort:u1')).toBe('status');
|
||||
});
|
||||
|
||||
it('wendet eine gemerkte Sortierung beim Laden an', async () => {
|
||||
window.localStorage.setItem('tessera:nextcloud-status:sort:u1', 'status');
|
||||
mockListInstances.mockResolvedValue(LIST);
|
||||
render(<NextcloudStatusPage />);
|
||||
await screen.findAllByTestId('cloud-tile');
|
||||
await waitFor(() => expect(tileNames()[0]).toBe('Kunde Rot'));
|
||||
expect((screen.getByLabelText('Sortieren nach') as HTMLSelectElement).value).toBe('status');
|
||||
});
|
||||
|
||||
it('„Jetzt prüfen“ ruft checkAll und ersetzt die Liste', async () => {
|
||||
mockCanManage = true;
|
||||
mockListInstances.mockResolvedValue(LIST);
|
||||
mockCheckAll.mockResolvedValue({ ...LIST, instances: [LIST.instances[0]] });
|
||||
render(<NextcloudStatusPage />);
|
||||
await screen.findAllByTestId('cloud-tile');
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Jetzt prüfen' }));
|
||||
await waitFor(() => expect(screen.getAllByTestId('cloud-tile')).toHaveLength(1));
|
||||
expect(mockCheckAll).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('Kachel-Prüfung ersetzt nur diese Kachel', async () => {
|
||||
mockCanManage = true;
|
||||
mockListInstances.mockResolvedValue(LIST);
|
||||
mockCheckOne.mockResolvedValue({
|
||||
...LIST.instances[2],
|
||||
customerName: 'Kunde Rot',
|
||||
rating: { level: 'green', reason: 'current', updateTo: null, eolDate: null, cycle: 35 },
|
||||
});
|
||||
render(<NextcloudStatusPage />);
|
||||
const tiles = await screen.findAllByTestId('cloud-tile');
|
||||
const red = tiles.find((t) =>
|
||||
t.getAttribute('aria-label')?.startsWith('Kunde Rot'),
|
||||
) as HTMLElement;
|
||||
fireEvent.click(within(red).getByRole('button', { name: 'Diese Cloud jetzt prüfen' }));
|
||||
await waitFor(() => expect(mockCheckOne).toHaveBeenCalledWith('c'));
|
||||
await waitFor(() => expect(screen.getAllByTestId('cloud-tile')).toHaveLength(4));
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
screen.getAllByTestId('cloud-tile').find((t) => t.textContent?.includes('Kunde Rot')),
|
||||
).toHaveAttribute('data-level', 'green'),
|
||||
);
|
||||
});
|
||||
|
||||
it('„Cloud hinzufügen“ öffnet das Formular', async () => {
|
||||
mockCanManage = true;
|
||||
mockListInstances.mockResolvedValue(LIST);
|
||||
render(<NextcloudStatusPage />);
|
||||
await screen.findAllByTestId('cloud-tile');
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Cloud hinzufügen' }));
|
||||
expect(screen.getByRole('dialog')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user