Skip to content

Commit 4cee552

Browse files
Merge branch 'main' into test-2949
2 parents cc00610 + cc7f161 commit 4cee552

24 files changed

Lines changed: 1821 additions & 7 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: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { render } from '@testing-library/react';
2+
import { beforeEach, describe, expect, it, vi } from 'vitest';
3+
4+
import ScrollRestoration from './ScrollRestoration';
5+
6+
const mockUsePathname = vi.fn();
7+
8+
vi.mock('next/navigation', () => ({
9+
usePathname: () => mockUsePathname(),
10+
}));
11+
12+
describe('ScrollRestoration empty fallback behavior', () => {
13+
beforeEach(() => {
14+
vi.clearAllMocks();
15+
sessionStorage.clear();
16+
mockUsePathname.mockReturnValue('/');
17+
window.scrollTo = vi.fn();
18+
});
19+
20+
it('renders null without crashing when no saved scroll position exists', () => {
21+
const { container } = render(<ScrollRestoration />);
22+
23+
expect(container.firstChild).toBeNull();
24+
});
25+
26+
it('does not scroll when sessionStorage has no saved position', () => {
27+
render(<ScrollRestoration />);
28+
29+
expect(window.scrollTo).not.toHaveBeenCalled();
30+
});
31+
32+
it('restores saved scroll position when value exists', () => {
33+
sessionStorage.setItem('scroll-position-/', '250');
34+
35+
render(<ScrollRestoration />);
36+
37+
expect(window.scrollTo).toHaveBeenCalledWith(0, 250);
38+
});
39+
40+
it('stores current scroll position on scroll event', () => {
41+
Object.defineProperty(window, 'scrollY', {
42+
value: 300,
43+
configurable: true,
44+
});
45+
46+
render(<ScrollRestoration />);
47+
48+
window.dispatchEvent(new Event('scroll'));
49+
50+
expect(sessionStorage.getItem('scroll-position-/')).toBe('300');
51+
});
52+
53+
it('uses pathname-specific storage keys for empty fallback safety', () => {
54+
mockUsePathname.mockReturnValue('/dashboard');
55+
56+
render(<ScrollRestoration />);
57+
58+
window.dispatchEvent(new Event('scroll'));
59+
60+
expect(sessionStorage.getItem('scroll-position-/dashboard')).toBeDefined();
61+
});
62+
});
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { render } from '@testing-library/react';
2+
import { beforeEach, describe, expect, it, vi } from 'vitest';
3+
4+
import ScrollRestoration from './ScrollRestoration';
5+
6+
const mockUsePathname = vi.fn();
7+
8+
vi.mock('next/navigation', () => ({
9+
usePathname: () => mockUsePathname(),
10+
}));
11+
12+
describe('ScrollRestoration massive scaling behavior', () => {
13+
beforeEach(() => {
14+
vi.clearAllMocks();
15+
sessionStorage.clear();
16+
mockUsePathname.mockReturnValue('/');
17+
window.scrollTo = vi.fn();
18+
});
19+
20+
it('renders repeatedly without crashing under high mount volume', () => {
21+
expect(() => {
22+
for (let index = 0; index < 100; index++) {
23+
render(<ScrollRestoration />);
24+
}
25+
}).not.toThrow();
26+
});
27+
28+
it('handles many stored scroll positions without breaking restoration', () => {
29+
for (let index = 0; index < 500; index++) {
30+
sessionStorage.setItem(`scroll-position-/page-${index}`, String(index));
31+
}
32+
33+
mockUsePathname.mockReturnValue('/page-499');
34+
35+
render(<ScrollRestoration />);
36+
37+
expect(window.scrollTo).toHaveBeenCalledWith(0, 499);
38+
});
39+
40+
it('stores scroll position correctly after many scroll events', () => {
41+
render(<ScrollRestoration />);
42+
43+
for (let index = 0; index < 100; index++) {
44+
Object.defineProperty(window, 'scrollY', {
45+
value: index,
46+
configurable: true,
47+
});
48+
49+
window.dispatchEvent(new Event('scroll'));
50+
}
51+
52+
expect(sessionStorage.getItem('scroll-position-/')).toBe('99');
53+
});
54+
55+
it('keeps pathname-specific keys isolated at scale', () => {
56+
for (let index = 0; index < 50; index++) {
57+
mockUsePathname.mockReturnValue(`/dashboard-${index}`);
58+
59+
const { unmount } = render(<ScrollRestoration />);
60+
61+
Object.defineProperty(window, 'scrollY', {
62+
value: index * 10,
63+
configurable: true,
64+
});
65+
66+
window.dispatchEvent(new Event('scroll'));
67+
unmount();
68+
}
69+
70+
expect(sessionStorage.getItem('scroll-position-/dashboard-0')).toBe('0');
71+
expect(sessionStorage.getItem('scroll-position-/dashboard-49')).toBe('490');
72+
});
73+
it('removes scroll listener safely after many unmount cycles', () => {
74+
const removeEventListenerSpy = vi.spyOn(window, 'removeEventListener');
75+
76+
for (let index = 0; index < 25; index++) {
77+
const { unmount } = render(<ScrollRestoration />);
78+
unmount();
79+
}
80+
81+
expect(removeEventListenerSpy).toHaveBeenCalledTimes(25);
82+
});
83+
});
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import { describe, it, expect, expectTypeOf } from 'vitest';
2+
import { z } from 'zod';
3+
4+
// Test suite: verify TypeScript compiler stability and runtime schema boundaries
5+
describe('Contributors loading type & schema compiler checks (Variation 10)', () => {
6+
// Baseline expected contributor shape (matches ContributorsClient internal shape)
7+
type ExpectedContributor = {
8+
id: number;
9+
login: string;
10+
avatar_url: string;
11+
contributions: number;
12+
html_url: string;
13+
};
14+
15+
it('Case 1: Enforce field property configuration types match expected baseline structures exactly', () => {
16+
// A matching shape should be exactly equal to the expected contributor type
17+
type Matching = {
18+
id: number;
19+
login: string;
20+
avatar_url: string;
21+
contributions: number;
22+
html_url: string;
23+
};
24+
25+
expectTypeOf<Matching>().toEqualTypeOf<ExpectedContributor>();
26+
});
27+
28+
it('Case 2: Assert that invalid parameter shapes are structurally incompatible', () => {
29+
// Wrong types / missing required fields should not match ExpectedContributor
30+
type InvalidContributor = {
31+
id: string; // wrong type
32+
login?: string; // optional where required
33+
};
34+
35+
expectTypeOf<InvalidContributor>().not.toMatchTypeOf<ExpectedContributor>();
36+
});
37+
38+
it('Case 3: Verify custom configurations accept optional parameters without compiler warnings', () => {
39+
// Define an expected loading config with optional parameters
40+
type LoadingConfig = {
41+
limit?: number;
42+
showAvatars?: boolean;
43+
filters?: { org?: string } | undefined;
44+
};
45+
46+
// A custom consumer may provide only a subset of optional parameters
47+
const customConfig = { limit: 6 } satisfies LoadingConfig;
48+
49+
// runtime assertion to keep TS happy and ensure the optional property works
50+
expect(customConfig.limit).toBe(6);
51+
52+
// compile-time: ensure the custom minimal shape is assignable to LoadingConfig
53+
type CustomMinimal = typeof customConfig;
54+
expectTypeOf<CustomMinimal>().toMatchTypeOf<LoadingConfig>();
55+
});
56+
57+
it('Case 4: Runtime validation schemas flag structural boundary anomalies with strict error reports', () => {
58+
const contributorSchema = z
59+
.object({
60+
id: z.number(),
61+
login: z.string(),
62+
avatar_url: z.string(),
63+
contributions: z.number(),
64+
html_url: z.string(),
65+
})
66+
.strict();
67+
68+
const badPayload = {
69+
id: 1,
70+
login: 'alice',
71+
avatar_url: 'https://example.com/a.png',
72+
contributions: 42,
73+
html_url: 'https://github.com/alice',
74+
unexpected_extra: 'surprise',
75+
};
76+
77+
try {
78+
contributorSchema.parse(badPayload);
79+
// If parse does not throw, force a failure
80+
expect(false).toBe(true);
81+
} catch (err) {
82+
// ZodError -> inspect issues for unrecognized keys
83+
const zErr = err as z.ZodError;
84+
const hasUnrecognized = zErr.issues.some((i) => i.code === z.ZodIssueCode.unrecognized_keys);
85+
expect(hasUnrecognized).toBe(true);
86+
}
87+
});
88+
89+
it('Case 5: Ensure type parameters resolve down to stable readonly or structured object contracts', () => {
90+
type ReadonlyContributor = Readonly<ExpectedContributor>;
91+
92+
// Expected readonly contract
93+
type ExplicitReadonly = {
94+
readonly id: number;
95+
readonly login: string;
96+
readonly avatar_url: string;
97+
readonly contributions: number;
98+
readonly html_url: string;
99+
};
100+
101+
expectTypeOf<ReadonlyContributor>().toEqualTypeOf<ExplicitReadonly>();
102+
});
103+
});

0 commit comments

Comments
 (0)