Skip to content

Commit 695bad8

Browse files
authored
Merge branch 'main' into test-discordbutton-accessibility
2 parents 87c60aa + c8fb1e5 commit 695bad8

16 files changed

Lines changed: 1563 additions & 9 deletions
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { render, screen } from '@testing-library/react';
2+
import '@testing-library/jest-dom/vitest';
3+
import { describe, expect, it, vi } from 'vitest';
4+
5+
vi.mock('./CompareClient', () => ({
6+
default: () => <div>Mock Compare Client</div>,
7+
}));
8+
9+
vi.mock('../components/Footer', () => ({
10+
Footer: () => <div>Mock Footer</div>,
11+
}));
12+
13+
import ComparePage from './page';
14+
15+
describe('ComparePage mock integrations', () => {
16+
it('renders CompareClient through mocked service layer', () => {
17+
render(<ComparePage />);
18+
19+
expect(screen.getByText('Mock Compare Client')).toBeInTheDocument();
20+
});
21+
22+
it('renders Footer component', () => {
23+
render(<ComparePage />);
24+
25+
expect(screen.getByText('Mock Footer')).toBeInTheDocument();
26+
});
27+
28+
it('renders CompareClient only once', () => {
29+
render(<ComparePage />);
30+
31+
expect(screen.getAllByText('Mock Compare Client')).toHaveLength(1);
32+
});
33+
34+
it('renders Footer only once', () => {
35+
render(<ComparePage />);
36+
37+
expect(screen.getAllByText('Mock Footer')).toHaveLength(1);
38+
});
39+
40+
it('renders page layout with both mocked integrations', () => {
41+
render(<ComparePage />);
42+
43+
expect(screen.getByText('Mock Compare Client')).toBeInTheDocument();
44+
expect(screen.getByText('Mock Footer')).toBeInTheDocument();
45+
});
46+
});

components/BrandParticles.test.tsx

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { render, screen } from '@testing-library/react';
2+
import '@testing-library/jest-dom/vitest';
3+
import { describe, expect, it, vi, beforeEach } from 'vitest';
4+
import BrandParticles from './BrandParticles';
5+
6+
let mockReducedMotion = false;
7+
8+
// Mock framer-motion to inspect properties passed to motion elements and control reduced motion state
9+
vi.mock('framer-motion', () => ({
10+
motion: {
11+
div: ({
12+
animate,
13+
transition,
14+
style,
15+
...props
16+
}: {
17+
animate?: unknown;
18+
transition?: unknown;
19+
style?: React.CSSProperties;
20+
[key: string]: unknown;
21+
}) => (
22+
<div
23+
{...props}
24+
style={style}
25+
data-testid="motion-div"
26+
data-animate={JSON.stringify(animate)}
27+
data-transition={JSON.stringify(transition)}
28+
/>
29+
),
30+
},
31+
useReducedMotion: () => mockReducedMotion,
32+
}));
33+
34+
describe('BrandParticles Component', () => {
35+
beforeEach(() => {
36+
mockReducedMotion = false;
37+
});
38+
39+
it('renders nothing on the server side prior to mounting', () => {
40+
// BrandParticles has a mounted check. When mounting is false, it returns null.
41+
// To simulate pre-mount, we inspect the behavior before useEffect runs.
42+
// In React testing library, render runs useEffect synchronously, but we can verify it renders successfully when mounted.
43+
const { container } = render(<BrandParticles />);
44+
expect(container.firstChild).not.toBeNull();
45+
});
46+
47+
it('renders 40 particles once mounted', () => {
48+
render(<BrandParticles />);
49+
const particles = screen.getAllByTestId('motion-div');
50+
expect(particles).toHaveLength(40);
51+
});
52+
53+
it('renders particles with random styles and properties', () => {
54+
render(<BrandParticles />);
55+
const particles = screen.getAllByTestId('motion-div');
56+
57+
// Check first particle styles
58+
const firstParticle = particles[0];
59+
expect(firstParticle.className).toContain('absolute');
60+
expect(firstParticle.style.width).toBeTruthy();
61+
expect(firstParticle.style.height).toBeTruthy();
62+
expect(firstParticle.style.backgroundColor).toBeTruthy();
63+
expect(firstParticle.style.left).toBeTruthy();
64+
expect(firstParticle.style.top).toBeTruthy();
65+
expect(firstParticle.style.opacity).toBeTruthy();
66+
expect(firstParticle.style.borderRadius).toBeTruthy();
67+
});
68+
69+
it('applies animation paths when motion is enabled (reduced motion = false)', () => {
70+
mockReducedMotion = false;
71+
render(<BrandParticles />);
72+
const particles = screen.getAllByTestId('motion-div');
73+
74+
const animateAttr = particles[0].getAttribute('data-animate');
75+
expect(animateAttr).not.toBe('{}');
76+
expect(animateAttr).toContain('y');
77+
expect(animateAttr).toContain('x');
78+
expect(animateAttr).toContain('rotate');
79+
80+
const transitionAttr = particles[0].getAttribute('data-transition');
81+
expect(transitionAttr).not.toBe('{}');
82+
expect(transitionAttr).toContain('duration');
83+
expect(transitionAttr).toContain('delay');
84+
});
85+
86+
it('disables animation and transition when reduced motion is enabled', () => {
87+
mockReducedMotion = true;
88+
render(<BrandParticles />);
89+
const particles = screen.getAllByTestId('motion-div');
90+
91+
const animateAttr = particles[0].getAttribute('data-animate');
92+
expect(animateAttr).toBe('{}');
93+
94+
const transitionAttr = particles[0].getAttribute('data-transition');
95+
expect(transitionAttr).toBe('{}');
96+
});
97+
});

