Remove rendering of custom emoji using the database (#37284)

This commit is contained in:
Echo
2025-12-17 15:40:34 +01:00
committed by GitHub
parent 71af094f97
commit dbc5af6641
5 changed files with 41 additions and 65 deletions

View File

@@ -27,6 +27,7 @@ const config: StorybookConfig = {
'oops.gif', 'oops.gif',
'oops.png', 'oops.png',
].map((path) => ({ from: `../public/${path}`, to: `/${path}` })), ].map((path) => ({ from: `../public/${path}`, to: `/${path}` })),
{ from: '../app/javascript/images/logo.svg', to: '/custom-emoji/logo.svg' },
], ],
viteFinal(config) { viteFinal(config) {
// For an unknown reason, Storybook does not use the root // For an unknown reason, Storybook does not use the root

View File

@@ -2,6 +2,9 @@ import type { ComponentProps } from 'react';
import type { Meta, StoryObj } from '@storybook/react-vite'; import type { Meta, StoryObj } from '@storybook/react-vite';
import { customEmojiFactory } from '@/testing/factories';
import { CustomEmojiProvider } from './context';
import { Emoji } from './index'; import { Emoji } from './index';
type EmojiProps = ComponentProps<typeof Emoji> & { type EmojiProps = ComponentProps<typeof Emoji> & {
@@ -34,7 +37,11 @@ const meta = {
}, },
}, },
render(args) { render(args) {
return <Emoji {...args} />; return (
<CustomEmojiProvider emojis={[customEmojiFactory()]}>
<Emoji {...args} />
</CustomEmojiProvider>
);
}, },
} satisfies Meta<EmojiProps>; } satisfies Meta<EmojiProps>;
@@ -49,9 +56,3 @@ export const CustomEmoji: Story = {
code: ':custom:', code: ':custom:',
}, },
}; };
export const LegacyEmoji: Story = {
args: {
code: ':copyright:',
},
};

View File

