Skip to content

Commit 85e3a80

Browse files
authored
feat: use primer IssueLabelToken component for labels and reactions (#2584)
* feat: primer label component Signed-off-by: Adam Setch <adam.setch@outlook.com> * feat: primer label component Signed-off-by: Adam Setch <adam.setch@outlook.com> * feat: primer label component Signed-off-by: Adam Setch <adam.setch@outlook.com> * feat: primer label component Signed-off-by: Adam Setch <adam.setch@outlook.com> * feat: primer label component Signed-off-by: Adam Setch <adam.setch@outlook.com> * feat: primer label component Signed-off-by: Adam Setch <adam.setch@outlook.com> * feat: labels Signed-off-by: Adam Setch <adam.setch@outlook.com> * feat: skip fetching if offline Signed-off-by: Adam Setch <adam.setch@outlook.com> * feat(settings): update metric setting tooltip Signed-off-by: Adam Setch <adam.setch@outlook.com> --------- Signed-off-by: Adam Setch <adam.setch@outlook.com>
1 parent 36775ad commit 85e3a80

36 files changed

+1375
-233
lines changed

src/renderer/__helpers__/jest.setup.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,41 @@ Object.defineProperty(navigator, 'clipboard', {
7878
},
7979
configurable: true,
8080
});
81+
82+
// Simple IntersectionObserver mock for test environments (jsdom)
83+
class MockIntersectionObserver {
84+
readonly root: Element | Document | null;
85+
readonly rootMargin: string;
86+
readonly thresholds: number | number[];
87+
88+
constructor(
89+
// callback unused in this mock
90+
_callback: IntersectionObserverCallback,
91+
options?: IntersectionObserverInit,
92+
) {
93+
this.root = (options?.root as Element | Document) ?? null;
94+
this.rootMargin = options?.rootMargin ?? '';
95+
this.thresholds = options?.threshold ?? 0;
96+
}
97+
98+
observe() {
99+
return null;
100+
}
101+
102+
unobserve() {
103+
return null;
104+
}
105+
106+
disconnect() {
107+
return null;
108+
}
109+
110+
takeRecords(): IntersectionObserverEntry[] {
111+
return [];
112+
}
113+
}
114+
115+
// Attach to global if not present
116+
if (typeof (globalThis as any).IntersectionObserver === 'undefined') {
117+
(globalThis as any).IntersectionObserver = MockIntersectionObserver;
118+
}

src/renderer/components/metrics/CommentsPill.test.tsx

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,32 @@
11
import { renderWithAppContext } from '../../__helpers__/test-utils';
2-
import { mockGitifyNotification } from '../../__mocks__/notifications-mocks';
32

4-
import { CommentsPill } from './CommentsPill';
3+
import { CommentsPill, type CommentsPillProps } from './CommentsPill';
54