components/FeatureCards.test.tsx

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { render, screen, fireEvent } from '@testing-library/react';
2+
import '@testing-library/jest-dom/vitest';
3+
import { describe, expect, it, vi, beforeEach } from 'vitest';
4+
import { FeatureCard, FeatureCardsSection } from './FeatureCards';
5+
6+
// Mock GSAP
7+
const mockTimeline = {
8+
to: vi.fn().mockReturnThis(),
9+
fromTo: vi.fn().mockReturnThis(),
10+
set: vi.fn().mockReturnThis(),
11+
kill: vi.fn(),
12+
};
13+
14+
vi.mock('gsap', () => {
15+
const gsapMock = {
16+
registerPlugin: vi.fn(),
17+
context: (cb: () => void) => {
18+
cb();
19+
return { revert: vi.fn() };
20+
},
21+
timeline: () => mockTimeline,
22+
to: vi.fn().mockImplementation((target, vars) => {
23+
if (typeof vars.onComplete === 'function') {
24+
vars.onComplete();
25+
}
26+
return mockTimeline;
27+
}),
28+
fromTo: vi.fn().mockReturnThis(),
29+
set: vi.fn().mockReturnThis(),
30+
};
31+
return {
32+
default: gsapMock,
33+
...gsapMock,
34+
};
35+
});
36+
37+
vi.mock('gsap/ScrollTrigger', () => ({
38+
ScrollTrigger: {},
39+
}));
40+
41+
describe('FeatureCards Component Suite', () => {
42+
beforeEach(() => {
43+
vi.clearAllMocks();
44+
});
45+
46+
describe('FeatureCard Component', () => {
47+
const defaultProps = {
48+
icon: <span data-testid="test-icon">💡</span>,
49+
title: 'Amazing Visuals',
50+
desc: 'Beautiful SVG isometric rendering of your commits.',
51+
accent: 'text-emerald-500',
52+
index: 1,
53+
accentColor: '#10b981',
54+
};
55+
56+
it('renders card title, description and icon correctly', () => {
57+
render(<FeatureCard {...defaultProps} />);
58+
59+
expect(screen.getByText('Amazing Visuals')).toBeInTheDocument();
60+
expect(
61+
screen.getByText('Beautiful SVG isometric rendering of your commits.')
62+
).toBeInTheDocument();
63+
expect(screen.getByTestId('test-icon')).toBeInTheDocument();
64+
});
65+
66+
it('renders background gradient blob and accent line matching the accentColor', () => {
67+
const { container } = render(<FeatureCard {...defaultProps} />);
68+
69+
// The background blob has class "absolute -right-20 -top-20"
70+
const blob = container.querySelector('.absolute.-right-20.-top-20') as HTMLElement;
71+
expect(blob).toBeInTheDocument();
72+
expect(blob.style.background).toBeTruthy();
73+
74+
// The bottom accent line has class "absolute bottom-0 left-0"
75+
const accentLine = container.querySelector('.absolute.bottom-0.left-0') as HTMLElement;
76+
expect(accentLine).toBeInTheDocument();
77+
expect(accentLine.style.background).toMatch(/rgb\(16, 185, 129\)|#10b981/);
78+
});
79+
80+
it('triggers mouse movement and hover hover-effects to activate magnetic spotlight', () => {
81+
const { container } = render(<FeatureCard {...defaultProps} />);
82+
const card = container.firstChild as HTMLDivElement;
83+
84+
// Mouse enter triggers hover state
85+
fireEvent.mouseEnter(card);
86+
87+
// Mouse move triggers magnetic movement recalculations
88+
fireEvent.mouseMove(card, { clientX: 100, clientY: 100 });
89+
90+
// Mouse leave resets magnetic coordinates
91+
fireEvent.mouseLeave(card);
92+
});
93+
});
94+
95+
describe('FeatureCardsSection Wrapper Component', () => {
96+
it('renders children components and Why CommitPulse heading correctly', () => {
97+
render(
98+
<FeatureCardsSection>
99+
<div data-testid="child-card">Child Component</div>
100+
</FeatureCardsSection>
101+
);
102+
103+
expect(screen.getByRole('heading', { name: 'Why CommitPulse?' })).toBeInTheDocument();
104+
expect(screen.getByTestId('child-card')).toBeInTheDocument();
105+
});
106+
});
107+
});

0 commit comments

Comments
 (0)