Skip to content

Commit fc50408

Browse files
authored
feat: milestone indicator (#1178)
1 parent 8ee3bc3 commit fc50408

File tree

5 files changed

+1799
-344
lines changed

5 files changed

+1799
-344
lines changed

src/components/NotificationRow.test.tsx

Lines changed: 141 additions & 97 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 { mockAuth, mockSettings } from '../__mocks__/state-mocks';
44
import { AppContext } from '../context/App';
5-
import type { UserType } from '../typesGitHub';
5+
import type { Milestone, UserType } from '../typesGitHub';
66
import { mockSingleNotification } from '../utils/api/__mocks__/response-mocks';
77
import * as helpers from '../utils/helpers';
88
import { NotificationRow } from './NotificationRow';
@@ -64,111 +64,155 @@ describe('components/NotificationRow.tsx', () => {
6464
expect(tree).toMatchSnapshot();
6565
});
6666

67-
describe('rendering for notification comments count', () => {
68-
it('should render when no comments', async () => {
69-
jest
70-
.spyOn(global.Date, 'now')
71-
.mockImplementation(() => new Date('2024').valueOf());
72-
73-
const mockNotification = mockSingleNotification;
74-
mockNotification.subject.comments = null;
75-
76-
const props = {
77-
notification: mockNotification,
78-
hostname: 'github.com',
79-
};
80-
81-
const tree = render(<NotificationRow {...props} />);
82-
expect(tree).toMatchSnapshot();
67+
describe('notification pills / metrics', () => {
68+
describe('linked issue pills', () => {
69+
it('should render issues pill when linked to one issue/pr', async () => {
70+
jest
71+
.spyOn(global.Date, 'now')
72+
.mockImplementation(() => new Date('2024').valueOf());
73+
74+
const mockNotification = mockSingleNotification;
75+
mockNotification.subject.linkedIssues = ['#1'];
76+
77+
const props = {
78+
notification: mockNotification,
79+
hostname: 'github.com',
80+
};
81+
82+
const tree = render(<NotificationRow {...props} />);
83+
expect(tree).toMatchSnapshot();
84+
});
85+
86+
it('should render issues pill when linked to multiple issues/prs', async () => {
87+
jest
88+
.spyOn(global.Date, 'now')
89+
.mockImplementation(() => new Date('2024').valueOf());
90+
91+
const mockNotification = mockSingleNotification;
92+
mockNotification.subject.linkedIssues = ['#1', '#2'];
93+
94+
const props = {
95+
notification: mockNotification,
96+
hostname: 'github.com',
97+
};
98+
99+
const tree = render(<NotificationRow {...props} />);
100+
expect(tree).toMatchSnapshot();
101+
});
83102
});
84103

85-
it('should render when 1 comment', async () => {
86-
jest
87-
.spyOn(global.Date, 'now')
88-
.mockImplementation(() => new Date('2024').valueOf());
89-
90-
const mockNotification = mockSingleNotification;
91-
mockNotification.subject.comments = 1;
92-
93-
const props = {
94-
notification: mockNotification,
95-
hostname: 'github.com',
96-
};
97-
98-
const tree = render(<NotificationRow {...props} />);
99-
expect(tree).toMatchSnapshot();
104+
describe('comment pills', () => {
105+
it('should render when no comments', async () => {
106+
jest
107+
.spyOn(global.Date, 'now')
108+
.mockImplementation(() => new Date('2024').valueOf());
109+
110+
const mockNotification = mockSingleNotification;
111+
mockNotification.subject.comments = null;
112+
113+
const props = {
114+
notification: mockNotification,
115+
hostname: 'github.com',
116+
};
117+
118+
const tree = render(<NotificationRow {...props} />);
119+
expect(tree).toMatchSnapshot();
120+
});
121+
122+
it('should render when 1 comment', async () => {
123+
jest
124+
.spyOn(global.Date, 'now')
125+
.mockImplementation(() => new Date('2024').valueOf());
126+
127+
const mockNotification = mockSingleNotification;
128+
mockNotification.subject.comments = 1;
129+
130+
const props = {
131+
notification: mockNotification,
132+
hostname: 'github.com',
133+
};
134+
135+
const tree = render(<NotificationRow {...props} />);
136+
expect(tree).toMatchSnapshot();
137+
});
138+
139+
it('should render when more than 1 comments', async () => {
140+
jest
141+
.spyOn(global.Date, 'now')
142+
.mockImplementation(() => new Date('2024').valueOf());
143+
144+
const mockNotification = mockSingleNotification;
145+
mockNotification.subject.comments = 2;
146+
147+
const props = {
148+
notification: mockNotification,
149+
hostname: 'github.com',
150+
};
151+
152+
const tree = render(<NotificationRow {...props} />);
153+
expect(tree).toMatchSnapshot();
154+
});
100155
});
101156

102-
it('should render when more than 1 comments', async () => {
103-
jest
104-
.spyOn(global.Date, 'now')
105-
.mockImplementation(() => new Date('2024').valueOf());
157+
describe('label pills', () => {
158+
it('should render labels pill', async () => {
159+
jest
160+
.spyOn(global.Date, 'now')
161+
.mockImplementation(() => new Date('2024').valueOf());
106162

107-
const mockNotification = mockSingleNotification;
108-
mockNotification.subject.comments = 2;
163+
const mockNotification = mockSingleNotification;
164+
mockNotification.subject.labels = ['enhancement', 'good-first-issue'];
109165

110-
const props = {
111-
notification: mockNotification,
112-
hostname: 'github.com',
113-
};
166+
const props = {
167+
notification: mockNotification,
168+
hostname: 'github.com',
169+
};
114170

115-
const tree = render(<NotificationRow {...props} />);
116-
expect(tree).toMatchSnapshot();
171+
const tree = render(<NotificationRow {...props} />);
172+
expect(tree).toMatchSnapshot();
173+
});
117174
});
118-
});
119-
120-
describe('notification labels', () => {
121-
it('should render labels metric when available', async () => {
122-
jest
123-
.spyOn(global.Date, 'now')
124-
.mockImplementation(() => new Date('2024').valueOf());
125-
126-
const mockNotification = mockSingleNotification;
127-
mockNotification.subject.labels = ['enhancement', 'good-first-issue'];
128-
129-
const props = {
130-
notification: mockNotification,
131-
hostname: 'github.com',
132-
};
133-
134-
const tree = render(<NotificationRow {...props} />);
135-
expect(tree).toMatchSnapshot();
136-
});
137-
});
138-
139-
describe('linked issues/prs', () => {
140-
it('should render when linked to one issue/pr', async () => {
141-
jest
142-
.spyOn(global.Date, 'now')
143-
.mockImplementation(() => new Date('2024').valueOf());
144-
145-
const mockNotification = mockSingleNotification;
146-
mockNotification.subject.linkedIssues = ['#1'];
147-
148-
const props = {
149-
notification: mockNotification,
150-
hostname: 'github.com',
151-
};
152-
153-
const tree = render(<NotificationRow {...props} />);
154-
expect(tree).toMatchSnapshot();
155-
});
156-
157-
it('should render when linked to multiple issues/prs', async () => {
158-
jest
159-
.spyOn(global.Date, 'now')
160-
.mockImplementation(() => new Date('2024').valueOf());
161-
162-
const mockNotification = mockSingleNotification;
163-
mockNotification.subject.linkedIssues = ['#1', '#2'];
164-
165-
const props = {
166-
notification: mockNotification,
167-
hostname: 'github.com',
168-
};
169175

170-
const tree = render(<NotificationRow {...props} />);
171-
expect(tree).toMatchSnapshot();
176+
describe('milestone pills', () => {
177+
it('should render open milestone pill', async () => {
178+
jest
179+
.spyOn(global.Date, 'now')
180+
.mockImplementation(() => new Date('2024').valueOf());
181+
182+
const mockNotification = mockSingleNotification;
183+
mockNotification.subject.milestone = {
184+
title: 'Milestone 1',
185+
state: 'open',
186+
} as Milestone;
187+
188+
const props = {
189+
notification: mockNotification,
190+
hostname: 'github.com',
191+
};
192+
193+
const tree = render(<NotificationRow {...props} />);
194+
expect(tree).toMatchSnapshot();
195+
});
196+
197+
it('should render closed milestone pill', async () => {
198+
jest
199+
.spyOn(global.Date, 'now')
200+
.mockImplementation(() => new Date('2024').valueOf());
201+
202+
const mockNotification = mockSingleNotification;
203+
mockNotification.subject.milestone = {
204+
title: 'Milestone 1',
205+
state: 'closed',
206+
} as Milestone;
207+
208+
const props = {
209+
notification: mockNotification,
210+
hostname: 'github.com',
211+
};
212+
213+
const tree = render(<NotificationRow {...props} />);
214+
expect(tree).toMatchSnapshot();
215+
});
172216
});
173217
});
174218

src/components/NotificationRow.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
CommentIcon,
55
FeedPersonIcon,
66
IssueClosedIcon,
7+
MilestoneIcon,
78
ReadIcon,
89
TagIcon,
910
} from '@primer/octicons-react';
@@ -218,6 +219,24 @@ export const NotificationRow: FC<IProps> = ({ notification, hostname }) => {
218219
</button>
219220
</span>
220221
)}
222+
{notification.subject.milestone && (
223+
<span
224+
className="ml-1"
225+
title={notification.subject.milestone.title}
226+
>
227+
<button type="button" className={Constants.PILL_CLASS_NAME}>
228+
<MilestoneIcon
229+
size={12}
230+
className={
231+
notification.subject.milestone.state === 'open'
232+
? IconColor.GREEN
233+
: IconColor.RED
234+
}
235+
aria-label={notification.subject.milestone.title}
236+
/>
237+
</button>
238+
</span>
239+
)}
221240
</span>
222241
</span>
223242
</div>

0 commit comments

Comments
 (0)