diff --git a/src/__mocks__/electron.js b/src/__mocks__/electron.js index 97bf91c0f..83fd9ad4c 100644 --- a/src/__mocks__/electron.js +++ b/src/__mocks__/electron.js @@ -31,7 +31,7 @@ let instance; class BrowserWindow { constructor() { - if(!instance){ + if (!instance) { instance = this; } return instance; @@ -43,11 +43,11 @@ class BrowserWindow { clearStorageData: jest.fn(), }, }; - on(){}; + on() {} close = jest.fn(); hide = jest.fn(); destroy = jest.fn(); -}; +} const dialog = { showErrorBox: jest.fn(), @@ -65,7 +65,7 @@ module.exports = { getLoginItemSettings: jest.fn(), setLoginItemSettings: () => {}, }, - getCurrentWindow: jest.fn(() => instance || new BrowserWindow), + getCurrentWindow: jest.fn(() => instance || new BrowserWindow()), }, ipcRenderer: { send: jest.fn(), diff --git a/src/__mocks__/mockedData.ts b/src/__mocks__/mockedData.ts index 08b94e0b5..ab2616d3f 100644 --- a/src/__mocks__/mockedData.ts +++ b/src/__mocks__/mockedData.ts @@ -276,114 +276,114 @@ export const mockedSingleAccountNotifications: AccountNotifications[] = [ ]; export const mockedGraphQLResponse: GraphQLSearch = { - "data": { - "data": { - "search": { - "edges": [ + data: { + data: { + search: { + edges: [ + { + node: { + viewerSubscription: 'SUBSCRIBED', + title: '1.16.0', + url: 'https://github.com/manosim/notifications-test/discussions/612', + comments: { + edges: [ { - "node": { - "viewerSubscription": "SUBSCRIBED", - "title": "1.16.0", - "url": "https://github.com/manosim/notifications-test/discussions/612", - "comments": { - "edges": [ - { - "node": { - "databaseId": 2215656, - "createdAt": "2022-02-20T18:33:39Z", - "replies": { - "edges": [] - } - } - }, - { - "node": { - "databaseId": 2217789, - "createdAt": "2022-02-21T03:30:42Z", - "replies": { - "edges": [] - } - } - }, - { - "node": { - "databaseId": 2223243, - "createdAt": "2022-02-21T18:26:27Z", - "replies": { - "edges": [ - { - "node": { - "databaseId": 2232922, - "createdAt": "2022-02-23T00:57:58Z" - } - } - ] - } - } - }, - { - "node": { - "databaseId": 2232921, - "createdAt": "2022-02-23T00:57:49Z", - "replies": { - "edges": [] - } - } - }, - { - "node": { - "databaseId": 2258799, - "createdAt": "2022-02-27T01:22:20Z", - "replies": { - "edges": [ - { - "node": { - "databaseId": 2300902, - "createdAt": "2022-03-05T17:43:52Z" - } - } - ] - } - } - }, - { - "node": { - "databaseId": 2297637, - "createdAt": "2022-03-04T20:39:44Z", - "replies": { - "edges": [ - { - "node": { - "databaseId": 2300893, - "createdAt": "2022-03-05T17:41:04Z" - } - } - ] - } - } - }, - { - "node": { - "databaseId": 2299763, - "createdAt": "2022-03-05T11:05:42Z", - "replies": { - "edges": [ - { - "node": { - "databaseId": 2300895, - "createdAt": "2022-03-05T17:41:44Z" - } - } - ] - } - } - } - ] - } - } - } - ] - } - } - } -} + node: { + databaseId: 2215656, + createdAt: '2022-02-20T18:33:39Z', + replies: { + edges: [], + }, + }, + }, + { + node: { + databaseId: 2217789, + createdAt: '2022-02-21T03:30:42Z', + replies: { + edges: [], + }, + }, + }, + { + node: { + databaseId: 2223243, + createdAt: '2022-02-21T18:26:27Z', + replies: { + edges: [ + { + node: { + databaseId: 2232922, + createdAt: '2022-02-23T00:57:58Z', + }, + }, + ], + }, + }, + }, + { + node: { + databaseId: 2232921, + createdAt: '2022-02-23T00:57:49Z', + replies: { + edges: [], + }, + }, + }, + { + node: { + databaseId: 2258799, + createdAt: '2022-02-27T01:22:20Z', + replies: { + edges: [ + { + node: { + databaseId: 2300902, + createdAt: '2022-03-05T17:43:52Z', + }, + }, + ], + }, + }, + }, + { + node: { + databaseId: 2297637, + createdAt: '2022-03-04T20:39:44Z', + replies: { + edges: [ + { + node: { + databaseId: 2300893, + createdAt: '2022-03-05T17:41:04Z', + }, + }, + ], + }, + }, + }, + { + node: { + databaseId: 2299763, + createdAt: '2022-03-05T11:05:42Z', + replies: { + edges: [ + { + node: { + databaseId: 2300895, + createdAt: '2022-03-05T17:41:44Z', + }, + }, + ], + }, + }, + }, + ], + }, + }, + }, + ], + }, + }, + }, +}; diff --git a/src/components/NotificationRow.tsx b/src/components/NotificationRow.tsx index 9baff8237..199bd3c60 100644 --- a/src/components/NotificationRow.tsx +++ b/src/components/NotificationRow.tsx @@ -16,12 +16,8 @@ export const NotificationRow: React.FC = ({ notification, hostname, }) => { - const { - settings, - accounts, - markNotification, - unsubscribeNotification, - } = useContext(AppContext); + const { settings, accounts, markNotification, unsubscribeNotification } = + useContext(AppContext); const pressTitle = useCallback(() => { openBrowser(); @@ -31,7 +27,10 @@ export const NotificationRow: React.FC = ({ } }, [settings]); - const openBrowser = useCallback(() => openInBrowser(notification, accounts), [notification]); + const openBrowser = useCallback( + () => openInBrowser(notification, accounts), + [notification] + ); const unsubscribe = (event: React.MouseEvent) => { // Don't trigger onClick of parent element. diff --git a/src/hooks/useNotifications.ts b/src/hooks/useNotifications.ts index e44068392..b6172c79f 100644 --- a/src/hooks/useNotifications.ts +++ b/src/hooks/useNotifications.ts @@ -96,12 +96,7 @@ export const useNotifications = (): NotificationsState => { ] : [...enterpriseNotifications]; - triggerNativeNotifications( - notifications, - data, - settings, - accounts - ); + triggerNativeNotifications(notifications, data, settings, accounts); setNotifications(data); setIsFetching(false); }) diff --git a/src/routes/LoginWithToken.tsx b/src/routes/LoginWithToken.tsx index 59d9d4807..ab1659368 100644 --- a/src/routes/LoginWithToken.tsx +++ b/src/routes/LoginWithToken.tsx @@ -63,7 +63,11 @@ export const LoginWithToken: React.FC = () => { To generate a token, go to GitHub,{' '} openLink('https://github.com/settings/tokens/new?scopes=notifications,read:user&description=gitify_token')} + onClick={() => + openLink( + 'https://github.com/settings/tokens/new?scopes=notifications,read:user&description=gitify_token' + ) + } > personal access tokens {' '} diff --git a/src/typesGithub.ts b/src/typesGithub.ts index a8c23999d..3f69440c3 100644 --- a/src/typesGithub.ts +++ b/src/typesGithub.ts @@ -22,10 +22,7 @@ export type SubjectType = | 'RepositoryInvitation' | 'RepositoryVulnerabilityAlert'; -export type ViewerSubscription = - | 'IGNORED' - | 'SUBSCRIBED' - | 'UNSUBSCRIBED' +export type ViewerSubscription = 'IGNORED' | 'SUBSCRIBED' | 'UNSUBSCRIBED'; export interface Notification { id: string; @@ -126,10 +123,10 @@ export interface GraphQLSearch { data: { data: { search: { - edges: DiscussionEdge[] - } - } - } + edges: DiscussionEdge[]; + }; + }; + }; } export interface DiscussionEdge { @@ -138,24 +135,24 @@ export interface DiscussionEdge { title: string; url: string; comments: { - edges: DiscussionCommentEdge[] - } - } + edges: DiscussionCommentEdge[]; + }; + }; } export interface DiscussionCommentEdge { node: { - databaseId: string|number; + databaseId: string | number; createdAt: string; replies: { - edges: DiscussionSubcommentEdge[] - } - } + edges: DiscussionSubcommentEdge[]; + }; + }; } export interface DiscussionSubcommentEdge { node: { - databaseId: string|number; + databaseId: string | number; createdAt: string; - } + }; } diff --git a/src/utils/auth.test.ts b/src/utils/auth.test.ts index d31708d66..504e91530 100644 --- a/src/utils/auth.test.ts +++ b/src/utils/auth.test.ts @@ -1,7 +1,7 @@ import { AxiosPromise, AxiosResponse } from 'axios'; import { remote } from 'electron'; -const browserWindow = new remote.BrowserWindow +const browserWindow = new remote.BrowserWindow(); import * as auth from './auth'; import * as apiRequests from './api-requests'; @@ -16,14 +16,12 @@ describe('utils/auth.tsx', () => { }); it('should call authGithub - success', async () => { - spyOn(browserWindow.webContents, 'on').and.callFake( - (event, callback) => { - if (event === 'will-redirect') { - const event = new Event('will-redirect'); - callback(event, 'http://github.com/?code=123-456'); - } + spyOn(browserWindow.webContents, 'on').and.callFake((event, callback) => { + if (event === 'will-redirect') { + const event = new Event('will-redirect'); + callback(event, 'http://github.com/?code=123-456'); } - ); + }); const res = await auth.authGitHub(); @@ -42,14 +40,12 @@ describe('utils/auth.tsx', () => { }); it('should call authGithub - failure', async () => { - spyOn(browserWindow.webContents, 'on').and.callFake( - (event, callback) => { - if (event === 'will-redirect') { - const event = new Event('will-redirect'); - callback(event, 'http://www.github.com/?error=Oops'); - } + spyOn(browserWindow.webContents, 'on').and.callFake((event, callback) => { + if (event === 'will-redirect') { + const event = new Event('will-redirect'); + callback(event, 'http://www.github.com/?error=Oops'); } - ); + }); await expect(async () => await auth.authGitHub()).rejects.toEqual( "Oops! Something went wrong and we couldn't log you in using Github. Please try again." diff --git a/src/utils/helpers.test.ts b/src/utils/helpers.test.ts index 1ea265874..6f65f868d 100644 --- a/src/utils/helpers.test.ts +++ b/src/utils/helpers.test.ts @@ -3,20 +3,24 @@ import { generateGitHubAPIUrl, generateNotificationReferrerId, getCommentId, - getLatestDiscussionCommentId + getLatestDiscussionCommentId, } from './helpers'; -import { mockedSingleNotification, mockedUser, mockedGraphQLResponse } from '../__mocks__/mockedData'; +import { + mockedSingleNotification, + mockedUser, + mockedGraphQLResponse, +} from '../__mocks__/mockedData'; const URL = { normal: { api: 'https://api.github.com/repos/myuser/notifications-test', - default: 'https://github.com/myuser/notifications-test' + default: 'https://github.com/myuser/notifications-test', }, enterprise: { api: 'https://github.gitify.io/api/v3/repos/myorg/notifications-test', - default: 'https://github.gitify.io/myorg/notifications-test' - } -} + default: 'https://github.gitify.io/myorg/notifications-test', + }, +}; describe('utils/helpers.ts', () => { describe('generateNotificationReferrerId', () => { @@ -40,67 +44,81 @@ describe('utils/helpers.ts', () => { ); }); - it('should generate the GitHub url - non enterprise - (issue)', () => testGenerateUrl( - `${URL.normal.api}/issues/3`, - `${URL.normal.default}/issues/3?${notificationReferrerId}`) - ); - - it('should generate the GitHub url - non enterprise - (pull request)', () => testGenerateUrl( - `${URL.normal.api}/pulls/123`, - `${URL.normal.default}/pull/123?${notificationReferrerId}`) - ); - - it('should generate the GitHub url - non enterprise - (release)', () => testGenerateUrl( - `${URL.normal.api}/releases/3988077`, - `${URL.normal.default}/releases?${notificationReferrerId}`) - ); - - it('should generate the GitHub url - non enterprise - (discussion)', () => testGenerateUrl( - `${URL.normal.api}/discussions/630`, - `${URL.normal.default}/discussions/630?${notificationReferrerId}`) - ); - - it('should generate the GitHub url - enterprise - (issue)', () => testGenerateUrl( - `${URL.enterprise.api}/issues/123`, - `${URL.enterprise.default}/issues/123?${notificationReferrerId}`) - ); - - it('should generate the GitHub url - enterprise - (pull request)', () => testGenerateUrl( - `${URL.enterprise.api}/pulls/3`, - `${URL.enterprise.default}/pull/3?${notificationReferrerId}`) - ); - - it('should generate the GitHub url - enterprise - (release)', () => testGenerateUrl( - `${URL.enterprise.api}/releases/1`, - `${URL.enterprise.default}/releases?${notificationReferrerId}`) - ); - - it('should generate the GitHub url - enterprise - (discussion)', () => testGenerateUrl( - `${URL.enterprise.api}/discussions/343`, - `${URL.enterprise.default}/discussions/343?${notificationReferrerId}`) - ); - - it('should generate the GitHub issue url with correct commentId', () => testGenerateUrl( - `${URL.normal.api}/issues/5`, - `${URL.normal.default}/issues/5?${notificationReferrerId}#issuecomment-1059824632`, - '#issuecomment-' + getCommentId(`${URL.normal.api}/issues/comments/1059824632`)) - ); - - it('should generate the GitHub discussion url with correct commentId', () => testGenerateUrl( - `${URL.normal.api}/discussions/75`, - `${URL.normal.default}/discussions/75?${notificationReferrerId}#discussioncomment-2300902`, - '#discussioncomment-' + getLatestDiscussionCommentId(mockedGraphQLResponse.data.data.search.edges[0].node.comments.edges)) - ); - - - function testGenerateUrl(apiUrl, ExpectedResult, comment? ) { + it('should generate the GitHub url - non enterprise - (issue)', () => + testGenerateUrl( + `${URL.normal.api}/issues/3`, + `${URL.normal.default}/issues/3?${notificationReferrerId}` + )); + + it('should generate the GitHub url - non enterprise - (pull request)', () => + testGenerateUrl( + `${URL.normal.api}/pulls/123`, + `${URL.normal.default}/pull/123?${notificationReferrerId}` + )); + + it('should generate the GitHub url - non enterprise - (release)', () => + testGenerateUrl( + `${URL.normal.api}/releases/3988077`, + `${URL.normal.default}/releases?${notificationReferrerId}` + )); + + it('should generate the GitHub url - non enterprise - (discussion)', () => + testGenerateUrl( + `${URL.normal.api}/discussions/630`, + `${URL.normal.default}/discussions/630?${notificationReferrerId}` + )); + + it('should generate the GitHub url - enterprise - (issue)', () => + testGenerateUrl( + `${URL.enterprise.api}/issues/123`, + `${URL.enterprise.default}/issues/123?${notificationReferrerId}` + )); + + it('should generate the GitHub url - enterprise - (pull request)', () => + testGenerateUrl( + `${URL.enterprise.api}/pulls/3`, + `${URL.enterprise.default}/pull/3?${notificationReferrerId}` + )); + + it('should generate the GitHub url - enterprise - (release)', () => + testGenerateUrl( + `${URL.enterprise.api}/releases/1`, + `${URL.enterprise.default}/releases?${notificationReferrerId}` + )); + + it('should generate the GitHub url - enterprise - (discussion)', () => + testGenerateUrl( + `${URL.enterprise.api}/discussions/343`, + `${URL.enterprise.default}/discussions/343?${notificationReferrerId}` + )); + + it('should generate the GitHub issue url with correct commentId', () => + testGenerateUrl( + `${URL.normal.api}/issues/5`, + `${URL.normal.default}/issues/5?${notificationReferrerId}#issuecomment-1059824632`, + '#issuecomment-' + + getCommentId(`${URL.normal.api}/issues/comments/1059824632`) + )); + + it('should generate the GitHub discussion url with correct commentId', () => + testGenerateUrl( + `${URL.normal.api}/discussions/75`, + `${URL.normal.default}/discussions/75?${notificationReferrerId}#discussioncomment-2300902`, + '#discussioncomment-' + + getLatestDiscussionCommentId( + mockedGraphQLResponse.data.data.search.edges[0].node.comments.edges + ) + )); + + function testGenerateUrl(apiUrl, ExpectedResult, comment?) { const notif = { ...mockedSingleNotification, subject: { url: apiUrl } }; expect( generateGitHubWebUrl( notif.subject.url, notif.id, mockedUser.id, - comment) + comment + ) ).toBe(ExpectedResult); } }); diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index c4e2f1c07..af3d54a33 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -1,5 +1,9 @@ import { EnterpriseAccount, AuthState } from '../types'; -import { Notification, GraphQLSearch, DiscussionCommentEdge } from '../typesGithub'; +import { + Notification, + GraphQLSearch, + DiscussionCommentEdge, +} from '../typesGithub'; import { apiRequestAuth } from '../utils/api-requests'; import { openExternalLink } from '../utils/comms'; import { Constants } from './constants'; @@ -72,9 +76,13 @@ const queryString = (repo: string, title: string, lastUpdated: string) => async function getDiscussionUrl( notification: Notification, token: string -): Promise<{ url: string, latestCommentId: string|number}> { - const response: GraphQLSearch = await apiRequestAuth(`https://api.github.com/graphql`, 'POST', token, { - query: `{ +): Promise<{ url: string; latestCommentId: string | number }> { + const response: GraphQLSearch = await apiRequestAuth( + `https://api.github.com/graphql`, + 'POST', + token, + { + query: `{ search(query:"${queryString( notification.repository.full_name, notification.subject.title, @@ -106,40 +114,51 @@ async function getDiscussionUrl( } } } - }` - }); - let edges = response?.data?.data?.search?.edges?.filter( - edge => edge.node.title === notification.subject.title - ) || []; + }`, + } + ); + let edges = + response?.data?.data?.search?.edges?.filter( + (edge) => edge.node.title === notification.subject.title + ) || []; if (edges.length > 1) edges = edges.filter( - edge => edge.node.viewerSubscription === 'SUBSCRIBED' + (edge) => edge.node.viewerSubscription === 'SUBSCRIBED' ); - - let comments = edges[0]?.node.comments.edges - let latestCommentId: string|number; + let comments = edges[0]?.node.comments.edges; + + let latestCommentId: string | number; if (comments?.length) { latestCommentId = getLatestDiscussionCommentId(comments); } return { url: edges[0]?.node.url, - latestCommentId - } + latestCommentId, + }; } -export const getLatestDiscussionCommentId = (comments: DiscussionCommentEdge[]) => comments - .flatMap(comment => comment.node.replies.edges) - .concat([comments.at(-1)]) - .reduce((a, b) => a.node.createdAt > b.node.createdAt ? a : b) - ?.node.databaseId; +export const getLatestDiscussionCommentId = ( + comments: DiscussionCommentEdge[] +) => + comments + .flatMap((comment) => comment.node.replies.edges) + .concat([comments.at(-1)]) + .reduce((a, b) => (a.node.createdAt > b.node.createdAt ? a : b))?.node + .databaseId; -export const getCommentId = (url: string) => /comments\/(?\d+)/g.exec(url)?.groups?.id; +export const getCommentId = (url: string) => + /comments\/(?\d+)/g.exec(url)?.groups?.id; -export async function openInBrowser(notification: Notification, accounts: AuthState) { +export async function openInBrowser( + notification: Notification, + accounts: AuthState +) { if (notification.subject.url) { - const latestCommentId = getCommentId(notification.subject.latest_comment_url); + const latestCommentId = getCommentId( + notification.subject.latest_comment_url + ); openExternalLink( generateGitHubWebUrl( notification.subject.url, @@ -149,15 +168,18 @@ export async function openInBrowser(notification: Notification, accounts: AuthSt ) ); } else if (notification.subject.type === 'Discussion') { - getDiscussionUrl(notification, accounts.token).then(({ url, latestCommentId }) => - openExternalLink( - generateGitHubWebUrl( - url || `${notification.repository.url}/discussions`, - notification.id, - accounts.user?.id, - latestCommentId ? '#discussioncomment-' + latestCommentId : undefined + getDiscussionUrl(notification, accounts.token).then( + ({ url, latestCommentId }) => + openExternalLink( + generateGitHubWebUrl( + url || `${notification.repository.url}/discussions`, + notification.id, + accounts.user?.id, + latestCommentId + ? '#discussioncomment-' + latestCommentId + : undefined + ) ) - ) ); } }