Skip to content

Commit

Permalink
Merge branch 'develop' into feat/AECO-197-UI-Kit-button-variants
Browse files Browse the repository at this point in the history
  • Loading branch information
csuarez committed Jul 7, 2023
2 parents cb90860 + 28b41fb commit 54109f1
Show file tree
Hide file tree
Showing 38 changed files with 1,431 additions and 187 deletions.
5 changes: 5 additions & 0 deletions .changeset/kind-coats-worry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@rocket.chat/meteor': patch
---

Fixed Canned Response custom tags breaking the GUI on enterprise
6 changes: 6 additions & 0 deletions .changeset/mentions-emoji-emphasis.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@rocket.chat/gazzodown': minor
---

Fixed mentions and emojis inside inside bold, italic or strikethrough texts

1 change: 1 addition & 0 deletions apps/meteor/.mocharc.client.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,5 @@ module.exports = {
'tests/unit/lib/**/*.tests.ts',
'tests/unit/client/**/*.test.ts',
],
exclude: ['client/hooks/*.spec.{ts,tsx}'],
};
3 changes: 2 additions & 1 deletion apps/meteor/client/components/GenericMenu/GenericMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ const GenericMenu = ({ title, icon = 'menu', onAction, ...props }: GenericMenuPr
const handleAction = useHandleMenuAction(itemsList || []);

const hasIcon = itemsList.some(({ icon }) => icon);
const handleItems = (items: GenericMenuItemProps[]) => (hasIcon ? items.map((item) => ({ ...item, gap: !item.icon })) : items);
const handleItems = (items: GenericMenuItemProps[]) =>
hasIcon ? items.map((item) => ({ ...item, gap: !item.icon && !item.status })) : items;

return (
<>
Expand Down
51 changes: 26 additions & 25 deletions apps/meteor/client/components/Omnichannel/Tags.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,21 @@ import { Field, TextInput, Chip, Button } from '@rocket.chat/fuselage';
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { useToastMessageDispatch, useTranslation } from '@rocket.chat/ui-contexts';
import type { ChangeEvent, ReactElement } from 'react';
import React, { useState } from 'react';
import React, { useMemo, useState } from 'react';

import { useFormsSubscription } from '../../views/omnichannel/additionalForms';
import { FormSkeleton } from './Skeleton';
import { useLivechatTags } from './hooks/useLivechatTags';

const Tags = ({
tags = [],
handler,
error,
tagRequired,
department,
}: {
type TagsProps = {
tags?: string[];
handler: (value: string[]) => void;
error?: string;
tagRequired?: boolean;
department?: string;
}): ReactElement => {
};

const Tags = ({ tags = [], handler, error, tagRequired, department }: TagsProps): ReactElement => {
const t = useTranslation();
const forms = useFormsSubscription() as any;

Expand All @@ -33,16 +29,20 @@ const Tags = ({
department,
});

const customTags = useMemo(() => {
return tags.filter((tag) => !tagsResult?.tags.find((rtag) => rtag._id === tag));
}, [tags, tagsResult?.tags]);

const dispatchToastMessage = useToastMessageDispatch();

const [tagValue, handleTagValue] = useState('');
const [paginatedTagValue, handlePaginatedTagValue] = useState<{ label: string; value: string }[]>();

const paginatedTagValue = useMemo(() => tags.map((tag) => ({ label: tag, value: tag })), [tags]);

const removeTag = (tagToRemove: string): void => {
if (tags) {
const tagsFiltered = tags.filter((tag: string) => tag !== tagToRemove);
handler(tagsFiltered);
}
if (!tags) return;

handler(tags.filter((tag) => tag !== tagToRemove));
};

const handleTagTextSubmit = useMutableCallback(() => {
Expand All @@ -56,7 +56,7 @@ const Tags = ({
return;
}

if (tags.includes(tagValue)) {
if (tags.some((tag) => tag === tagValue)) {
dispatchToastMessage({ type: 'error', message: t('Tag_already_exists') });
return;
}
Expand All @@ -79,8 +79,7 @@ const Tags = ({
<EETagsComponent
value={paginatedTagValue}
handler={(tags: { label: string; value: string }[]): void => {
handler(tags.map((tag) => tag.label));
handlePaginatedTagValue(tags);
handler(tags.map((tag) => tag.value));
}}
department={department}
/>
Expand All @@ -99,16 +98,18 @@ const Tags = ({
{t('Add')}
</Button>
</Field.Row>

<Field.Row justifyContent='flex-start'>
{tags?.map((tag, i) => (
<Chip key={i} onClick={(): void => removeTag(tag)} mie='x8'>
{tag}
</Chip>
))}
</Field.Row>
</>
)}

{customTags.length > 0 && (
<Field.Row justifyContent='flex-start'>
{customTags?.map((tag, i) => (
<Chip key={i} onClick={(): void => removeTag(tag)} mie='x8'>
{tag}
</Chip>
))}
</Field.Row>
)}
</>
);
};
Expand Down
177 changes: 177 additions & 0 deletions apps/meteor/client/hooks/useFeaturePreview.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
/* eslint-disable react/no-multi-comp */
import type { ISetting } from '@rocket.chat/core-typings';
import type { LoginService } from '@rocket.chat/ui-contexts';
import { SettingsContext, UserContext } from '@rocket.chat/ui-contexts';
import { renderHook } from '@testing-library/react-hooks';
import type { ObjectId } from 'mongodb';
import type { ContextType } from 'react';
import React from 'react';

import { useFeaturePreview } from './useFeaturePreview';

const userContextValue: ContextType<typeof UserContext> = {
userId: 'john.doe',
user: {
_id: 'john.doe',
username: 'john.doe',
name: 'John Doe',
createdAt: new Date(),
active: true,
_updatedAt: new Date(),
roles: ['admin'],
type: 'user',
},
queryPreference: <T,>(pref: string | ObjectId, defaultValue: T) => [
() => () => undefined,
() => (typeof pref === 'string' ? undefined : defaultValue),
],
querySubscriptions: () => [() => () => undefined, () => []],
querySubscription: () => [() => () => undefined, () => undefined],
queryRoom: () => [() => () => undefined, () => undefined],

queryAllServices: () => [() => (): void => undefined, (): LoginService[] => []],
loginWithService: () => () => Promise.reject('loginWithService not implemented'),
loginWithPassword: async () => Promise.reject('loginWithPassword not implemented'),
loginWithToken: async () => Promise.reject('loginWithToken not implemented'),
logout: () => Promise.resolve(),
};

const settingContextValue: ContextType<typeof SettingsContext> = {
hasPrivateAccess: true,
isLoading: false,
querySetting: (_id: string) => [() => () => undefined, () => undefined],
querySettings: () => [() => () => undefined, () => []],
dispatch: async () => undefined,
};

it('should return false if featurePreviewEnabled is false', () => {
const { result } = renderHook(
() => {
return useFeaturePreview('quickReactions');
},
{
wrapper: ({ children }) => (
<MockedSettingsContext
settings={{
Accounts_AllowFeaturePreview: false,
}}
>
<MockedUserContext userPreferences={{}}>{children}</MockedUserContext>
</MockedSettingsContext>
),
},
);

expect(result.all[0]).toBe(false);
});

it('should return false if featurePreviewEnabled is true but feature is not in userPreferences', () => {
const { result } = renderHook(
() => {
return useFeaturePreview('quickReactions');
},
{
wrapper: ({ children }) => (
<MockedSettingsContext
settings={{
Accounts_AllowFeaturePreview: false,
}}
>
<MockedUserContext
userPreferences={{
featuresPreview: [
{
name: 'quickReactions',
value: true,
},
],
}}
>
{children}
</MockedUserContext>
</MockedSettingsContext>
),
},
);

expect(result.all[0]).toBe(false);
});

it('should return true if featurePreviewEnabled is true and feature is in userPreferences', () => {
const { result } = renderHook(
() => {
return useFeaturePreview('quickReactions');
},
{
wrapper: ({ children }) => (
<MockedSettingsContext
settings={{
Accounts_AllowFeaturePreview: true,
}}
>
<MockedUserContext
userPreferences={{
featuresPreview: [
{
name: 'quickReactions',
value: true,
},
],
}}
>
{children}
</MockedUserContext>
</MockedSettingsContext>
),
},
);

expect(result.all[0]).toBe(true);
});

const createUserContextValue = ({ userPreferences }: { userPreferences?: Record<string, unknown> }): ContextType<typeof UserContext> => {
return {
...userContextValue,
...(userPreferences && { queryPreference: (id) => [() => () => undefined, () => userPreferences[id as unknown as string] as any] }),
};
};

const createSettingContextValue = ({ settings }: { settings?: Record<string, ISetting['value']> }): ContextType<typeof SettingsContext> => {
const cache = new Map<string, ISetting['value']>();

return {
...settingContextValue,
...(settings && {
querySetting: (_id: string) => [
() => () => undefined,
() => {
if (cache.has(_id)) {
return cache.get(_id) as any;
}
cache.set(_id, { value: settings[_id] } as any);
return cache.get(_id) as any;
},
],
}),
};
};

export const MockedSettingsContext = ({
settings,
children,
}: {
children: React.ReactNode;
settings?: Record<string, ISetting['value']>;
}) => {
return <SettingsContext.Provider value={createSettingContextValue({ settings })}>{children}</SettingsContext.Provider>;
};

export const MockedUserContext = ({
userPreferences,
children,
}: {
children: React.ReactNode;
userPreferences?: Record<string, unknown>;
}) => {
return <UserContext.Provider value={createUserContextValue({ userPreferences })}>{children}</UserContext.Provider>;
};
2 changes: 1 addition & 1 deletion apps/meteor/client/hooks/useFeaturePreview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { FeaturesAvailable, FeaturePreviewProps } from './useFeaturePreview

export const useFeaturePreview = (featureName: FeaturesAvailable) => {
const featurePreviewEnabled = useSetting('Accounts_AllowFeaturePreview');

const features = useUserPreference<FeaturePreviewProps[]>('featuresPreview');

const currentFeature = features?.find((feature) => feature.name === featureName);
Expand All @@ -13,7 +14,6 @@ export const useFeaturePreview = (featureName: FeaturesAvailable) => {
}

if (!currentFeature) {
console.error(`Feature ${featureName} not found`);
return false;
}

Expand Down
Loading

0 comments on commit 54109f1

Please sign in to comment.