Skip to content

Commit af48f5c

Browse files
authored
refactor: random emoji core util (#2727)
Signed-off-by: Adam Setch <adam.setch@outlook.com>
1 parent 459d863 commit af48f5c

File tree

11 files changed

+77
-55
lines changed

11 files changed

+77
-55
lines changed

src/renderer/__helpers__/test-utils.tsx

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -110,10 +110,3 @@ export function renderWithAppContext(
110110
),
111111
});
112112
}
113-
114-
/**
115-
* Ensure stable snapshots for our randomized emoji use-cases
116-
*/
117-
export function ensureStableEmojis() {
118-
globalThis.Math.random = vi.fn(() => 0.1);
119-
}

src/renderer/__helpers__/vitest.setup.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ vi.mock('react-router-dom', async () => ({
1111
useNavigate: () => navigateMock,
1212
}));
1313

14+
// Ensure stability in EmojiSplash component snapshots
15+
vi.mock('../utils/core/random', () => ({
16+
randomElement: vi.fn((arr: unknown[]) => arr[0]),
17+
}));
18+
1419
// Sets timezone to UTC for consistent date/time in tests and snapshots
1520
process.env.TZ = 'UTC';
1621

src/renderer/components/AllRead.test.tsx

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,12 @@
11
import { act } from '@testing-library/react';
22

3-
import {
4-
ensureStableEmojis,
5-
renderWithAppContext,
6-
} from '../__helpers__/test-utils';
3+
import { renderWithAppContext } from '../__helpers__/test-utils';
74
import { mockSettings } from '../__mocks__/state-mocks';
85

96
import { useFiltersStore } from '../stores';
107
import { AllRead } from './AllRead';
118

