Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
9 changes: 9 additions & 0 deletions .changeset/clean-feet-worry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@rocket.chat/meteor": minor
"@rocket.chat/gazzodown": patch
"@rocket.chat/rest-typings": minor
---

Fixes search by name in custom emojis list, by adding a correct parameter to the endpoint `emoji-custom.all`

Now the endpoint `emoji-custom.all` accepts a `name` as parameter, so the filter should work on emojis page withouth the necessity of set `ALLOW_UNSAFE_QUERY_AND_FIELDS_API_PARAMS` env var
11 changes: 10 additions & 1 deletion apps/meteor/app/api/server/v1/emoji-custom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Media } from '@rocket.chat/core-services';
import type { IEmojiCustom } from '@rocket.chat/core-typings';
import { EmojiCustom } from '@rocket.chat/models';
import { isEmojiCustomList } from '@rocket.chat/rest-typings';
import { escapeRegExp } from '@rocket.chat/string-helpers';
import { Meteor } from 'meteor/meteor';

import { SystemLogger } from '../../../../server/lib/logger/system';
Expand Down Expand Up @@ -79,10 +80,18 @@ API.v1.addRoute(
async get() {
const { offset, count } = await getPaginationItems(this.queryParams);
const { sort, query } = await this.parseJsonQuery();
const { name } = this.queryParams;

return API.v1.success(
await findEmojisCustom({
query,
query: name
? {
name: {
$regex: escapeRegExp(name),
$options: 'i',
},
}
: query,
pagination: {
offset,
count,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Box, Pagination, States, StatesActions, StatesAction, StatesIcon, StatesTitle } from '@rocket.chat/fuselage';
import { useDebouncedValue } from '@rocket.chat/fuselage-hooks';
import { escapeRegExp } from '@rocket.chat/string-helpers';
import { useTranslation, useEndpoint } from '@rocket.chat/ui-contexts';
import { useQuery } from '@tanstack/react-query';
import type { MutableRefObject } from 'react';
Expand Down Expand Up @@ -35,7 +34,7 @@ const CustomEmoji = ({ onClick, reload }: CustomEmojiProps) => {
const query = useDebouncedValue(
useMemo(
() => ({
query: JSON.stringify({ name: { $regex: escapeRegExp(text), $options: 'i' } }),
name: text,
sort: `{ "${sortBy}": ${sortDirection === 'asc' ? 1 : -1} }`,
count: itemsPerPage,
offset: current,
Expand Down
53 changes: 52 additions & 1 deletion apps/meteor/tests/e2e/emojis.spec.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { Users } from './fixtures/userStates';
import { HomeChannel } from './page-objects';
import { HomeChannel, AdminEmoji } from './page-objects';
import { createTargetChannel } from './utils';
import { test, expect } from './utils/test';

test.use({ storageState: Users.admin.state });

test.describe.serial('emoji', () => {
let poHomeChannel: HomeChannel;
let poAdminEmoji: AdminEmoji;
let targetChannel: string;

test.beforeAll(async ({ api }) => {
Expand Down Expand Up @@ -57,4 +58,54 @@ test.describe.serial('emoji', () => {
await poHomeChannel.content.sendMessage('® © ™ # *');
await expect(poHomeChannel.content.lastUserMessage).toContainText('® © ™ # *');
});

test('should add a custom emoji, send it, rename it, and check render', async ({ page }) => {
const emojiName = 'customemoji';
const newEmojiName = 'renamedemoji';
const emojiUrl = './tests/e2e/fixtures/files/test-image.jpeg';

poAdminEmoji = new AdminEmoji(page);

await test.step('Add custom emoji', async () => {
await poHomeChannel.sidenav.openAdministrationByLabel('Workspace');
await page.locator('role=link[name="Emoji"]').click();
await poAdminEmoji.newButton.click();
await poAdminEmoji.addEmoji.nameInput.fill(emojiName);

const [fileChooser] = await Promise.all([page.waitForEvent('filechooser'), page.locator('role=button[name="Custom Emoji"]').click()]);
await fileChooser.setFiles(emojiUrl);

await poAdminEmoji.addEmoji.btnSave.click();
await poAdminEmoji.closeAdminButton.click();

await poHomeChannel.sidenav.openChat(targetChannel);

await poHomeChannel.content.sendMessage(`:${emojiName}:`);
await page.keyboard.press('Enter');
await expect(poHomeChannel.content.lastUserMessage.getByTitle(`:${emojiName}:`)).toBeVisible();
});

await test.step('Rename custom emoji', async () => {
await poHomeChannel.sidenav.openAdministrationByLabel('Workspace');
await page.locator('role=link[name="Emoji"]').click();
await poAdminEmoji.findEmojiByName(emojiName);
await poAdminEmoji.addEmoji.nameInput.fill(newEmojiName);

await poAdminEmoji.addEmoji.btnSave.click();
await poAdminEmoji.closeAdminButton.click();

await poHomeChannel.sidenav.openChat(targetChannel);

await poHomeChannel.content.sendMessage(`:${newEmojiName}:`);
await page.keyboard.press('Enter');
await expect(poHomeChannel.content.lastUserMessage.getByTitle(`:${newEmojiName}:`)).toBeVisible();
});

await test.step('Delete custom emoji', async () => {
await poHomeChannel.sidenav.openAdministrationByLabel('Workspace');
await page.locator('role=link[name="Emoji"]').click();
await poAdminEmoji.findEmojiByName(newEmojiName);
await poAdminEmoji.addEmoji.btnDelete.click();
});
});
});
31 changes: 31 additions & 0 deletions apps/meteor/tests/e2e/page-objects/admin-emojis.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { Locator, Page } from '@playwright/test';

import { AdminFlextabEmoji } from './fragments/admin-flextab-emoji';

export class AdminEmoji {
private readonly page: Page;

readonly addEmoji: AdminFlextabEmoji;

constructor(page: Page) {
this.page = page;
this.addEmoji = new AdminFlextabEmoji(page);
}

get newButton(): Locator {
return this.page.locator('role=button[name="New"]');
}

get closeAdminButton(): Locator {
return this.page.getByRole('navigation').getByRole('button', { name: 'Close' });
}

get searchInput(): Locator {
return this.page.locator('role=textbox[name="Search"]');
}

async findEmojiByName(emojiName: string) {
await this.searchInput.fill(emojiName);
await this.page.locator(`role=link[name=${emojiName}]`).click();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { Locator, Page } from '@playwright/test';

export class AdminFlextabEmoji {
private readonly page: Page;

constructor(page: Page) {
this.page = page;
}

get nameInput(): Locator {
return this.page.locator('role=textbox[name="Name"]');
}

get btnSave(): Locator {
return this.page.locator('role=button[name="Save"]');
}

get btnDelete(): Locator {
return this.page.locator('role=button[name="Delete"]');
}
}
1 change: 1 addition & 0 deletions apps/meteor/tests/e2e/page-objects/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './account-profile';
export * from './admin-email-inboxes';
export * from './admin-emojis';
export * from './admin';
export * from './auth';
export * from './home-channel';
Expand Down
16 changes: 16 additions & 0 deletions apps/meteor/tests/end-to-end/api/emoji-custom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,22 @@ describe('[EmojiCustom]', () => {
})
.end(done);
});
it('should return only filtered by name emojis', (done) => {
void request
.get(api('emoji-custom.all'))
.set(credentials)
.query({
name: `${customEmojiName}-without-aliases`,
})
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('emojis').and.to.be.an('array').and.to.have.lengthOf(1);
expect(res.body).to.have.property('total');
expect(res.body).to.have.property('offset');
expect(res.body).to.have.property('count');
})
.end(done);
});
});

describe('Accessing custom emojis', () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/gazzodown/src/emoji/EmojiRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const EmojiRenderer = ({ big = false, preview = false, ...emoji }: EmojiProps):
)}
</span>
)) ?? (
<span role='img' aria-label={sanitizedFallback.charAt(0) === ':' ? sanitizedFallback : undefined}>
<span title={sanitizedFallback} role='img' aria-label={sanitizedFallback.charAt(0) === ':' ? sanitizedFallback : undefined}>
{sanitizedFallback}
</span>
)}
Expand Down
2 changes: 1 addition & 1 deletion packages/rest-typings/src/v1/emojiCustom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export const isEmojiCustomList = ajv.compile<emojiCustomList>(emojiCustomListSch

export type EmojiCustomEndpoints = {
'/v1/emoji-custom.all': {
GET: (params: PaginatedRequest<{ query: string }, 'name'>) => PaginatedResult<{
GET: (params: PaginatedRequest<{ name?: string }, 'name'>) => PaginatedResult<{
emojis: IEmojiCustom[];
}>;
};
Expand Down
Loading