import { defineComponent } from 'vue'; import { createTestingPinia } from '@pinia/testing'; import { screen, within } from '@testing-library/vue'; import { vi } from 'vitest'; import { ROLE, type UsersList } from '@n8n/api-types'; import { type UserAction } from '@n8n/design-system'; import SettingsUsersTable from '@/components/SettingsUsers/SettingsUsersTable.vue'; import { createComponentRenderer } from '@/__tests__/render'; import { useEmitters } from '@/__tests__/utils'; import type { IUser } from '@/Interface'; const { emitters, addEmitter } = useEmitters< 'settingsUsersRoleCell' | 'settingsUsersActionsCell' | 'n8nDataTableServer' >(); const hasPermission = vi.fn(() => true); vi.mock('@/utils/rbac/permissions', () => ({ hasPermission: () => hasPermission(), })); vi.mock('@/components/SettingsUsers/SettingsUsersRoleCell.vue', () => ({ default: defineComponent({ setup(_, { emit }) { addEmitter('settingsUsersRoleCell', emit); }, template: '
', }), })); vi.mock('@/components/SettingsUsers/SettingsUsersActionsCell.vue', () => ({ default: defineComponent({ props: { data: { type: Object, required: true }, actions: { type: Array, required: true }, }, setup(_, { emit }) { addEmitter('settingsUsersActionsCell', emit); }, template: '
', }), })); // Mock N8nDataTableServer to emit events vi.mock('@n8n/design-system', async (importOriginal) => { const original = await importOriginal(); return { ...original, N8nDataTableServer: defineComponent({ props: { headers: { type: Array, required: true }, items: { type: Array, required: true }, itemsLength: { type: Number, required: true }, }, setup(_, { emit }) { addEmitter('n8nDataTableServer', emit); }, template: `
  • {{ header.value(item) }} {{ item[header.key] }}
`, }), }; }); const mockUsersList: UsersList = { items: [ { id: '1', email: 'owner@example.com', firstName: 'Owner', lastName: 'User', role: ROLE.Owner, isOwner: true, isPending: false, mfaEnabled: true, settings: {}, }, { id: '2', email: 'member@example.com', firstName: 'Member', lastName: 'User', role: ROLE.Member, isOwner: false, isPending: false, mfaEnabled: false, settings: {}, }, { id: '3', email: 'pending@example.com', firstName: '', lastName: '', role: ROLE.Member, isOwner: false, isPending: true, mfaEnabled: false, settings: {}, inviteAcceptUrl: 'https://example.com/invite/123', }, ], count: 3, }; const mockActions: Array> = [ { value: 'delete', label: 'Delete', }, { value: 'reinvite', label: 'Reinvite', guard: (user) => user.isPendingUser, }, ]; let renderComponent: ReturnType; describe('SettingsUsersTable', () => { beforeEach(() => { renderComponent = createComponentRenderer(SettingsUsersTable, { pinia: createTestingPinia(), props: { data: mockUsersList, actions: mockActions, loading: false, }, }); hasPermission.mockReturnValue(true); // Default to having permission }); afterEach(() => { vi.clearAllMocks(); }); it('should render user data correctly in the table', () => { renderComponent(); // Check for owner user const ownerRow = screen.getByTestId('user-row-1'); expect(within(ownerRow).getByText(/Owner User/)).toBeInTheDocument(); expect(within(ownerRow).getByText('owner@example.com')).toBeInTheDocument(); expect(within(ownerRow).getByText('Enabled')).toBeInTheDocument(); // 2FA // Check for member user const memberRow = screen.getByTestId('user-row-2'); expect(within(memberRow).getByText(/Member User/)).toBeInTheDocument(); expect(within(memberRow).getByText('member@example.com')).toBeInTheDocument(); expect(within(memberRow).getByText('Disabled')).toBeInTheDocument(); // 2FA }); it('should delegate update:options event from N8nDataTableServer', () => { const { emitted } = renderComponent(); emitters.n8nDataTableServer.emit('update:options', { page: 1, itemsPerPage: 20 }); expect(emitted()).toHaveProperty('update:options'); expect(emitted()['update:options'][0]).toEqual([{ page: 1, itemsPerPage: 20 }]); }); describe('role changing', () => { it('should render role update component when user has permission', () => { hasPermission.mockReturnValue(true); renderComponent(); screen.getAllByTestId('user-role').forEach((roleCell) => { expect(roleCell).toBeVisible(); }); }); it('should emit "update:role" when a new role is selected', () => { const { emitted } = renderComponent(); emitters.settingsUsersRoleCell.emit('update:role', { role: 'global:admin', userId: '2' }); expect(emitted()).toHaveProperty('update:role'); expect(emitted()['update:role'][0]).toEqual([{ role: 'global:admin', userId: '2' }]); }); it('should emit "action" with "delete" payload when delete is selected from role change', () => { const { emitted } = renderComponent(); emitters.settingsUsersRoleCell.emit('update:role', { role: 'delete', userId: '2' }); // It should not emit 'update:role' expect(emitted()).not.toHaveProperty('update:role'); // It should emit 'action' expect(emitted()).toHaveProperty('action'); expect(emitted().action[0]).toEqual([{ action: 'delete', userId: '2' }]); }); it('should render role as plain text when user lacks permission', () => { hasPermission.mockReturnValue(false); renderComponent(); const memberRow = screen.getByTestId('user-row-2'); expect(within(memberRow).queryByTestId('user-role')).not.toBeInTheDocument(); expect(within(memberRow).getByText('Member')).toBeInTheDocument(); }); }); describe('user actions', () => { it('should filter actions for the owner user (should be none)', () => { renderComponent(); const ownerActions = screen.getByTestId('actions-cell-1'); expect(ownerActions.dataset.actionsCount).toBe('0'); }); it('should filter actions based on guard function', () => { renderComponent(); // Member user is not pending, so 'reinvite' action should be filtered out const memberActions = screen.getByTestId('actions-cell-2'); expect(memberActions.dataset.actionsCount).toBe('1'); // Only 'delete' // Pending user should have 'reinvite' action const pendingActions = screen.getByTestId('actions-cell-3'); expect(pendingActions.dataset.actionsCount).toBe('2'); // 'delete' and 'reinvite' }); it('should delegate action events from SettingsUsersActionsCell', async () => { const { emitted } = renderComponent(); emitters.settingsUsersActionsCell.emit('action', { action: 'delete', userId: '2' }); expect(emitted()).toHaveProperty('action'); expect(emitted().action[0]).toEqual([{ action: 'delete', userId: '2' }]); }); }); });