65
describe('renderer/components/metrics/CommentsPill.tsx', () => {
76
it('renders with no comments (null)', () => {
8-
const mockNotification = { ...mockGitifyNotification };
9-
mockNotification.subject.commentCount = null;
7+
const props: CommentsPillProps = null;
108

11-
const tree = renderWithAppContext(
12-
<CommentsPill commentCount={mockNotification.subject.commentCount} />,
13-
);
9+
const tree = renderWithAppContext(<CommentsPill {...props} />);
1410

1511
expect(tree).toMatchSnapshot();
1612
});
1713

1814
it('renders with 1 comment', () => {
19-
const mockNotification = { ...mockGitifyNotification };
20-
mockNotification.subject.commentCount = 1;
15+
const props: CommentsPillProps = {
16+
commentCount: 1,
17+
};
2118

22-
const tree = renderWithAppContext(
23-
<CommentsPill commentCount={mockNotification.subject.commentCount} />,
24-
);
19+
const tree = renderWithAppContext(<CommentsPill {...props} />);
2520

2621
expect(tree).toMatchSnapshot();
2722
});
2823

2924
it('renders with multiple comments', () => {
30-
const mockNotification = { ...mockGitifyNotification };
31-
mockNotification.subject.commentCount = 2;
25+
const props: CommentsPillProps = {
26+
commentCount: 2,
27+
};
3228

33-
const tree = renderWithAppContext(
34-
<CommentsPill commentCount={mockNotification.subject.commentCount} />,
35-
);
29+
const tree = renderWithAppContext(<CommentsPill {...props} />);
3630

3731
expect(tree).toMatchSnapshot();
3832
});

src/renderer/components/metrics/CommentsPill.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ export const CommentsPill: FC<CommentsPillProps> = ({ commentCount }) => {
2121
return (
2222
<MetricPill
2323
color={IconColor.GRAY}
24+
contents={description}
2425
icon={CommentIcon}
2526
metric={commentCount}
26-
title={description}
2727
/>
2828
);
2929
};

src/renderer/components/metrics/LabelsPill.test.tsx

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,25 @@
11
import { renderWithAppContext } from '../../__helpers__/test-utils';
2-
import { mockGitifyNotification } from '../../__mocks__/notifications-mocks';
32

4-
import { LabelsPill } from './LabelsPill';
3+
import { LabelsPill, type LabelsPillProps } from './LabelsPill';
54

65
describe('renderer/components/metrics/LabelsPill.tsx', () => {
7-
it('renders labels pill', () => {
8-
const mockNotification = { ...mockGitifyNotification };
9-
mockNotification.subject.labels = ['enhancement', 'good-first-issue'];
6+
it('renders without labels', () => {
7+
const props: LabelsPillProps = { labels: [] };
108

11-
const tree = renderWithAppContext(
12-
<LabelsPill labels={mockNotification.subject.labels} />,
13-
);
9+
const tree = renderWithAppContext(<LabelsPill {...props} />);
10+
11+
expect(tree).toMatchSnapshot();
12+
});
13+
14+
it('renders with labels', () => {
15+
const props: LabelsPillProps = {
16+
labels: [
17+
{ name: 'enhancement', color: 'a2eeef' },
18+
{ name: 'good-first-issue', color: '7057ff' },
19+
],
20+
};
21+
22+
const tree = renderWithAppContext(<LabelsPill {...props} />);
1423

1524
expect(tree).toMatchSnapshot();
1625
});
Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,42 @@
11
import type { FC } from 'react';
22

33
import { TagIcon } from '@primer/octicons-react';
4+
import { IssueLabelToken, LabelGroup } from '@primer/react';
45

5-
import { IconColor } from '../../types';
6+
import { type GitifyLabels, IconColor } from '../../types';
67

7-
import { formatMetricDescription } from '../../utils/notifications/formatters';
88
import { MetricPill } from './MetricPill';
99

1010
export interface LabelsPillProps {
11-
labels: string[];
11+
labels: GitifyLabels[];
1212
}
1313

1414
export const LabelsPill: FC<LabelsPillProps> = ({ labels }) => {
1515
if (!labels?.length) {
1616
return null;
1717
}
1818

19-
const description = formatMetricDescription(
20-
labels.length,
21-
'label',
22-
(count, noun) => {
23-
const formatted = labels.map((label) => `🏷️ ${label}`).join(', ');
24-
25-
return `${count} ${noun}: ${formatted}`;
26-
},
19+
const labelsContent = (
20+
<LabelGroup>
21+
{labels.map((label) => {
22+
return (
23+
<IssueLabelToken
24+
fillColor={label.color ? `#${label.color}` : undefined}
25+
key={label.name}
26+
size="small"
27+
text={label.name}
28+
/>
29+
);
30+
})}
31+
</LabelGroup>
2732
);
2833

2934
return (
3035
<MetricPill
3136
color={IconColor.GRAY}
37+
contents={labelsContent}
3238
icon={TagIcon}
3339
metric={labels.length}
34-
title={description}
3540
/>
3641
);
3742
};

src/renderer/components/metrics/LinkedIssuesPill.test.tsx

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,31 @@
11
import { renderWithAppContext } from '../../__helpers__/test-utils';
2-
import { mockGitifyNotification } from '../../__mocks__/notifications-mocks';
32

4-
import { LinkedIssuesPill } from './LinkedIssuesPill';
3+
import {
4+
LinkedIssuesPill,
5+
type LinkedIssuesPillProps,
6+
} from './LinkedIssuesPill';
57

68
describe('renderer/components/metrics/LinkedIssuesPill.tsx', () => {
9+
it('renders when no linked issues/prs', () => {
10+
const props: LinkedIssuesPillProps = { linkedIssues: [] };
11+
12+
const tree = renderWithAppContext(<LinkedIssuesPill {...props} />);
13+
14+
expect(tree).toMatchSnapshot();
15+
});
16+
717
it('renders when linked to one issue/pr', () => {
8-
const mockNotification = { ...mockGitifyNotification };
9-
mockNotification.subject.linkedIssues = ['#1'];
18+
const props: LinkedIssuesPillProps = { linkedIssues: ['#1'] };
1019

11-
const tree = renderWithAppContext(
12-
<LinkedIssuesPill linkedIssues={mockNotification.subject.linkedIssues} />,
13-
);
20+
const tree = renderWithAppContext(<LinkedIssuesPill {...props} />);
1421

1522
expect(tree).toMatchSnapshot();
1623
});
1724

1825
it('renders when linked to multiple issues/prs', () => {
19-
const mockNotification = { ...mockGitifyNotification };
20-
mockNotification.subject.linkedIssues = ['#1', '#2'];
26+
const props: LinkedIssuesPillProps = { linkedIssues: ['#1', '#2'] };
2127

22-
const tree = renderWithAppContext(
23-
<LinkedIssuesPill linkedIssues={mockNotification.subject.linkedIssues} />,
24-
);
28+
const tree = renderWithAppContext(<LinkedIssuesPill {...props} />);
2529

2630
expect(tree).toMatchSnapshot();
2731
});

src/renderer/components/metrics/LinkedIssuesPill.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,15 @@ export const LinkedIssuesPill: FC<LinkedIssuesPillProps> = ({
2121
const description = formatMetricDescription(
2222
linkedIssues.length,
2323
'issue',
24-
(count, noun) => `Linked to ${count} ${noun}: ${linkedIssues.join(', ')}`,
24+
(noun) => `Linked to ${noun}: ${linkedIssues.join(', ')}`,
2525
);
2626

2727
return (
2828
<MetricPill
2929
color={IconColor.GRAY}
30+
contents={description}
3031
icon={IssueOpenedIcon}
3132
metric={linkedIssues.length}
32-
title={description}
3333
/>
3434
);
3535
};

src/renderer/components/metrics/MetricGroup.test.tsx

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,29 @@ import { mockSettings } from '../../__mocks__/state-mocks';
55
import { MetricGroup, type MetricGroupProps } from './MetricGroup';
66

77
describe('renderer/components/metrics/MetricGroup.tsx', () => {
8-
describe('showPills disabled', () => {
9-
it('should not render any pills when showPills is disabled', async () => {
10-
const mockNotification = mockGitifyNotification;
11-
const props: MetricGroupProps = {
12-
notification: mockNotification,
13-
};
8+
it('should not render any pills when showPills is disabled', async () => {
9+
const mockNotification = mockGitifyNotification;
10+
const props: MetricGroupProps = {
11+
notification: mockNotification,
12+
};
1413

15-
const tree = renderWithAppContext(<MetricGroup {...props} />, {
16-
settings: { ...mockSettings, showPills: false },
17-
});
14+
const tree = renderWithAppContext(<MetricGroup {...props} />, {
15+
settings: { ...mockSettings, showPills: false },
16+
});
17+
18+
expect(tree).toMatchSnapshot();
19+
});
1820

19-
expect(tree).toMatchSnapshot();
21+
it('should render pills when showPills is enabled', async () => {
22+
const mockNotification = mockGitifyNotification;
23+
const props: MetricGroupProps = {
24+
notification: mockNotification,
25+
};
26+
27+
const tree = renderWithAppContext(<MetricGroup {...props} />, {
28+
settings: { ...mockSettings, showPills: true },
2029
});
30+
31+
expect(tree).toMatchSnapshot();
2132
});
2233
});

src/renderer/components/metrics/MetricGroup.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@ export const MetricGroup: FC<MetricGroupProps> = ({ notification }) => {
3737

3838
<CommentsPill commentCount={notification.subject.commentCount ?? 0} />
3939

40-
<LabelsPill labels={notification.subject.labels ?? []} />
41-
4240
<MilestonePill milestone={notification.subject.milestone} />
41+
42+
<LabelsPill labels={notification.subject.labels ?? []} />
4343
</div>
4444
);
4545
};

src/renderer/components/metrics/MetricPill.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { MetricPill, type MetricPillProps } from './MetricPill';
99
describe('renderer/components/metrics/MetricPill.tsx', () => {
1010
it('should render with metric', () => {
1111
const props: MetricPillProps = {
12-
title: 'Mock Pill',
12+
contents: 'Mock Pill',
1313
metric: 1,
1414
icon: MarkGithubIcon,
1515
color: IconColor.GREEN,
@@ -22,7 +22,7 @@ describe('renderer/components/metrics/MetricPill.tsx', () => {
2222

2323
it('should render without metric', () => {
2424
const props: MetricPillProps = {
25-
title: 'Mock Pill',
25+
contents: 'Mock Pill',
2626
icon: MarkGithubIcon,
2727
color: IconColor.GREEN,
2828
};

0 commit comments

Comments
 (0)