Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const ContactInfo = ({ contact, onClose }: ContactInfoProps) => {
const { name, emails, phones, conflictingFields, createdAt, lastChat, contactManager, customFields: userCustomFields } = contact;

const hasConflicts = conflictingFields && conflictingFields?.length > 0;
const customFieldEntries = useValidCustomFields(userCustomFields);
const customFieldEntries = useValidCustomFields(userCustomFields, 'visitor');

return (
<ContextualbarDialog onClose={onClose}>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import type { ILivechatCustomField } from '@rocket.chat/core-typings';
import { mockAppRoot } from '@rocket.chat/mock-providers';
import { renderHook } from '@testing-library/react';

import { useValidCustomFields } from './useValidCustomFields';
import { useCustomFieldsQuery } from '../../hooks/useCustomFieldsQuery';

jest.mock('../../hooks/useCustomFieldsQuery', () => ({
useCustomFieldsQuery: jest.fn(),
}));

const mockedUseCustomFieldsQuery = jest.mocked(useCustomFieldsQuery);

const makeField = (partial: Pick<ILivechatCustomField, '_id' | 'scope'> & Partial<ILivechatCustomField>): ILivechatCustomField => ({
_updatedAt: new Date(),
label: 'Field',
visibility: 'visible',
...partial,
});

describe('useValidCustomFields', () => {
const visitorField = makeField({ _id: 'cf_visitor', scope: 'visitor' });
const roomField = makeField({ _id: 'cf_room', scope: 'room' });

const withViewPermission = mockAppRoot().withPermission('view-livechat-room-customfields').build();
const withoutViewPermission = mockAppRoot().build();

beforeEach(() => {
mockedUseCustomFieldsQuery.mockReturnValue({
data: { customFields: [visitorField, roomField] },
isError: false,
} as ReturnType<typeof useCustomFieldsQuery>);
});

describe('scope', () => {
const userCustomFields = {
cf_visitor: 'visitor value',
cf_room: 'room value',
};

it('keeps only visitor-scoped custom fields when scope is visitor', () => {
const { result } = renderHook(() => useValidCustomFields(userCustomFields, 'visitor'), {
wrapper: withViewPermission,
});

expect(result.current).toEqual([['cf_visitor', 'visitor value']]);
});

it('keeps only room-scoped custom fields when scope is room', () => {
const { result } = renderHook(() => useValidCustomFields(userCustomFields, 'room'), { wrapper: withViewPermission });

expect(result.current).toEqual([['cf_room', 'room value']]);
});

it('omits a key when the field is defined for the other scope', () => {
mockedUseCustomFieldsQuery.mockReturnValue({
data: { customFields: [roomField] },
isError: false,
} as ReturnType<typeof useCustomFieldsQuery>);

const { result } = renderHook(() => useValidCustomFields({ cf_room: 'room only' }, 'visitor'), {
wrapper: withViewPermission,
});

expect(result.current).toEqual([]);
});
});

describe('other filters (baseline)', () => {
it('returns an empty list without view permission even when scope matches', () => {
const { result } = renderHook(() => useValidCustomFields({ cf_visitor: 'x' }, 'visitor'), { wrapper: withoutViewPermission });

expect(result.current).toEqual([]);
});

it('excludes fields that are not set as visible', () => {
mockedUseCustomFieldsQuery.mockReturnValue({
data: {
customFields: [makeField({ _id: 'cf_hidden', scope: 'visitor', visibility: 'hidden' }), visitorField],
},
isError: false,
} as ReturnType<typeof useCustomFieldsQuery>);

const { result } = renderHook(() => useValidCustomFields({ cf_hidden: 'secret', cf_visitor: 'ok' }, 'visitor'), {
wrapper: withViewPermission,
});

expect(result.current).toEqual([['cf_visitor', 'ok']]);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import { useMemo } from 'react';

import { useCustomFieldsQuery } from '../../hooks/useCustomFieldsQuery';

const checkIsVisibleAndScopeVisitor = (key: string, customFields: ILivechatCustomField[]) => {
const checkIsVisibleAndCorrectScope = (key: string, customFields: ILivechatCustomField[], scope: 'visitor' | 'room') => {
const field = customFields?.find(({ _id }) => _id === key);
return field?.visibility === 'visible' && field?.scope === 'visitor';
return field?.visibility === 'visible' && field?.scope === scope;
};

export const useValidCustomFields = (userCustomFields: Record<string, string | unknown> | undefined) => {
export const useValidCustomFields = (userCustomFields: Record<string, string | unknown> | undefined, scope: 'visitor' | 'room') => {
const { data, isError } = useCustomFieldsQuery();
const canViewCustomFields = usePermission('view-livechat-room-customfields');

Expand All @@ -18,8 +18,10 @@ export const useValidCustomFields = (userCustomFields: Record<string, string | u
return [];
}

return Object.entries(userCustomFields).filter(([key, value]) => checkIsVisibleAndScopeVisitor(key, data?.customFields) && value);
}, [data?.customFields, userCustomFields, canViewCustomFields, isError]);
return Object.entries(userCustomFields).filter(
([key, value]) => checkIsVisibleAndCorrectScope(key, data?.customFields, scope) && value,
);
}, [canViewCustomFields, userCustomFields, data?.customFields, isError, scope]);

return customFieldEntries;
};
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ function ChatInfo({ id, route }: ChatInfoProps) {

const queueTime = useMemo(() => formatQueuedAt(room), [room]);

const customFieldEntries = useValidCustomFields(livechatData);
const customFieldEntries = useValidCustomFields(livechatData, 'room');

const onEditClick = useEffectEvent(() => {
const hasEditAccess = !!subscription || hasLocalEditRoomPermission || hasGlobalEditRoomPermission;
Expand Down
Loading