From d76a5578ed0e14fae2a041c0e09d565b28630d76 Mon Sep 17 00:00:00 2001 From: Tiago Evangelista Pinto Date: Wed, 20 Aug 2025 19:04:19 -0300 Subject: [PATCH] fix: Deprecated filter by query in `emoji-custom.all` api call (#36723) --- .changeset/clean-feet-worry.md | 9 ++++ apps/meteor/app/api/server/v1/emoji-custom.ts | 11 +++- .../views/admin/customEmoji/CustomEmoji.tsx | 3 +- apps/meteor/tests/e2e/emojis.spec.ts | 53 ++++++++++++++++++- .../tests/e2e/page-objects/admin-emojis.ts | 31 +++++++++++ .../fragments/admin-flextab-emoji.ts | 21 ++++++++ apps/meteor/tests/e2e/page-objects/index.ts | 1 + .../tests/end-to-end/api/emoji-custom.ts | 16 ++++++ .../gazzodown/src/emoji/EmojiRenderer.tsx | 2 +- packages/rest-typings/src/v1/emojiCustom.ts | 2 +- 10 files changed, 143 insertions(+), 6 deletions(-) create mode 100644 .changeset/clean-feet-worry.md create mode 100644 apps/meteor/tests/e2e/page-objects/admin-emojis.ts create mode 100644 apps/meteor/tests/e2e/page-objects/fragments/admin-flextab-emoji.ts diff --git a/.changeset/clean-feet-worry.md b/.changeset/clean-feet-worry.md new file mode 100644 index 00000000000..107aa31a1ac --- /dev/null +++ b/.changeset/clean-feet-worry.md @@ -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 diff --git a/apps/meteor/app/api/server/v1/emoji-custom.ts b/apps/meteor/app/api/server/v1/emoji-custom.ts index a2f886afb1c..fa84d3cc6b9 100644 --- a/apps/meteor/app/api/server/v1/emoji-custom.ts +++ b/apps/meteor/app/api/server/v1/emoji-custom.ts @@ -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, diff --git a/apps/meteor/client/views/admin/customEmoji/CustomEmoji.tsx b/apps/meteor/client/views/admin/customEmoji/CustomEmoji.tsx index 66829705f5b..e0476e26ff4 100644 --- a/apps/meteor/client/views/admin/customEmoji/CustomEmoji.tsx +++ b/apps/meteor/client/views/admin/customEmoji/CustomEmoji.tsx @@ -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, diff --git a/apps/meteor/tests/e2e/emojis.spec.ts b/apps/meteor/tests/e2e/emojis.spec.ts index c19d2f6b13b..a938b6ca069 100644 --- a/apps/meteor/tests/e2e/emojis.spec.ts +++ b/apps/meteor/tests/e2e/emojis.spec.ts @@ -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(); + }); + }); }); diff --git a/apps/meteor/tests/e2e/page-objects/admin-emojis.ts b/apps/meteor/tests/e2e/page-objects/admin-emojis.ts new file mode 100644 index 00000000000..b489a611436 --- /dev/null +++ b/apps/meteor/tests/e2e/page-objects/admin-emojis.ts @@ -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(); + } +} diff --git a/apps/meteor/tests/e2e/page-objects/fragments/admin-flextab-emoji.ts b/apps/meteor/tests/e2e/page-objects/fragments/admin-flextab-emoji.ts new file mode 100644 index 00000000000..e28508729e5 --- /dev/null +++ b/apps/meteor/tests/e2e/page-objects/fragments/admin-flextab-emoji.ts @@ -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"]'); + } +} diff --git a/apps/meteor/tests/e2e/page-objects/index.ts b/apps/meteor/tests/e2e/page-objects/index.ts index e503d367e87..80914a7b75d 100644 --- a/apps/meteor/tests/e2e/page-objects/index.ts +++ b/apps/meteor/tests/e2e/page-objects/index.ts @@ -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'; diff --git a/apps/meteor/tests/end-to-end/api/emoji-custom.ts b/apps/meteor/tests/end-to-end/api/emoji-custom.ts index 07285b928f8..744480ea6f7 100644 --- a/apps/meteor/tests/end-to-end/api/emoji-custom.ts +++ b/apps/meteor/tests/end-to-end/api/emoji-custom.ts @@ -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', () => { diff --git a/packages/gazzodown/src/emoji/EmojiRenderer.tsx b/packages/gazzodown/src/emoji/EmojiRenderer.tsx index 84116361157..2021442d100 100644 --- a/packages/gazzodown/src/emoji/EmojiRenderer.tsx +++ b/packages/gazzodown/src/emoji/EmojiRenderer.tsx @@ -37,7 +37,7 @@ const EmojiRenderer = ({ big = false, preview = false, ...emoji }: EmojiProps): )} )) ?? ( - + {sanitizedFallback} )} diff --git a/packages/rest-typings/src/v1/emojiCustom.ts b/packages/rest-typings/src/v1/emojiCustom.ts index 1ffb41f671a..ad67bbf7d0e 100644 --- a/packages/rest-typings/src/v1/emojiCustom.ts +++ b/packages/rest-typings/src/v1/emojiCustom.ts @@ -52,7 +52,7 @@ export const isEmojiCustomList = ajv.compile(emojiCustomListSch export type EmojiCustomEndpoints = { '/v1/emoji-custom.all': { - GET: (params: PaginatedRequest<{ query: string }, 'name'>) => PaginatedResult<{ + GET: (params: PaginatedRequest<{ name?: string }, 'name'>) => PaginatedResult<{ emojis: IEmojiCustom[]; }>; };