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:
@@ -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(() => {
|
||||
|
||||
Reference in New Issue
Block a user