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:
2026-09-24 16:02:45 +02:00
parent dd09c08311
commit b10734f382
5 changed files with 73 additions and 59 deletions
@@ -1,6 +1,9 @@
import { act, cleanup, render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
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', () => ({
useTranslations: () => (key: string, params?: Record<string, string>) => {
@@ -110,14 +113,14 @@ afterEach(() => {
vi.restoreAllMocks();
});
async function importPage() {
const mod = await import('./page');
return mod.default;
}
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(() => {
vi.useFakeTimers({ shouldAdvanceTime: true });
user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime });
mockAuthStore.mockImplementation(
(selector: (state: { user: { id: string; username: string; displayName: string; role: string; tenantId: string } }) => unknown) =>
selector({
@@ -132,7 +135,6 @@ describe('Marketplace Filters', () => {
});
it('typing in search filters the grid to only matching modules after debounce', async () => {
const Page = await importPage();
render(<Page />);
await waitFor(() => {
@@ -142,7 +144,7 @@ describe('Marketplace Filters', () => {
expect(screen.getByText('Email Tool')).toBeInTheDocument();
const searchInput = screen.getByPlaceholderText('Module suchen...');
await userEvent.type(searchInput, 'Domain');
await user.type(searchInput, 'Domain');
act(() => {
vi.advanceTimersByTime(350);
@@ -155,7 +157,6 @@ describe('Marketplace Filters', () => {
});
it('selecting the Aktiviert status tab shows only activated modules', async () => {
const Page = await importPage();
render(<Page />);
await waitFor(() => {
@@ -163,7 +164,7 @@ describe('Marketplace Filters', () => {
});
const activeTab = screen.getByRole('tab', { name: /Aktiviert/i });
await userEvent.click(activeTab);
await user.click(activeTab);
await waitFor(() => {
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 () => {
const Page = await importPage();
render(<Page />);
await waitFor(() => {
@@ -181,7 +181,7 @@ describe('Marketplace Filters', () => {
});
const utilitiesChip = screen.getByRole('button', { name: 'Utilities' });
await userEvent.click(utilitiesChip);
await user.click(utilitiesChip);
await waitFor(() => {
expect(screen.getByText('Converter')).toBeInTheDocument();
@@ -189,7 +189,7 @@ describe('Marketplace Filters', () => {
});
const allChip = screen.getByRole('button', { name: 'Alle' });
await userEvent.click(allChip);
await user.click(allChip);
await waitFor(() => {
expect(screen.getByText('Domaincheck')).toBeInTheDocument();
@@ -198,7 +198,6 @@ describe('Marketplace Filters', () => {
});
it('search + status + category filters compose together (AND)', async () => {
const Page = await importPage();
render(<Page />);
await waitFor(() => {
@@ -207,11 +206,11 @@ describe('Marketplace Filters', () => {
// Filter to Domain-Tools category
const domainChip = screen.getByRole('button', { name: 'Domain-Tools' });
await userEvent.click(domainChip);
await user.click(domainChip);
// Filter to available only
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)
await waitFor(() => {
@@ -222,7 +221,6 @@ describe('Marketplace Filters', () => {
});
it('renders filtered-empty heading when filters produce zero results', async () => {
const Page = await importPage();
render(<Page />);
await waitFor(() => {
@@ -230,7 +228,7 @@ describe('Marketplace Filters', () => {
});
const searchInput = screen.getByPlaceholderText('Module suchen...');
await userEvent.type(searchInput, 'zzz-nonexistent');
await user.type(searchInput, 'zzz-nonexistent');
act(() => {
vi.advanceTimersByTime(350);
@@ -1,5 +1,8 @@
import { cleanup, render, screen, waitFor } from '@testing-library/react';
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
vi.mock('next-intl', () => ({
@@ -79,12 +82,6 @@ afterEach(() => {
vi.restoreAllMocks();
});
// Lazy import after mocks
async function importPage() {
const mod = await import('./page');
return mod.default;
}
describe('MarketplacePage', () => {
beforeEach(() => {
// Default: admin user
@@ -106,7 +103,6 @@ describe('MarketplacePage', () => {
}),
);
const Page = await importPage();
render(<Page />);
await waitFor(() => {
@@ -126,7 +122,6 @@ describe('MarketplacePage', () => {
}),
);
const Page = await importPage();
render(<Page />);
await waitFor(() => {
@@ -156,7 +151,6 @@ describe('MarketplacePage', () => {
}),
);
const Page = await importPage();
render(<Page />);
await waitFor(() => {
@@ -176,7 +170,6 @@ describe('MarketplacePage', () => {
}),
);
const Page = await importPage();
render(<Page />);
await waitFor(() => {
@@ -1,6 +1,13 @@
import { cleanup, render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
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', () => ({
useTranslations: () => (key: string, params?: Record<string, string>) => {
@@ -86,19 +93,8 @@ afterEach(() => {
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', () => {
it('renders tenant options for SUPER_ADMIN; renders nothing for ADMIN', async () => {
// SUPER_ADMIN case
it('renders tenant options for SUPER_ADMIN', async () => {
mockAuthStore.mockImplementation(
(selector: (state: { user: { id: string; username: string; displayName: string; role: string; tenantId: string } }) => unknown) =>
selector({
@@ -116,16 +112,13 @@ describe('TenantContextSelector', () => {
}),
);
const TenantContextSelector = await importTenantSelector();
const { unmount } = render(<TenantContextSelector />);
render(<TenantContextSelector />);
await waitFor(() => {
expect(screen.getByText('Tenant Alpha')).toBeInTheDocument();
});
expect(await screen.findByText('Tenant Alpha')).toBeInTheDocument();
expect(screen.getByText('Tenant Beta')).toBeInTheDocument();
unmount();
});
// ADMIN case
it('renders nothing for ADMIN and does not load the tenant list', () => {
mockAuthStore.mockImplementation(
(selector: (state: { user: { id: string; username: string; displayName: string; role: string; tenantId: string } }) => unknown) =>
selector({
@@ -133,8 +126,12 @@ describe('TenantContextSelector', () => {
}),
);
const fetchMock = vi.fn();
vi.stubGlobal('fetch', fetchMock);
const { container } = render(<TenantContextSelector />);
expect(container.innerHTML).toBe('');
expect(fetchMock).not.toHaveBeenCalled();
});
it('calls setSelectedTenantId when selector value changes', async () => {
@@ -155,7 +152,6 @@ describe('TenantContextSelector', () => {
}),
);
const TenantContextSelector = await importTenantSelector();
render(<TenantContextSelector />);
await waitFor(() => {
@@ -190,7 +186,6 @@ describe('ActivationDialog', () => {
}),
);
const Page = await importPage();
render(<Page />);
await waitFor(() => {
@@ -224,7 +219,6 @@ describe('ActivationDialog', () => {
}),
);
const Page = await importPage();
render(<Page />);
await waitFor(() => {