Skip to content

Commit accaba1

Browse files
Merge branch 'main' into fix/debounce-preview-requests-clean
2 parents 7f3ce51 + 22a94d5 commit accaba1

11 files changed

Lines changed: 760 additions & 2 deletions
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { render } from '@testing-library/react';
2+
import { describe, expect, it, vi } from 'vitest';
3+
4+
import { BoxIcon, CheckIcon, CloseIcon, CopyIcon, ZapIcon } from './Icons';
5+
6+
type IconProps = React.SVGProps<SVGSVGElement>;
7+
8+
vi.mock('lucide-react', () => ({
9+
Copy: (props: IconProps) => <svg data-testid="copy-icon" {...props} />,
10+
Zap: (props: IconProps) => <svg data-testid="zap-icon" {...props} />,
11+
Box: (props: IconProps) => <svg data-testid="box-icon" {...props} />,
12+
Check: (props: IconProps) => <svg data-testid="check-icon" {...props} />,
13+
X: (props: IconProps) => <svg data-testid="close-icon" {...props} />,
14+
}));
15+
16+
describe('Icons empty fallback behavior', () => {
17+
it('renders CopyIcon without crashing', () => {
18+
const { getByTestId } = render(<CopyIcon />);
19+
expect(getByTestId('copy-icon')).toBeDefined();
20+
});
21+
22+
it('renders ZapIcon without crashing', () => {
23+
const { getByTestId } = render(<ZapIcon />);
24+
expect(getByTestId('zap-icon')).toBeDefined();
25+
});
26+
27+
it('renders BoxIcon without crashing', () => {
28+
const { getByTestId } = render(<BoxIcon />);
29+
expect(getByTestId('box-icon')).toBeDefined();
30+
});
31+
32+
it('renders CheckIcon with fallback styling', () => {
33+
const { getByTestId } = render(<CheckIcon />);
34+
const icon = getByTestId('check-icon');
35+
36+
expect(icon.getAttribute('stroke')).toBe('#10b981');
37+
});
38+
39+
it('renders CloseIcon without runtime errors', () => {
40+
const { getByTestId } = render(<CloseIcon />);
41+
expect(getByTestId('close-icon')).toBeDefined();
42+
});
43+
});
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import type React from 'react';
2+
import { render, screen } from '@testing-library/react';
3+
import { describe, expect, it, vi } from 'vitest';
4+
5+
import { BoxIcon, CheckIcon, CloseIcon, CopyIcon, ZapIcon } from './Icons';
6+
7+
type IconProps = React.SVGProps<SVGSVGElement>;
8+
9+
vi.mock('lucide-react', () => ({
10+
Copy: (props: IconProps) => <svg data-testid="copy-icon" {...props} />,
11+
Zap: (props: IconProps) => <svg data-testid="zap-icon" {...props} />,
12+
Box: (props: IconProps) => <svg data-testid="box-icon" {...props} />,
13+
Check: (props: IconProps) => <svg data-testid="check-icon" {...props} />,
14+
X: (props: IconProps) => <svg data-testid="close-icon" {...props} />,
15+
}));
16+
17+
describe('Icons massive scaling behavior', () => {
18+
it('renders 500 CopyIcon instances without crashing', () => {
19+
render(
20+
<>
21+
{Array.from({ length: 500 }, (_, index) => (
22+
<CopyIcon key={index} />
23+
))}
24+
</>
25+
);
26+
27+
expect(screen.getAllByTestId('copy-icon')).toHaveLength(500);
28+
});
29+
30+
it('renders 500 ZapIcon instances without crashing', () => {
31+
render(
32+
<>
33+
{Array.from({ length: 500 }, (_, index) => (
34+
<ZapIcon key={index} />
35+
))}
36+
</>
37+
);
38+
39+
expect(screen.getAllByTestId('zap-icon')).toHaveLength(500);
40+
});
41+
42+
it('renders mixed icon sets at high volume', () => {
43+
render(
44+
<>
45+
{Array.from({ length: 100 }, (_, index) => (
46+
<div key={index}>
47+
<CopyIcon />
48+
<ZapIcon />
49+
<BoxIcon />
50+
<CheckIcon />
51+
<CloseIcon />
52+
</div>
53+
))}
54+
</>
55+
);
56+
57+
expect(screen.getAllByTestId('copy-icon')).toHaveLength(100);
58+
expect(screen.getAllByTestId('zap-icon')).toHaveLength(100);
59+
expect(screen.getAllByTestId('box-icon')).toHaveLength(100);
60+
expect(screen.getAllByTestId('check-icon')).toHaveLength(100);
61+
expect(screen.getAllByTestId('close-icon')).toHaveLength(100);
62+
});
63+
64+
it('preserves icon dimension attributes across many rendered icons', () => {
65+
render(
66+
<>
67+
{Array.from({ length: 100 }, (_, index) => (
68+
<CopyIcon key={index} />
69+
))}
70+
</>
71+
);
72+
73+
expect(
74+
screen.getAllByTestId('copy-icon').every((icon) => icon.getAttribute('width') === '20')
75+
).toBe(true);
76+
});
77+
78+
it('preserves CheckIcon stroke styling across high-volume renders', () => {
79+
render(
80+
<>
81+
{Array.from({ length: 100 }, (_, index) => (
82+
<CheckIcon key={index} />
83+
))}
84+
</>
85+
);
86+
87+
expect(
88+
screen.getAllByTestId('check-icon').every((icon) => icon.getAttribute('stroke') === '#10b981')
89+
).toBe(true);
90+
});
91+
});
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
2+
import { beforeEach, describe, expect, it, vi } from 'vitest';
3+
import { render, screen } from '@testing-library/react';
4+
import ContributorsPage from './page';
5+
6+
vi.mock('next/image', () => ({
7+
__esModule: true,
8+
default: (props: any) => <img {...props} />,
9+
}));
10+
11+
vi.mock('next/link', () => ({
12+
__esModule: true,
13+
default: ({ children, href, ...props }: any) => (
14+
<a href={href} {...props}>
15+
{children}
16+
</a>
17+
),
18+
}));
19+
20+
vi.mock('gsap', () => {
21+
const tween = { kill: vi.fn() };
22+
const mockGsap = {
23+
registerPlugin: vi.fn(),
24+
to: vi.fn().mockReturnValue(tween),
25+
fromTo: vi.fn().mockReturnValue(tween),
26+
set: vi.fn(),
27+
context: vi.fn((callback: any) => {
28+
if (typeof callback === 'function') {
29+
callback();
30+
}
31+
return { revert: vi.fn() };
32+
}),
33+
};
34+
return { default: mockGsap, gsap: mockGsap };
35+
});
36+
37+
vi.mock('gsap/ScrollTrigger', () => ({
38+
ScrollTrigger: {
39+
getAll: vi.fn(() => []),
40+
},
41+
}));
42+
43+
vi.mock('framer-motion', () => ({
44+
motion: {
45+
div: 'div',
46+
span: 'span',
47+
p: 'p',
48+
h1: 'h1',
49+
h2: 'h2',
50+
h3: 'h3',
51+
h4: 'h4',
52+
h5: 'h5',
53+
h6: 'h6',
54+
section: 'section',
55+
a: 'a',
56+
button: 'button',
57+
img: 'img',
58+
},
59+
AnimatePresence: ({ children }: any) => <>{children}</>,
60+
useMotionValue: (initial: any) => ({ current: initial, set: vi.fn() }),
61+
useSpring: (value: any) => value,
62+
useTransform: (value: any, fn: any) => fn(value.current ?? value),
63+
}));
64+
65+
describe('ContributorsPage empty fallback', () => {
66+
beforeEach(() => {
67+
vi.restoreAllMocks();
68+
69+
global.fetch = vi.fn(() =>
70+
Promise.resolve({
71+
ok: true,
72+
json: async () => [],
73+
})
74+
) as any;
75+
76+
window.HTMLElement.prototype.scrollIntoView = vi.fn();
77+
78+
if (!window.requestAnimationFrame) {
79+
window.requestAnimationFrame = (callback: FrameRequestCallback) =>
80+
setTimeout(callback, 0) as unknown as number;
81+
}
82+
});
83+
84+
it('renders fallback UI when contributors are empty', async () => {
85+
const element = await ContributorsPage();
86+
render(element);
87+
88+
expect(screen.getByText(/No architects found/i)).toBeTruthy();
89+
expect(screen.getByText(/0 of 0 contributors/i)).toBeTruthy();
90+
expect(screen.getByRole('heading', { name: /THE COLLECTIVE/i })).toBeTruthy();
91+
expect(screen.getByText(/READY TO BUILD\?/i)).toBeTruthy();
92+
});
93+
94+
it('maintains page layout and empty markers for empty contributor input', async () => {
95+
const element = await ContributorsPage();
96+
render(element);
97+
98+
expect(screen.getByRole('heading', { name: /THE VANGUARD/i })).toBeTruthy();
99+
expect(screen.getByRole('heading', { name: /THE COLLECTIVE/i })).toBeTruthy();
100+
expect(screen.getByText(/Explore The Elite/i)).toBeTruthy();
101+
expect(screen.queryByText(/View Profile/i)).toBeNull();
102+
});
103+
104+
it('handles fetch failures gracefully and still renders fallback state', async () => {
105+
global.fetch = vi.fn(() => Promise.reject(new Error('Network failure'))) as any;
106+
const element = await ContributorsPage();
107+
render(element);
108+
109+
expect(screen.getByText(/No architects found/i)).toBeTruthy();
110+
expect(screen.getByText(/0 of 0 contributors/i)).toBeTruthy();
111+
});
112+
113+
it('handles non-ok API responses without breaking the page', async () => {
114+
global.fetch = vi.fn(() =>
115+
Promise.resolve({
116+
ok: false,
117+
status: 500,
118+
headers: { get: vi.fn(() => null) },
119+
json: async () => [],
120+
})
121+
) as any;
122+
123+
const element = await ContributorsPage();
124+
render(element);
125+
126+
expect(screen.getByText(/No architects found/i)).toBeTruthy();
127+
expect(screen.getByText(/0 of 0 contributors/i)).toBeTruthy();
128+
});
129+
130+
it('does not emit console errors when the fallback page renders', async () => {
131+
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
132+
const element = await ContributorsPage();
133+
render(element);
134+
135+
expect(errorSpy).not.toHaveBeenCalled();
136+
});
137+
});

components/ShareButtons.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,12 @@ interface ShareButtonsProps {
66
}
77

88
export default function ShareButtons({ url, title = '' }: ShareButtonsProps) {
9-
const linkedinUrl = `https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(url)}`;
10-
const twitterUrl = `https://twitter.com/intent/tweet?url=${encodeURIComponent(url)}&text=${encodeURIComponent(title)}`;
9+
const linkedinUrl = `https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(
10+
url
11+
)}`;
12+
const twitterUrl =
13+
`https://x.com/intent/tweet?url=${encodeURIComponent(url)}` +
14+
(title ? `&text=${encodeURIComponent(title)}` : '');
1115

1216
return (
1317
<div className="flex gap-3">

0 commit comments

Comments
 (0)