test(260924-m4n): flackernden Marktplatz-Test entschaerfen, act-Warnungen der Proxmox-Kachel weg
- tenant-selector: Komponenten statisch statt im Test dynamisch importiert (Laden zaehlte in die 5-s-Frist des ersten Tests), SUPER_ADMIN/ADMIN in zwei it aufgetrennt - marketplace/marketplace-filters: gleiche Umstellung; userEvent an die falsche Uhr gekoppelt statt auf shouldAdvanceTime zu warten - proxmox-widget: Rendern wartet das erste Laden in act() ab (81 Warnungen weg) - Todo 2026-09-23 nach completed/ Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+14
@@ -49,3 +49,17 @@ auslöst. Der Fall wiederholt sich also, nicht zufällig.
|
|||||||
Die drei parallelen Pipelines abschalten: der Lauf auf `main` und der auf dem
|
Die drei parallelen Pipelines abschalten: der Lauf auf `main` und der auf dem
|
||||||
Tag prüfen unterschiedliche Dinge, und der `live`-Lauf ist die Absicherung,
|
Tag prüfen unterschiedliche Dinge, und der `live`-Lauf ist die Absicherung,
|
||||||
dass der Zweig für sich genommen grün ist.
|
dass der Zweig für sich genommen grün ist.
|
||||||
|
|
||||||
|
## Erledigt in quick-260924-m4n (24.09.2026)
|
||||||
|
|
||||||
|
- Ursache: `TenantContextSelector` und die Marktplatzseite wurden per
|
||||||
|
`await import(...)` INNERHALB der Tests geladen. Das Laden und Umwandeln der
|
||||||
|
Module zählte damit in die 5-s-Frist des ersten Tests — unter Läuferlast
|
||||||
|
(drei Pipelines je Freigabe) reicht das, um die Frist zu reißen.
|
||||||
|
- Behoben: statische Importe (vi.mock wird darüber gehoben), der Doppelfall
|
||||||
|
SUPER_ADMIN/ADMIN in zwei `it` aufgetrennt (der ADMIN-Fall prüft zusätzlich,
|
||||||
|
dass kein Abruf passiert). Dieselbe Umstellung in `marketplace.test.tsx` und
|
||||||
|
`marketplace-filters.test.tsx`; dort ist userEvent zusätzlich an die falsche
|
||||||
|
Uhr gekoppelt (`advanceTimers`). Kein globales `testTimeout`.
|
||||||
|
- Messung (ganze Web-Suite, lokal und auf 2 Kerne gedrosselt): kein Test über
|
||||||
|
2 s; der langsamste lag gedrosselt bei rund 1,3 s.
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
import { act, cleanup, render, screen, waitFor } from '@testing-library/react';
|
import { act, cleanup, render, screen, waitFor } from '@testing-library/react';
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
// Statisch statt je Test dynamisch importiert: Laden/Umwandeln der Seite faellt
|
||||||
|
// in die Sammelphase, nicht in die 5-s-Frist eines Tests (quick-260924-m4n).
|
||||||
|
import Page from './page';
|
||||||
|
|
||||||
vi.mock('next-intl', () => ({
|
vi.mock('next-intl', () => ({
|
||||||
useTranslations: () => (key: string, params?: Record<string, string>) => {
|
useTranslations: () => (key: string, params?: Record<string, string>) => {
|
||||||
@@ -110,14 +113,14 @@ afterEach(() => {
|
|||||||
vi.restoreAllMocks();
|
vi.restoreAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
async function importPage() {
|
|
||||||
const mod = await import('./page');
|
|
||||||
return mod.default;
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('Marketplace Filters', () => {
|
describe('Marketplace Filters', () => {
|
||||||
|
// userEvent an die falsche Uhr koppeln: sonst wartet jede Eingabe auf das
|
||||||
|
// langsame Echtzeit-Nachschieben von shouldAdvanceTime (quick-260924-m4n).
|
||||||
|
let user: ReturnType<typeof userEvent.setup>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.useFakeTimers({ shouldAdvanceTime: true });
|
vi.useFakeTimers({ shouldAdvanceTime: true });
|
||||||
|
user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime });
|
||||||
mockAuthStore.mockImplementation(
|
mockAuthStore.mockImplementation(
|
||||||
(selector: (state: { user: { id: string; username: string; displayName: string; role: string; tenantId: string } }) => unknown) =>
|
(selector: (state: { user: { id: string; username: string; displayName: string; role: string; tenantId: string } }) => unknown) =>
|
||||||
selector({
|
selector({
|
||||||
@@ -132,7 +135,6 @@ describe('Marketplace Filters', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('typing in search filters the grid to only matching modules after debounce', async () => {
|
it('typing in search filters the grid to only matching modules after debounce', async () => {
|
||||||
const Page = await importPage();
|
|
||||||
render(<Page />);
|
render(<Page />);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
@@ -142,7 +144,7 @@ describe('Marketplace Filters', () => {
|
|||||||
expect(screen.getByText('Email Tool')).toBeInTheDocument();
|
expect(screen.getByText('Email Tool')).toBeInTheDocument();
|
||||||
|
|
||||||
const searchInput = screen.getByPlaceholderText('Module suchen...');
|
const searchInput = screen.getByPlaceholderText('Module suchen...');
|
||||||
await userEvent.type(searchInput, 'Domain');
|
await user.type(searchInput, 'Domain');
|
||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
vi.advanceTimersByTime(350);
|
vi.advanceTimersByTime(350);
|
||||||
@@ -155,7 +157,6 @@ describe('Marketplace Filters', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('selecting the Aktiviert status tab shows only activated modules', async () => {
|
it('selecting the Aktiviert status tab shows only activated modules', async () => {
|
||||||
const Page = await importPage();
|
|
||||||
render(<Page />);
|
render(<Page />);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
@@ -163,7 +164,7 @@ describe('Marketplace Filters', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const activeTab = screen.getByRole('tab', { name: /Aktiviert/i });
|
const activeTab = screen.getByRole('tab', { name: /Aktiviert/i });
|
||||||
await userEvent.click(activeTab);
|
await user.click(activeTab);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByText('Domaincheck')).toBeInTheDocument();
|
expect(screen.getByText('Domaincheck')).toBeInTheDocument();
|
||||||
@@ -173,7 +174,6 @@ describe('Marketplace Filters', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('selecting a category chip shows only modules of that category; Alle shows all', async () => {
|
it('selecting a category chip shows only modules of that category; Alle shows all', async () => {
|
||||||
const Page = await importPage();
|
|
||||||
render(<Page />);
|
render(<Page />);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
@@ -181,7 +181,7 @@ describe('Marketplace Filters', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const utilitiesChip = screen.getByRole('button', { name: 'Utilities' });
|
const utilitiesChip = screen.getByRole('button', { name: 'Utilities' });
|
||||||
await userEvent.click(utilitiesChip);
|
await user.click(utilitiesChip);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByText('Converter')).toBeInTheDocument();
|
expect(screen.getByText('Converter')).toBeInTheDocument();
|
||||||
@@ -189,7 +189,7 @@ describe('Marketplace Filters', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const allChip = screen.getByRole('button', { name: 'Alle' });
|
const allChip = screen.getByRole('button', { name: 'Alle' });
|
||||||
await userEvent.click(allChip);
|
await user.click(allChip);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByText('Domaincheck')).toBeInTheDocument();
|
expect(screen.getByText('Domaincheck')).toBeInTheDocument();
|
||||||
@@ -198,7 +198,6 @@ describe('Marketplace Filters', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('search + status + category filters compose together (AND)', async () => {
|
it('search + status + category filters compose together (AND)', async () => {
|
||||||
const Page = await importPage();
|
|
||||||
render(<Page />);
|
render(<Page />);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
@@ -207,11 +206,11 @@ describe('Marketplace Filters', () => {
|
|||||||
|
|
||||||
// Filter to Domain-Tools category
|
// Filter to Domain-Tools category
|
||||||
const domainChip = screen.getByRole('button', { name: 'Domain-Tools' });
|
const domainChip = screen.getByRole('button', { name: 'Domain-Tools' });
|
||||||
await userEvent.click(domainChip);
|
await user.click(domainChip);
|
||||||
|
|
||||||
// Filter to available only
|
// Filter to available only
|
||||||
const availableTab = screen.getByRole('tab', { name: /Verfuegbar/i });
|
const availableTab = screen.getByRole('tab', { name: /Verfuegbar/i });
|
||||||
await userEvent.click(availableTab);
|
await user.click(availableTab);
|
||||||
|
|
||||||
// Domain-Tools + Available = only Email Tool (Domaincheck is active)
|
// Domain-Tools + Available = only Email Tool (Domaincheck is active)
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
@@ -222,7 +221,6 @@ describe('Marketplace Filters', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('renders filtered-empty heading when filters produce zero results', async () => {
|
it('renders filtered-empty heading when filters produce zero results', async () => {
|
||||||
const Page = await importPage();
|
|
||||||
render(<Page />);
|
render(<Page />);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
@@ -230,7 +228,7 @@ describe('Marketplace Filters', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const searchInput = screen.getByPlaceholderText('Module suchen...');
|
const searchInput = screen.getByPlaceholderText('Module suchen...');
|
||||||
await userEvent.type(searchInput, 'zzz-nonexistent');
|
await user.type(searchInput, 'zzz-nonexistent');
|
||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
vi.advanceTimersByTime(350);
|
vi.advanceTimersByTime(350);
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import { cleanup, render, screen, waitFor } from '@testing-library/react';
|
import { cleanup, render, screen, waitFor } from '@testing-library/react';
|
||||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
// Statisch statt je Test dynamisch importiert: Laden/Umwandeln der Seite faellt
|
||||||
|
// in die Sammelphase, nicht in die 5-s-Frist eines Tests (quick-260924-m4n).
|
||||||
|
import Page from './page';
|
||||||
|
|
||||||
// Mock next-intl
|
// Mock next-intl
|
||||||
vi.mock('next-intl', () => ({
|
vi.mock('next-intl', () => ({
|
||||||
@@ -79,12 +82,6 @@ afterEach(() => {
|
|||||||
vi.restoreAllMocks();
|
vi.restoreAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Lazy import after mocks
|
|
||||||
async function importPage() {
|
|
||||||
const mod = await import('./page');
|
|
||||||
return mod.default;
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('MarketplacePage', () => {
|
describe('MarketplacePage', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// Default: admin user
|
// Default: admin user
|
||||||
@@ -106,7 +103,6 @@ describe('MarketplacePage', () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const Page = await importPage();
|
|
||||||
render(<Page />);
|
render(<Page />);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
@@ -126,7 +122,6 @@ describe('MarketplacePage', () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const Page = await importPage();
|
|
||||||
render(<Page />);
|
render(<Page />);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
@@ -156,7 +151,6 @@ describe('MarketplacePage', () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const Page = await importPage();
|
|
||||||
render(<Page />);
|
render(<Page />);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
@@ -176,7 +170,6 @@ describe('MarketplacePage', () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const Page = await importPage();
|
|
||||||
render(<Page />);
|
render(<Page />);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
import { cleanup, render, screen, waitFor } from '@testing-library/react';
|
import { cleanup, render, screen, waitFor } from '@testing-library/react';
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
// Komponenten statisch importiert (vi.mock wird ueber die Importe gehoben): das
|
||||||
|
// Laden und Umwandeln faellt so in die Sammelphase der Datei und nicht in die
|
||||||
|
// 5-s-Frist des ersten Tests. Frueher lag der dynamische Import im Test und hat
|
||||||
|
// unter Laeuferlast (drei parallele Pipelines bei jeder Freigabe) die Frist
|
||||||
|
// gerissen (Todo 2026-09-23, quick-260924-m4n).
|
||||||
|
import { TenantContextSelector } from './components/TenantContextSelector';
|
||||||
|
import Page from './page';
|
||||||
|
|
||||||
vi.mock('next-intl', () => ({
|
vi.mock('next-intl', () => ({
|
||||||
useTranslations: () => (key: string, params?: Record<string, string>) => {
|
useTranslations: () => (key: string, params?: Record<string, string>) => {
|
||||||
@@ -86,19 +93,8 @@ afterEach(() => {
|
|||||||
mockBumpSidebarRefresh.mockClear();
|
mockBumpSidebarRefresh.mockClear();
|
||||||
});
|
});
|
||||||
|
|
||||||
async function importPage() {
|
|
||||||
const mod = await import('./page');
|
|
||||||
return mod.default;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function importTenantSelector() {
|
|
||||||
const mod = await import('./components/TenantContextSelector');
|
|
||||||
return mod.TenantContextSelector;
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('TenantContextSelector', () => {
|
describe('TenantContextSelector', () => {
|
||||||
it('renders tenant options for SUPER_ADMIN; renders nothing for ADMIN', async () => {
|
it('renders tenant options for SUPER_ADMIN', async () => {
|
||||||
// SUPER_ADMIN case
|
|
||||||
mockAuthStore.mockImplementation(
|
mockAuthStore.mockImplementation(
|
||||||
(selector: (state: { user: { id: string; username: string; displayName: string; role: string; tenantId: string } }) => unknown) =>
|
(selector: (state: { user: { id: string; username: string; displayName: string; role: string; tenantId: string } }) => unknown) =>
|
||||||
selector({
|
selector({
|
||||||
@@ -116,16 +112,13 @@ describe('TenantContextSelector', () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const TenantContextSelector = await importTenantSelector();
|
render(<TenantContextSelector />);
|
||||||
const { unmount } = render(<TenantContextSelector />);
|
|
||||||
|
|
||||||
await waitFor(() => {
|
expect(await screen.findByText('Tenant Alpha')).toBeInTheDocument();
|
||||||
expect(screen.getByText('Tenant Alpha')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
expect(screen.getByText('Tenant Beta')).toBeInTheDocument();
|
expect(screen.getByText('Tenant Beta')).toBeInTheDocument();
|
||||||
unmount();
|
});
|
||||||
|
|
||||||
// ADMIN case
|
it('renders nothing for ADMIN and does not load the tenant list', () => {
|
||||||
mockAuthStore.mockImplementation(
|
mockAuthStore.mockImplementation(
|
||||||
(selector: (state: { user: { id: string; username: string; displayName: string; role: string; tenantId: string } }) => unknown) =>
|
(selector: (state: { user: { id: string; username: string; displayName: string; role: string; tenantId: string } }) => unknown) =>
|
||||||
selector({
|
selector({
|
||||||
@@ -133,8 +126,12 @@ describe('TenantContextSelector', () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const fetchMock = vi.fn();
|
||||||
|
vi.stubGlobal('fetch', fetchMock);
|
||||||
|
|
||||||
const { container } = render(<TenantContextSelector />);
|
const { container } = render(<TenantContextSelector />);
|
||||||
expect(container.innerHTML).toBe('');
|
expect(container.innerHTML).toBe('');
|
||||||
|
expect(fetchMock).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls setSelectedTenantId when selector value changes', async () => {
|
it('calls setSelectedTenantId when selector value changes', async () => {
|
||||||
@@ -155,7 +152,6 @@ describe('TenantContextSelector', () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const TenantContextSelector = await importTenantSelector();
|
|
||||||
render(<TenantContextSelector />);
|
render(<TenantContextSelector />);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
@@ -190,7 +186,6 @@ describe('ActivationDialog', () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const Page = await importPage();
|
|
||||||
render(<Page />);
|
render(<Page />);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
@@ -224,7 +219,6 @@ describe('ActivationDialog', () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const Page = await importPage();
|
|
||||||
render(<Page />);
|
render(<Page />);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ import type { ReactElement } from 'react';
|
|||||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
import type { ProxmoxMetrics, ProxmoxServer } from '@/lib/proxmox-api';
|
import type { ProxmoxMetrics, ProxmoxServer } from '@/lib/proxmox-api';
|
||||||
import de from '@/messages/de.json';
|
import de from '@/messages/de.json';
|
||||||
|
// Statisch importiert (vi.mock wird darueber gehoben) — das Laden faellt in die
|
||||||
|
// Sammelphase, nicht in die Frist des ersten Tests (quick-260924-m4n).
|
||||||
|
import { ProxmoxWidget } from './proxmox-widget';
|
||||||
|
|
||||||
// Echter next-intl-Provider mit den deutschen Texten (Muster
|
// Echter next-intl-Provider mit den deutschen Texten (Muster
|
||||||
// proxmox-page-roles.test.tsx) — die Kachel braucht ICU-Plural und useLocale.
|
// proxmox-page-roles.test.tsx) — die Kachel braucht ICU-Plural und useLocale.
|
||||||
@@ -119,17 +122,27 @@ function makeServer(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rendert die Kachel und wartet INNERHALB von act() das Ende des ersten
|
||||||
|
* Ladens ab (aufgeloestes listServers-Versprechen -> setServers/setLoadFailed/
|
||||||
|
* setNow). Ohne das liefen diese Zustandsaenderungen nach dem Rendern ins Leere
|
||||||
|
* und React meldete je Test drei „not wrapped in act(...)“-Warnungen
|
||||||
|
* (quick-260924-m4n).
|
||||||
|
*/
|
||||||
async function renderWidget(
|
async function renderWidget(
|
||||||
props: { config?: Record<string, unknown>; isEditMode?: boolean } = {},
|
props: { config?: Record<string, unknown>; isEditMode?: boolean } = {},
|
||||||
) {
|
) {
|
||||||
const { ProxmoxWidget } = await import('./proxmox-widget');
|
let result!: ReturnType<typeof render>;
|
||||||
return render(
|
await act(async () => {
|
||||||
<ProxmoxWidget
|
result = render(
|
||||||
instanceId="w-1"
|
<ProxmoxWidget
|
||||||
config={props.config ?? {}}
|
instanceId="w-1"
|
||||||
isEditMode={props.isEditMode ?? false}
|
config={props.config ?? {}}
|
||||||
/>,
|
isEditMode={props.isEditMode ?? false}
|
||||||
);
|
/>,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -617,13 +630,15 @@ describe('ProxmoxWidget: Bearbeitungsmodus mit Titel und Serverauswahl (quick-26
|
|||||||
|
|
||||||
it('Verlassen des Bearbeitungsmodus schliesst die Auswahl', async () => {
|
it('Verlassen des Bearbeitungsmodus schliesst die Auswahl', async () => {
|
||||||
mockListServers.mockResolvedValue([makeServer('ok')]);
|
mockListServers.mockResolvedValue([makeServer('ok')]);
|
||||||
const { ProxmoxWidget } = await import('./proxmox-widget');
|
|
||||||
const ui = (isEditMode: boolean) => (
|
const ui = (isEditMode: boolean) => (
|
||||||
<NextIntlClientProvider locale="de" messages={de} timeZone="Europe/Berlin">
|
<NextIntlClientProvider locale="de" messages={de} timeZone="Europe/Berlin">
|
||||||
<ProxmoxWidget instanceId="w-1" config={{}} isEditMode={isEditMode} />
|
<ProxmoxWidget instanceId="w-1" config={{}} isEditMode={isEditMode} />
|
||||||
</NextIntlClientProvider>
|
</NextIntlClientProvider>
|
||||||
);
|
);
|
||||||
const { rerender } = rtlRender(ui(true));
|
let rerender!: ReturnType<typeof rtlRender>['rerender'];
|
||||||
|
await act(async () => {
|
||||||
|
({ rerender } = rtlRender(ui(true)));
|
||||||
|
});
|
||||||
await screen.findByTestId('proxmox-summary');
|
await screen.findByTestId('proxmox-summary');
|
||||||
|
|
||||||
fireEvent.click(screen.getByRole('button', { name: 'Server auswählen' }));
|
fireEvent.click(screen.getByRole('button', { name: 'Server auswählen' }));
|
||||||
|
|||||||
Reference in New Issue
Block a user