129
describe('renderer/components/AllRead.tsx', () => {
13-
beforeEach(() => {
14-
ensureStableEmojis();
15-
});
16-
1710
it('should render itself & its children - no filters', async () => {
1811
let tree: ReturnType<typeof renderWithAppContext> | null = null;
1912

src/renderer/components/AllRead.tsx

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Constants } from '../constants';
55
import { EmojiSplash } from './layout/EmojiSplash';
66

77
import { useFiltersStore } from '../stores';
8+
import { randomElement } from '../utils/core/random';
89

910
interface AllReadProps {
1011
fullHeight?: boolean;
@@ -15,13 +16,7 @@ export const AllRead: FC<AllReadProps> = ({
1516
}: AllReadProps) => {
1617
const hasFilters = useFiltersStore((s) => s.hasActiveFilters());
1718

18-
const emoji = useMemo(
19-
() =>
20-
Constants.EMOJIS.ALL_READ[
21-
Math.floor(Math.random() * Constants.EMOJIS.ALL_READ.length)
22-
],
23-
[],
24-
);
19+
const emoji = useMemo(() => randomElement(Constants.EMOJIS.ALL_READ), []);
2520

2621
const heading = `No new ${hasFilters ? 'filtered ' : ''} notifications`;
2722

src/renderer/components/Oops.test.tsx

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,11 @@ import userEvent from '@testing-library/user-event';
33

44
import { PersonIcon } from '@primer/octicons-react';
55

6-
import {
7-
ensureStableEmojis,
8-
navigateMock,
9-
renderWithAppContext,
10-
} from '../__helpers__/test-utils';
6+
import { navigateMock, renderWithAppContext } from '../__helpers__/test-utils';
117

128
import { Oops } from './Oops';
139

1410
describe('renderer/components/Oops.tsx', () => {
15-
beforeEach(() => {
16-
ensureStableEmojis();
17-
});
18-
1911
it('should render itself & its children - specified error', async () => {
2012
const mockError = {
2113
title: 'Error title',

src/renderer/components/Oops.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { EmojiSplash } from './layout/EmojiSplash';
88
import type { GitifyError } from '../types';
99

1010
import { Errors } from '../utils/core/errors';
11+
import { randomElement } from '../utils/core/random';
1112

1213
interface OopsProps {
1314
error: GitifyError;
@@ -21,10 +22,7 @@ export const Oops: FC<OopsProps> = ({
2122
const err = error ?? Errors.UNKNOWN;
2223
const navigate = useNavigate();
2324

24-
const emoji = useMemo(
25-
() => err.emojis[Math.floor(Math.random() * err.emojis.length)],
26-
[err],
27-
);
25+
const emoji = useMemo(() => randomElement(err.emojis), [err]);
2826

2927
const actions = err.actions?.length
3028
? err.actions.map((action) => (

src/renderer/components/__snapshots__/AllRead.test.tsx.snap

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/renderer/components/notifications/AccountNotifications.test.tsx

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
import { act, screen } from '@testing-library/react';
22
import userEvent from '@testing-library/user-event';
33

4-
import {
5-
ensureStableEmojis,
6-
renderWithAppContext,
7-
} from '../../__helpers__/test-utils';
4+
import { renderWithAppContext } from '../../__helpers__/test-utils';
85
import { mockGitHubCloudAccount } from '../../__mocks__/account-mocks';
96
import { mockGitHubCloudGitifyNotifications } from '../../__mocks__/notifications-mocks';
107
import { mockSettings } from '../../__mocks__/state-mocks';
@@ -17,21 +14,11 @@ import {
1714
type AccountNotificationsProps,
1815
} from './AccountNotifications';
1916

20-
const navigateMock = vi.fn();
21-
vi.mock('react-router-dom', async () => ({
22-
...(await vi.importActual('react-router-dom')),
23-
useNavigate: () => navigateMock,
24-
}));
25-
2617
vi.mock('./RepositoryNotifications', () => ({
2718
RepositoryNotifications: () => <div>RepositoryNotifications</div>,
2819
}));
2920

3021
describe('renderer/components/notifications/AccountNotifications.tsx', () => {
31-
beforeEach(() => {
32-
ensureStableEmojis();
33-
});
34-
3522
it('should render itself - group notifications by repositories', () => {
3623
const props: AccountNotificationsProps = {
3724
account: mockGitHubCloudAccount,

src/renderer/components/notifications/__snapshots__/AccountNotifications.test.tsx.snap

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
vi.unmock('./random');
2+
3+
import { randomElement } from './random';
4+
5+
const FRUITS = ['apple', 'banana', 'cherry', 'date', 'elderberry'];
6+
7+
describe('randomElement', () => {
8+
it('should return an element from the array', () => {
9+
for (let i = 0; i < 100; i++) {
10+
const result = randomElement(FRUITS);
11+
expect(FRUITS).toContain(result);
12+
}
13+
});
14+
15+
it('should use crypto.getRandomValues', () => {
16+
const spy = vi.spyOn(globalThis.crypto, 'getRandomValues');
17+
randomElement(FRUITS);
18+
expect(spy).toHaveBeenCalledOnce();
19+
spy.mockRestore();
20+
});
21+
22+
it('should return first element when crypto returns 0', () => {
23+
vi.spyOn(globalThis.crypto, 'getRandomValues').mockImplementation(
24+
<T extends ArrayBufferView>(array: T): T => {
25+
if (array instanceof Uint32Array) {
26+
array[0] = 0;
27+
}
28+
return array;
29+
},
30+
);
31+
32+
expect(randomElement(FRUITS)).toBe('apple');
33+
vi.restoreAllMocks();
34+
});
35+
36+
it('should wrap large values via modulo', () => {
37+
vi.spyOn(globalThis.crypto, 'getRandomValues').mockImplementation(
38+
<T extends ArrayBufferView>(array: T): T => {
39+
if (array instanceof Uint32Array) {
40+
array[0] = 13;
41+
}
42+
return array;
43+
},
44+
);
45+
46+
expect(randomElement(FRUITS)).toBe('date'); // 13 % 5 === 3
47+
vi.restoreAllMocks();
48+
});
49+
});

0 commit comments

Comments
 (0)