From 88ba9607a0873b9b0b8f8720028965772b350939 Mon Sep 17 00:00:00 2001
From: Adam Setch
Date: Mon, 19 Aug 2024 13:50:56 -0400
Subject: [PATCH] fix: cross-platform emoji compatibility (#1449)
---
package.json | 1 +
pnpm-lock.yaml | 27 +++++++
src/__mocks__/utils.ts | 8 +-
src/components/AccountNotifications.test.tsx | 3 +-
src/components/AllRead.test.tsx | 3 +-
src/components/AllRead.tsx | 7 +-
src/components/EmojiText.test.tsx | 17 ++++
src/components/EmojiText.tsx | 37 +++++++++
src/components/Oops.test.tsx | 5 ++
src/components/Oops.tsx | 7 +-
.../AccountNotifications.test.tsx.snap | 70 +++++++++++-----
.../__snapshots__/AllRead.test.tsx.snap | 34 +++++---
.../__snapshots__/EmojiText.test.tsx.snap | 80 +++++++++++++++++++
.../__snapshots__/Oops.test.tsx.snap | 36 ++++++---
src/electron/index.html | 11 +++
src/utils/helpers.ts | 4 +
16 files changed, 300 insertions(+), 50 deletions(-)
create mode 100644 src/components/EmojiText.test.tsx
create mode 100644 src/components/EmojiText.tsx
create mode 100644 src/components/__snapshots__/EmojiText.test.tsx.snap
diff --git a/package.json b/package.json
index 21bf189b8..3f78ce7f4 100644
--- a/package.json
+++ b/package.json
@@ -100,6 +100,7 @@
"afterSign": "scripts/notarize.js"
},
"dependencies": {
+ "@discordapp/twemoji": "15.0.3",
"@electron/remote": "2.1.2",
"@primer/octicons-react": "19.11.0",
"axios": "1.7.4",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 6742457c3..d6e733bdd 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -8,6 +8,9 @@ importers:
.:
dependencies:
+ '@discordapp/twemoji':
+ specifier: 15.0.3
+ version: 15.0.3
'@electron/remote':
specifier: 2.1.2
version: 2.1.2(electron@31.4.0)
@@ -396,6 +399,9 @@ packages:
resolution: {integrity: sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==}
engines: {node: '>= 8.9.0'}
+ '@discordapp/twemoji@15.0.3':
+ resolution: {integrity: sha512-5t0LLrNaSqViG0cSaomWwfR0+3fWqok+xLq40M8hJHxNX7s8gIoyNZYybQJo+s5/rGMjgdldpt8Ox8MapGvBUA==}
+
'@discoveryjs/json-ext@0.5.7':
resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==}
engines: {node: '>=10.0.0'}
@@ -619,6 +625,9 @@ packages:
'@tsconfig/node16@1.0.4':
resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==}
+ '@twemoji/parser@15.0.0':
+ resolution: {integrity: sha512-lh9515BNsvKSNvyUqbj5yFu83iIDQ77SwVcsN/SnEGawczhsKU6qWuogewN1GweTi5Imo5ToQ9s+nNTf97IXvg==}
+
'@types/aria-query@5.0.4':
resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==}
@@ -2106,6 +2115,9 @@ packages:
jsonfile@4.0.0:
resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==}
+ jsonfile@5.0.0:
+ resolution: {integrity: sha512-NQRZ5CRo74MhMMC3/3r5g2k4fjodJ/wh8MxjFbCViWKFjxrnudWSY5vomh+23ZaXzAS7J3fBZIR2dV6WbmfM0w==}
+
jsonfile@6.1.0:
resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
@@ -3511,6 +3523,13 @@ snapshots:
ajv: 6.12.6
ajv-keywords: 3.5.2(ajv@6.12.6)
+ '@discordapp/twemoji@15.0.3':
+ dependencies:
+ '@twemoji/parser': 15.0.0
+ fs-extra: 8.1.0
+ jsonfile: 5.0.0
+ universalify: 0.1.2
+
'@discoveryjs/json-ext@0.5.7': {}
'@electron/asar@3.2.9':
@@ -3865,6 +3884,8 @@ snapshots:
'@tsconfig/node16@1.0.4': {}
+ '@twemoji/parser@15.0.0': {}
+
'@types/aria-query@5.0.4': {}
'@types/babel__core@7.20.5':
@@ -5726,6 +5747,12 @@ snapshots:
optionalDependencies:
graceful-fs: 4.2.11
+ jsonfile@5.0.0:
+ dependencies:
+ universalify: 0.1.2
+ optionalDependencies:
+ graceful-fs: 4.2.11
+
jsonfile@6.1.0:
dependencies:
universalify: 2.0.1
diff --git a/src/__mocks__/utils.ts b/src/__mocks__/utils.ts
index cf1c57cb9..0e42e61a2 100644
--- a/src/__mocks__/utils.ts
+++ b/src/__mocks__/utils.ts
@@ -1,7 +1,7 @@
-export function setPlatform(platform: NodeJS.Platform) {
- Object.defineProperty(process, 'platform', {
- value: platform,
- });
+import * as helpers from '../utils/helpers';
+
+export function mockDirectoryPath() {
+ jest.spyOn(helpers, 'getDirectoryPath').mockReturnValue('/mocked/dir/name');
}
/**
diff --git a/src/components/AccountNotifications.test.tsx b/src/components/AccountNotifications.test.tsx
index e5e844d4d..1c7d7e2e6 100644
--- a/src/components/AccountNotifications.test.tsx
+++ b/src/components/AccountNotifications.test.tsx
@@ -1,6 +1,6 @@
import { act, fireEvent, render, screen } from '@testing-library/react';
import { mockGitHubCloudAccount, mockSettings } from '../__mocks__/state-mocks';
-import { ensureStableEmojis } from '../__mocks__/utils';
+import { ensureStableEmojis, mockDirectoryPath } from '../__mocks__/utils';
import { AppContext } from '../context/App';
import { GroupBy } from '../types';
import { mockGitHubNotifications } from '../utils/api/__mocks__/response-mocks';
@@ -14,6 +14,7 @@ jest.mock('./RepositoryNotifications', () => ({
describe('components/AccountNotifications.tsx', () => {
beforeEach(() => {
ensureStableEmojis();
+ mockDirectoryPath();
});
it('should render itself - group notifications by repositories', () => {
diff --git a/src/components/AllRead.test.tsx b/src/components/AllRead.test.tsx
index c58becdd6..d5067b0f3 100644
--- a/src/components/AllRead.test.tsx
+++ b/src/components/AllRead.test.tsx
@@ -1,10 +1,11 @@
import { render } from '@testing-library/react';
-import { ensureStableEmojis } from '../__mocks__/utils';
+import { ensureStableEmojis, mockDirectoryPath } from '../__mocks__/utils';
import { AllRead } from './AllRead';
describe('components/AllRead.tsx', () => {
beforeEach(() => {
ensureStableEmojis();
+ mockDirectoryPath();
});
it('should render itself & its children', () => {
diff --git a/src/components/AllRead.tsx b/src/components/AllRead.tsx
index b34c932b9..206c31c43 100644
--- a/src/components/AllRead.tsx
+++ b/src/components/AllRead.tsx
@@ -1,5 +1,6 @@
import { type FC, useMemo } from 'react';
import { Constants } from '../utils/constants';
+import { EmojiText } from './EmojiText';
export const AllRead: FC = () => {
const emoji = useMemo(
@@ -12,9 +13,11 @@ export const AllRead: FC = () => {
return (
-
{emoji}
+
+
+
-
No new notifications.
+
No new notifications.
);
};
diff --git a/src/components/EmojiText.test.tsx b/src/components/EmojiText.test.tsx
new file mode 100644
index 000000000..8387e3d5d
--- /dev/null
+++ b/src/components/EmojiText.test.tsx
@@ -0,0 +1,17 @@
+import { render } from '@testing-library/react';
+import { mockDirectoryPath } from '../__mocks__/utils';
+import { EmojiText, type IEmojiText } from './EmojiText';
+
+describe('components/icons/Emoji.tsx', () => {
+ beforeEach(() => {
+ mockDirectoryPath();
+ });
+
+ it('should render', () => {
+ const props: IEmojiText = {
+ text: '🍺',
+ };
+ const tree = render();
+ expect(tree).toMatchSnapshot();
+ });
+});
diff --git a/src/components/EmojiText.tsx b/src/components/EmojiText.tsx
new file mode 100644
index 000000000..4c7e1d355
--- /dev/null
+++ b/src/components/EmojiText.tsx
@@ -0,0 +1,37 @@
+import path from 'node:path';
+import twemoji from '@discordapp/twemoji';
+import { type FC, useEffect, useRef } from 'react';
+import { getDirectoryPath } from '../utils/helpers';
+
+export interface IEmojiText {
+ text: string;
+}
+
+type TwemojiOptions = {
+ base: string;
+ size: string;
+ ext: string;
+};
+
+export const EmojiText: FC = ({ text }) => {
+ const ref = useRef(null);
+
+ useEffect(() => {
+ if (ref.current) {
+ ref.current.innerHTML = twemoji.parse(text, {
+ folder: 'svg',
+ ext: '.svg',
+ callback: (icon: string, options: TwemojiOptions, _variant: string) => {
+ const source = path.resolve(
+ getDirectoryPath(),
+ '../../node_modules/@discordapp/twemoji/dist',
+ );
+
+ return ''.concat(source, '/', options.size, '/', icon, options.ext);
+ },
+ });
+ }
+ }, [text]);
+
+ return ;
+};
diff --git a/src/components/Oops.test.tsx b/src/components/Oops.test.tsx
index 128055d3d..730d029bd 100644
--- a/src/components/Oops.test.tsx
+++ b/src/components/Oops.test.tsx
@@ -1,7 +1,12 @@
import { render } from '@testing-library/react';
+import { mockDirectoryPath } from '../__mocks__/utils';
import { Oops } from './Oops';
describe('components/Oops.tsx', () => {
+ beforeEach(() => {
+ mockDirectoryPath();
+ });
+
it('should render itself & its children', () => {
const mockError = {
title: 'Error title',
diff --git a/src/components/Oops.tsx b/src/components/Oops.tsx
index 7f7b21ea4..c5ac1e724 100644
--- a/src/components/Oops.tsx
+++ b/src/components/Oops.tsx
@@ -1,5 +1,6 @@
import { type FC, useMemo } from 'react';
import type { GitifyError } from '../types';
+import { EmojiText } from './EmojiText';
interface IOops {
error: GitifyError;
@@ -13,9 +14,11 @@ export const Oops: FC = ({ error }: IOops) => {
return (
-
{emoji}
+
+
+
-
{error.title}
+
{error.title}
{error.descriptions.map((description, i) => {
return (
// biome-ignore lint/suspicious/noArrayIndexKey: using index for key to keep the error constants clean
diff --git a/src/components/__snapshots__/AccountNotifications.test.tsx.snap b/src/components/__snapshots__/AccountNotifications.test.tsx.snap
index 295295cda..17a0b271c 100644
--- a/src/components/__snapshots__/AccountNotifications.test.tsx.snap
+++ b/src/components/__snapshots__/AccountNotifications.test.tsx.snap
@@ -121,16 +121,24 @@ exports[`components/AccountNotifications.tsx should render itself - account erro
-
- 🔥
-
-
+
+ foo
+
+
+
Error title
-
+
@@ -256,16 +264,24 @@ exports[`components/AccountNotifications.tsx should render itself - account erro
-
- 🔥
-
-
+
+ foo
+
+
+
Error title
-
+
@@ -1764,16 +1780,23 @@ exports[`components/AccountNotifications.tsx should render itself - no notificat
-
- 🎊
-
-
+
+
+
+
No new notifications.
-
+
+
+
+
+
+
+