@@ -83,12 +83,8 @@ describe('stringToEmojiState', () => {
}); });
}); });
test('returns custom emoji state for valid custom emoji', () => { test('returns null for custom emoji without data', () => {
expect(stringToEmojiState(':smile:')).toEqual({ expect(stringToEmojiState(':smile:')).toBeNull();
type: 'custom',
code: 'smile',
data: undefined,
});
}); });
test('returns custom emoji state with data when provided', () => { test('returns custom emoji state with data when provided', () => {
@@ -108,7 +104,6 @@ describe('stringToEmojiState', () => {
test('returns null for invalid emoji strings', () => { test('returns null for invalid emoji strings', () => {
expect(stringToEmojiState('notanemoji')).toBeNull(); expect(stringToEmojiState('notanemoji')).toBeNull();
expect(stringToEmojiState(':invalid-emoji:')).toBeNull();
}); });
}); });
@@ -142,21 +137,13 @@ describe('loadEmojiDataToState', () => {
}); });
}); });
test('loads custom emoji data into state', async () => { test('returns null for custom emoji without data', async () => {
const dbCall = vi
.spyOn(db, 'loadCustomEmojiByShortcode')
.mockResolvedValueOnce(customEmojiFactory());
const customState = { const customState = {
type: 'custom', type: 'custom',
code: 'smile', code: 'smile',
} as const satisfies EmojiStateCustom; } as const satisfies EmojiStateCustom;
const result = await loadEmojiDataToState(customState, 'en'); const result = await loadEmojiDataToState(customState, 'en');
expect(dbCall).toHaveBeenCalledWith('smile'); expect(result).toBeNull();
expect(result).toEqual({
type: 'custom',
code: 'smile',
data: customEmojiFactory(),
});
}); });
test('loads unicode data using legacy shortcode', async () => { test('loads unicode data using legacy shortcode', async () => {
@@ -194,16 +181,6 @@ describe('loadEmojiDataToState', () => {
expect(result).toBeNull(); expect(result).toBeNull();
}); });
test('returns null if custom emoji not found in database', async () => {
vi.spyOn(db, 'loadCustomEmojiByShortcode').mockResolvedValueOnce(undefined);
const customState = {
type: 'custom',
code: 'smile',
} as const satisfies EmojiStateCustom;
const result = await loadEmojiDataToState(customState, 'en');
expect(result).toBeNull();
});
test('retries loading emoji data once if initial load fails', async () => { test('retries loading emoji data once if initial load fails', async () => {
const dbCall = vi const dbCall = vi
.spyOn(db, 'loadEmojiByHexcode') .spyOn(db, 'loadEmojiByHexcode')

View File

@@ -5,7 +5,6 @@ import {
EMOJI_TYPE_CUSTOM, EMOJI_TYPE_CUSTOM,
} from './constants'; } from './constants';
import { import {
loadCustomEmojiByShortcode,
loadEmojiByHexcode, loadEmojiByHexcode,
loadLegacyShortcodesByShortcode, loadLegacyShortcodesByShortcode,
LocaleNotLoadedError, LocaleNotLoadedError,
@@ -80,7 +79,7 @@ export function tokenizeText(text: string): TokenizedText {
export function stringToEmojiState( export function stringToEmojiState(
code: string, code: string,
customEmoji: ExtraCustomEmojiMap = {}, customEmoji: ExtraCustomEmojiMap = {},
): EmojiState | null { ): EmojiStateUnicode | Required<EmojiStateCustom> | null {
if (isUnicodeEmoji(code)) { if (isUnicodeEmoji(code)) {
return { return {
type: EMOJI_TYPE_UNICODE, type: EMOJI_TYPE_UNICODE,
@@ -90,12 +89,14 @@ export function stringToEmojiState(
if (isCustomEmoji(code)) { if (isCustomEmoji(code)) {
const shortCode = code.slice(1, -1); const shortCode = code.slice(1, -1);
if (customEmoji[shortCode]) {
return { return {
type: EMOJI_TYPE_CUSTOM, type: EMOJI_TYPE_CUSTOM,
code: shortCode, code: shortCode,
data: customEmoji[shortCode], data: customEmoji[shortCode],
}; };
} }
}
return null; return null;
} }
@@ -115,11 +116,15 @@ export async function loadEmojiDataToState(
return state; return state;
} }
// Don't try to load data for custom emoji.
if (state.type === EMOJI_TYPE_CUSTOM) {
return null;
}
// First, try to load the data from IndexedDB. // First, try to load the data from IndexedDB.
try { try {
const legacyCode = await loadLegacyShortcodesByShortcode(state.code); const legacyCode = await loadLegacyShortcodesByShortcode(state.code);
// This is duplicative, but that's because TS can't distinguish the state type easily. // This is duplicative, but that's because TS can't distinguish the state type easily.
if (state.type === EMOJI_TYPE_UNICODE || legacyCode) {
const data = await loadEmojiByHexcode( const data = await loadEmojiByHexcode(
legacyCode?.hexcode ?? state.code, legacyCode?.hexcode ?? state.code,
locale, locale,
@@ -133,15 +138,7 @@ export async function loadEmojiDataToState(
shortcode: legacyCode?.shortcodes.at(0), shortcode: legacyCode?.shortcodes.at(0),
}; };
} }
} else {
const data = await loadCustomEmojiByShortcode(state.code);
if (data) {
return {
...state,
data,
};
}
}
// If not found, assume it's not an emoji and return null. // If not found, assume it's not an emoji and return null.
log( log(
'Could not find emoji %s of type %s for locale %s', 'Could not find emoji %s of type %s for locale %s',

View File

@@ -128,8 +128,8 @@ export function customEmojiFactory(
): CustomEmojiData { ): CustomEmojiData {
return { return {
shortcode: 'custom', shortcode: 'custom',
static_url: 'emoji/custom/static', static_url: '/custom-emoji/logo.svg',
url: 'emoji/custom', url: '/custom-emoji/logo.svg',
visible_in_picker: true, visible_in_picker: true,
...data, ...data,
}; };