fix: Deprecated filter by query in emoji-custom.all api call (#36723)

This commit is contained in:
Tiago Evangelista Pinto 2025-08-20 19:04:19 -03:00 committed by GitHub
parent b450f818eb
commit d76a5578ed
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 143 additions and 6 deletions

View File

@ -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

View File

@ -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';
@ -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,

View File

@ -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';
@ -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,

View File

@ -1,5 +1,5 @@
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';
@ -7,6 +7,7 @@ test.use({ storageState: Users.admin.state });
test.describe.serial('emoji', () => {
let poHomeChannel: HomeChannel;
let poAdminEmoji: AdminEmoji;
let targetChannel: string;
test.beforeAll(async ({ api }) => {
@ -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();
});
});
});

View File

@ -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();
}
}

View File

@ -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"]');
}
}

View File

@ -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';

View File

@ -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', () => {

View File

@ -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>
)}

View File

@ -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[];
}>;
};