Files
leocrm/frontend/src/__tests__/settings/WorkspaceManager.scopeEditor.test.tsx
T

128 lines
4.5 KiB
TypeScript
Raw Normal View History

/**
* N2 — WorkspaceManager integration: dynamic scope editor replaces the
* raw JSON textarea (Phase N). The module editor renders filter UI from
* /scope-definitions and saves scope values inside workspace_modules.config.
*/
import React from 'react';
import { describe, it, expect, vi } from 'vitest';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { WorkspaceManager } from '@/components/settings/WorkspaceManager';
const mockWorkspaces = {
items: [
{
id: 'ws1',
name: 'Vertrieb',
icon: 'LayoutGrid',
description: null,
is_default: false,
is_active: true,
created_by: null,
created_at: null,
updated_at: null,
user_count: 2,
modules: [
{
module_key: 'contacts',
is_visible: true,
menu_order: 0,
config: { contact_types: ['company'] },
},
],
},
],
total: 1,
};
const mockScopeDefinitions = {
modules: {
contacts: [
{
key: 'contact_types',
label: 'Kontakt-Typen',
control: 'multiselect',
options: [
{ value: 'company', label: 'Firmen' },
{ value: 'person', label: 'Personen' },
],
value_source: null,
default: null,
},
],
// dashboard: module without scope dimensions
},
};
const setModulesMock = vi.fn().mockResolvedValue({});
vi.mock('@/api/hooks/workspaces', () => ({
useWorkspaces: () => ({ data: mockWorkspaces, isLoading: false, isError: false, error: null, refetch: vi.fn() }),
useCreateWorkspace: () => ({ mutateAsync: vi.fn().mockResolvedValue({ id: 'ws2' }), isPending: false }),
useUpdateWorkspace: () => ({ mutateAsync: vi.fn().mockResolvedValue({}), isPending: false }),
useDeleteWorkspace: () => ({ mutateAsync: vi.fn().mockResolvedValue({}), isPending: false }),
useSetWorkspaceModules: () => ({ mutateAsync: setModulesMock, isPending: false }),
useAssignWorkspaceUser: () => ({ mutateAsync: vi.fn(), isPending: false }),
useRemoveWorkspaceUser: () => ({ mutateAsync: vi.fn(), isPending: false }),
useWorkspaceScopeDefinitions: () => ({ data: mockScopeDefinitions, isLoading: false, isError: false }),
useScopeValues: () => ({ data: [], isLoading: false, isError: false }),
}));
vi.mock('@/store/pluginStore', () => ({
usePluginStore: (selector: (state: { manifests: unknown[] }) => unknown) =>
selector({
manifests: [
{ menu_items: [{ path: '/contacts', label: 'Kontakte', label_key: 'nav.contacts' }] },
],
}),
}));
function renderManager() {
return render(<WorkspaceManager />);
}
function openModuleEditor() {
fireEvent.click(screen.getByTitle('Module'));
}
describe('WorkspaceManager N2 scope editor integration', () => {
it('replaces the raw JSON textarea with the dynamic scope editor', () => {
renderManager();
openModuleEditor();
// N1 scope editor fields are rendered ...
expect(screen.getByTestId('scope-field-contact_types')).toBeInTheDocument();
// ... and the legacy JSON textarea is gone.
expect(document.querySelector('textarea')).toBeNull();
});
it('loads existing scope values from the stored module config', () => {
renderManager();
openModuleEditor();
const company = screen.getByTestId('scope-option-contact_types-company') as HTMLInputElement;
const person = screen.getByTestId('scope-option-contact_types-person') as HTMLInputElement;
expect(company.checked).toBe(true);
expect(person.checked).toBe(false);
});
it('saves scope values inside module config on save', async () => {
renderManager();
openModuleEditor();
fireEvent.click(screen.getByTestId('scope-option-contact_types-person'));
fireEvent.click(screen.getByText('Speichern'));
await waitFor(() => expect(setModulesMock).toHaveBeenCalled());
const payload = setModulesMock.mock.calls[0][0];
expect(payload.workspaceId).toBe('ws1');
const contactsModule = payload.modules.find((m: any) => m.module_key === 'contacts');
expect(contactsModule.config).toEqual({ contact_types: ['company', 'person'] });
});
it('shows the no-dimensions hint for modules without scope definitions', () => {
renderManager();
openModuleEditor();
// dashboard starts hidden (workspace config only covers contacts) —
// make it visible first, then the scope editor renders its hint.
fireEvent.click(screen.getByLabelText('Dashboard'));
expect(screen.getByTestId('scope-no-dimensions-dashboard')).toBeInTheDocument();
});
});