Skip to content

Commit d3a4b17

Browse files
dammy95setchy
andauthored
refactor: introduce branded types (#1223)
* Add the hostname Branded type * Commit branded-types file * Add the token Branded type * Add the ClientSecret and ClientID Branded types * Add the AuthCode Branded type * Rename HostName to Hostname * Move types in branded-types to src/types * Create WebUrl Branded type * refactor: rename `WebUrl` to `Link` --------- Co-authored-by: Adam Setch <[email protected]>
1 parent 8ccaf9f commit d3a4b17

35 files changed

+580
-476
lines changed

src/__mocks__/partial-mocks.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { Hostname, Link } from '../types';
12
import type { Notification, Subject, User } from '../typesGitHub';
23
import Constants from '../utils/constants';
34
import { mockGitifyUser, mockToken } from './state-mocks';
@@ -9,7 +10,7 @@ export function partialMockNotification(
910
account: {
1011
method: 'Personal Access Token',
1112
platform: 'GitHub Cloud',
12-
hostname: Constants.GITHUB_API_BASE_URL,
13+
hostname: Constants.GITHUB_API_BASE_URL as Hostname,
1314
token: mockToken,
1415
user: mockGitifyUser,
1516
},
@@ -22,8 +23,8 @@ export function partialMockNotification(
2223
export function partialMockUser(login: string): User {
2324
const mockUser: Partial<User> = {
2425
login: login,
25-
html_url: `https://github.com/${login}`,
26-
avatar_url: 'https://avatars.githubusercontent.com/u/583231?v=4',
26+
html_url: `https://github.com/${login}` as Link,
27+
avatar_url: 'https://avatars.githubusercontent.com/u/583231?v=4' as Link,
2728
type: 'User',
2829
};
2930

src/__mocks__/state-mocks.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,18 @@ import {
33
type AuthState,
44
type GitifyState,
55
type GitifyUser,
6+
type Hostname,
67
type SettingsState,
78
Theme,
9+
type Token,
810
} from '../types';
911
import type { EnterpriseAccount } from '../utils/auth/types';
1012
import Constants from '../utils/constants';
1113

1214
export const mockEnterpriseAccounts: EnterpriseAccount[] = [
1315
{
14-
hostname: 'github.gitify.io',
15-
token: '1234568790',
16+
hostname: 'github.gitify.io' as Hostname,
17+
token: '1234568790' as Token,
1618
},
1719
];
1820

@@ -25,39 +27,39 @@ export const mockGitifyUser: GitifyUser = {
2527
export const mockPersonalAccessTokenAccount: Account = {
2628
platform: 'GitHub Cloud',
2729
method: 'Personal Access Token',
28-
token: 'token-123-456',
30+
token: 'token-123-456' as Token,
2931
hostname: Constants.DEFAULT_AUTH_OPTIONS.hostname,
3032
user: mockGitifyUser,
3133
};
3234

3335
export const mockOAuthAccount: Account = {
3436
platform: 'GitHub Enterprise Server',
3537
method: 'OAuth App',
36-
token: '1234568790',
37-
hostname: 'github.gitify.io',
38+
token: '1234568790' as Token,
39+
hostname: 'github.gitify.io' as Hostname,
3840
user: mockGitifyUser,
3941
};
4042

4143
export const mockGitHubCloudAccount: Account = {
4244
platform: 'GitHub Cloud',
4345
method: 'Personal Access Token',
44-
token: 'token-123-456',
46+
token: 'token-123-456' as Token,
4547
hostname: Constants.DEFAULT_AUTH_OPTIONS.hostname,
4648
user: mockGitifyUser,
4749
};
4850

4951
export const mockGitHubEnterpriseServerAccount: Account = {
5052
platform: 'GitHub Enterprise Server',
5153
method: 'Personal Access Token',
52-
token: '1234568790',
53-
hostname: 'github.gitify.io',
54+
token: '1234568790' as Token,
55+
hostname: 'github.gitify.io' as Hostname,
5456
user: mockGitifyUser,
5557
};
5658

5759
export const mockGitHubAppAccount: Account = {
5860
platform: 'GitHub Cloud',
5961
method: 'GitHub App',
60-
token: '987654321',
62+
token: '987654321' as Token,
6163
hostname: Constants.DEFAULT_AUTH_OPTIONS.hostname,
6264
user: mockGitifyUser,
6365
};
@@ -66,7 +68,7 @@ export const mockAuth: AuthState = {
6668
accounts: [mockGitHubCloudAccount, mockGitHubEnterpriseServerAccount],
6769
};
6870

69-
export const mockToken = 'token-123-456';
71+
export const mockToken = 'token-123-456' as Token;
7072

7173
export const mockSettings: SettingsState = {
7274
participating: false,

src/components/NotificationRow.test.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
mockSettings,
66
} from '../__mocks__/state-mocks';
77
import { AppContext } from '../context/App';
8+
import type { Link } from '../types';
89
import type { Milestone, UserType } from '../typesGitHub';
910
import { mockSingleNotification } from '../utils/api/__mocks__/response-mocks';
1011
import * as comms from '../utils/comms';
@@ -473,9 +474,9 @@ describe('components/NotificationRow.tsx', () => {
473474
...mockSingleNotification.subject,
474475
user: {
475476
login: 'some-user',
476-
html_url: 'https://github.com/some-user',
477+
html_url: 'https://github.com/some-user' as Link,
477478
avatar_url:
478-
'https://avatars.githubusercontent.com/u/123456789?v=4',
479+
'https://avatars.githubusercontent.com/u/123456789?v=4' as Link,
479480
type: 'User' as UserType,
480481
},
481482
reviews: null,

src/components/Repository.test.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { fireEvent, render, screen } from '@testing-library/react';
22
import { mockGitHubCloudAccount } from '../__mocks__/state-mocks';
33
import { AppContext } from '../context/App';
4+
import type { Link } from '../types';
45
import {
56
mockGitHubNotifications,
67
mockSingleNotification,
@@ -81,7 +82,7 @@ describe('components/Repository.tsx', () => {
8182
});
8283

8384
it('should use default repository icon when avatar is not available', () => {
84-
props.repoNotifications[0].repository.owner.avatar_url = '';
85+
props.repoNotifications[0].repository.owner.avatar_url = '' as Link;
8586

8687
const tree = render(
8788
<AppContext.Provider value={{}}>

src/components/fields/Button.test.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { MarkGithubIcon } from '@primer/octicons-react';
22
import { fireEvent, render, screen } from '@testing-library/react';
33
import { shell } from 'electron';
4+
import type { Link } from '../../types';
45
import { Button, type IButton } from './Button';
56

67
describe('components/fields/Button.tsx', () => {
@@ -27,7 +28,7 @@ describe('components/fields/Button.tsx', () => {
2728
});
2829

2930
it('should render with url', () => {
30-
render(<Button {...props} url="https://github.com" />);
31+
render(<Button {...props} url={'https://github.com' as Link} />);
3132

3233
const buttonElement = screen.getByLabelText('button');
3334

src/components/fields/Button.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { Icon } from '@primer/octicons-react';
22
import type { FC } from 'react';
3+
import type { Link } from '../../types';
34
import { cn } from '../../utils/cn';
45
import { openExternalLink } from '../../utils/comms';
56

@@ -9,7 +10,7 @@ export interface IButton {
910
className?: string;
1011
icon?: Icon;
1112
size?: number;
12-
url?: string;
13+
url?: Link;
1314
onClick?: () => void;
1415
disabled?: boolean;
1516
type?: 'button' | 'submit';

src/context/App.test.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { act, fireEvent, render, waitFor } from '@testing-library/react';
22
import { useContext } from 'react';
33
import { mockAuth, mockSettings } from '../__mocks__/state-mocks';
44
import { useNotifications } from '../hooks/useNotifications';
5-
import type { AuthState, SettingsState } from '../types';
5+
import type { AuthState, Hostname, SettingsState, Token } from '../types';
66
import { mockSingleNotification } from '../utils/api/__mocks__/response-mocks';
77
import * as apiRequests from '../utils/api/request';
88
import * as comms from '../utils/comms';
@@ -280,8 +280,8 @@ describe('context/App.tsx', () => {
280280
type="button"
281281
onClick={() =>
282282
loginWithPersonalAccessToken({
283-
hostname: 'github.com',
284-
token: '123-456',
283+
hostname: 'github.com' as Hostname,
284+
token: '123-456' as Token,
285285
})
286286
}
287287
>

src/routes/LoginWithOAuthApp.test.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { fireEvent, render, screen } from '@testing-library/react';
22
import { shell } from 'electron';
33
import { MemoryRouter } from 'react-router-dom';
44
import { AppContext } from '../context/App';
5-
import type { AuthState } from '../types';
5+
import type { AuthState, ClientID, ClientSecret, Hostname } from '../types';
66
import { LoginWithOAuthApp, validate } from './LoginWithOAuthApp';
77

88
const mockNavigate = jest.fn();
@@ -64,9 +64,9 @@ describe('routes/LoginWithOAuthApp.tsx', () => {
6464

6565
values = {
6666
...emptyValues,
67-
hostname: 'hello',
68-
clientId: '!@£INVALID-.1',
69-
clientSecret: '!@£INVALID-.1',
67+
hostname: 'hello' as Hostname,
68+
clientId: '!@£INVALID-.1' as ClientID,
69+
clientSecret: '!@£INVALID-.1' as ClientSecret,
7070
};
7171
expect(validate(values).hostname).toBe('Invalid hostname.');
7272
expect(validate(values).clientId).toBe('Invalid client id.');

src/routes/LoginWithOAuthApp.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { useNavigate } from 'react-router-dom';
1010
import { Button } from '../components/fields/Button';
1111
import { FieldInput } from '../components/fields/FieldInput';
1212
import { AppContext } from '../context/App';
13+
import type { ClientID, ClientSecret, Hostname, Token } from '../types';
1314
import type { LoginOAuthAppOptions } from '../utils/auth/types';
1415
import {
1516
getNewOAuthAppURL,
@@ -20,9 +21,9 @@ import {
2021
import Constants from '../utils/constants';
2122

2223
interface IValues {
23-
hostname?: string;
24-
clientId?: string;
25-
clientSecret?: string;
24+
hostname?: Hostname;
25+
clientId?: ClientID;
26+
clientSecret?: ClientSecret;
2627
}
2728

2829
interface IFormErrors {
@@ -48,7 +49,7 @@ export const validate = (values: IValues): IFormErrors => {
4849

4950
if (!values.clientSecret) {
5051
errors.clientSecret = 'Required';
51-
} else if (!isValidToken(values.clientSecret)) {
52+
} else if (!isValidToken(values.clientSecret as unknown as Token)) {
5253
errors.clientSecret = 'Invalid client secret.';
5354
}
5455

src/routes/LoginWithPersonalAccessToken.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { useNavigate } from 'react-router-dom';
1010
import { Button } from '../components/fields/Button';
1111
import { FieldInput } from '../components/fields/FieldInput';
1212
import { AppContext } from '../context/App';
13+
import type { Hostname, Token } from '../types';
1314
import type { LoginPersonalAccessTokenOptions } from '../utils/auth/types';
1415
import {
1516
getNewTokenURL,
@@ -19,8 +20,8 @@ import {
1920
import { Constants } from '../utils/constants';
2021

2122
interface IValues {
22-
token?: string;
23-
hostname?: string;
23+
token?: Token;
24+
hostname?: Hostname;
2425
}
2526

2627
interface IFormErrors {
@@ -158,7 +159,7 @@ export const LoginWithPersonalAccessToken: FC = () => {
158159
<Form
159160
initialValues={{
160161
hostname: Constants.DEFAULT_AUTH_OPTIONS.hostname,
161-
token: '',
162+
token: '' as Token,
162163
}}
163164
onSubmit={login}
164165
validate={validate}

0 commit comments

Comments
 (0)